improve pthread_exit synchronization with functions targeting tid
[musl] / src / thread / pthread_create.c
index ac06d7a..a86b5e1 100644 (file)
@@ -39,9 +39,11 @@ _Noreturn void __pthread_exit(void *result)
 
        LOCK(self->exitlock);
 
-       /* Mark this thread dead before decrementing count */
+       /* Access to target the exiting thread with syscalls that use
+        * its kernel tid is controlled by killlock. For detached threads,
+        * any use past this point would have undefined behavior, but for
+        * joinable threads it's a valid usage that must be handled. */
        LOCK(self->killlock);
-       self->dead = 1;
 
        /* Block all signals before decrementing the live thread count.
         * This is important to ensure that dynamically allocated TLS
@@ -49,20 +51,14 @@ _Noreturn void __pthread_exit(void *result)
         * reasons as well. */
        __block_all_sigs(&set);
 
-       /* Wait to unlock the kill lock, which governs functions like
-        * pthread_kill which target a thread id, until signals have
-        * been blocked. This precludes observation of the thread id
-        * as a live thread (with application code running in it) after
-        * the thread was reported dead by ESRCH being returned. */
-       UNLOCK(self->killlock);
-
        /* It's impossible to determine whether this is "the last thread"
         * until performing the atomic decrement, since multiple threads
         * could exit at the same time. For the last thread, revert the
-        * decrement and unblock signals to give the atexit handlers and
-        * stdio cleanup code a consistent state. */
+        * decrement, restore the tid, and unblock signals to give the
+        * atexit handlers and stdio cleanup code a consistent state. */
        if (a_fetch_add(&libc.threads_minus_1, -1)==0) {
                libc.threads_minus_1 = 0;
+               UNLOCK(self->killlock);
                __restore_sigs(&set);
                exit(0);
        }
@@ -113,6 +109,12 @@ _Noreturn void __pthread_exit(void *result)
                __unmapself(self->map_base, self->map_size);
        }
 
+       /* After the kernel thread exits, its tid may be reused. Clear it
+        * to prevent inadvertent use and inform functions that would use
+        * it that it's no longer available. */
+       self->tid = 0;
+       UNLOCK(self->killlock);
+
        for (;;) __syscall(SYS_exit, 0);
 }