1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #pragma once
18 
19 #include <deque>
20 #include <mutex>
21 #include <thread>
22 #include <utils/Mutex.h> // has thread safety annotations
23 
24 namespace android::audio_utils {
25 
26 /**
27  * CommandThread is used for serial execution of commands
28  * on a single worker thread.
29  *
30  * This class is thread-safe.
31  */
32 
33 class CommandThread {
34 public:
CommandThread()35     CommandThread() {
36         // threadLoop() should be started after the class is initialized.
37         mThread = std::thread([this](){this->threadLoop();});
38     }
39 
~CommandThread()40     ~CommandThread() {
41         quit();
42         mThread.join();
43     }
44 
45     /**
46      * Add a command to the command queue.
47      *
48      * If the func is a closure containing references, suggest using shared_ptr
49      * instead to maintain proper lifetime.
50      *
51      * @param name for dump() purposes.
52      * @param func command to execute
53      */
add(std::string_view name,std::function<void ()> && func)54     void add(std::string_view name, std::function<void()>&& func) {
55         std::lock_guard lg(mMutex);
56         if (mQuit) return;
57         mCommands.emplace_back(name, std::move(func));
58         if (mCommands.size() == 1) mConditionVariable.notify_one();
59     }
60 
61     /**
62      * Returns the string of commands, separated by newlines.
63      */
dump()64     std::string dump() const {
65         std::string result;
66         std::lock_guard lg(mMutex);
67         for (const auto &p : mCommands) {
68             result.append(p.first).append("\n");
69         }
70         return result;
71     }
72 
73     /**
74      * Quits the command thread and empties the command queue.
75      */
quit()76     void quit() {
77         std::lock_guard lg(mMutex);
78         if (mQuit) return;
79         mQuit = true;
80         mCommands.clear();
81         mConditionVariable.notify_one();
82     }
83 
84     /**
85      * Returns the number of commands on the queue.
86      */
size()87     size_t size() const {
88         std::lock_guard lg(mMutex);
89         return mCommands.size();
90     }
91 
92 private:
93     std::thread mThread;
94     mutable std::mutex mMutex;
95     std::condition_variable mConditionVariable GUARDED_BY(mMutex);
96     std::deque<std::pair<std::string, std::function<void()>>> mCommands GUARDED_BY(mMutex);
97     bool mQuit GUARDED_BY(mMutex) = false;
98 
threadLoop()99     void threadLoop() NO_THREAD_SAFETY_ANALYSIS {
100         std::unique_lock ul(mMutex);
101         while (!mQuit) {
102             if (!mCommands.empty()) {
103                 auto name = std::move(mCommands.front().first);
104                 auto func = std::move(mCommands.front().second);
105                 mCommands.pop_front();
106                 ul.unlock();
107                 // ALOGD("%s: executing %s", __func__, name.c_str());
108                 func();
109                 ul.lock();
110                 continue;
111             }
112             mConditionVariable.wait(ul);
113         }
114     }
115 };
116 
117 }  // namespace android::audio_utils
118