xref: /aosp_15_r20/external/libvpx/vpx_util/vpx_thread.c (revision fb1b10ab9aebc7c7068eedab379b749d7e3900be)
1 // Copyright 2013 Google Inc. All Rights Reserved.
2 //
3 // Use of this source code is governed by a BSD-style license
4 // that can be found in the COPYING file in the root of the source
5 // tree. An additional intellectual property rights grant can be found
6 // in the file PATENTS. All contributing project authors may
7 // be found in the AUTHORS file in the root of the source tree.
8 // -----------------------------------------------------------------------------
9 //
10 // Multi-threaded worker
11 //
12 // Original source:
13 //  https://chromium.googlesource.com/webm/libwebp
14 
15 // Enable GNU extensions in glibc so that we can call pthread_setname_np().
16 // This must be before any #include statements.
17 #ifndef _GNU_SOURCE
18 #define _GNU_SOURCE
19 #endif
20 
21 #include <assert.h>
22 #include <string.h>  // for memset()
23 #include "./vpx_config.h"
24 #include "./vpx_thread.h"
25 #include "vpx_mem/vpx_mem.h"
26 #include "vpx_util/vpx_pthread.h"
27 
28 #if CONFIG_MULTITHREAD
29 
30 struct VPxWorkerImpl {
31   pthread_mutex_t mutex_;
32   pthread_cond_t condition_;
33   pthread_t thread_;
34 };
35 
36 //------------------------------------------------------------------------------
37 
38 static void execute(VPxWorker *const worker);  // Forward declaration.
39 
thread_loop(void * ptr)40 static THREADFN thread_loop(void *ptr) {
41   VPxWorker *const worker = (VPxWorker *)ptr;
42 #ifdef __APPLE__
43   if (worker->thread_name != NULL) {
44     // Apple's version of pthread_setname_np takes one argument and operates on
45     // the current thread only. The maximum size of the thread_name buffer was
46     // noted in the Chromium source code and was confirmed by experiments. If
47     // thread_name is too long, pthread_setname_np returns -1 with errno
48     // ENAMETOOLONG (63).
49     char thread_name[64];
50     strncpy(thread_name, worker->thread_name, sizeof(thread_name) - 1);
51     thread_name[sizeof(thread_name) - 1] = '\0';
52     pthread_setname_np(thread_name);
53   }
54 #elif (defined(__GLIBC__) && !defined(__GNU__)) || defined(__BIONIC__)
55   if (worker->thread_name != NULL) {
56     // Linux and Android require names (with nul) fit in 16 chars, otherwise
57     // pthread_setname_np() returns ERANGE (34).
58     char thread_name[16];
59     strncpy(thread_name, worker->thread_name, sizeof(thread_name) - 1);
60     thread_name[sizeof(thread_name) - 1] = '\0';
61     pthread_setname_np(pthread_self(), thread_name);
62   }
63 #endif
64   pthread_mutex_lock(&worker->impl_->mutex_);
65   for (;;) {
66     while (worker->status_ == VPX_WORKER_STATUS_OK) {  // wait in idling mode
67       pthread_cond_wait(&worker->impl_->condition_, &worker->impl_->mutex_);
68     }
69     if (worker->status_ == VPX_WORKER_STATUS_WORKING) {
70       // When worker->status_ is VPX_WORKER_STATUS_WORKING, the main thread
71       // doesn't change worker->status_ and will wait until the worker changes
72       // worker->status_ to VPX_WORKER_STATUS_OK. See change_state(). So the
73       // worker can safely call execute() without holding worker->impl_->mutex_.
74       // When the worker reacquires worker->impl_->mutex_, worker->status_ must
75       // still be VPX_WORKER_STATUS_WORKING.
76       pthread_mutex_unlock(&worker->impl_->mutex_);
77       execute(worker);
78       pthread_mutex_lock(&worker->impl_->mutex_);
79       assert(worker->status_ == VPX_WORKER_STATUS_WORKING);
80       worker->status_ = VPX_WORKER_STATUS_OK;
81       // signal to the main thread that we're done (for sync())
82       pthread_cond_signal(&worker->impl_->condition_);
83     } else {
84       assert(worker->status_ == VPX_WORKER_STATUS_NOT_OK);  // finish the worker
85       break;
86     }
87   }
88   pthread_mutex_unlock(&worker->impl_->mutex_);
89   return THREAD_EXIT_SUCCESS;  // Thread is finished
90 }
91 
92 // main thread state control
change_state(VPxWorker * const worker,VPxWorkerStatus new_status)93 static void change_state(VPxWorker *const worker, VPxWorkerStatus new_status) {
94   // No-op when attempting to change state on a thread that didn't come up.
95   // Checking status_ without acquiring the lock first would result in a data
96   // race.
97   if (worker->impl_ == NULL) return;
98 
99   pthread_mutex_lock(&worker->impl_->mutex_);
100   if (worker->status_ >= VPX_WORKER_STATUS_OK) {
101     // wait for the worker to finish
102     while (worker->status_ != VPX_WORKER_STATUS_OK) {
103       pthread_cond_wait(&worker->impl_->condition_, &worker->impl_->mutex_);
104     }
105     // assign new status and release the working thread if needed
106     if (new_status != VPX_WORKER_STATUS_OK) {
107       worker->status_ = new_status;
108       pthread_cond_signal(&worker->impl_->condition_);
109     }
110   }
111   pthread_mutex_unlock(&worker->impl_->mutex_);
112 }
113 
114 #endif  // CONFIG_MULTITHREAD
115 
116 //------------------------------------------------------------------------------
117 
init(VPxWorker * const worker)118 static void init(VPxWorker *const worker) {
119   memset(worker, 0, sizeof(*worker));
120   worker->status_ = VPX_WORKER_STATUS_NOT_OK;
121 }
122 
sync(VPxWorker * const worker)123 static int sync(VPxWorker *const worker) {
124 #if CONFIG_MULTITHREAD
125   change_state(worker, VPX_WORKER_STATUS_OK);
126 #endif
127   assert(worker->status_ <= VPX_WORKER_STATUS_OK);
128   return !worker->had_error;
129 }
130 
reset(VPxWorker * const worker)131 static int reset(VPxWorker *const worker) {
132   int ok = 1;
133   worker->had_error = 0;
134   if (worker->status_ < VPX_WORKER_STATUS_OK) {
135 #if CONFIG_MULTITHREAD
136     worker->impl_ = (VPxWorkerImpl *)vpx_calloc(1, sizeof(*worker->impl_));
137     if (worker->impl_ == NULL) {
138       return 0;
139     }
140     if (pthread_mutex_init(&worker->impl_->mutex_, NULL)) {
141       goto Error;
142     }
143     if (pthread_cond_init(&worker->impl_->condition_, NULL)) {
144       pthread_mutex_destroy(&worker->impl_->mutex_);
145       goto Error;
146     }
147     pthread_mutex_lock(&worker->impl_->mutex_);
148     ok = !pthread_create(&worker->impl_->thread_, NULL, thread_loop, worker);
149     if (ok) worker->status_ = VPX_WORKER_STATUS_OK;
150     pthread_mutex_unlock(&worker->impl_->mutex_);
151     if (!ok) {
152       pthread_mutex_destroy(&worker->impl_->mutex_);
153       pthread_cond_destroy(&worker->impl_->condition_);
154     Error:
155       vpx_free(worker->impl_);
156       worker->impl_ = NULL;
157       return 0;
158     }
159 #else
160     worker->status_ = VPX_WORKER_STATUS_OK;
161 #endif
162   } else if (worker->status_ > VPX_WORKER_STATUS_OK) {
163     ok = sync(worker);
164   }
165   assert(!ok || (worker->status_ == VPX_WORKER_STATUS_OK));
166   return ok;
167 }
168 
execute(VPxWorker * const worker)169 static void execute(VPxWorker *const worker) {
170   if (worker->hook != NULL) {
171     worker->had_error |= !worker->hook(worker->data1, worker->data2);
172   }
173 }
174 
launch(VPxWorker * const worker)175 static void launch(VPxWorker *const worker) {
176 #if CONFIG_MULTITHREAD
177   change_state(worker, VPX_WORKER_STATUS_WORKING);
178 #else
179   execute(worker);
180 #endif
181 }
182 
end(VPxWorker * const worker)183 static void end(VPxWorker *const worker) {
184 #if CONFIG_MULTITHREAD
185   if (worker->impl_ != NULL) {
186     change_state(worker, VPX_WORKER_STATUS_NOT_OK);
187     pthread_join(worker->impl_->thread_, NULL);
188     pthread_mutex_destroy(&worker->impl_->mutex_);
189     pthread_cond_destroy(&worker->impl_->condition_);
190     vpx_free(worker->impl_);
191     worker->impl_ = NULL;
192   }
193 #else
194   worker->status_ = VPX_WORKER_STATUS_NOT_OK;
195   assert(worker->impl_ == NULL);
196 #endif
197   assert(worker->status_ == VPX_WORKER_STATUS_NOT_OK);
198 }
199 
200 //------------------------------------------------------------------------------
201 
202 static VPxWorkerInterface g_worker_interface = { init,   reset,   sync,
203                                                  launch, execute, end };
204 
vpx_set_worker_interface(const VPxWorkerInterface * const winterface)205 int vpx_set_worker_interface(const VPxWorkerInterface *const winterface) {
206   if (winterface == NULL || winterface->init == NULL ||
207       winterface->reset == NULL || winterface->sync == NULL ||
208       winterface->launch == NULL || winterface->execute == NULL ||
209       winterface->end == NULL) {
210     return 0;
211   }
212   g_worker_interface = *winterface;
213   return 1;
214 }
215 
vpx_get_worker_interface(void)216 const VPxWorkerInterface *vpx_get_worker_interface(void) {
217   return &g_worker_interface;
218 }
219 
220 //------------------------------------------------------------------------------
221