• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 #include <pthread.h>
18 #include <sched.h>
19 #include <sys/resource.h>
20 
21 #include "include/StreamWorker.h"
22 
23 namespace android::hardware::audio::common::internal {
24 
start(const std::string & name,int priority)25 bool ThreadController::start(const std::string& name, int priority) {
26     mThreadName = name;
27     mThreadPriority = priority;
28     if (kTestSingleThread != name) {
29         mWorker = std::thread(&ThreadController::workerThread, this);
30     } else {
31         // Simulate the case when the workerThread completes prior
32         // to the moment when we being waiting for its start.
33         workerThread();
34     }
35     std::unique_lock<std::mutex> lock(mWorkerLock);
36     android::base::ScopedLockAssertion lock_assertion(mWorkerLock);
37     mWorkerCv.wait(lock, [&]() {
38         android::base::ScopedLockAssertion lock_assertion(mWorkerLock);
39         return mWorkerState != WorkerState::INITIAL || !mError.empty();
40     });
41     return mError.empty();
42 }
43 
stop()44 void ThreadController::stop() {
45     {
46         std::lock_guard<std::mutex> lock(mWorkerLock);
47         if (mWorkerState != WorkerState::STOPPED) {
48             mWorkerState = WorkerState::STOPPED;
49             mWorkerStateChangeRequest = true;
50         }
51     }
52     join();
53 }
54 
join()55 void ThreadController::join() {
56     if (mWorker.joinable()) {
57         mWorker.join();
58     }
59 }
60 
waitForAtLeastOneCycle()61 bool ThreadController::waitForAtLeastOneCycle() {
62     WorkerState newState;
63     switchWorkerStateSync(WorkerState::RUNNING, WorkerState::PAUSE_REQUESTED, &newState);
64     if (newState != WorkerState::PAUSED) return false;
65     switchWorkerStateSync(newState, WorkerState::RESUME_REQUESTED, &newState);
66     return newState == WorkerState::RUNNING;
67 }
68 
switchWorkerStateSync(WorkerState oldState,WorkerState newState,WorkerState * finalState)69 void ThreadController::switchWorkerStateSync(WorkerState oldState, WorkerState newState,
70                                              WorkerState* finalState) {
71     std::unique_lock<std::mutex> lock(mWorkerLock);
72     android::base::ScopedLockAssertion lock_assertion(mWorkerLock);
73     if (mWorkerState != oldState) {
74         if (finalState) *finalState = mWorkerState;
75         return;
76     }
77     mWorkerState = newState;
78     mWorkerStateChangeRequest = true;
79     mWorkerCv.wait(lock, [&]() {
80         android::base::ScopedLockAssertion lock_assertion(mWorkerLock);
81         return mWorkerState != newState;
82     });
83     if (finalState) *finalState = mWorkerState;
84 }
85 
workerThread()86 void ThreadController::workerThread() {
87     using Status = StreamLogic::Status;
88 
89     std::string error;
90     if (!mThreadName.empty()) {
91         std::string compliantName(mThreadName.substr(0, 15));
92         if (int errCode = pthread_setname_np(pthread_self(), compliantName.c_str()); errCode != 0) {
93             error.append("Failed to set thread name: ").append(strerror(errCode));
94         }
95     }
96     if (error.empty() && mThreadPriority != ANDROID_PRIORITY_DEFAULT) {
97         if (int result = setpriority(PRIO_PROCESS, 0, mThreadPriority); result != 0) {
98             int errCode = errno;
99             error.append("Failed to set thread priority: ").append(strerror(errCode));
100         }
101     }
102     if (error.empty()) {
103         error.append(mLogic->init());
104     }
105     {
106         std::lock_guard<std::mutex> lock(mWorkerLock);
107         mWorkerState = error.empty() ? WorkerState::RUNNING : WorkerState::STOPPED;
108         mError = error;
109 #if defined(__ANDROID__)
110         mTid = pthread_gettid_np(pthread_self());
111 #endif
112     }
113     mWorkerCv.notify_one();
114     if (!error.empty()) return;
115 
116     for (WorkerState state = WorkerState::RUNNING; state != WorkerState::STOPPED;) {
117         bool needToNotify = false;
118         if (Status status = state != WorkerState::PAUSED ? mLogic->cycle()
119                                                          : (sched_yield(), Status::CONTINUE);
120             status == Status::CONTINUE) {
121             {
122                 // See https://developer.android.com/training/articles/smp#nonracing
123                 android::base::ScopedLockAssertion lock_assertion(mWorkerLock);
124                 if (!mWorkerStateChangeRequest.load(std::memory_order_relaxed)) continue;
125             }
126             //
127             // Pause and resume are synchronous. One worker cycle must complete
128             // before the worker indicates a state change. This is how 'mWorkerState' and
129             // 'state' interact:
130             //
131             // mWorkerState == RUNNING
132             // client sets mWorkerState := PAUSE_REQUESTED
133             // last workerCycle gets executed, state := mWorkerState := PAUSED by us
134             //   (or the workers enters the 'error' state if workerCycle fails)
135             // client gets notified about state change in any case
136             // thread is doing a busy wait while 'state == PAUSED'
137             // client sets mWorkerState := RESUME_REQUESTED
138             // state := mWorkerState (RESUME_REQUESTED)
139             // mWorkerState := RUNNING, but we don't notify the client yet
140             // first workerCycle gets executed, the code below triggers a client notification
141             //   (or if workerCycle fails, worker enters 'error' state and also notifies)
142             // state := mWorkerState (RUNNING)
143             std::lock_guard<std::mutex> lock(mWorkerLock);
144             if (state == WorkerState::RESUME_REQUESTED) {
145                 needToNotify = true;
146             }
147             state = mWorkerState;
148             if (mWorkerState == WorkerState::PAUSE_REQUESTED) {
149                 state = mWorkerState = WorkerState::PAUSED;
150                 needToNotify = true;
151             } else if (mWorkerState == WorkerState::RESUME_REQUESTED) {
152                 mWorkerState = WorkerState::RUNNING;
153             }
154         } else {
155             std::lock_guard<std::mutex> lock(mWorkerLock);
156             if (state == WorkerState::RESUME_REQUESTED ||
157                 mWorkerState == WorkerState::PAUSE_REQUESTED) {
158                 needToNotify = true;
159             }
160             state = mWorkerState = WorkerState::STOPPED;
161             if (status == Status::ABORT) {
162                 mError = "Received ABORT from the logic cycle";
163             }
164         }
165         if (needToNotify) {
166             {
167                 std::lock_guard<std::mutex> lock(mWorkerLock);
168                 mWorkerStateChangeRequest = false;
169             }
170             mWorkerCv.notify_one();
171         }
172     }
173 }
174 
175 }  // namespace android::hardware::audio::common::internal
176