Re: [Mesa-dev] [PATCH 1/4] gallium/u_queue: fix random crashes when the app calls exit()

2017-02-22 Thread Michel Dänzer
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()

2017-02-22 Thread Michel Dänzer
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()

2017-02-22 Thread Nicolai Hähnle

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, _list, head) {
+  util_queue_killall_and_wait(iter);
+   }
+   pipe_mutex_unlock(exit_mutex);
+}
+
+static void
+global_init(void)
+{
+   LIST_INITHEAD(_list);
+   atexit(atexit_handler);
+}
+
+static void
+add_to_atexit_list(struct util_queue *queue)
+{
+   call_once(_once_flag, global_init);
+
+   pipe_mutex_lock(exit_mutex);
+   LIST_ADD(>head, _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, _list, head) {
+  if (iter == queue) {
+ LIST_DEL(>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
 

[Mesa-dev] [PATCH 1/4] gallium/u_queue: fix random crashes when the app calls exit()

2017-02-21 Thread Marek Olšák
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, _list, head) {
+  util_queue_killall_and_wait(iter);
+   }
+   pipe_mutex_unlock(exit_mutex);
+}
+
+static void
+global_init(void)
+{
+   LIST_INITHEAD(_list);
+   atexit(atexit_handler);
+}
+
+static void
+add_to_atexit_list(struct util_queue *queue)
+{
+   call_once(_once_flag, global_init);
+
+   pipe_mutex_lock(exit_mutex);
+   LIST_ADD(>head, _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, _list, head) {
+  if (iter == queue) {
+ LIST_DEL(>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 

Re: [Mesa-dev] [PATCH 1/4] gallium/u_queue: fix random crashes when the app calls exit()

2017-02-21 Thread Nicolai Hähnle

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(_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 = _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;

[Mesa-dev] [PATCH 1/4] gallium/u_queue: fix random crashes when the app calls exit()

2017-02-20 Thread Marek Olšák
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(_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);