Branch data Line data Source code
1 : : /* GLIB - Library of useful routines for C programming
2 : : * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3 : : *
4 : : * GThreadPool: thread pool implementation.
5 : : * Copyright (C) 2000 Sebastian Wilhelmi; University of Karlsruhe
6 : : *
7 : : * SPDX-License-Identifier: LGPL-2.1-or-later
8 : : *
9 : : * This library is free software; you can redistribute it and/or
10 : : * modify it under the terms of the GNU Lesser General Public
11 : : * License as published by the Free Software Foundation; either
12 : : * version 2.1 of the License, or (at your option) any later version.
13 : : *
14 : : * This library is distributed in the hope that it will be useful,
15 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 : : * Lesser General Public License for more details.
18 : : *
19 : : * You should have received a copy of the GNU Lesser General Public
20 : : * License along with this library; if not, see <http://www.gnu.org/licenses/>.
21 : : */
22 : :
23 : : /*
24 : : * MT safe
25 : : */
26 : :
27 : : #include "config.h"
28 : :
29 : : #include "gthreadpool.h"
30 : :
31 : : #include "gasyncqueue.h"
32 : : #include "gasyncqueueprivate.h"
33 : : #include "glib-private.h"
34 : : #include "gmain.h"
35 : : #include "gtestutils.h"
36 : : #include "gthreadprivate.h"
37 : : #include "gtimer.h"
38 : : #include "gutils.h"
39 : :
40 : : #define DEBUG_MSG(x)
41 : : /* #define DEBUG_MSG(args) g_printerr args ; g_printerr ("\n"); */
42 : :
43 : : typedef struct _GRealThreadPool GRealThreadPool;
44 : :
45 : : /**
46 : : * GThreadPool:
47 : : * @func: the function to execute in the threads of this pool
48 : : * @user_data: the user data for the threads of this pool
49 : : * @exclusive: are all threads exclusive to this pool
50 : : *
51 : : * The `GThreadPool` struct represents a thread pool.
52 : : *
53 : : * A thread pool is useful when you wish to asynchronously fork out the execution of work
54 : : * and continue working in your own thread. If that will happen often, the overhead of starting
55 : : * and destroying a thread each time might be too high. In such cases reusing already started
56 : : * threads seems like a good idea. And it indeed is, but implementing this can be tedious
57 : : * and error-prone.
58 : : *
59 : : * Therefore GLib provides thread pools for your convenience. An added advantage is, that the
60 : : * threads can be shared between the different subsystems of your program, when they are using GLib.
61 : : *
62 : : * To create a new thread pool, you use [func@GLib.ThreadPool.new].
63 : : * It is destroyed by [method@GLib.ThreadPool.free].
64 : : *
65 : : * If you want to execute a certain task within a thread pool, use [method@GLib.ThreadPool.push].
66 : : *
67 : : * To get the current number of running threads you call [method@GLib.ThreadPool.get_num_threads].
68 : : * To get the number of still unprocessed tasks you call [method@GLib.ThreadPool.unprocessed].
69 : : * To control the maximum number of threads for a thread pool, you use
70 : : * [method@GLib.ThreadPool.get_max_threads]. and [method@GLib.ThreadPool.set_max_threads].
71 : : *
72 : : * Finally you can control the number of unused threads, that are kept alive by GLib for future use.
73 : : * The current number can be fetched with [func@GLib.ThreadPool.get_num_unused_threads].
74 : : * The maximum number can be controlled by [func@GLib.ThreadPool.get_max_unused_threads] and
75 : : * [func@GLib.ThreadPool.set_max_unused_threads]. All currently unused threads
76 : : * can be stopped by calling [func@GLib.ThreadPool.stop_unused_threads].
77 : : */
78 : : struct _GRealThreadPool
79 : : {
80 : : GThreadPool pool;
81 : : GAsyncQueue *queue;
82 : : GCond cond;
83 : : gint max_threads;
84 : : guint num_threads;
85 : : gboolean running;
86 : : gboolean immediate;
87 : : gboolean waiting;
88 : : GCompareDataFunc sort_func;
89 : : gpointer sort_user_data;
90 : : };
91 : :
92 : : /* The following is just an address to mark the wakeup order for a
93 : : * thread, it could be any address (as long, as it isn't a valid
94 : : * GThreadPool address)
95 : : */
96 : : static const gpointer wakeup_thread_marker = (gpointer) &g_thread_pool_new;
97 : : static gint wakeup_thread_serial = 0;
98 : :
99 : : /* Here all unused threads are waiting */
100 : : static GAsyncQueue *unused_thread_queue = NULL;
101 : : static gint unused_threads = 0;
102 : : static gint max_unused_threads = 8;
103 : : static gint kill_unused_threads = 0;
104 : : static guint max_idle_time = 15 * 1000;
105 : :
106 : : static int thread_counter = 0;
107 : :
108 : : typedef struct
109 : : {
110 : : /* Either thread or error are set in the end. Both transfer-full. */
111 : : GThreadPool *pool;
112 : : GThread *thread;
113 : : GError *error;
114 : : } SpawnThreadData;
115 : :
116 : : static GCond spawn_thread_cond;
117 : : static GAsyncQueue *spawn_thread_queue;
118 : :
119 : : static void g_thread_pool_queue_push_unlocked (GRealThreadPool *pool,
120 : : gpointer data);
121 : : static void g_thread_pool_free_internal (GRealThreadPool *pool);
122 : : static gpointer g_thread_pool_thread_proxy (gpointer data);
123 : : static gboolean g_thread_pool_start_thread (GRealThreadPool *pool,
124 : : GError **error);
125 : : static void g_thread_pool_wakeup_and_stop_all (GRealThreadPool *pool);
126 : : static GRealThreadPool* g_thread_pool_wait_for_new_pool (void);
127 : : static gpointer g_thread_pool_wait_for_new_task (GRealThreadPool *pool);
128 : :
129 : : static void
130 : 105069 : g_thread_pool_queue_push_unlocked (GRealThreadPool *pool,
131 : : gpointer data)
132 : : {
133 : 105069 : if (pool->sort_func)
134 : 3584 : g_async_queue_push_sorted_unlocked (pool->queue,
135 : : data,
136 : : pool->sort_func,
137 : : pool->sort_user_data);
138 : : else
139 : 101485 : g_async_queue_push_unlocked (pool->queue, data);
140 : 105069 : }
141 : :
142 : : static GRealThreadPool*
143 : 274 : g_thread_pool_wait_for_new_pool (void)
144 : : {
145 : : GRealThreadPool *pool;
146 : : gint local_wakeup_thread_serial;
147 : : guint local_max_unused_threads;
148 : : gint local_max_idle_time;
149 : : gint last_wakeup_thread_serial;
150 : 274 : gboolean have_relayed_thread_marker = FALSE;
151 : :
152 : 274 : local_max_unused_threads = (guint) g_atomic_int_get (&max_unused_threads);
153 : 274 : local_max_idle_time = g_atomic_int_get (&max_idle_time);
154 : 274 : last_wakeup_thread_serial = g_atomic_int_get (&wakeup_thread_serial);
155 : :
156 : : do
157 : : {
158 : 274 : if ((guint) g_atomic_int_get (&unused_threads) >= local_max_unused_threads)
159 : : {
160 : : /* If this is a superfluous thread, stop it. */
161 : 33 : pool = NULL;
162 : : }
163 : 241 : else if (local_max_idle_time > 0)
164 : : {
165 : : /* If a maximal idle time is given, wait for the given time. */
166 : : DEBUG_MSG (("thread %p waiting in global pool for %f seconds.",
167 : : g_thread_self (), local_max_idle_time / 1000.0));
168 : :
169 : 78 : pool = g_async_queue_timeout_pop (unused_thread_queue,
170 : 78 : local_max_idle_time * 1000);
171 : : }
172 : : else
173 : : {
174 : : /* If no maximal idle time is given, wait indefinitely. */
175 : : DEBUG_MSG (("thread %p waiting in global pool.", g_thread_self ()));
176 : 163 : pool = g_async_queue_pop (unused_thread_queue);
177 : : }
178 : :
179 : 250 : if (pool == wakeup_thread_marker)
180 : : {
181 : 144 : local_wakeup_thread_serial = g_atomic_int_get (&wakeup_thread_serial);
182 : 144 : if (last_wakeup_thread_serial == local_wakeup_thread_serial)
183 : : {
184 : 0 : if (!have_relayed_thread_marker)
185 : : {
186 : : /* If this wakeup marker has been received for
187 : : * the second time, relay it.
188 : : */
189 : : DEBUG_MSG (("thread %p relaying wakeup message to "
190 : : "waiting thread with lower serial.",
191 : : g_thread_self ()));
192 : :
193 : 0 : g_async_queue_push (unused_thread_queue, wakeup_thread_marker);
194 : 0 : have_relayed_thread_marker = TRUE;
195 : :
196 : : /* If a wakeup marker has been relayed, this thread
197 : : * will get out of the way for 100 microseconds to
198 : : * avoid receiving this marker again.
199 : : */
200 : 0 : g_usleep (100);
201 : : }
202 : : }
203 : : else
204 : : {
205 : 144 : if (g_atomic_int_add (&kill_unused_threads, -1) > 0)
206 : : {
207 : 144 : pool = NULL;
208 : 144 : break;
209 : : }
210 : :
211 : : DEBUG_MSG (("thread %p updating to new limits.",
212 : : g_thread_self ()));
213 : :
214 : 0 : local_max_unused_threads = (guint) g_atomic_int_get (&max_unused_threads);
215 : 0 : local_max_idle_time = g_atomic_int_get (&max_idle_time);
216 : 0 : last_wakeup_thread_serial = local_wakeup_thread_serial;
217 : :
218 : 0 : have_relayed_thread_marker = FALSE;
219 : : }
220 : : }
221 : : }
222 : 106 : while (pool == wakeup_thread_marker);
223 : :
224 : 250 : return pool;
225 : : }
226 : :
227 : : static gpointer
228 : 105008 : g_thread_pool_wait_for_new_task (GRealThreadPool *pool)
229 : : {
230 : 105008 : gpointer task = NULL;
231 : :
232 : 114633 : if (pool->running || (!pool->immediate &&
233 : 9625 : g_async_queue_length_unlocked (pool->queue) > 0))
234 : : {
235 : : /* This thread pool is still active. */
236 : 104912 : if (pool->max_threads != -1 && pool->num_threads > (guint) pool->max_threads)
237 : : {
238 : : /* This is a superfluous thread, so it goes to the global pool. */
239 : : DEBUG_MSG (("superfluous thread %p in pool %p.",
240 : : g_thread_self (), pool));
241 : : }
242 : 104906 : else if (pool->pool.exclusive)
243 : : {
244 : : /* Exclusive threads stay attached to the pool. */
245 : 100627 : task = g_async_queue_pop_unlocked (pool->queue);
246 : :
247 : : DEBUG_MSG (("thread %p in exclusive pool %p waits for task "
248 : : "(%d running, %d unprocessed).",
249 : : g_thread_self (), pool, pool->num_threads,
250 : : g_async_queue_length_unlocked (pool->queue)));
251 : : }
252 : : else
253 : : {
254 : : /* A thread will wait for new tasks for at most 1/2
255 : : * second before going to the global pool.
256 : : */
257 : : DEBUG_MSG (("thread %p in pool %p waits for up to a 1/2 second for task "
258 : : "(%d running, %d unprocessed).",
259 : : g_thread_self (), pool, pool->num_threads,
260 : : g_async_queue_length_unlocked (pool->queue)));
261 : :
262 : 4279 : task = g_async_queue_timeout_pop_unlocked (pool->queue,
263 : : G_USEC_PER_SEC / 2);
264 : : }
265 : : }
266 : : else
267 : : {
268 : : /* This thread pool is inactive, it will no longer process tasks. */
269 : : DEBUG_MSG (("pool %p not active, thread %p will go to global pool "
270 : : "(running: %s, immediate: %s, len: %d).",
271 : : pool, g_thread_self (),
272 : : pool->running ? "true" : "false",
273 : : pool->immediate ? "true" : "false",
274 : : g_async_queue_length_unlocked (pool->queue)));
275 : : }
276 : :
277 : 104893 : return task;
278 : : }
279 : :
280 : : static gpointer
281 : 177 : g_thread_pool_spawn_thread (gpointer data)
282 : : {
283 : : while (TRUE)
284 : 281 : {
285 : : SpawnThreadData *spawn_thread_data;
286 : 458 : GThread *thread = NULL;
287 : 458 : GError *error = NULL;
288 : : gchar name[16];
289 : :
290 : 458 : g_snprintf (name, sizeof (name), "pool-%d", g_atomic_int_add (&thread_counter, 1));
291 : :
292 : 458 : g_async_queue_lock (spawn_thread_queue);
293 : : /* Spawn a new thread for the given pool and wake the requesting thread
294 : : * up again with the result. This new thread will have the scheduler
295 : : * settings inherited from this thread and in extension of the thread
296 : : * that created the first non-exclusive thread-pool. */
297 : 458 : spawn_thread_data = g_async_queue_pop_unlocked (spawn_thread_queue);
298 : 281 : thread = g_thread_try_new (name, g_thread_pool_thread_proxy, spawn_thread_data->pool, &error);
299 : :
300 : 281 : spawn_thread_data->thread = g_steal_pointer (&thread);
301 : 281 : spawn_thread_data->error = g_steal_pointer (&error);
302 : :
303 : 281 : g_cond_broadcast (&spawn_thread_cond);
304 : 281 : g_async_queue_unlock (spawn_thread_queue);
305 : : }
306 : :
307 : : return NULL;
308 : : }
309 : :
310 : : static gpointer
311 : 326 : g_thread_pool_thread_proxy (gpointer data)
312 : : {
313 : : GRealThreadPool *pool;
314 : :
315 : 326 : pool = data;
316 : :
317 : : DEBUG_MSG (("thread %p started for pool %p.", g_thread_self (), pool));
318 : :
319 : 326 : g_async_queue_lock (pool->queue);
320 : :
321 : : while (TRUE)
322 : 104682 : {
323 : : gpointer task;
324 : :
325 : 105008 : task = g_thread_pool_wait_for_new_task (pool);
326 : 104893 : if (task)
327 : : {
328 : 104619 : if (pool->running || !pool->immediate)
329 : : {
330 : : /* A task was received and the thread pool is active,
331 : : * so execute the function.
332 : : */
333 : 104576 : g_async_queue_unlock (pool->queue);
334 : : DEBUG_MSG (("thread %p in pool %p calling func.",
335 : : g_thread_self (), pool));
336 : 104576 : pool->pool.func (task, pool->pool.user_data);
337 : 104576 : g_async_queue_lock (pool->queue);
338 : : }
339 : : }
340 : : else
341 : : {
342 : : /* No task was received, so this thread goes to the global pool. */
343 : 274 : gboolean free_pool = FALSE;
344 : :
345 : : DEBUG_MSG (("thread %p leaving pool %p for global pool.",
346 : : g_thread_self (), pool));
347 : 274 : pool->num_threads--;
348 : :
349 : 274 : if (!pool->running)
350 : : {
351 : 96 : if (!pool->waiting)
352 : : {
353 : 47 : if (pool->num_threads == 0)
354 : : {
355 : : /* If the pool is not running and no other
356 : : * thread is waiting for this thread pool to
357 : : * finish and this is the last thread of this
358 : : * pool, free the pool.
359 : : */
360 : 25 : free_pool = TRUE;
361 : : }
362 : : else
363 : : {
364 : : /* If the pool is not running and no other
365 : : * thread is waiting for this thread pool to
366 : : * finish and this is not the last thread of
367 : : * this pool and there are no tasks left in the
368 : : * queue, wakeup the remaining threads.
369 : : */
370 : 22 : if (g_async_queue_length_unlocked (pool->queue) ==
371 : 22 : (gint) -pool->num_threads)
372 : 0 : g_thread_pool_wakeup_and_stop_all (pool);
373 : : }
374 : : }
375 : 78 : else if (pool->immediate ||
376 : 29 : g_async_queue_length_unlocked (pool->queue) <= 0)
377 : : {
378 : : /* If the pool is not running and another thread is
379 : : * waiting for this thread pool to finish and there
380 : : * are either no tasks left or the pool shall stop
381 : : * immediately, inform the waiting thread of a change
382 : : * of the thread pool state.
383 : : */
384 : 49 : g_cond_broadcast (&pool->cond);
385 : : }
386 : : }
387 : :
388 : 274 : g_atomic_int_inc (&unused_threads);
389 : 274 : g_async_queue_unlock (pool->queue);
390 : :
391 : 274 : if (free_pool)
392 : 25 : g_thread_pool_free_internal (pool);
393 : :
394 : 274 : pool = g_thread_pool_wait_for_new_pool ();
395 : 250 : g_atomic_int_add (&unused_threads, -1);
396 : :
397 : 250 : if (pool == NULL)
398 : 187 : break;
399 : :
400 : 63 : g_async_queue_lock (pool->queue);
401 : :
402 : : DEBUG_MSG (("thread %p entering pool %p from global pool.",
403 : : g_thread_self (), pool));
404 : :
405 : : /* pool->num_threads++ is not done here, but in
406 : : * g_thread_pool_start_thread to make the new started
407 : : * thread known to the pool before itself can do it.
408 : : */
409 : : }
410 : : }
411 : :
412 : 187 : return NULL;
413 : : }
414 : :
415 : : static gboolean
416 : 569739 : g_thread_pool_start_thread (GRealThreadPool *pool,
417 : : GError **error)
418 : : {
419 : 569739 : gboolean success = FALSE;
420 : :
421 : 569739 : if (pool->max_threads != -1 && pool->num_threads >= (guint) pool->max_threads)
422 : : /* Enough threads are already running */
423 : 569349 : return TRUE;
424 : :
425 : 390 : g_async_queue_lock (unused_thread_queue);
426 : :
427 : 390 : if (g_async_queue_length_unlocked (unused_thread_queue) < 0)
428 : : {
429 : 63 : g_async_queue_push_unlocked (unused_thread_queue, pool);
430 : 63 : success = TRUE;
431 : : }
432 : :
433 : 390 : g_async_queue_unlock (unused_thread_queue);
434 : :
435 : 390 : if (!success)
436 : : {
437 : : GThread *thread;
438 : :
439 : : /* No thread was found, we have to start a new one */
440 : 327 : if (pool->pool.exclusive)
441 : : {
442 : : /* For exclusive thread-pools this is directly called from new() and
443 : : * we simply start new threads that inherit the scheduler settings
444 : : * from the current thread.
445 : : */
446 : : char name[16];
447 : :
448 : 46 : g_snprintf (name, sizeof (name), "pool-%d", g_atomic_int_add (&thread_counter, 1));
449 : :
450 : 46 : thread = g_thread_try_new (name, g_thread_pool_thread_proxy, pool, error);
451 : : }
452 : : else
453 : : {
454 : : /* For non-exclusive thread-pools this can be called at any time
455 : : * when a new thread is needed. We make sure to create a new thread
456 : : * here with the correct scheduler settings by going via our helper
457 : : * thread.
458 : : */
459 : 281 : SpawnThreadData spawn_thread_data = { (GThreadPool *) pool, NULL, NULL };
460 : :
461 : 281 : g_async_queue_lock (spawn_thread_queue);
462 : :
463 : 281 : g_async_queue_push_unlocked (spawn_thread_queue, &spawn_thread_data);
464 : :
465 : 562 : while (!spawn_thread_data.thread && !spawn_thread_data.error)
466 : 281 : g_cond_wait (&spawn_thread_cond, _g_async_queue_get_mutex (spawn_thread_queue));
467 : :
468 : 281 : thread = spawn_thread_data.thread;
469 : 281 : if (!thread)
470 : 0 : g_propagate_error (error, g_steal_pointer (&spawn_thread_data.error));
471 : 281 : g_async_queue_unlock (spawn_thread_queue);
472 : : }
473 : :
474 : 327 : if (thread == NULL)
475 : 1 : return FALSE;
476 : :
477 : 326 : g_thread_unref (thread);
478 : : }
479 : :
480 : : /* See comment in g_thread_pool_thread_proxy as to why this is done
481 : : * here and not there
482 : : */
483 : 389 : pool->num_threads++;
484 : :
485 : 389 : return TRUE;
486 : : }
487 : :
488 : : /**
489 : : * g_thread_pool_new:
490 : : * @func: a function to execute in the threads of the new thread pool
491 : : * @user_data: user data that is handed over to @func every time it
492 : : * is called
493 : : * @max_threads: the maximal number of threads to execute concurrently
494 : : * in the new thread pool, -1 means no limit
495 : : * @exclusive: should this thread pool be exclusive?
496 : : * @error: return location for error, or %NULL
497 : : *
498 : : * This function creates a new thread pool.
499 : : *
500 : : * Whenever you call g_thread_pool_push(), either a new thread is
501 : : * created or an unused one is reused. At most @max_threads threads
502 : : * are running concurrently for this thread pool. @max_threads = -1
503 : : * allows unlimited threads to be created for this thread pool. The
504 : : * newly created or reused thread now executes the function @func
505 : : * with the two arguments. The first one is the parameter to
506 : : * g_thread_pool_push() and the second one is @user_data.
507 : : *
508 : : * Pass g_get_num_processors() to @max_threads to create as many threads as
509 : : * there are logical processors on the system. This will not pin each thread to
510 : : * a specific processor.
511 : : *
512 : : * The parameter @exclusive determines whether the thread pool owns
513 : : * all threads exclusive or shares them with other thread pools.
514 : : * If @exclusive is %TRUE, @max_threads threads are started
515 : : * immediately and they will run exclusively for this thread pool
516 : : * until it is destroyed by g_thread_pool_free(). If @exclusive is
517 : : * %FALSE, threads are created when needed and shared between all
518 : : * non-exclusive thread pools. This implies that @max_threads may
519 : : * not be -1 for exclusive thread pools. Besides, exclusive thread
520 : : * pools are not affected by g_thread_pool_set_max_idle_time()
521 : : * since their threads are never considered idle and returned to the
522 : : * global pool.
523 : : *
524 : : * Note that the threads used by exclusive thread pools will all inherit the
525 : : * scheduler settings of the current thread while the threads used by
526 : : * non-exclusive thread pools will inherit the scheduler settings from the
527 : : * first thread that created such a thread pool.
528 : : *
529 : : * At least one thread will be spawned when this function is called, either to
530 : : * create the @max_threads exclusive threads, or to preserve the scheduler
531 : : * settings of the current thread for future spawns.
532 : : *
533 : : * @error can be %NULL to ignore errors, or non-%NULL to report
534 : : * errors. An error can only occur when @exclusive is set to %TRUE
535 : : * and not all @max_threads threads could be created.
536 : : * See #GThreadError for possible errors that may occur.
537 : : * Note, even in case of error a valid #GThreadPool is returned.
538 : : *
539 : : * Returns: the new #GThreadPool
540 : : */
541 : : GThreadPool *
542 : 228 : g_thread_pool_new (GFunc func,
543 : : gpointer user_data,
544 : : gint max_threads,
545 : : gboolean exclusive,
546 : : GError **error)
547 : : {
548 : 228 : return g_thread_pool_new_full (func, user_data, NULL, max_threads, exclusive, error);
549 : : }
550 : :
551 : : /**
552 : : * g_thread_pool_new_full:
553 : : * @func: a function to execute in the threads of the new thread pool
554 : : * @user_data: user data that is handed over to @func every time it
555 : : * is called
556 : : * @item_free_func: (nullable): used to pass as a free function to
557 : : * g_async_queue_new_full()
558 : : * @max_threads: the maximal number of threads to execute concurrently
559 : : * in the new thread pool, `-1` means no limit
560 : : * @exclusive: should this thread pool be exclusive?
561 : : * @error: return location for error, or %NULL
562 : : *
563 : : * This function creates a new thread pool similar to g_thread_pool_new()
564 : : * but allowing @item_free_func to be specified to free the data passed
565 : : * to g_thread_pool_push() in the case that the #GThreadPool is stopped
566 : : * and freed before all tasks have been executed.
567 : : *
568 : : * @item_free_func will *not* be called on items successfully passed to @func.
569 : : * @func is responsible for freeing the items passed to it.
570 : : *
571 : : * Returns: (transfer full): the new #GThreadPool
572 : : *
573 : : * Since: 2.70
574 : : */
575 : : GThreadPool *
576 : 235 : g_thread_pool_new_full (GFunc func,
577 : : gpointer user_data,
578 : : GDestroyNotify item_free_func,
579 : : gint max_threads,
580 : : gboolean exclusive,
581 : : GError **error)
582 : : {
583 : : GRealThreadPool *retval;
584 : : G_LOCK_DEFINE_STATIC (init);
585 : 235 : GError *local_error = NULL;
586 : :
587 : 235 : g_return_val_if_fail (func, NULL);
588 : 235 : g_return_val_if_fail (!exclusive || max_threads != -1, NULL);
589 : 235 : g_return_val_if_fail (max_threads >= -1, NULL);
590 : :
591 : 235 : retval = g_new (GRealThreadPool, 1);
592 : :
593 : 235 : retval->pool.func = func;
594 : 235 : retval->pool.user_data = user_data;
595 : 235 : retval->pool.exclusive = exclusive;
596 : 235 : retval->queue = g_async_queue_new_full (item_free_func);
597 : 235 : g_cond_init (&retval->cond);
598 : 235 : retval->max_threads = max_threads;
599 : 235 : retval->num_threads = 0;
600 : 235 : retval->running = TRUE;
601 : 235 : retval->immediate = FALSE;
602 : 235 : retval->waiting = FALSE;
603 : 235 : retval->sort_func = NULL;
604 : 235 : retval->sort_user_data = NULL;
605 : :
606 : 235 : G_LOCK (init);
607 : 235 : if (!unused_thread_queue)
608 : 178 : unused_thread_queue = g_async_queue_new ();
609 : :
610 : : /*
611 : : * Spawn a helper thread that is only responsible for spawning new threads
612 : : * with the scheduler settings of the current thread.
613 : : *
614 : : * This is then used for making sure that all threads created on the
615 : : * non-exclusive thread-pool have the same scheduler settings, and more
616 : : * importantly don't just inherit them from the thread that just happened to
617 : : * push a new task and caused a new thread to be created.
618 : : *
619 : : * Not doing so could cause real-time priority threads or otherwise
620 : : * threads with problematic scheduler settings to be part of the
621 : : * non-exclusive thread-pools.
622 : : *
623 : : * For exclusive thread-pools this is not required as all threads are
624 : : * created immediately below and are running forever, so they will
625 : : * automatically inherit the scheduler settings from this very thread.
626 : : */
627 : 235 : if (!exclusive && !spawn_thread_queue)
628 : : {
629 : 178 : GThread *pool_spawner = NULL;
630 : :
631 : 178 : spawn_thread_queue = g_async_queue_new ();
632 : 178 : g_cond_init (&spawn_thread_cond);
633 : 178 : pool_spawner = g_thread_try_new ("pool-spawner", g_thread_pool_spawn_thread, NULL, &local_error);
634 : 178 : if (pool_spawner == NULL)
635 : : {
636 : : /* The only way to know that the pool_spawner exists is
637 : : * if (spawn_thread_queue != NULL), so if creating the pool_spawner
638 : : * failed, we must destroy the queue.
639 : : */
640 : 1 : g_clear_pointer (&spawn_thread_queue, g_async_queue_unref);
641 : : /* We must also clear spawn_thread_cond, so that a future attempt
642 : : * to create a non-exclusive pool can safely initialize it.
643 : : */
644 : 1 : g_cond_clear (&spawn_thread_cond);
645 : : }
646 : 178 : g_ignore_leak (pool_spawner);
647 : : }
648 : 235 : G_UNLOCK (init);
649 : :
650 : 235 : if (retval->pool.exclusive && local_error == NULL)
651 : : {
652 : 9 : g_async_queue_lock (retval->queue);
653 : :
654 : 68 : while (retval->num_threads < (guint) retval->max_threads)
655 : : {
656 : 60 : if (!g_thread_pool_start_thread (retval, &local_error))
657 : : {
658 : 1 : break;
659 : : }
660 : : }
661 : :
662 : 9 : g_async_queue_unlock (retval->queue);
663 : : }
664 : :
665 : 235 : if (local_error != NULL)
666 : : {
667 : : /* Failed to create pool spawner or failed to start a thread,
668 : : * so we must return NULL */
669 : 2 : g_propagate_error (error, local_error);
670 : :
671 : 2 : g_clear_pointer (&retval->queue, g_async_queue_unref);
672 : 2 : g_cond_clear (&retval->cond);
673 : :
674 : 2 : g_clear_pointer (&retval, g_free);
675 : : }
676 : :
677 : 235 : return (GThreadPool*) retval;
678 : : }
679 : :
680 : : /**
681 : : * g_thread_pool_push:
682 : : * @pool: a #GThreadPool
683 : : * @data: a new task for @pool
684 : : * @error: return location for error, or %NULL
685 : : *
686 : : * Inserts @data into the list of tasks to be executed by @pool.
687 : : *
688 : : * When the number of currently running threads is lower than the
689 : : * maximal allowed number of threads, a new thread is started (or
690 : : * reused) with the properties given to g_thread_pool_new().
691 : : * Otherwise, @data stays in the queue until a thread in this pool
692 : : * finishes its previous task and processes @data.
693 : : *
694 : : * @error can be %NULL to ignore errors, or non-%NULL to report
695 : : * errors. An error can only occur when a new thread couldn't be
696 : : * created. In that case @data is simply appended to the queue of
697 : : * work to do.
698 : : *
699 : : * Before version 2.32, this function did not return a success status.
700 : : *
701 : : * Returns: %TRUE on success, %FALSE if an error occurred
702 : : */
703 : : gboolean
704 : 105069 : g_thread_pool_push (GThreadPool *pool,
705 : : gpointer data,
706 : : GError **error)
707 : : {
708 : : GRealThreadPool *real;
709 : : gboolean result;
710 : :
711 : 105069 : real = (GRealThreadPool*) pool;
712 : :
713 : 105069 : g_return_val_if_fail (real, FALSE);
714 : 105069 : g_return_val_if_fail (real->running, FALSE);
715 : :
716 : 105069 : result = TRUE;
717 : :
718 : 105069 : g_async_queue_lock (real->queue);
719 : :
720 : 105069 : if (g_async_queue_length_unlocked (real->queue) >= 0)
721 : : {
722 : : /* No thread is waiting in the queue */
723 : 50771 : GError *local_error = NULL;
724 : :
725 : 50771 : if (!g_thread_pool_start_thread (real, &local_error))
726 : : {
727 : 0 : g_propagate_error (error, local_error);
728 : 0 : result = FALSE;
729 : : }
730 : : }
731 : :
732 : 105069 : g_thread_pool_queue_push_unlocked (real, data);
733 : 105069 : g_async_queue_unlock (real->queue);
734 : :
735 : 105069 : return result;
736 : : }
737 : :
738 : : /**
739 : : * g_thread_pool_set_max_threads:
740 : : * @pool: a #GThreadPool
741 : : * @max_threads: a new maximal number of threads for @pool,
742 : : * or -1 for unlimited
743 : : * @error: return location for error, or %NULL
744 : : *
745 : : * Sets the maximal allowed number of threads for @pool.
746 : : * A value of -1 means that the maximal number of threads
747 : : * is unlimited. If @pool is an exclusive thread pool, setting
748 : : * the maximal number of threads to -1 is not allowed.
749 : : *
750 : : * Setting @max_threads to 0 means stopping all work for @pool.
751 : : * It is effectively frozen until @max_threads is set to a non-zero
752 : : * value again.
753 : : *
754 : : * A thread is never terminated while calling @func, as supplied by
755 : : * g_thread_pool_new(). Instead the maximal number of threads only
756 : : * has effect for the allocation of new threads in g_thread_pool_push().
757 : : * A new thread is allocated, whenever the number of currently
758 : : * running threads in @pool is smaller than the maximal number.
759 : : *
760 : : * @error can be %NULL to ignore errors, or non-%NULL to report
761 : : * errors. An error can only occur when a new thread couldn't be
762 : : * created.
763 : : *
764 : : * Before version 2.32, this function did not return a success status.
765 : : *
766 : : * Returns: %TRUE on success, %FALSE if an error occurred
767 : : */
768 : : gboolean
769 : 3492 : g_thread_pool_set_max_threads (GThreadPool *pool,
770 : : gint max_threads,
771 : : GError **error)
772 : : {
773 : : GRealThreadPool *real;
774 : : gint to_start;
775 : : gboolean result;
776 : :
777 : 3492 : real = (GRealThreadPool*) pool;
778 : :
779 : 3492 : g_return_val_if_fail (real, FALSE);
780 : 3492 : g_return_val_if_fail (real->running, FALSE);
781 : 3492 : g_return_val_if_fail (!real->pool.exclusive || max_threads != -1, FALSE);
782 : 3492 : g_return_val_if_fail (max_threads >= -1, FALSE);
783 : :
784 : 3492 : result = TRUE;
785 : :
786 : 3492 : g_async_queue_lock (real->queue);
787 : :
788 : 3492 : real->max_threads = max_threads;
789 : :
790 : 3492 : if (pool->exclusive)
791 : 0 : to_start = real->max_threads - real->num_threads;
792 : : else
793 : 3492 : to_start = g_async_queue_length_unlocked (real->queue);
794 : :
795 : 522400 : for ( ; to_start > 0; to_start--)
796 : : {
797 : 518908 : GError *local_error = NULL;
798 : :
799 : 518908 : if (!g_thread_pool_start_thread (real, &local_error))
800 : : {
801 : 0 : g_propagate_error (error, local_error);
802 : 0 : result = FALSE;
803 : 0 : break;
804 : : }
805 : : }
806 : :
807 : 3492 : g_async_queue_unlock (real->queue);
808 : :
809 : 3492 : return result;
810 : : }
811 : :
812 : : /**
813 : : * g_thread_pool_get_max_threads:
814 : : * @pool: a #GThreadPool
815 : : *
816 : : * Returns the maximal number of threads for @pool.
817 : : *
818 : : * Returns: the maximal number of threads
819 : : */
820 : : gint
821 : 5 : g_thread_pool_get_max_threads (GThreadPool *pool)
822 : : {
823 : : GRealThreadPool *real;
824 : : gint retval;
825 : :
826 : 5 : real = (GRealThreadPool*) pool;
827 : :
828 : 5 : g_return_val_if_fail (real, 0);
829 : 5 : g_return_val_if_fail (real->running, 0);
830 : :
831 : 5 : g_async_queue_lock (real->queue);
832 : 5 : retval = real->max_threads;
833 : 5 : g_async_queue_unlock (real->queue);
834 : :
835 : 5 : return retval;
836 : : }
837 : :
838 : : /**
839 : : * g_thread_pool_get_num_threads:
840 : : * @pool: a #GThreadPool
841 : : *
842 : : * Returns the number of threads currently running in @pool.
843 : : *
844 : : * Returns: the number of threads currently running
845 : : */
846 : : guint
847 : 3679548 : g_thread_pool_get_num_threads (GThreadPool *pool)
848 : : {
849 : : GRealThreadPool *real;
850 : : guint retval;
851 : :
852 : 3679548 : real = (GRealThreadPool*) pool;
853 : :
854 : 3679548 : g_return_val_if_fail (real, 0);
855 : 3679548 : g_return_val_if_fail (real->running, 0);
856 : :
857 : 3679548 : g_async_queue_lock (real->queue);
858 : 3679548 : retval = real->num_threads;
859 : 3679548 : g_async_queue_unlock (real->queue);
860 : :
861 : 3679548 : return retval;
862 : : }
863 : :
864 : : /**
865 : : * g_thread_pool_unprocessed:
866 : : * @pool: a #GThreadPool
867 : : *
868 : : * Returns the number of tasks still unprocessed in @pool.
869 : : *
870 : : * Returns: the number of unprocessed tasks
871 : : */
872 : : guint
873 : 3764 : g_thread_pool_unprocessed (GThreadPool *pool)
874 : : {
875 : : GRealThreadPool *real;
876 : : gint unprocessed;
877 : :
878 : 3764 : real = (GRealThreadPool*) pool;
879 : :
880 : 3764 : g_return_val_if_fail (real, 0);
881 : 3764 : g_return_val_if_fail (real->running, 0);
882 : :
883 : 3764 : unprocessed = g_async_queue_length (real->queue);
884 : :
885 : 3764 : return MAX (unprocessed, 0);
886 : : }
887 : :
888 : : /**
889 : : * g_thread_pool_free:
890 : : * @pool: a #GThreadPool
891 : : * @immediate: should @pool shut down immediately?
892 : : * @wait_: should the function wait for all tasks to be finished?
893 : : *
894 : : * Frees all resources allocated for @pool.
895 : : *
896 : : * If @immediate is %TRUE, no new task is processed for @pool.
897 : : * Otherwise @pool is not freed before the last task is processed.
898 : : * Note however, that no thread of this pool is interrupted while
899 : : * processing a task. Instead at least all still running threads
900 : : * can finish their tasks before the @pool is freed.
901 : : *
902 : : * If @wait_ is %TRUE, this function does not return before all
903 : : * tasks to be processed (dependent on @immediate, whether all
904 : : * or only the currently running) are ready.
905 : : * Otherwise this function returns immediately.
906 : : *
907 : : * After calling this function @pool must not be used anymore.
908 : : */
909 : : void
910 : 48 : g_thread_pool_free (GThreadPool *pool,
911 : : gboolean immediate,
912 : : gboolean wait_)
913 : : {
914 : : GRealThreadPool *real;
915 : :
916 : 48 : real = (GRealThreadPool*) pool;
917 : :
918 : 48 : g_return_if_fail (real);
919 : 48 : g_return_if_fail (real->running);
920 : :
921 : : /* If there's no thread allowed here, there is not much sense in
922 : : * not stopping this pool immediately, when it's not empty
923 : : */
924 : 48 : g_return_if_fail (immediate ||
925 : : real->max_threads != 0 ||
926 : : g_async_queue_length (real->queue) == 0);
927 : :
928 : 48 : g_async_queue_lock (real->queue);
929 : :
930 : 48 : real->running = FALSE;
931 : 48 : real->immediate = immediate;
932 : 48 : real->waiting = wait_;
933 : :
934 : 48 : if (wait_)
935 : : {
936 : 62 : while (g_async_queue_length_unlocked (real->queue) != (gint) -real->num_threads &&
937 : 25 : !(immediate && real->num_threads == 0))
938 : 37 : g_cond_wait (&real->cond, _g_async_queue_get_mutex (real->queue));
939 : : }
940 : :
941 : 48 : if (immediate || g_async_queue_length_unlocked (real->queue) == (gint) -real->num_threads)
942 : : {
943 : : /* No thread is currently doing something (and nothing is left
944 : : * to process in the queue)
945 : : */
946 : 46 : if (real->num_threads == 0)
947 : : {
948 : : /* No threads left, we clean up */
949 : 23 : g_async_queue_unlock (real->queue);
950 : 23 : g_thread_pool_free_internal (real);
951 : 23 : return;
952 : : }
953 : :
954 : 23 : g_thread_pool_wakeup_and_stop_all (real);
955 : : }
956 : :
957 : : /* The last thread should cleanup the pool */
958 : 25 : real->waiting = FALSE;
959 : 25 : g_async_queue_unlock (real->queue);
960 : : }
961 : :
962 : : static void
963 : 48 : g_thread_pool_free_internal (GRealThreadPool* pool)
964 : : {
965 : 48 : g_return_if_fail (pool);
966 : 48 : g_return_if_fail (pool->running == FALSE);
967 : 48 : g_return_if_fail (pool->num_threads == 0);
968 : :
969 : : /* Ensure the dummy item pushed on by g_thread_pool_wakeup_and_stop_all() is
970 : : * removed, before it’s potentially passed to the user-provided
971 : : * @item_free_func. */
972 : 48 : g_async_queue_remove (pool->queue, GUINT_TO_POINTER (1));
973 : :
974 : 48 : g_async_queue_unref (pool->queue);
975 : 48 : g_cond_clear (&pool->cond);
976 : :
977 : 48 : g_free (pool);
978 : : }
979 : :
980 : : static void
981 : 23 : g_thread_pool_wakeup_and_stop_all (GRealThreadPool *pool)
982 : : {
983 : : guint i;
984 : :
985 : 23 : g_return_if_fail (pool);
986 : 23 : g_return_if_fail (pool->running == FALSE);
987 : 23 : g_return_if_fail (pool->num_threads != 0);
988 : :
989 : 23 : pool->immediate = TRUE;
990 : :
991 : : /*
992 : : * So here we're sending bogus data to the pool threads, which
993 : : * should cause them each to wake up, and check the above
994 : : * pool->immediate condition. However we don't want that
995 : : * data to be sorted (since it'll crash the sorter).
996 : : */
997 : 68 : for (i = 0; i < pool->num_threads; i++)
998 : 45 : g_async_queue_push_unlocked (pool->queue, GUINT_TO_POINTER (1));
999 : : }
1000 : :
1001 : : /**
1002 : : * g_thread_pool_set_max_unused_threads:
1003 : : * @max_threads: maximal number of unused threads
1004 : : *
1005 : : * Sets the maximal number of unused threads to @max_threads.
1006 : : * If @max_threads is -1, no limit is imposed on the number
1007 : : * of unused threads.
1008 : : *
1009 : : * The default value is 8 since GLib 2.84. Previously the default value was 2.
1010 : : */
1011 : : void
1012 : 20 : g_thread_pool_set_max_unused_threads (gint max_threads)
1013 : : {
1014 : 20 : g_return_if_fail (max_threads >= -1);
1015 : :
1016 : 20 : g_atomic_int_set (&max_unused_threads, max_threads);
1017 : :
1018 : 20 : if (max_threads != -1)
1019 : : {
1020 : 13 : max_threads -= g_atomic_int_get (&unused_threads);
1021 : 13 : if (max_threads < 0)
1022 : : {
1023 : 3 : g_atomic_int_set (&kill_unused_threads, -max_threads);
1024 : 3 : g_atomic_int_inc (&wakeup_thread_serial);
1025 : :
1026 : 3 : g_async_queue_lock (unused_thread_queue);
1027 : :
1028 : : do
1029 : : {
1030 : 144 : g_async_queue_push_unlocked (unused_thread_queue,
1031 : : wakeup_thread_marker);
1032 : : }
1033 : 144 : while (++max_threads);
1034 : :
1035 : 3 : g_async_queue_unlock (unused_thread_queue);
1036 : : }
1037 : : }
1038 : : }
1039 : :
1040 : : /**
1041 : : * g_thread_pool_get_max_unused_threads:
1042 : : *
1043 : : * Returns the maximal allowed number of unused threads.
1044 : : *
1045 : : * Returns: the maximal number of unused threads
1046 : : */
1047 : : gint
1048 : 7 : g_thread_pool_get_max_unused_threads (void)
1049 : : {
1050 : 7 : return g_atomic_int_get (&max_unused_threads);
1051 : : }
1052 : :
1053 : : /**
1054 : : * g_thread_pool_get_num_unused_threads:
1055 : : *
1056 : : * Returns the number of currently unused threads.
1057 : : *
1058 : : * Returns: the number of currently unused threads
1059 : : */
1060 : : guint
1061 : 183 : g_thread_pool_get_num_unused_threads (void)
1062 : : {
1063 : 183 : return (guint) g_atomic_int_get (&unused_threads);
1064 : : }
1065 : :
1066 : : /**
1067 : : * g_thread_pool_stop_unused_threads:
1068 : : *
1069 : : * Stops all currently unused threads. This does not change the
1070 : : * maximal number of unused threads. This function can be used to
1071 : : * regularly stop all unused threads e.g. from g_timeout_add().
1072 : : */
1073 : : void
1074 : 5 : g_thread_pool_stop_unused_threads (void)
1075 : : {
1076 : : guint oldval;
1077 : :
1078 : 5 : oldval = g_thread_pool_get_max_unused_threads ();
1079 : :
1080 : 5 : g_thread_pool_set_max_unused_threads (0);
1081 : 5 : g_thread_pool_set_max_unused_threads (oldval);
1082 : 5 : }
1083 : :
1084 : : /**
1085 : : * g_thread_pool_set_sort_function:
1086 : : * @pool: a #GThreadPool
1087 : : * @func: the #GCompareDataFunc used to sort the list of tasks.
1088 : : * This function is passed two tasks. It should return
1089 : : * 0 if the order in which they are handled does not matter,
1090 : : * a negative value if the first task should be processed before
1091 : : * the second or a positive value if the second task should be
1092 : : * processed first.
1093 : : * @user_data: user data passed to @func
1094 : : *
1095 : : * Sets the function used to sort the list of tasks. This allows the
1096 : : * tasks to be processed by a priority determined by @func, and not
1097 : : * just in the order in which they were added to the pool.
1098 : : *
1099 : : * Note, if the maximum number of threads is more than 1, the order
1100 : : * that threads are executed cannot be guaranteed 100%. Threads are
1101 : : * scheduled by the operating system and are executed at random. It
1102 : : * cannot be assumed that threads are executed in the order they are
1103 : : * created.
1104 : : *
1105 : : * Since: 2.10
1106 : : */
1107 : : void
1108 : 174 : g_thread_pool_set_sort_function (GThreadPool *pool,
1109 : : GCompareDataFunc func,
1110 : : gpointer user_data)
1111 : : {
1112 : : GRealThreadPool *real;
1113 : :
1114 : 174 : real = (GRealThreadPool*) pool;
1115 : :
1116 : 174 : g_return_if_fail (real);
1117 : 174 : g_return_if_fail (real->running);
1118 : :
1119 : 174 : g_async_queue_lock (real->queue);
1120 : :
1121 : 174 : real->sort_func = func;
1122 : 174 : real->sort_user_data = user_data;
1123 : :
1124 : 174 : if (func)
1125 : 174 : g_async_queue_sort_unlocked (real->queue,
1126 : : real->sort_func,
1127 : : real->sort_user_data);
1128 : :
1129 : 174 : g_async_queue_unlock (real->queue);
1130 : : }
1131 : :
1132 : : /**
1133 : : * g_thread_pool_move_to_front:
1134 : : * @pool: a #GThreadPool
1135 : : * @data: an unprocessed item in the pool
1136 : : *
1137 : : * Moves the item to the front of the queue of unprocessed
1138 : : * items, so that it will be processed next.
1139 : : *
1140 : : * Returns: %TRUE if the item was found and moved
1141 : : *
1142 : : * Since: 2.46
1143 : : */
1144 : : gboolean
1145 : 1036 : g_thread_pool_move_to_front (GThreadPool *pool,
1146 : : gpointer data)
1147 : : {
1148 : 1036 : GRealThreadPool *real = (GRealThreadPool*) pool;
1149 : : gboolean found;
1150 : :
1151 : 1036 : g_async_queue_lock (real->queue);
1152 : :
1153 : 1036 : found = g_async_queue_remove_unlocked (real->queue, data);
1154 : 1036 : if (found)
1155 : 1015 : g_async_queue_push_front_unlocked (real->queue, data);
1156 : :
1157 : 1036 : g_async_queue_unlock (real->queue);
1158 : :
1159 : 1036 : return found;
1160 : : }
1161 : :
1162 : : /**
1163 : : * g_thread_pool_set_max_idle_time:
1164 : : * @interval: the maximum @interval (in milliseconds)
1165 : : * a thread can be idle
1166 : : *
1167 : : * This function will set the maximum @interval that a thread
1168 : : * waiting in the pool for new tasks can be idle for before
1169 : : * being stopped. This function is similar to calling
1170 : : * g_thread_pool_stop_unused_threads() on a regular timeout,
1171 : : * except this is done on a per thread basis.
1172 : : *
1173 : : * By setting @interval to 0, idle threads will not be stopped.
1174 : : *
1175 : : * The default value is 15000 (15 seconds).
1176 : : *
1177 : : * Since: 2.10
1178 : : */
1179 : : void
1180 : 3 : g_thread_pool_set_max_idle_time (guint interval)
1181 : : {
1182 : : guint i;
1183 : :
1184 : 3 : g_atomic_int_set (&max_idle_time, interval);
1185 : :
1186 : 3 : i = (guint) g_atomic_int_get (&unused_threads);
1187 : 3 : if (i > 0)
1188 : : {
1189 : 0 : g_atomic_int_inc (&wakeup_thread_serial);
1190 : 0 : g_async_queue_lock (unused_thread_queue);
1191 : :
1192 : : do
1193 : : {
1194 : 0 : g_async_queue_push_unlocked (unused_thread_queue,
1195 : : wakeup_thread_marker);
1196 : : }
1197 : 0 : while (--i);
1198 : :
1199 : 0 : g_async_queue_unlock (unused_thread_queue);
1200 : : }
1201 : 3 : }
1202 : :
1203 : : /**
1204 : : * g_thread_pool_get_max_idle_time:
1205 : : *
1206 : : * This function will return the maximum @interval that a
1207 : : * thread will wait in the thread pool for new tasks before
1208 : : * being stopped.
1209 : : *
1210 : : * If this function returns 0, threads waiting in the thread
1211 : : * pool for new work are not stopped.
1212 : : *
1213 : : * Returns: the maximum @interval (milliseconds) to wait
1214 : : * for new tasks in the thread pool before stopping the
1215 : : * thread
1216 : : *
1217 : : * Since: 2.10
1218 : : */
1219 : : guint
1220 : 3 : g_thread_pool_get_max_idle_time (void)
1221 : : {
1222 : 3 : return (guint) g_atomic_int_get (&max_idle_time);
1223 : : }
|