1 /*
2 * Copyright (c) 2016, Alliance for Open Media. All rights reserved.
3 *
4 * This source code is subject to the terms of the BSD 2 Clause License and
5 * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
6 * was not distributed with this source code in the LICENSE file, you can
7 * obtain it at www.aomedia.org/license/software. If the Alliance for Open
8 * Media Patent License 1.0 was not distributed with this source code in the
9 * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
10 */
11 //
12 // Multi-threaded worker
13 //
14 // Original source:
15 // https://chromium.googlesource.com/webm/libwebp
16
17 // Enable GNU extensions in glibc so that we can call pthread_setname_np().
18 // This must be before any #include statements.
19 #ifndef _GNU_SOURCE
20 #define _GNU_SOURCE
21 #endif
22
23 #include <assert.h>
24 #include <string.h> // for memset()
25
26 #include "config/aom_config.h"
27
28 #include "aom_mem/aom_mem.h"
29 #include "aom_ports/sanitizer.h"
30 #include "aom_util/aom_pthread.h"
31 #include "aom_util/aom_thread.h"
32
33 #if CONFIG_MULTITHREAD
34
35 struct AVxWorkerImpl {
36 pthread_mutex_t mutex_;
37 pthread_cond_t condition_;
38 pthread_t thread_;
39 };
40
41 //------------------------------------------------------------------------------
42
43 static void execute(AVxWorker *const worker); // Forward declaration.
44
thread_loop(void * ptr)45 static THREADFN thread_loop(void *ptr) {
46 AVxWorker *const worker = (AVxWorker *)ptr;
47 #ifdef __APPLE__
48 if (worker->thread_name != NULL) {
49 // Apple's version of pthread_setname_np takes one argument and operates on
50 // the current thread only. The maximum size of the thread_name buffer was
51 // noted in the Chromium source code and was confirmed by experiments. If
52 // thread_name is too long, pthread_setname_np returns -1 with errno
53 // ENAMETOOLONG (63).
54 char thread_name[64];
55 strncpy(thread_name, worker->thread_name, sizeof(thread_name) - 1);
56 thread_name[sizeof(thread_name) - 1] = '\0';
57 pthread_setname_np(thread_name);
58 }
59 #elif (defined(__GLIBC__) && !defined(__GNU__)) || defined(__BIONIC__)
60 if (worker->thread_name != NULL) {
61 // Linux and Android require names (with nul) fit in 16 chars, otherwise
62 // pthread_setname_np() returns ERANGE (34).
63 char thread_name[16];
64 strncpy(thread_name, worker->thread_name, sizeof(thread_name) - 1);
65 thread_name[sizeof(thread_name) - 1] = '\0';
66 pthread_setname_np(pthread_self(), thread_name);
67 }
68 #endif
69 pthread_mutex_lock(&worker->impl_->mutex_);
70 for (;;) {
71 while (worker->status_ == AVX_WORKER_STATUS_OK) { // wait in idling mode
72 pthread_cond_wait(&worker->impl_->condition_, &worker->impl_->mutex_);
73 }
74 if (worker->status_ == AVX_WORKER_STATUS_WORKING) {
75 // When worker->status_ is AVX_WORKER_STATUS_WORKING, the main thread
76 // doesn't change worker->status_ and will wait until the worker changes
77 // worker->status_ to AVX_WORKER_STATUS_OK. See change_state(). So the
78 // worker can safely call execute() without holding worker->impl_->mutex_.
79 // When the worker reacquires worker->impl_->mutex_, worker->status_ must
80 // still be AVX_WORKER_STATUS_WORKING.
81 pthread_mutex_unlock(&worker->impl_->mutex_);
82 execute(worker);
83 pthread_mutex_lock(&worker->impl_->mutex_);
84 assert(worker->status_ == AVX_WORKER_STATUS_WORKING);
85 worker->status_ = AVX_WORKER_STATUS_OK;
86 // signal to the main thread that we're done (for sync())
87 pthread_cond_signal(&worker->impl_->condition_);
88 } else {
89 assert(worker->status_ == AVX_WORKER_STATUS_NOT_OK); // finish the worker
90 break;
91 }
92 }
93 pthread_mutex_unlock(&worker->impl_->mutex_);
94 return THREAD_EXIT_SUCCESS; // Thread is finished
95 }
96
97 // main thread state control
change_state(AVxWorker * const worker,AVxWorkerStatus new_status)98 static void change_state(AVxWorker *const worker, AVxWorkerStatus new_status) {
99 // No-op when attempting to change state on a thread that didn't come up.
100 // Checking status_ without acquiring the lock first would result in a data
101 // race.
102 if (worker->impl_ == NULL) return;
103
104 pthread_mutex_lock(&worker->impl_->mutex_);
105 if (worker->status_ >= AVX_WORKER_STATUS_OK) {
106 // wait for the worker to finish
107 while (worker->status_ != AVX_WORKER_STATUS_OK) {
108 pthread_cond_wait(&worker->impl_->condition_, &worker->impl_->mutex_);
109 }
110 // assign new status and release the working thread if needed
111 if (new_status != AVX_WORKER_STATUS_OK) {
112 worker->status_ = new_status;
113 pthread_cond_signal(&worker->impl_->condition_);
114 }
115 }
116 pthread_mutex_unlock(&worker->impl_->mutex_);
117 }
118
119 #endif // CONFIG_MULTITHREAD
120
121 //------------------------------------------------------------------------------
122
init(AVxWorker * const worker)123 static void init(AVxWorker *const worker) {
124 memset(worker, 0, sizeof(*worker));
125 worker->status_ = AVX_WORKER_STATUS_NOT_OK;
126 }
127
sync(AVxWorker * const worker)128 static int sync(AVxWorker *const worker) {
129 #if CONFIG_MULTITHREAD
130 change_state(worker, AVX_WORKER_STATUS_OK);
131 #endif
132 assert(worker->status_ <= AVX_WORKER_STATUS_OK);
133 return !worker->had_error;
134 }
135
reset(AVxWorker * const worker)136 static int reset(AVxWorker *const worker) {
137 int ok = 1;
138 worker->had_error = 0;
139 if (worker->status_ < AVX_WORKER_STATUS_OK) {
140 #if CONFIG_MULTITHREAD
141 worker->impl_ = (AVxWorkerImpl *)aom_calloc(1, sizeof(*worker->impl_));
142 if (worker->impl_ == NULL) {
143 return 0;
144 }
145 if (pthread_mutex_init(&worker->impl_->mutex_, NULL)) {
146 goto Error;
147 }
148 if (pthread_cond_init(&worker->impl_->condition_, NULL)) {
149 pthread_mutex_destroy(&worker->impl_->mutex_);
150 goto Error;
151 }
152 pthread_attr_t attr;
153 if (pthread_attr_init(&attr)) goto Error2;
154 // Debug ASan builds require at least ~1MiB of stack; prevents
155 // failures on macOS arm64 where the default is 512KiB.
156 // See: https://crbug.com/aomedia/3379
157 #if defined(AOM_ADDRESS_SANITIZER) && defined(__APPLE__) && AOM_ARCH_ARM && \
158 !defined(NDEBUG)
159 const size_t kMinStackSize = 1024 * 1024;
160 #else
161 const size_t kMinStackSize = 256 * 1024;
162 #endif
163 size_t stacksize;
164 if (!pthread_attr_getstacksize(&attr, &stacksize)) {
165 if (stacksize < kMinStackSize &&
166 pthread_attr_setstacksize(&attr, kMinStackSize)) {
167 pthread_attr_destroy(&attr);
168 goto Error2;
169 }
170 }
171 pthread_mutex_lock(&worker->impl_->mutex_);
172 ok = !pthread_create(&worker->impl_->thread_, &attr, thread_loop, worker);
173 if (ok) worker->status_ = AVX_WORKER_STATUS_OK;
174 pthread_mutex_unlock(&worker->impl_->mutex_);
175 pthread_attr_destroy(&attr);
176 if (!ok) {
177 Error2:
178 pthread_mutex_destroy(&worker->impl_->mutex_);
179 pthread_cond_destroy(&worker->impl_->condition_);
180 Error:
181 aom_free(worker->impl_);
182 worker->impl_ = NULL;
183 return 0;
184 }
185 #else
186 worker->status_ = AVX_WORKER_STATUS_OK;
187 #endif
188 } else if (worker->status_ > AVX_WORKER_STATUS_OK) {
189 ok = sync(worker);
190 }
191 assert(!ok || (worker->status_ == AVX_WORKER_STATUS_OK));
192 return ok;
193 }
194
execute(AVxWorker * const worker)195 static void execute(AVxWorker *const worker) {
196 if (worker->hook != NULL) {
197 worker->had_error |= !worker->hook(worker->data1, worker->data2);
198 }
199 }
200
launch(AVxWorker * const worker)201 static void launch(AVxWorker *const worker) {
202 #if CONFIG_MULTITHREAD
203 change_state(worker, AVX_WORKER_STATUS_WORKING);
204 #else
205 execute(worker);
206 #endif
207 }
208
end(AVxWorker * const worker)209 static void end(AVxWorker *const worker) {
210 #if CONFIG_MULTITHREAD
211 if (worker->impl_ != NULL) {
212 change_state(worker, AVX_WORKER_STATUS_NOT_OK);
213 pthread_join(worker->impl_->thread_, NULL);
214 pthread_mutex_destroy(&worker->impl_->mutex_);
215 pthread_cond_destroy(&worker->impl_->condition_);
216 aom_free(worker->impl_);
217 worker->impl_ = NULL;
218 }
219 #else
220 worker->status_ = AVX_WORKER_STATUS_NOT_OK;
221 assert(worker->impl_ == NULL);
222 #endif
223 assert(worker->status_ == AVX_WORKER_STATUS_NOT_OK);
224 }
225
226 //------------------------------------------------------------------------------
227
228 static AVxWorkerInterface g_worker_interface = { init, reset, sync,
229 launch, execute, end };
230
aom_set_worker_interface(const AVxWorkerInterface * const winterface)231 int aom_set_worker_interface(const AVxWorkerInterface *const winterface) {
232 if (winterface == NULL || winterface->init == NULL ||
233 winterface->reset == NULL || winterface->sync == NULL ||
234 winterface->launch == NULL || winterface->execute == NULL ||
235 winterface->end == NULL) {
236 return 0;
237 }
238 g_worker_interface = *winterface;
239 return 1;
240 }
241
aom_get_worker_interface(void)242 const AVxWorkerInterface *aom_get_worker_interface(void) {
243 return &g_worker_interface;
244 }
245
246 //------------------------------------------------------------------------------
247