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 *****************************************************************************/
24 #include <vlc_common.h>
25 #include <vlc_atomic.h>
27 #include <vlc_threads.h>
30 #include "background_worker.h"
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 */
50 struct background_worker
{
52 struct background_worker_config conf
;
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
));
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
);
81 static void task_Destroy(struct background_worker
*worker
, struct task
*task
)
83 worker
->conf
.pf_release(task
->entity
);
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
);
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
)
100 struct task
*task
= vlc_list_first_entry_or_null(&worker
->queue
,
103 vlc_list_remove(&task
->node
);
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
);
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
));
136 vlc_cond_init(&thread
->probe_cancel_wait
);
137 thread
->probe
= false;
138 thread
->cancel
= false;
140 thread
->owner
= owner
;
144 static void background_thread_Destroy(struct background_thread
*thread
)
146 vlc_cond_destroy(&thread
->probe_cancel_wait
);
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
))
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;
171 static void background_worker_Destroy(struct background_worker
*worker
)
173 vlc_cond_destroy(&worker
->queue_wait
);
174 vlc_mutex_destroy(&worker
->lock
);
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
);
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
);
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
;
214 vlc_mutex_lock(&worker
->lock
);
215 struct task
*task
= QueueTake(worker
, 5000);
218 vlc_mutex_unlock(&worker
->lock
);
219 /* terminate this thread */
224 thread
->cancel
= false;
225 thread
->probe
= false;
227 if (task
->timeout
> 0)
228 deadline
= vlc_tick_now() + task
->timeout
;
230 deadline
= INT64_MAX
; /* no deadline */
231 vlc_mutex_unlock(&worker
->lock
);
234 if (worker
->conf
.pf_start(worker
->owner
, task
->entity
, &handle
))
236 TerminateTask(thread
, task
);
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
);
264 RemoveThread(thread
);
269 static bool SpawnThread(struct background_worker
*worker
)
271 vlc_mutex_assert(&worker
->lock
);
273 struct background_thread
*thread
= background_thread_Create(worker
);
277 if (vlc_clone_detach(NULL
, Thread
, thread
, VLC_THREAD_PRIORITY_LOW
))
283 vlc_list_append(&thread
->node
, &worker
->threads
);
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
);
301 vlc_mutex_lock(&worker
->lock
);
302 QueuePush(worker
, task
);
303 if (++worker
->uncompleted
> worker
->nthreads
304 && worker
->nthreads
< worker
->conf
.max_threads
)
306 vlc_mutex_unlock(&worker
->lock
);
311 static void BackgroundWorkerCancelLocked(struct background_worker
*worker
,
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
);