/src/samba/lib/pthreadpool/pthreadpool.c
Line | Count | Source |
1 | | /* |
2 | | * Unix SMB/CIFS implementation. |
3 | | * thread pool implementation |
4 | | * Copyright (C) Volker Lendecke 2009 |
5 | | * |
6 | | * This program is free software; you can redistribute it and/or modify |
7 | | * it under the terms of the GNU General Public License as published by |
8 | | * the Free Software Foundation; either version 3 of the License, or |
9 | | * (at your option) any later version. |
10 | | * |
11 | | * This program is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | * GNU General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU General Public License |
17 | | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
18 | | */ |
19 | | |
20 | | #include "replace.h" |
21 | | #include "system/time.h" |
22 | | #include "system/wait.h" |
23 | | #include "system/threads.h" |
24 | | #include "system/filesys.h" |
25 | | #include "pthreadpool.h" |
26 | | #include "lib/util/dlinklist.h" |
27 | | |
28 | | #ifdef NDEBUG |
29 | | #undef NDEBUG |
30 | | #endif |
31 | | |
32 | | #include <assert.h> |
33 | | |
34 | | struct pthreadpool_job { |
35 | | int id; |
36 | | void (*fn)(void *private_data); |
37 | | void *private_data; |
38 | | }; |
39 | | |
40 | | struct pthreadpool { |
41 | | /* |
42 | | * List pthreadpools for fork safety |
43 | | */ |
44 | | struct pthreadpool *prev, *next; |
45 | | |
46 | | /* |
47 | | * Control access to this struct |
48 | | */ |
49 | | pthread_mutex_t mutex; |
50 | | |
51 | | /* |
52 | | * Threads waiting for work do so here |
53 | | */ |
54 | | pthread_cond_t condvar; |
55 | | |
56 | | /* |
57 | | * Array of jobs |
58 | | */ |
59 | | size_t jobs_array_len; |
60 | | struct pthreadpool_job *jobs; |
61 | | |
62 | | size_t head; |
63 | | size_t num_jobs; |
64 | | |
65 | | /* |
66 | | * Indicate job completion |
67 | | */ |
68 | | int (*signal_fn)(int jobid, |
69 | | void (*job_fn)(void *private_data), |
70 | | void *job_fn_private_data, |
71 | | void *private_data); |
72 | | void *signal_fn_private_data; |
73 | | |
74 | | /* |
75 | | * indicator to worker threads to stop processing further jobs |
76 | | * and exit. |
77 | | */ |
78 | | bool stopped; |
79 | | |
80 | | /* |
81 | | * indicator to the last worker thread to free the pool |
82 | | * resources. |
83 | | */ |
84 | | bool destroyed; |
85 | | |
86 | | /* |
87 | | * maximum number of threads |
88 | | * 0 means no real thread, only strict sync processing. |
89 | | */ |
90 | | unsigned max_threads; |
91 | | |
92 | | /* |
93 | | * Number of threads |
94 | | */ |
95 | | unsigned num_threads; |
96 | | |
97 | | /* |
98 | | * Number of idle threads |
99 | | */ |
100 | | unsigned num_idle; |
101 | | |
102 | | /* |
103 | | * Condition variable indicating that helper threads should |
104 | | * quickly go away making way for fork() without anybody |
105 | | * waiting on pool->condvar. |
106 | | */ |
107 | | pthread_cond_t *prefork_cond; |
108 | | |
109 | | /* |
110 | | * Waiting position for helper threads while fork is |
111 | | * running. The forking thread will have locked it, and all |
112 | | * idle helper threads will sit here until after the fork, |
113 | | * where the forking thread will unlock it again. |
114 | | */ |
115 | | pthread_mutex_t fork_mutex; |
116 | | }; |
117 | | |
118 | | static pthread_mutex_t pthreadpools_mutex = PTHREAD_MUTEX_INITIALIZER; |
119 | | static struct pthreadpool *pthreadpools = NULL; |
120 | | static pthread_once_t pthreadpool_atfork_initialized = PTHREAD_ONCE_INIT; |
121 | | |
122 | | static void pthreadpool_prep_atfork(void); |
123 | | |
124 | | /* |
125 | | * Initialize a thread pool |
126 | | */ |
127 | | |
128 | | int pthreadpool_init(unsigned max_threads, struct pthreadpool **presult, |
129 | | int (*signal_fn)(int jobid, |
130 | | void (*job_fn)(void *private_data), |
131 | | void *job_fn_private_data, |
132 | | void *private_data), |
133 | | void *signal_fn_private_data) |
134 | 0 | { |
135 | 0 | struct pthreadpool *pool; |
136 | 0 | int ret; |
137 | |
|
138 | 0 | pool = (struct pthreadpool *)malloc(sizeof(struct pthreadpool)); |
139 | 0 | if (pool == NULL) { |
140 | 0 | return ENOMEM; |
141 | 0 | } |
142 | 0 | pool->signal_fn = signal_fn; |
143 | 0 | pool->signal_fn_private_data = signal_fn_private_data; |
144 | |
|
145 | 0 | pool->jobs_array_len = 4; |
146 | 0 | pool->jobs = calloc( |
147 | 0 | pool->jobs_array_len, sizeof(struct pthreadpool_job)); |
148 | |
|
149 | 0 | if (pool->jobs == NULL) { |
150 | 0 | free(pool); |
151 | 0 | return ENOMEM; |
152 | 0 | } |
153 | | |
154 | 0 | pool->head = pool->num_jobs = 0; |
155 | |
|
156 | 0 | ret = pthread_mutex_init(&pool->mutex, NULL); |
157 | 0 | if (ret != 0) { |
158 | 0 | free(pool->jobs); |
159 | 0 | free(pool); |
160 | 0 | return ret; |
161 | 0 | } |
162 | | |
163 | 0 | ret = pthread_cond_init(&pool->condvar, NULL); |
164 | 0 | if (ret != 0) { |
165 | 0 | pthread_mutex_destroy(&pool->mutex); |
166 | 0 | free(pool->jobs); |
167 | 0 | free(pool); |
168 | 0 | return ret; |
169 | 0 | } |
170 | | |
171 | 0 | ret = pthread_mutex_init(&pool->fork_mutex, NULL); |
172 | 0 | if (ret != 0) { |
173 | 0 | pthread_cond_destroy(&pool->condvar); |
174 | 0 | pthread_mutex_destroy(&pool->mutex); |
175 | 0 | free(pool->jobs); |
176 | 0 | free(pool); |
177 | 0 | return ret; |
178 | 0 | } |
179 | | |
180 | 0 | pool->stopped = false; |
181 | 0 | pool->destroyed = false; |
182 | 0 | pool->num_threads = 0; |
183 | 0 | pool->max_threads = max_threads; |
184 | 0 | pool->num_idle = 0; |
185 | 0 | pool->prefork_cond = NULL; |
186 | |
|
187 | 0 | ret = pthread_mutex_lock(&pthreadpools_mutex); |
188 | 0 | if (ret != 0) { |
189 | 0 | pthread_mutex_destroy(&pool->fork_mutex); |
190 | 0 | pthread_cond_destroy(&pool->condvar); |
191 | 0 | pthread_mutex_destroy(&pool->mutex); |
192 | 0 | free(pool->jobs); |
193 | 0 | free(pool); |
194 | 0 | return ret; |
195 | 0 | } |
196 | 0 | DLIST_ADD(pthreadpools, pool); |
197 | |
|
198 | 0 | ret = pthread_mutex_unlock(&pthreadpools_mutex); |
199 | 0 | assert(ret == 0); |
200 | |
|
201 | 0 | pthread_once(&pthreadpool_atfork_initialized, pthreadpool_prep_atfork); |
202 | |
|
203 | 0 | *presult = pool; |
204 | |
|
205 | 0 | return 0; |
206 | 0 | } |
207 | | |
208 | | size_t pthreadpool_max_threads(struct pthreadpool *pool) |
209 | 0 | { |
210 | 0 | if (pool->stopped) { |
211 | 0 | return 0; |
212 | 0 | } |
213 | | |
214 | 0 | return pool->max_threads; |
215 | 0 | } |
216 | | |
217 | | size_t pthreadpool_queued_jobs(struct pthreadpool *pool) |
218 | 0 | { |
219 | 0 | int res; |
220 | 0 | int unlock_res; |
221 | 0 | size_t ret; |
222 | |
|
223 | 0 | if (pool->stopped) { |
224 | 0 | return 0; |
225 | 0 | } |
226 | | |
227 | 0 | res = pthread_mutex_lock(&pool->mutex); |
228 | 0 | if (res != 0) { |
229 | 0 | return res; |
230 | 0 | } |
231 | | |
232 | 0 | if (pool->stopped) { |
233 | 0 | unlock_res = pthread_mutex_unlock(&pool->mutex); |
234 | 0 | assert(unlock_res == 0); |
235 | 0 | return 0; |
236 | 0 | } |
237 | | |
238 | 0 | ret = pool->num_jobs; |
239 | |
|
240 | 0 | unlock_res = pthread_mutex_unlock(&pool->mutex); |
241 | 0 | assert(unlock_res == 0); |
242 | 0 | return ret; |
243 | 0 | } |
244 | | |
245 | | static void pthreadpool_prepare_pool(struct pthreadpool *pool) |
246 | 0 | { |
247 | 0 | int ret; |
248 | |
|
249 | 0 | ret = pthread_mutex_lock(&pool->fork_mutex); |
250 | 0 | assert(ret == 0); |
251 | |
|
252 | 0 | ret = pthread_mutex_lock(&pool->mutex); |
253 | 0 | assert(ret == 0); |
254 | |
|
255 | 0 | while (pool->num_idle != 0) { |
256 | 0 | unsigned num_idle = pool->num_idle; |
257 | 0 | pthread_cond_t prefork_cond; |
258 | |
|
259 | 0 | ret = pthread_cond_init(&prefork_cond, NULL); |
260 | 0 | assert(ret == 0); |
261 | | |
262 | | /* |
263 | | * Push all idle threads off pool->condvar. In the |
264 | | * child we can destroy the pool, which would result |
265 | | * in undefined behaviour in the |
266 | | * pthread_cond_destroy(pool->condvar). glibc just |
267 | | * blocks here. |
268 | | */ |
269 | 0 | pool->prefork_cond = &prefork_cond; |
270 | |
|
271 | 0 | ret = pthread_cond_signal(&pool->condvar); |
272 | 0 | assert(ret == 0); |
273 | |
|
274 | 0 | while (pool->num_idle == num_idle) { |
275 | 0 | ret = pthread_cond_wait(&prefork_cond, &pool->mutex); |
276 | 0 | assert(ret == 0); |
277 | 0 | } |
278 | |
|
279 | 0 | pool->prefork_cond = NULL; |
280 | |
|
281 | 0 | ret = pthread_cond_destroy(&prefork_cond); |
282 | 0 | assert(ret == 0); |
283 | 0 | } |
284 | | |
285 | | /* |
286 | | * Probably it's well-defined somewhere: What happens to |
287 | | * condvars after a fork? The rationale of pthread_atfork only |
288 | | * writes about mutexes. So better be safe than sorry and |
289 | | * destroy/reinit pool->condvar across a fork. |
290 | | */ |
291 | |
|
292 | 0 | ret = pthread_cond_destroy(&pool->condvar); |
293 | 0 | assert(ret == 0); |
294 | 0 | } |
295 | | |
296 | | static void pthreadpool_prepare(void) |
297 | 0 | { |
298 | 0 | int ret; |
299 | 0 | struct pthreadpool *pool; |
300 | |
|
301 | 0 | ret = pthread_mutex_lock(&pthreadpools_mutex); |
302 | 0 | assert(ret == 0); |
303 | |
|
304 | 0 | pool = pthreadpools; |
305 | |
|
306 | 0 | while (pool != NULL) { |
307 | 0 | pthreadpool_prepare_pool(pool); |
308 | 0 | pool = pool->next; |
309 | 0 | } |
310 | 0 | } |
311 | | |
312 | | static void pthreadpool_parent(void) |
313 | 0 | { |
314 | 0 | int ret; |
315 | 0 | struct pthreadpool *pool; |
316 | |
|
317 | 0 | for (pool = DLIST_TAIL(pthreadpools); |
318 | 0 | pool != NULL; |
319 | 0 | pool = DLIST_PREV(pool)) { |
320 | 0 | ret = pthread_cond_init(&pool->condvar, NULL); |
321 | 0 | assert(ret == 0); |
322 | 0 | ret = pthread_mutex_unlock(&pool->mutex); |
323 | 0 | assert(ret == 0); |
324 | 0 | ret = pthread_mutex_unlock(&pool->fork_mutex); |
325 | 0 | assert(ret == 0); |
326 | 0 | } |
327 | |
|
328 | 0 | ret = pthread_mutex_unlock(&pthreadpools_mutex); |
329 | 0 | assert(ret == 0); |
330 | 0 | } |
331 | | |
332 | | static void pthreadpool_child(void) |
333 | 0 | { |
334 | 0 | int ret; |
335 | 0 | struct pthreadpool *pool; |
336 | |
|
337 | 0 | for (pool = DLIST_TAIL(pthreadpools); |
338 | 0 | pool != NULL; |
339 | 0 | pool = DLIST_PREV(pool)) { |
340 | |
|
341 | 0 | pool->num_threads = 0; |
342 | 0 | pool->num_idle = 0; |
343 | 0 | pool->head = 0; |
344 | 0 | pool->num_jobs = 0; |
345 | 0 | pool->stopped = true; |
346 | |
|
347 | 0 | ret = pthread_cond_init(&pool->condvar, NULL); |
348 | 0 | assert(ret == 0); |
349 | |
|
350 | 0 | ret = pthread_mutex_unlock(&pool->mutex); |
351 | 0 | assert(ret == 0); |
352 | |
|
353 | 0 | ret = pthread_mutex_unlock(&pool->fork_mutex); |
354 | 0 | assert(ret == 0); |
355 | 0 | } |
356 | |
|
357 | 0 | ret = pthread_mutex_unlock(&pthreadpools_mutex); |
358 | 0 | assert(ret == 0); |
359 | 0 | } |
360 | | |
361 | | static void pthreadpool_prep_atfork(void) |
362 | 0 | { |
363 | 0 | pthread_atfork(pthreadpool_prepare, pthreadpool_parent, |
364 | 0 | pthreadpool_child); |
365 | 0 | } |
366 | | |
367 | | static int pthreadpool_free(struct pthreadpool *pool) |
368 | 0 | { |
369 | 0 | int ret, ret1, ret2; |
370 | |
|
371 | 0 | ret = pthread_mutex_lock(&pthreadpools_mutex); |
372 | 0 | if (ret != 0) { |
373 | 0 | return ret; |
374 | 0 | } |
375 | 0 | DLIST_REMOVE(pthreadpools, pool); |
376 | 0 | ret = pthread_mutex_unlock(&pthreadpools_mutex); |
377 | 0 | assert(ret == 0); |
378 | |
|
379 | 0 | ret = pthread_mutex_lock(&pool->mutex); |
380 | 0 | assert(ret == 0); |
381 | 0 | ret = pthread_mutex_unlock(&pool->mutex); |
382 | 0 | assert(ret == 0); |
383 | |
|
384 | 0 | ret = pthread_mutex_destroy(&pool->mutex); |
385 | 0 | ret1 = pthread_cond_destroy(&pool->condvar); |
386 | 0 | ret2 = pthread_mutex_destroy(&pool->fork_mutex); |
387 | |
|
388 | 0 | if (ret != 0) { |
389 | 0 | return ret; |
390 | 0 | } |
391 | 0 | if (ret1 != 0) { |
392 | 0 | return ret1; |
393 | 0 | } |
394 | 0 | if (ret2 != 0) { |
395 | 0 | return ret2; |
396 | 0 | } |
397 | | |
398 | 0 | free(pool->jobs); |
399 | 0 | free(pool); |
400 | |
|
401 | 0 | return 0; |
402 | 0 | } |
403 | | |
404 | | /* |
405 | | * Stop a thread pool. Wake up all idle threads for exit. |
406 | | */ |
407 | | |
408 | | static int pthreadpool_stop_locked(struct pthreadpool *pool) |
409 | 0 | { |
410 | 0 | int ret; |
411 | |
|
412 | 0 | pool->stopped = true; |
413 | |
|
414 | 0 | if (pool->num_threads == 0) { |
415 | 0 | return 0; |
416 | 0 | } |
417 | | |
418 | | /* |
419 | | * We have active threads, tell them to finish. |
420 | | */ |
421 | | |
422 | 0 | ret = pthread_cond_broadcast(&pool->condvar); |
423 | |
|
424 | 0 | return ret; |
425 | 0 | } |
426 | | |
427 | | /* |
428 | | * Stop a thread pool. Wake up all idle threads for exit. |
429 | | */ |
430 | | |
431 | | int pthreadpool_stop(struct pthreadpool *pool) |
432 | 0 | { |
433 | 0 | int ret, ret1; |
434 | |
|
435 | 0 | ret = pthread_mutex_lock(&pool->mutex); |
436 | 0 | if (ret != 0) { |
437 | 0 | return ret; |
438 | 0 | } |
439 | | |
440 | 0 | if (!pool->stopped) { |
441 | 0 | ret = pthreadpool_stop_locked(pool); |
442 | 0 | } |
443 | |
|
444 | 0 | ret1 = pthread_mutex_unlock(&pool->mutex); |
445 | 0 | assert(ret1 == 0); |
446 | |
|
447 | 0 | return ret; |
448 | 0 | } |
449 | | |
450 | | /* |
451 | | * Destroy a thread pool. Wake up all idle threads for exit. The last |
452 | | * one will free the pool. |
453 | | */ |
454 | | |
455 | | int pthreadpool_destroy(struct pthreadpool *pool) |
456 | 0 | { |
457 | 0 | int ret, ret1; |
458 | 0 | bool free_it; |
459 | |
|
460 | 0 | assert(!pool->destroyed); |
461 | |
|
462 | 0 | ret = pthread_mutex_lock(&pool->mutex); |
463 | 0 | if (ret != 0) { |
464 | 0 | return ret; |
465 | 0 | } |
466 | | |
467 | 0 | pool->destroyed = true; |
468 | |
|
469 | 0 | if (!pool->stopped) { |
470 | 0 | ret = pthreadpool_stop_locked(pool); |
471 | 0 | } |
472 | |
|
473 | 0 | free_it = (pool->num_threads == 0); |
474 | |
|
475 | 0 | ret1 = pthread_mutex_unlock(&pool->mutex); |
476 | 0 | assert(ret1 == 0); |
477 | |
|
478 | 0 | if (free_it) { |
479 | 0 | pthreadpool_free(pool); |
480 | 0 | } |
481 | |
|
482 | 0 | return ret; |
483 | 0 | } |
484 | | /* |
485 | | * Prepare for pthread_exit(), pool->mutex must be locked and will be |
486 | | * unlocked here. This is a bit of a layering violation, but here we |
487 | | * also take care of removing the pool if we're the last thread. |
488 | | */ |
489 | | static void pthreadpool_server_exit(struct pthreadpool *pool) |
490 | 0 | { |
491 | 0 | int ret; |
492 | 0 | bool free_it; |
493 | |
|
494 | 0 | pool->num_threads -= 1; |
495 | |
|
496 | 0 | free_it = (pool->destroyed && (pool->num_threads == 0)); |
497 | |
|
498 | 0 | ret = pthread_mutex_unlock(&pool->mutex); |
499 | 0 | assert(ret == 0); |
500 | |
|
501 | 0 | if (free_it) { |
502 | 0 | pthreadpool_free(pool); |
503 | 0 | } |
504 | 0 | } |
505 | | |
506 | | static bool pthreadpool_get_job(struct pthreadpool *p, |
507 | | struct pthreadpool_job *job) |
508 | 0 | { |
509 | 0 | if (p->stopped) { |
510 | 0 | return false; |
511 | 0 | } |
512 | | |
513 | 0 | if (p->num_jobs == 0) { |
514 | 0 | return false; |
515 | 0 | } |
516 | 0 | *job = p->jobs[p->head]; |
517 | 0 | p->head = (p->head+1) % p->jobs_array_len; |
518 | 0 | p->num_jobs -= 1; |
519 | 0 | return true; |
520 | 0 | } |
521 | | |
522 | | static bool pthreadpool_put_job(struct pthreadpool *p, |
523 | | int id, |
524 | | void (*fn)(void *private_data), |
525 | | void *private_data) |
526 | 0 | { |
527 | 0 | struct pthreadpool_job *job; |
528 | |
|
529 | 0 | if (p->num_jobs == p->jobs_array_len) { |
530 | 0 | struct pthreadpool_job *tmp; |
531 | 0 | size_t new_len = p->jobs_array_len * 2; |
532 | |
|
533 | 0 | tmp = realloc( |
534 | 0 | p->jobs, sizeof(struct pthreadpool_job) * new_len); |
535 | 0 | if (tmp == NULL) { |
536 | 0 | return false; |
537 | 0 | } |
538 | 0 | p->jobs = tmp; |
539 | | |
540 | | /* |
541 | | * We just doubled the jobs array. The array implements a FIFO |
542 | | * queue with a modulo-based wraparound, so we have to memcpy |
543 | | * the jobs that are logically at the queue end but physically |
544 | | * before the queue head into the reallocated area. The new |
545 | | * space starts at the current jobs_array_len, and we have to |
546 | | * copy everything before the current head job into the new |
547 | | * area. |
548 | | */ |
549 | 0 | memcpy(&p->jobs[p->jobs_array_len], p->jobs, |
550 | 0 | sizeof(struct pthreadpool_job) * p->head); |
551 | |
|
552 | 0 | p->jobs_array_len = new_len; |
553 | 0 | } |
554 | | |
555 | 0 | job = &p->jobs[(p->head + p->num_jobs) % p->jobs_array_len]; |
556 | 0 | job->id = id; |
557 | 0 | job->fn = fn; |
558 | 0 | job->private_data = private_data; |
559 | |
|
560 | 0 | p->num_jobs += 1; |
561 | |
|
562 | 0 | return true; |
563 | 0 | } |
564 | | |
565 | | static void pthreadpool_undo_put_job(struct pthreadpool *p) |
566 | 0 | { |
567 | 0 | p->num_jobs -= 1; |
568 | 0 | } |
569 | | |
570 | | static void *pthreadpool_server(void *arg) |
571 | 0 | { |
572 | 0 | struct pthreadpool *pool = (struct pthreadpool *)arg; |
573 | 0 | int res; |
574 | |
|
575 | 0 | res = pthread_mutex_lock(&pool->mutex); |
576 | 0 | if (res != 0) { |
577 | 0 | return NULL; |
578 | 0 | } |
579 | | |
580 | 0 | while (1) { |
581 | 0 | struct timespec ts; |
582 | 0 | struct pthreadpool_job job; |
583 | | |
584 | | /* |
585 | | * idle-wait at most 1 second. If nothing happens in that |
586 | | * time, exit this thread. |
587 | | */ |
588 | |
|
589 | 0 | clock_gettime(CLOCK_REALTIME, &ts); |
590 | 0 | ts.tv_sec += 1; |
591 | |
|
592 | 0 | while ((pool->num_jobs == 0) && !pool->stopped) { |
593 | |
|
594 | 0 | pool->num_idle += 1; |
595 | 0 | res = pthread_cond_timedwait( |
596 | 0 | &pool->condvar, &pool->mutex, &ts); |
597 | 0 | pool->num_idle -= 1; |
598 | |
|
599 | 0 | if (pool->prefork_cond != NULL) { |
600 | | /* |
601 | | * Me must allow fork() to continue |
602 | | * without anybody waiting on |
603 | | * &pool->condvar. Tell |
604 | | * pthreadpool_prepare_pool that we |
605 | | * got that message. |
606 | | */ |
607 | |
|
608 | 0 | res = pthread_cond_signal(pool->prefork_cond); |
609 | 0 | assert(res == 0); |
610 | |
|
611 | 0 | res = pthread_mutex_unlock(&pool->mutex); |
612 | 0 | assert(res == 0); |
613 | | |
614 | | /* |
615 | | * pthreadpool_prepare_pool has |
616 | | * already locked this mutex across |
617 | | * the fork. This makes us wait |
618 | | * without sitting in a condvar. |
619 | | */ |
620 | 0 | res = pthread_mutex_lock(&pool->fork_mutex); |
621 | 0 | assert(res == 0); |
622 | 0 | res = pthread_mutex_unlock(&pool->fork_mutex); |
623 | 0 | assert(res == 0); |
624 | |
|
625 | 0 | res = pthread_mutex_lock(&pool->mutex); |
626 | 0 | assert(res == 0); |
627 | 0 | } |
628 | |
|
629 | 0 | if (res == ETIMEDOUT) { |
630 | |
|
631 | 0 | if (pool->num_jobs == 0) { |
632 | | /* |
633 | | * we timed out and still no work for |
634 | | * us. Exit. |
635 | | */ |
636 | 0 | pthreadpool_server_exit(pool); |
637 | 0 | return NULL; |
638 | 0 | } |
639 | | |
640 | 0 | break; |
641 | 0 | } |
642 | 0 | assert(res == 0); |
643 | 0 | } |
644 | | |
645 | 0 | if (pthreadpool_get_job(pool, &job)) { |
646 | 0 | int ret; |
647 | | |
648 | | /* |
649 | | * Do the work with the mutex unlocked |
650 | | */ |
651 | |
|
652 | 0 | res = pthread_mutex_unlock(&pool->mutex); |
653 | 0 | assert(res == 0); |
654 | |
|
655 | 0 | job.fn(job.private_data); |
656 | |
|
657 | 0 | ret = pool->signal_fn(job.id, |
658 | 0 | job.fn, job.private_data, |
659 | 0 | pool->signal_fn_private_data); |
660 | |
|
661 | 0 | res = pthread_mutex_lock(&pool->mutex); |
662 | 0 | assert(res == 0); |
663 | |
|
664 | 0 | if (ret != 0) { |
665 | 0 | pthreadpool_server_exit(pool); |
666 | 0 | return NULL; |
667 | 0 | } |
668 | 0 | } |
669 | | |
670 | 0 | if (pool->stopped) { |
671 | | /* |
672 | | * we're asked to stop processing jobs, so exit |
673 | | */ |
674 | 0 | pthreadpool_server_exit(pool); |
675 | 0 | return NULL; |
676 | 0 | } |
677 | 0 | } |
678 | 0 | } |
679 | | |
680 | | static int pthreadpool_create_thread(struct pthreadpool *pool) |
681 | 0 | { |
682 | 0 | pthread_attr_t thread_attr; |
683 | 0 | pthread_t thread_id; |
684 | 0 | int res; |
685 | 0 | sigset_t mask, omask; |
686 | | |
687 | | /* |
688 | | * Create a new worker thread. It should not receive any signals. |
689 | | */ |
690 | |
|
691 | 0 | sigfillset(&mask); |
692 | |
|
693 | 0 | res = pthread_attr_init(&thread_attr); |
694 | 0 | if (res != 0) { |
695 | 0 | return res; |
696 | 0 | } |
697 | | |
698 | 0 | res = pthread_attr_setdetachstate( |
699 | 0 | &thread_attr, PTHREAD_CREATE_DETACHED); |
700 | 0 | if (res != 0) { |
701 | 0 | pthread_attr_destroy(&thread_attr); |
702 | 0 | return res; |
703 | 0 | } |
704 | | |
705 | 0 | res = pthread_sigmask(SIG_BLOCK, &mask, &omask); |
706 | 0 | if (res != 0) { |
707 | 0 | pthread_attr_destroy(&thread_attr); |
708 | 0 | return res; |
709 | 0 | } |
710 | | |
711 | 0 | res = pthread_create(&thread_id, &thread_attr, pthreadpool_server, |
712 | 0 | (void *)pool); |
713 | |
|
714 | 0 | assert(pthread_sigmask(SIG_SETMASK, &omask, NULL) == 0); |
715 | |
|
716 | 0 | pthread_attr_destroy(&thread_attr); |
717 | |
|
718 | 0 | if (res == 0) { |
719 | 0 | pool->num_threads += 1; |
720 | 0 | } |
721 | |
|
722 | 0 | return res; |
723 | 0 | } |
724 | | |
725 | | int pthreadpool_add_job(struct pthreadpool *pool, int job_id, |
726 | | void (*fn)(void *private_data), void *private_data) |
727 | 0 | { |
728 | 0 | int res; |
729 | 0 | int unlock_res; |
730 | |
|
731 | 0 | assert(!pool->destroyed); |
732 | |
|
733 | 0 | res = pthread_mutex_lock(&pool->mutex); |
734 | 0 | if (res != 0) { |
735 | 0 | return res; |
736 | 0 | } |
737 | | |
738 | 0 | if (pool->stopped) { |
739 | | /* |
740 | | * Protect against the pool being shut down while |
741 | | * trying to add a job |
742 | | */ |
743 | 0 | unlock_res = pthread_mutex_unlock(&pool->mutex); |
744 | 0 | assert(unlock_res == 0); |
745 | 0 | return EINVAL; |
746 | 0 | } |
747 | | |
748 | 0 | if (pool->max_threads == 0) { |
749 | 0 | unlock_res = pthread_mutex_unlock(&pool->mutex); |
750 | 0 | assert(unlock_res == 0); |
751 | | |
752 | | /* |
753 | | * If no thread are allowed we do strict sync processing. |
754 | | */ |
755 | 0 | fn(private_data); |
756 | 0 | res = pool->signal_fn(job_id, fn, private_data, |
757 | 0 | pool->signal_fn_private_data); |
758 | 0 | return res; |
759 | 0 | } |
760 | | |
761 | | /* |
762 | | * Add job to the end of the queue |
763 | | */ |
764 | 0 | if (!pthreadpool_put_job(pool, job_id, fn, private_data)) { |
765 | 0 | unlock_res = pthread_mutex_unlock(&pool->mutex); |
766 | 0 | assert(unlock_res == 0); |
767 | 0 | return ENOMEM; |
768 | 0 | } |
769 | | |
770 | 0 | if (pool->num_idle > 0) { |
771 | | /* |
772 | | * We have idle threads, wake one. |
773 | | */ |
774 | 0 | res = pthread_cond_signal(&pool->condvar); |
775 | 0 | if (res != 0) { |
776 | 0 | pthreadpool_undo_put_job(pool); |
777 | 0 | } |
778 | 0 | unlock_res = pthread_mutex_unlock(&pool->mutex); |
779 | 0 | assert(unlock_res == 0); |
780 | 0 | return res; |
781 | 0 | } |
782 | | |
783 | 0 | if (pool->num_threads >= pool->max_threads) { |
784 | | /* |
785 | | * No more new threads, we just queue the request |
786 | | */ |
787 | 0 | unlock_res = pthread_mutex_unlock(&pool->mutex); |
788 | 0 | assert(unlock_res == 0); |
789 | 0 | return 0; |
790 | 0 | } |
791 | | |
792 | 0 | res = pthreadpool_create_thread(pool); |
793 | 0 | if (res == 0) { |
794 | 0 | unlock_res = pthread_mutex_unlock(&pool->mutex); |
795 | 0 | assert(unlock_res == 0); |
796 | 0 | return 0; |
797 | 0 | } |
798 | | |
799 | 0 | if (pool->num_threads != 0) { |
800 | | /* |
801 | | * At least one thread is still available, let |
802 | | * that one run the queued job. |
803 | | */ |
804 | 0 | unlock_res = pthread_mutex_unlock(&pool->mutex); |
805 | 0 | assert(unlock_res == 0); |
806 | 0 | return 0; |
807 | 0 | } |
808 | | |
809 | 0 | pthreadpool_undo_put_job(pool); |
810 | |
|
811 | 0 | unlock_res = pthread_mutex_unlock(&pool->mutex); |
812 | 0 | assert(unlock_res == 0); |
813 | |
|
814 | 0 | return res; |
815 | 0 | } |
816 | | |
817 | | size_t pthreadpool_cancel_job(struct pthreadpool *pool, int job_id, |
818 | | void (*fn)(void *private_data), void *private_data) |
819 | 0 | { |
820 | 0 | int res; |
821 | 0 | size_t i, j; |
822 | 0 | size_t num = 0; |
823 | |
|
824 | 0 | assert(!pool->destroyed); |
825 | |
|
826 | 0 | res = pthread_mutex_lock(&pool->mutex); |
827 | 0 | if (res != 0) { |
828 | 0 | return res; |
829 | 0 | } |
830 | | |
831 | 0 | for (i = 0, j = 0; i < pool->num_jobs; i++) { |
832 | 0 | size_t idx = (pool->head + i) % pool->jobs_array_len; |
833 | 0 | size_t new_idx = (pool->head + j) % pool->jobs_array_len; |
834 | 0 | struct pthreadpool_job *job = &pool->jobs[idx]; |
835 | |
|
836 | 0 | if ((job->private_data == private_data) && |
837 | 0 | (job->id == job_id) && |
838 | 0 | (job->fn == fn)) |
839 | 0 | { |
840 | | /* |
841 | | * Just skip the entry. |
842 | | */ |
843 | 0 | num++; |
844 | 0 | continue; |
845 | 0 | } |
846 | | |
847 | | /* |
848 | | * If we already removed one or more jobs (so j will be smaller |
849 | | * then i), we need to fill possible gaps in the logical list. |
850 | | */ |
851 | 0 | if (j < i) { |
852 | 0 | pool->jobs[new_idx] = *job; |
853 | 0 | } |
854 | 0 | j++; |
855 | 0 | } |
856 | |
|
857 | 0 | pool->num_jobs -= num; |
858 | |
|
859 | 0 | res = pthread_mutex_unlock(&pool->mutex); |
860 | 0 | assert(res == 0); |
861 | |
|
862 | 0 | return num; |
863 | 0 | } |