On Thu, Oct 20, 2011 at 8:18 AM, Arun Sharma <[email protected]> wrote:

> Paul: The reason why libpthread loops over the keys multiple times
> seems to be related to
>
> "A destructor function might, however, re-associate non-NULL values to
> that key or some other key"
>
> I'm guessing that most destructors don't do this, which is why your
> solution works.

Correct.

> But if we get keys with crazy destructors which call
> pthread_setspecific(), could we be in trouble again on the last
> iteration?

Hence "tls_cache = NULL". If some other destructor delays the same way as
we do (rare) and attempts to record a stack trace (probably even rarer)
then we'll re-allocate thread_cache anew, and leak it.

> How about:
>
> static __thread bool trace_destroy_started = false;
>
> trace_cache_free():
>  trace_destroy_started = true
>
> trace_cache_get():
>  if trace_destroy_started: return NULL

That would also work; the disadvantage is that you'll fall back to slow
unwind for any subsequent free. If the other dtor deallocates a huge data
structure (e.g. 1GB tree), then you'll potentially execute a lot of slow
unwinds.

> I also worry a bit about libpthread changing
> PTHREAD_DESTRUCTOR_ITERATIONS underneath us.

The decrease of PTHREAD_DESTRUCTOR_ITERATIONS on a given system seems very
unlikely: you'll have to rebuild all binaries; not just libunwind, or they
will all leak (if they delay destruction).

OTOH, the increase is "mostly harmless" -- we'll free cache slightly
earlier than we could have.

I propose combined approach: delay until PTHREAD_DESTRUCTOR_ITERATIONS,
then set "cache_destroyed" boolean so we don't re-allocate (and don't leak).

Re-tested on Linux/x86_64 (Ubuntu 10.04); no new failures.

[This patch also cleans up 'return 0;' vs. 'return NULL;']

Thanks,
-- 
Paul Pluzhnikov
diff --git a/src/x86_64/Gtrace.c b/src/x86_64/Gtrace.c
index 4a5b583..e0592c3 100644
--- a/src/x86_64/Gtrace.c
+++ b/src/x86_64/Gtrace.c
@@ -25,6 +25,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
SOFTWARE.  */
 #include "unwind_i.h"
 #include "ucontext_i.h"
 #include <signal.h>
+#include <limits.h>
 
 #pragma weak pthread_once
 #pragma weak pthread_key_create
@@ -45,6 +46,8 @@ typedef struct
   unw_tdep_frame_t *frames;
   size_t log_size;
   size_t used;
+  size_t dtor_count;  /* Counts how many times our destructor has already
+                        been called. */
 } unw_trace_cache_t;
 
 static const unw_tdep_frame_t empty_frame = { 0, UNW_X86_64_FRAME_OTHER, -1, 
-1, 0, -1, -1 };
@@ -52,14 +55,26 @@ static pthread_mutex_t trace_init_lock = 
PTHREAD_MUTEX_INITIALIZER;
 static pthread_once_t trace_cache_once = PTHREAD_ONCE_INIT;
 static pthread_key_t trace_cache_key;
 static struct mempool trace_cache_pool;
+static __thread  unw_trace_cache_t *tls_cache;
+static __thread  int tls_cache_destructed;
 
 /* Free memory for a thread's trace cache. */
 static void
 trace_cache_free (void *arg)
 {
   unw_trace_cache_t *cache = arg;
+  if (++cache->dtor_count < PTHREAD_DESTRUCTOR_ITERATIONS)
+  {
+    /* Not yet our turn to get destroyed. Re-install ourselves into the key. */
+    pthread_setspecific(trace_cache_key, cache);
+    Debug(5, "delayed freeing cache %p (%llx to go)\n", cache,
+         PTHREAD_DESTRUCTOR_ITERATIONS - cache->dtor_count);
+    return;
+  }
   munmap (cache->frames, (1u << cache->log_size) * sizeof(unw_tdep_frame_t));
   mempool_free (&trace_cache_pool, cache);
+  tls_cache = NULL;
+  tls_cache_destructed = 1;
   Debug(5, "freed cache %p\n", cache);
 }
 
@@ -93,21 +108,32 @@ trace_cache_create (void)
 {
   unw_trace_cache_t *cache;
 
+  if (tls_cache_destructed)
+  {
+    /* The current thread is in the process of exiting. Don't recreate
+       cache, as we wouldn't have another chance to free it. */
+    Debug(5, "refusing to reallocate cache: "
+            "thread-locals are being deallocated\n");
+    return NULL;
+  }
+
   if (! (cache = mempool_alloc(&trace_cache_pool)))
   {
     Debug(5, "failed to allocate cache\n");
-    return 0;
+    return NULL;
   }
 
   if (! (cache->frames = trace_cache_buckets(1u << HASH_MIN_BITS)))
   {
     Debug(5, "failed to allocate buckets\n");
     mempool_free(&trace_cache_pool, cache);
-    return 0;
+    return NULL;
   }
 
   cache->log_size = HASH_MIN_BITS;
   cache->used = 0;
+  cache->dtor_count = 0;
+  tls_cache_destructed = 0;  /* Paranoia: should already be 0. */
   Debug(5, "allocated cache %p\n", cache);
   return cache;
 }
@@ -135,8 +161,6 @@ trace_cache_expand (unw_trace_cache_t *cache)
   return 0;
 }
 
-static __thread  unw_trace_cache_t *tls_cache;
-
 /* Get the frame cache for the current thread. Create it if there is none. */
 static unw_trace_cache_t *
 trace_cache_get (void)
@@ -285,7 +309,7 @@ trace_lookup (unw_cursor_t *cursor,
   if (unlikely(addr || cache->used >= cache_size / 2))
   {
     if (unlikely(trace_cache_expand (cache) < 0))
-      return 0;
+      return NULL;
 
     cache_size = 1u << cache->log_size;
     slot = ((rip * 0x9e3779b97f4a7c16) >> 43) & (cache_size-1);
_______________________________________________
Libunwind-devel mailing list
[email protected]
https://lists.nongnu.org/mailman/listinfo/libunwind-devel

Reply via email to