2 * Copyright (c) 2016, Alliance for Open Media. All rights reserved
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.
12 // Multi-threaded worker
15 // https://chromium.googlesource.com/webm/libwebp
17 // Enable GNU extensions in glibc so that we can call pthread_setname_np().
18 // This must be before any #include statements.
24 #include <string.h> // for memset()
26 #include "config/aom_config.h"
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"
33 #if CONFIG_MULTITHREAD
35 struct AVxWorkerImpl
{
36 pthread_mutex_t mutex_
;
37 pthread_cond_t condition_
;
41 //------------------------------------------------------------------------------
43 static void execute(AVxWorker
*const worker
); // Forward declaration.
45 static THREADFN
thread_loop(void *ptr
) {
46 AVxWorker
*const worker
= (AVxWorker
*)ptr
;
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
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
);
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).
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
);
69 pthread_mutex_lock(&worker
->impl_
->mutex_
);
71 while (worker
->status_
== AVX_WORKER_STATUS_OK
) { // wait in idling mode
72 pthread_cond_wait(&worker
->impl_
->condition_
, &worker
->impl_
->mutex_
);
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_
);
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_
);
89 assert(worker
->status_
== AVX_WORKER_STATUS_NOT_OK
); // finish the worker
93 pthread_mutex_unlock(&worker
->impl_
->mutex_
);
94 return THREAD_EXIT_SUCCESS
; // Thread is finished
97 // main thread state control
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
102 if (worker
->impl_
== NULL
) return;
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_
);
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_
);
116 pthread_mutex_unlock(&worker
->impl_
->mutex_
);
119 #endif // CONFIG_MULTITHREAD
121 //------------------------------------------------------------------------------
123 static void init(AVxWorker
*const worker
) {
124 memset(worker
, 0, sizeof(*worker
));
125 worker
->status_
= AVX_WORKER_STATUS_NOT_OK
;
128 static int sync(AVxWorker
*const worker
) {
129 #if CONFIG_MULTITHREAD
130 change_state(worker
, AVX_WORKER_STATUS_OK
);
132 assert(worker
->status_
<= AVX_WORKER_STATUS_OK
);
133 return !worker
->had_error
;
136 static int reset(AVxWorker
*const worker
) {
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
) {
145 if (pthread_mutex_init(&worker
->impl_
->mutex_
, NULL
)) {
148 if (pthread_cond_init(&worker
->impl_
->condition_
, NULL
)) {
149 pthread_mutex_destroy(&worker
->impl_
->mutex_
);
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 && \
160 if (!pthread_attr_getstacksize(&attr
, &stacksize
)) {
161 const size_t kMinStackSize
= 1 << 20; // 1 MiB
162 if (stacksize
< kMinStackSize
&&
163 pthread_attr_setstacksize(&attr
, kMinStackSize
)) {
164 pthread_attr_destroy(&attr
);
169 pthread_mutex_lock(&worker
->impl_
->mutex_
);
170 ok
= !pthread_create(&worker
->impl_
->thread_
, &attr
, thread_loop
, worker
);
171 if (ok
) worker
->status_
= AVX_WORKER_STATUS_OK
;
172 pthread_mutex_unlock(&worker
->impl_
->mutex_
);
173 pthread_attr_destroy(&attr
);
176 pthread_mutex_destroy(&worker
->impl_
->mutex_
);
177 pthread_cond_destroy(&worker
->impl_
->condition_
);
179 aom_free(worker
->impl_
);
180 worker
->impl_
= NULL
;
184 worker
->status_
= AVX_WORKER_STATUS_OK
;
186 } else if (worker
->status_
> AVX_WORKER_STATUS_OK
) {
189 assert(!ok
|| (worker
->status_
== AVX_WORKER_STATUS_OK
));
193 static void execute(AVxWorker
*const worker
) {
194 if (worker
->hook
!= NULL
) {
195 worker
->had_error
|= !worker
->hook(worker
->data1
, worker
->data2
);
199 static void launch(AVxWorker
*const worker
) {
200 #if CONFIG_MULTITHREAD
201 change_state(worker
, AVX_WORKER_STATUS_WORKING
);
207 static void end(AVxWorker
*const worker
) {
208 #if CONFIG_MULTITHREAD
209 if (worker
->impl_
!= NULL
) {
210 change_state(worker
, AVX_WORKER_STATUS_NOT_OK
);
211 pthread_join(worker
->impl_
->thread_
, NULL
);
212 pthread_mutex_destroy(&worker
->impl_
->mutex_
);
213 pthread_cond_destroy(&worker
->impl_
->condition_
);
214 aom_free(worker
->impl_
);
215 worker
->impl_
= NULL
;
218 worker
->status_
= AVX_WORKER_STATUS_NOT_OK
;
219 assert(worker
->impl_
== NULL
);
221 assert(worker
->status_
== AVX_WORKER_STATUS_NOT_OK
);
224 //------------------------------------------------------------------------------
226 static AVxWorkerInterface g_worker_interface
= { init
, reset
, sync
,
227 launch
, execute
, end
};
229 int aom_set_worker_interface(const AVxWorkerInterface
*const winterface
) {
230 if (winterface
== NULL
|| winterface
->init
== NULL
||
231 winterface
->reset
== NULL
|| winterface
->sync
== NULL
||
232 winterface
->launch
== NULL
|| winterface
->execute
== NULL
||
233 winterface
->end
== NULL
) {
236 g_worker_interface
= *winterface
;
240 const AVxWorkerInterface
*aom_get_worker_interface(void) {
241 return &g_worker_interface
;
244 //------------------------------------------------------------------------------