Fortunes about QT and "hearing" video codecs
[vlc.git] / src / misc / background_worker.c
blobacb93cd0dfb45ead312044acc44bbbe40f913ab6
1 /*****************************************************************************
2 * Copyright (C) 2017 VLC authors and VideoLAN
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; either version 2.1 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
17 *****************************************************************************/
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #endif
23 #include <assert.h>
24 #include <vlc_common.h>
25 #include <vlc_atomic.h>
26 #include <vlc_list.h>
27 #include <vlc_threads.h>
29 #include "libvlc.h"
30 #include "background_worker.h"
32 struct task {
33 struct vlc_list node;
34 void* id; /**< id associated with entity */
35 void* entity; /**< the entity to process */
36 vlc_tick_t timeout; /**< timeout duration in vlc_tick_t */
39 struct background_worker;
41 struct background_thread {
42 struct background_worker *owner;
43 vlc_cond_t probe_cancel_wait; /**< wait for probe request or cancelation */
44 bool probe; /**< true if a probe is requested */
45 bool cancel; /**< true if a cancel is requested */
46 struct task *task; /**< current task */
47 struct vlc_list node;
50 struct background_worker {
51 void* owner;
52 struct background_worker_config conf;
54 vlc_mutex_t lock;
56 int uncompleted; /**< number of tasks requested but not completed */
57 int nthreads; /**< number of threads in the threads list */
58 struct vlc_list threads; /**< list of active background_thread instances */
60 struct vlc_list queue; /**< queue of tasks */
61 vlc_cond_t queue_wait; /**< wait for the queue to be non-empty */
63 vlc_cond_t nothreads_wait; /**< wait for nthreads == 0 */
64 bool closing; /**< true if background worker deletion is requested */
67 static struct task *task_Create(struct background_worker *worker, void *id,
68 void *entity, int timeout)
70 struct task *task = malloc(sizeof(*task));
71 if (unlikely(!task))
72 return NULL;
74 task->id = id;
75 task->entity = entity;
76 task->timeout = timeout < 0 ? worker->conf.default_timeout : VLC_TICK_FROM_MS(timeout);
77 worker->conf.pf_hold(task->entity);
78 return task;
81 static void task_Destroy(struct background_worker *worker, struct task *task)
83 worker->conf.pf_release(task->entity);
84 free(task);
87 static struct task *QueueTake(struct background_worker *worker, int timeout_ms)
89 vlc_mutex_assert(&worker->lock);
91 vlc_tick_t deadline = vlc_tick_now() + VLC_TICK_FROM_MS(timeout_ms);
92 bool timeout = false;
93 while (!timeout && !worker->closing && vlc_list_is_empty(&worker->queue))
94 timeout = vlc_cond_timedwait(&worker->queue_wait,
95 &worker->lock, deadline) != 0;
97 if (worker->closing || timeout)
98 return NULL;
100 struct task *task = vlc_list_first_entry_or_null(&worker->queue,
101 struct task, node);
102 assert(task);
103 vlc_list_remove(&task->node);
105 return task;
108 static void QueuePush(struct background_worker *worker, struct task *task)
110 vlc_mutex_assert(&worker->lock);
111 vlc_list_append(&task->node, &worker->queue);
112 vlc_cond_signal(&worker->queue_wait);
115 static void QueueRemoveAll(struct background_worker *worker, void *id)
117 vlc_mutex_assert(&worker->lock);
118 struct task *task;
119 vlc_list_foreach(task, &worker->queue, node)
121 if (!id || task->id == id)
123 vlc_list_remove(&task->node);
124 task_Destroy(worker, task);
129 static struct background_thread *
130 background_thread_Create(struct background_worker *owner)
132 struct background_thread *thread = malloc(sizeof(*thread));
133 if (!thread)
134 return NULL;
136 vlc_cond_init(&thread->probe_cancel_wait);
137 thread->probe = false;
138 thread->cancel = false;
139 thread->task = NULL;
140 thread->owner = owner;
141 return thread;
144 static void background_thread_Destroy(struct background_thread *thread)
146 vlc_cond_destroy(&thread->probe_cancel_wait);
147 free(thread);
150 static struct background_worker *background_worker_Create(void *owner,
151 struct background_worker_config *conf)
153 struct background_worker* worker = malloc(sizeof(*worker));
154 if (unlikely(!worker))
155 return NULL;
157 worker->conf = *conf;
158 worker->owner = owner;
160 vlc_mutex_init(&worker->lock);
161 worker->uncompleted = 0;
162 worker->nthreads = 0;
163 vlc_list_init(&worker->threads);
164 vlc_list_init(&worker->queue);
165 vlc_cond_init(&worker->queue_wait);
166 vlc_cond_init(&worker->nothreads_wait);
167 worker->closing = false;
168 return worker;
171 static void background_worker_Destroy(struct background_worker *worker)
173 vlc_cond_destroy(&worker->queue_wait);
174 vlc_mutex_destroy(&worker->lock);
175 free(worker);
178 static void TerminateTask(struct background_thread *thread, struct task *task)
180 struct background_worker *worker = thread->owner;
181 task_Destroy(worker, task);
183 vlc_mutex_lock(&worker->lock);
184 thread->task = NULL;
185 worker->uncompleted--;
186 assert(worker->uncompleted >= 0);
187 vlc_mutex_unlock(&worker->lock);
190 static void RemoveThread(struct background_thread *thread)
192 struct background_worker *worker = thread->owner;
194 vlc_mutex_lock(&worker->lock);
196 vlc_list_remove(&thread->node);
197 worker->nthreads--;
198 assert(worker->nthreads >= 0);
199 if (!worker->nthreads)
200 vlc_cond_signal(&worker->nothreads_wait);
202 vlc_mutex_unlock(&worker->lock);
204 background_thread_Destroy(thread);
207 static void* Thread( void* data )
209 struct background_thread *thread = data;
210 struct background_worker *worker = thread->owner;
212 for (;;)
214 vlc_mutex_lock(&worker->lock);
215 struct task *task = QueueTake(worker, 5000);
216 if (!task)
218 vlc_mutex_unlock(&worker->lock);
219 /* terminate this thread */
220 break;
223 thread->task = task;
224 thread->cancel = false;
225 thread->probe = false;
226 vlc_tick_t deadline;
227 if (task->timeout > 0)
228 deadline = vlc_tick_now() + task->timeout;
229 else
230 deadline = INT64_MAX; /* no deadline */
231 vlc_mutex_unlock(&worker->lock);
233 void *handle;
234 if (worker->conf.pf_start(worker->owner, task->entity, &handle))
236 TerminateTask(thread, task);
237 continue;
240 for (;;)
242 vlc_mutex_lock(&worker->lock);
243 bool timeout = false;
244 while (!timeout && !thread->probe && !thread->cancel)
245 /* any non-zero return value means timeout */
246 timeout = vlc_cond_timedwait(&thread->probe_cancel_wait,
247 &worker->lock, deadline) != 0;
249 bool cancel = thread->cancel;
250 thread->cancel = false;
251 thread->probe = false;
252 vlc_mutex_unlock(&worker->lock);
254 if (timeout || cancel
255 || worker->conf.pf_probe(worker->owner, handle))
257 worker->conf.pf_stop(worker->owner, handle);
258 TerminateTask(thread, task);
259 break;
264 RemoveThread(thread);
266 return NULL;
269 static bool SpawnThread(struct background_worker *worker)
271 vlc_mutex_assert(&worker->lock);
273 struct background_thread *thread = background_thread_Create(worker);
274 if (!thread)
275 return false;
277 if (vlc_clone_detach(NULL, Thread, thread, VLC_THREAD_PRIORITY_LOW))
279 free(thread);
280 return false;
282 worker->nthreads++;
283 vlc_list_append(&thread->node, &worker->threads);
285 return true;
288 struct background_worker* background_worker_New( void* owner,
289 struct background_worker_config* conf )
291 return background_worker_Create(owner, conf);
294 int background_worker_Push( struct background_worker* worker, void* entity,
295 void* id, int timeout )
297 struct task *task = task_Create(worker, id, entity, timeout);
298 if (unlikely(!task))
299 return VLC_ENOMEM;
301 vlc_mutex_lock(&worker->lock);
302 QueuePush(worker, task);
303 if (++worker->uncompleted > worker->nthreads
304 && worker->nthreads < worker->conf.max_threads)
305 SpawnThread(worker);
306 vlc_mutex_unlock(&worker->lock);
308 return VLC_SUCCESS;
311 static void BackgroundWorkerCancelLocked(struct background_worker *worker,
312 void *id)
314 vlc_mutex_assert(&worker->lock);
316 QueueRemoveAll(worker, id);
318 struct background_thread *thread;
319 vlc_list_foreach(thread, &worker->threads, node)
321 if (!id || (thread->task && thread->task->id == id && !thread->cancel))
323 thread->cancel = true;
324 vlc_cond_signal(&thread->probe_cancel_wait);
329 void background_worker_Cancel( struct background_worker* worker, void* id )
331 vlc_mutex_lock(&worker->lock);
332 BackgroundWorkerCancelLocked(worker, id);
333 vlc_mutex_unlock(&worker->lock);
336 void background_worker_RequestProbe( struct background_worker* worker )
338 vlc_mutex_lock(&worker->lock);
340 struct background_thread *thread;
341 vlc_list_foreach(thread, &worker->threads, node)
343 thread->probe = true;
344 vlc_cond_signal(&thread->probe_cancel_wait);
347 vlc_mutex_unlock(&worker->lock);
350 void background_worker_Delete( struct background_worker* worker )
352 vlc_mutex_lock(&worker->lock);
354 worker->closing = true;
355 BackgroundWorkerCancelLocked(worker, NULL);
356 /* closing is now true, this will wake up any QueueTake() */
357 vlc_cond_broadcast(&worker->queue_wait);
359 while (worker->nthreads)
360 vlc_cond_wait(&worker->nothreads_wait, &worker->lock);
362 vlc_mutex_unlock(&worker->lock);
364 /* no threads use the worker anymore, we can destroy it */
365 background_worker_Destroy(worker);