Re: [Mesa-dev] [PATCH 1/4] gallium/u_queue: fix random crashes when the app calls exit()
On 23/02/17 03:44 PM, Michel Dänzer wrote: > On 22/02/17 06:05 AM, Marek Olšák wrote: >> From: Marek Olšák >> >> This fixes: >> vdpauinfo: ../lib/CodeGen/TargetPassConfig.cpp:579: virtual void >> llvm::TargetPassConfig::addMachinePasses(): Assertion `TPI && IPI && >> "Pass ID not registered!"' failed. >> >> v2: use list_head, switch the call order in destroy >> >> Cc: 13.0 17.0 > > Hold the press. > > Unfortunately, this broke the piglit tests > > glx@glx_ext_import_context@import context, multi process > glx@glx_ext_import_context@make current, single process > > so this appears unsafe for apps which call fork(). Forgot to include the backtrace, see below. Note that one has to do set follow-fork-mode child at the gdb prompt before reproducing the problem. Thread 2.1 "glx-make-curren" received signal SIGSEGV, Segmentation fault. [Switching to Thread 0x77f85780 (LWP 3219)] 0x72ccc5c9 in pthread_join (threadid=140737146943232, thread_return=thread_return@entry=0x7fffe518) at pthread_join.c:45 45 pthread_join.c: No such file or directory. (gdb) bt #0 0x72ccc5c9 in pthread_join (threadid=140737146943232, thread_return=thread_return@entry=0x7fffe518) at pthread_join.c:45 #1 0x707ea3a0 in thrd_join (res=0x0, thr=) at ../../../../include/c11/threads_posix.h:336 #2 pipe_thread_wait (thread=) at ../../../../src/gallium/auxiliary/os/os_thread.h:80 #3 util_queue_killall_and_wait (queue=0x557d9a48) at ../../../../src/gallium/auxiliary/util/u_queue.c:253 #4 atexit_handler () at ../../../../src/gallium/auxiliary/util/u_queue.c:53 #5 0x747df920 in __run_exit_handlers (status=0, listp=0x74b425d8 <__exit_funcs>, run_list_atexit=run_list_atexit@entry=true, run_dtors=run_dtors@entry=true) at exit.c:83 #6 0x747df97a in __GI_exit (status=) at exit.c:105 #7 0x5793 in main (argc=2, argv=0x7fffe6a8) at /home/daenzer/src/piglit-git/piglit/tests/spec/glx_ext_import_context/make-current-single-process.c:55 (gdb) frame 3 #3 util_queue_killall_and_wait (queue=0x557d9a48) at ../../../../src/gallium/auxiliary/util/u_queue.c:253 253 pipe_thread_wait(queue->threads[i]); (gdb) l 248queue->kill_threads = 1; 249pipe_condvar_broadcast(queue->has_queued_cond); 250pipe_mutex_unlock(queue->lock); 251 252for (i = 0; i < queue->num_threads; i++) 253 pipe_thread_wait(queue->threads[i]); 254 } 255 256 void 257 util_queue_destroy(struct util_queue *queue) (gdb) p queue $1 = (struct util_queue *) 0x557d9a48 (gdb) p *queue $2 = {name = 0x70c83a5a "si_shader", lock = {__data = {__lock = 0, __count = 0, __owner = 0, __nusers = 4, __kind = 0, __spins = 0, __elision = 0, __list = {__prev = 0x0, __next = 0x0}}, __size = '\000' , "\004", '\000' , __align = 0}, has_queued_cond = {__data = {__lock = 0, __futex = 8, __total_seq = 4, __wakeup_seq = 4, __woken_seq = 4, __mutex = 0x557d9a50, __nwaiters = 8, __broadcast_seq = 1}, __size = "\000\000\000\000\b\000\000\000\004\000\000\000\000\000\000\000\004\000\000\000\000\000\000\000\004\000\000\000\000\000\000\000P\232}UUU\000\000\b\000\000\000\001\000\000", __align = 34359738368}, has_space_cond = {__data = {__lock = 0, __futex = 0, __total_seq = 0, __wakeup_seq = 0, __woken_seq = 0, __mutex = 0x0, __nwaiters = 0, __broadcast_seq = 0}, __size = '\000' , __align = 0}, threads = 0x557e2750, num_queued = 0, num_threads = 4, kill_threads = 1, max_jobs = 32, write_idx = 0, read_idx = 0, jobs = 0x557e2340, head = {prev = 0x71147cb0 , next = 0x557d8b98}} [...] (gdb) p *queue->threads@4 $7 = {140737155335936, 140737146943232, 140737138550528, 140737130157824} -- Earthling Michel Dänzer | http://www.amd.com Libre software enthusiast | Mesa and X developer ___ mesa-dev mailing list mesa-dev@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/mesa-dev
Re: [Mesa-dev] [PATCH 1/4] gallium/u_queue: fix random crashes when the app calls exit()
On 22/02/17 06:05 AM, Marek Olšák wrote: > From: Marek Olšák > > This fixes: > vdpauinfo: ../lib/CodeGen/TargetPassConfig.cpp:579: virtual void > llvm::TargetPassConfig::addMachinePasses(): Assertion `TPI && IPI && > "Pass ID not registered!"' failed. > > v2: use list_head, switch the call order in destroy > > Cc: 13.0 17.0 Hold the press. Unfortunately, this broke the piglit tests glx@glx_ext_import_context@import context, multi process glx@glx_ext_import_context@make current, single process so this appears unsafe for apps which call fork(). -- Earthling Michel Dänzer | http://www.amd.com Libre software enthusiast | Mesa and X developer ___ mesa-dev mailing list mesa-dev@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/mesa-dev
Re: [Mesa-dev] [PATCH 1/4] gallium/u_queue: fix random crashes when the app calls exit()
Reviewed-by: Nicolai Hähnle On 21.02.2017 22:05, Marek Olšák wrote: From: Marek Olšák This fixes: vdpauinfo: ../lib/CodeGen/TargetPassConfig.cpp:579: virtual void llvm::TargetPassConfig::addMachinePasses(): Assertion `TPI && IPI && "Pass ID not registered!"' failed. v2: use list_head, switch the call order in destroy Cc: 13.0 17.0 --- src/gallium/auxiliary/util/u_queue.c | 76 +++- src/gallium/auxiliary/util/u_queue.h | 4 ++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/src/gallium/auxiliary/util/u_queue.c b/src/gallium/auxiliary/util/u_queue.c index 4da5d8e..52cfc0a 100644 --- a/src/gallium/auxiliary/util/u_queue.c +++ b/src/gallium/auxiliary/util/u_queue.c @@ -22,20 +22,82 @@ * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial portions * of the Software. */ #include "u_queue.h" #include "u_memory.h" #include "u_string.h" #include "os/os_time.h" +static void util_queue_killall_and_wait(struct util_queue *queue); + +/ + * Wait for all queues to assert idle when exit() is called. + * + * Otherwise, C++ static variable destructors can be called while threads + * are using the static variables. + */ + +static once_flag atexit_once_flag = ONCE_FLAG_INIT; +static struct list_head queue_list; +pipe_static_mutex(exit_mutex); + +static void +atexit_handler(void) +{ + struct util_queue *iter; + + pipe_mutex_lock(exit_mutex); + /* Wait for all queues to assert idle. */ + LIST_FOR_EACH_ENTRY(iter, &queue_list, head) { + util_queue_killall_and_wait(iter); + } + pipe_mutex_unlock(exit_mutex); +} + +static void +global_init(void) +{ + LIST_INITHEAD(&queue_list); + atexit(atexit_handler); +} + +static void +add_to_atexit_list(struct util_queue *queue) +{ + call_once(&atexit_once_flag, global_init); + + pipe_mutex_lock(exit_mutex); + LIST_ADD(&queue->head, &queue_list); + pipe_mutex_unlock(exit_mutex); +} + +static void +remove_from_atexit_list(struct util_queue *queue) +{ + struct util_queue *iter, *tmp; + + pipe_mutex_lock(exit_mutex); + LIST_FOR_EACH_ENTRY_SAFE(iter, tmp, &queue_list, head) { + if (iter == queue) { + LIST_DEL(&iter->head); + break; + } + } + pipe_mutex_unlock(exit_mutex); +} + +/ + * util_queue implementation + */ + static void util_queue_fence_signal(struct util_queue_fence *fence) { pipe_mutex_lock(fence->mutex); fence->signalled = true; pipe_condvar_broadcast(fence->cond); pipe_mutex_unlock(fence->mutex); } void @@ -97,20 +159,21 @@ static PIPE_THREAD_ROUTINE(util_queue_thread_func, input) } /* signal remaining jobs before terminating */ pipe_mutex_lock(queue->lock); while (queue->jobs[queue->read_idx].job) { util_queue_fence_signal(queue->jobs[queue->read_idx].fence); queue->jobs[queue->read_idx].job = NULL; queue->read_idx = (queue->read_idx + 1) % queue->max_jobs; } + queue->num_queued = 0; /* reset this when exiting the thread */ pipe_mutex_unlock(queue->lock); return 0; } bool util_queue_init(struct util_queue *queue, const char *name, unsigned max_jobs, unsigned num_threads) { @@ -150,49 +213,58 @@ util_queue_init(struct util_queue *queue, if (i == 0) { /* no threads created, fail */ goto fail; } else { /* at least one thread created, so use it */ queue->num_threads = i+1; break; } } } + + add_to_atexit_list(queue); return true; fail: FREE(queue->threads); if (queue->jobs) { pipe_condvar_destroy(queue->has_space_cond); pipe_condvar_destroy(queue->has_queued_cond); pipe_mutex_destroy(queue->lock); FREE(queue->jobs); } /* also util_queue_is_initialized can be used to check for success */ memset(queue, 0, sizeof(*queue)); return false; } -void -util_queue_destroy(struct util_queue *queue) +static void +util_queue_killall_and_wait(struct util_queue *queue) { unsigned i; /* Signal all threads to terminate. */ pipe_mutex_lock(queue->lock); queue->kill_threads = 1; pipe_condvar_broadcast(queue->has_queued_cond); pipe_mutex_unlock(queue->lock); for (i = 0; i < queue->num_threads; i++) pipe_thread_wait(queue->threads[i]); +} + +void +util_queue_destroy(struct util_queue *queue) +{ + util_queue_killall_and_wait(queue); + remove_from_atexit_list(queue); pipe_condvar_destroy(queue->has_space_cond); pipe_condvar_destroy(queue->has_queued_cond); pipe_mutex_destroy(queue->lock); FREE(queue->jobs); FREE(queue->threads); } void util_queue_fence_init(struct util_queue_
[Mesa-dev] [PATCH 1/4] gallium/u_queue: fix random crashes when the app calls exit()
From: Marek Olšák This fixes: vdpauinfo: ../lib/CodeGen/TargetPassConfig.cpp:579: virtual void llvm::TargetPassConfig::addMachinePasses(): Assertion `TPI && IPI && "Pass ID not registered!"' failed. v2: use list_head, switch the call order in destroy Cc: 13.0 17.0 --- src/gallium/auxiliary/util/u_queue.c | 76 +++- src/gallium/auxiliary/util/u_queue.h | 4 ++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/src/gallium/auxiliary/util/u_queue.c b/src/gallium/auxiliary/util/u_queue.c index 4da5d8e..52cfc0a 100644 --- a/src/gallium/auxiliary/util/u_queue.c +++ b/src/gallium/auxiliary/util/u_queue.c @@ -22,20 +22,82 @@ * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial portions * of the Software. */ #include "u_queue.h" #include "u_memory.h" #include "u_string.h" #include "os/os_time.h" +static void util_queue_killall_and_wait(struct util_queue *queue); + +/ + * Wait for all queues to assert idle when exit() is called. + * + * Otherwise, C++ static variable destructors can be called while threads + * are using the static variables. + */ + +static once_flag atexit_once_flag = ONCE_FLAG_INIT; +static struct list_head queue_list; +pipe_static_mutex(exit_mutex); + +static void +atexit_handler(void) +{ + struct util_queue *iter; + + pipe_mutex_lock(exit_mutex); + /* Wait for all queues to assert idle. */ + LIST_FOR_EACH_ENTRY(iter, &queue_list, head) { + util_queue_killall_and_wait(iter); + } + pipe_mutex_unlock(exit_mutex); +} + +static void +global_init(void) +{ + LIST_INITHEAD(&queue_list); + atexit(atexit_handler); +} + +static void +add_to_atexit_list(struct util_queue *queue) +{ + call_once(&atexit_once_flag, global_init); + + pipe_mutex_lock(exit_mutex); + LIST_ADD(&queue->head, &queue_list); + pipe_mutex_unlock(exit_mutex); +} + +static void +remove_from_atexit_list(struct util_queue *queue) +{ + struct util_queue *iter, *tmp; + + pipe_mutex_lock(exit_mutex); + LIST_FOR_EACH_ENTRY_SAFE(iter, tmp, &queue_list, head) { + if (iter == queue) { + LIST_DEL(&iter->head); + break; + } + } + pipe_mutex_unlock(exit_mutex); +} + +/ + * util_queue implementation + */ + static void util_queue_fence_signal(struct util_queue_fence *fence) { pipe_mutex_lock(fence->mutex); fence->signalled = true; pipe_condvar_broadcast(fence->cond); pipe_mutex_unlock(fence->mutex); } void @@ -97,20 +159,21 @@ static PIPE_THREAD_ROUTINE(util_queue_thread_func, input) } /* signal remaining jobs before terminating */ pipe_mutex_lock(queue->lock); while (queue->jobs[queue->read_idx].job) { util_queue_fence_signal(queue->jobs[queue->read_idx].fence); queue->jobs[queue->read_idx].job = NULL; queue->read_idx = (queue->read_idx + 1) % queue->max_jobs; } + queue->num_queued = 0; /* reset this when exiting the thread */ pipe_mutex_unlock(queue->lock); return 0; } bool util_queue_init(struct util_queue *queue, const char *name, unsigned max_jobs, unsigned num_threads) { @@ -150,49 +213,58 @@ util_queue_init(struct util_queue *queue, if (i == 0) { /* no threads created, fail */ goto fail; } else { /* at least one thread created, so use it */ queue->num_threads = i+1; break; } } } + + add_to_atexit_list(queue); return true; fail: FREE(queue->threads); if (queue->jobs) { pipe_condvar_destroy(queue->has_space_cond); pipe_condvar_destroy(queue->has_queued_cond); pipe_mutex_destroy(queue->lock); FREE(queue->jobs); } /* also util_queue_is_initialized can be used to check for success */ memset(queue, 0, sizeof(*queue)); return false; } -void -util_queue_destroy(struct util_queue *queue) +static void +util_queue_killall_and_wait(struct util_queue *queue) { unsigned i; /* Signal all threads to terminate. */ pipe_mutex_lock(queue->lock); queue->kill_threads = 1; pipe_condvar_broadcast(queue->has_queued_cond); pipe_mutex_unlock(queue->lock); for (i = 0; i < queue->num_threads; i++) pipe_thread_wait(queue->threads[i]); +} + +void +util_queue_destroy(struct util_queue *queue) +{ + util_queue_killall_and_wait(queue); + remove_from_atexit_list(queue); pipe_condvar_destroy(queue->has_space_cond); pipe_condvar_destroy(queue->has_queued_cond); pipe_mutex_destroy(queue->lock); FREE(queue->jobs); FREE(queue->threads); } void util_queue_fence_init(struct util_queue_fence *fence) diff --git a/src/gallium/auxiliary/util/u_que
Re: [Mesa-dev] [PATCH 1/4] gallium/u_queue: fix random crashes when the app calls exit()
On 20.02.2017 18:44, Marek Olšák wrote: From: Marek Olšák This fixes: vdpauinfo: ../lib/CodeGen/TargetPassConfig.cpp:579: virtual void llvm::TargetPassConfig::addMachinePasses(): Assertion `TPI && IPI && "Pass ID not registered!"' failed. Cc: 13.0 17.0 --- src/gallium/auxiliary/util/u_queue.c | 88 +++- src/gallium/auxiliary/util/u_queue.h | 3 ++ 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/src/gallium/auxiliary/util/u_queue.c b/src/gallium/auxiliary/util/u_queue.c index 4da5d8e..89fb235 100644 --- a/src/gallium/auxiliary/util/u_queue.c +++ b/src/gallium/auxiliary/util/u_queue.c @@ -22,20 +22,94 @@ * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial portions * of the Software. */ #include "u_queue.h" #include "u_memory.h" #include "u_string.h" #include "os/os_time.h" +static void util_queue_killall_and_wait(struct util_queue *queue); + +/ + * Wait for all queues to assert idle when exit() is called. + * + * Otherwise, C++ static variable destructors can be called while threads + * are using the static variables. + */ + +static once_flag atexit_once_flag = ONCE_FLAG_INIT; +static struct util_queue *first_queue; +pipe_static_mutex(exit_mutex); + +static void +atexit_handler(void) +{ + struct util_queue *queue; + + pipe_mutex_lock(exit_mutex); + queue = first_queue; + /* Wait for all queues to assert idle. */ + while (queue) { + util_queue_killall_and_wait(queue); + queue = queue->next; + } + pipe_mutex_unlock(exit_mutex); +} + +static void +call_atexit(void) +{ + atexit(atexit_handler); +} + +static void +add_to_atexit_list(struct util_queue *queue) +{ + call_once(&atexit_once_flag, call_atexit); + + pipe_mutex_lock(exit_mutex); + queue->next = first_queue; + first_queue = queue; + pipe_mutex_unlock(exit_mutex); +} + +static void +remove_from_atexit_list(struct util_queue *queue) +{ + struct util_queue *iter; + + pipe_mutex_lock(exit_mutex); + assert(first_queue); + iter = first_queue; + + /* Search the list except the first one. */ + while (iter->next) { + if (iter->next == queue) { + iter->next = queue->next; + pipe_mutex_unlock(exit_mutex); + return; + } + iter = iter->next; + } Please use double pointers to simplify this code, i.e. struct util_queue **iter = &first_queue; while (*iter != queue) iter = &(*iter)->next; *iter = queue->next; Actually, I think it would even be fine to use util/list.h to simplify the code, but I'm fine with either option. + + /* It must be the first one. */ + assert(first_queue == queue); + first_queue = first_queue->next; + pipe_mutex_unlock(exit_mutex); +} + +/ + * util_queue implementation + */ + static void util_queue_fence_signal(struct util_queue_fence *fence) { pipe_mutex_lock(fence->mutex); fence->signalled = true; pipe_condvar_broadcast(fence->cond); pipe_mutex_unlock(fence->mutex); } void @@ -97,20 +171,21 @@ static PIPE_THREAD_ROUTINE(util_queue_thread_func, input) } /* signal remaining jobs before terminating */ pipe_mutex_lock(queue->lock); while (queue->jobs[queue->read_idx].job) { util_queue_fence_signal(queue->jobs[queue->read_idx].fence); queue->jobs[queue->read_idx].job = NULL; queue->read_idx = (queue->read_idx + 1) % queue->max_jobs; } + queue->num_queued = 0; /* reset this when exiting the thread */ pipe_mutex_unlock(queue->lock); return 0; } bool util_queue_init(struct util_queue *queue, const char *name, unsigned max_jobs, unsigned num_threads) { @@ -150,49 +225,58 @@ util_queue_init(struct util_queue *queue, if (i == 0) { /* no threads created, fail */ goto fail; } else { /* at least one thread created, so use it */ queue->num_threads = i+1; break; } } } + + add_to_atexit_list(queue); return true; fail: FREE(queue->threads); if (queue->jobs) { pipe_condvar_destroy(queue->has_space_cond); pipe_condvar_destroy(queue->has_queued_cond); pipe_mutex_destroy(queue->lock); FREE(queue->jobs); } /* also util_queue_is_initialized can be used to check for success */ memset(queue, 0, sizeof(*queue)); return false; } -void -util_queue_destroy(struct util_queue *queue) +static void +util_queue_killall_and_wait(struct util_queue *queue) { unsigned i; /* Signal all threads to terminate. */ pipe_mutex_lock(queue->lock); queue->kill_threads = 1; pipe_condvar_broadcast(queue->has_queued_cond); pipe_mutex_unlock(queu
[Mesa-dev] [PATCH 1/4] gallium/u_queue: fix random crashes when the app calls exit()
From: Marek Olšák This fixes: vdpauinfo: ../lib/CodeGen/TargetPassConfig.cpp:579: virtual void llvm::TargetPassConfig::addMachinePasses(): Assertion `TPI && IPI && "Pass ID not registered!"' failed. Cc: 13.0 17.0 --- src/gallium/auxiliary/util/u_queue.c | 88 +++- src/gallium/auxiliary/util/u_queue.h | 3 ++ 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/src/gallium/auxiliary/util/u_queue.c b/src/gallium/auxiliary/util/u_queue.c index 4da5d8e..89fb235 100644 --- a/src/gallium/auxiliary/util/u_queue.c +++ b/src/gallium/auxiliary/util/u_queue.c @@ -22,20 +22,94 @@ * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial portions * of the Software. */ #include "u_queue.h" #include "u_memory.h" #include "u_string.h" #include "os/os_time.h" +static void util_queue_killall_and_wait(struct util_queue *queue); + +/ + * Wait for all queues to assert idle when exit() is called. + * + * Otherwise, C++ static variable destructors can be called while threads + * are using the static variables. + */ + +static once_flag atexit_once_flag = ONCE_FLAG_INIT; +static struct util_queue *first_queue; +pipe_static_mutex(exit_mutex); + +static void +atexit_handler(void) +{ + struct util_queue *queue; + + pipe_mutex_lock(exit_mutex); + queue = first_queue; + /* Wait for all queues to assert idle. */ + while (queue) { + util_queue_killall_and_wait(queue); + queue = queue->next; + } + pipe_mutex_unlock(exit_mutex); +} + +static void +call_atexit(void) +{ + atexit(atexit_handler); +} + +static void +add_to_atexit_list(struct util_queue *queue) +{ + call_once(&atexit_once_flag, call_atexit); + + pipe_mutex_lock(exit_mutex); + queue->next = first_queue; + first_queue = queue; + pipe_mutex_unlock(exit_mutex); +} + +static void +remove_from_atexit_list(struct util_queue *queue) +{ + struct util_queue *iter; + + pipe_mutex_lock(exit_mutex); + assert(first_queue); + iter = first_queue; + + /* Search the list except the first one. */ + while (iter->next) { + if (iter->next == queue) { + iter->next = queue->next; + pipe_mutex_unlock(exit_mutex); + return; + } + iter = iter->next; + } + + /* It must be the first one. */ + assert(first_queue == queue); + first_queue = first_queue->next; + pipe_mutex_unlock(exit_mutex); +} + +/ + * util_queue implementation + */ + static void util_queue_fence_signal(struct util_queue_fence *fence) { pipe_mutex_lock(fence->mutex); fence->signalled = true; pipe_condvar_broadcast(fence->cond); pipe_mutex_unlock(fence->mutex); } void @@ -97,20 +171,21 @@ static PIPE_THREAD_ROUTINE(util_queue_thread_func, input) } /* signal remaining jobs before terminating */ pipe_mutex_lock(queue->lock); while (queue->jobs[queue->read_idx].job) { util_queue_fence_signal(queue->jobs[queue->read_idx].fence); queue->jobs[queue->read_idx].job = NULL; queue->read_idx = (queue->read_idx + 1) % queue->max_jobs; } + queue->num_queued = 0; /* reset this when exiting the thread */ pipe_mutex_unlock(queue->lock); return 0; } bool util_queue_init(struct util_queue *queue, const char *name, unsigned max_jobs, unsigned num_threads) { @@ -150,49 +225,58 @@ util_queue_init(struct util_queue *queue, if (i == 0) { /* no threads created, fail */ goto fail; } else { /* at least one thread created, so use it */ queue->num_threads = i+1; break; } } } + + add_to_atexit_list(queue); return true; fail: FREE(queue->threads); if (queue->jobs) { pipe_condvar_destroy(queue->has_space_cond); pipe_condvar_destroy(queue->has_queued_cond); pipe_mutex_destroy(queue->lock); FREE(queue->jobs); } /* also util_queue_is_initialized can be used to check for success */ memset(queue, 0, sizeof(*queue)); return false; } -void -util_queue_destroy(struct util_queue *queue) +static void +util_queue_killall_and_wait(struct util_queue *queue) { unsigned i; /* Signal all threads to terminate. */ pipe_mutex_lock(queue->lock); queue->kill_threads = 1; pipe_condvar_broadcast(queue->has_queued_cond); pipe_mutex_unlock(queue->lock); for (i = 0; i < queue->num_threads; i++) pipe_thread_wait(queue->threads[i]); +} + +void +util_queue_destroy(struct util_queue *queue) +{ + remove_from_atexit_list(queue); + util_queue_killall_and_wait(queue); pipe_condvar_destroy(queue->has_space_cond); pipe_condvar_destroy(queue->has_queued_cond