@@ -50,6 +50,7 @@ routines = \
nptl_deallocate_tsd \
nptl_free_tcb \
nptl_nthreads \
+ nptl_robust_setup \
nptl_setxid \
nptlfreeres \
old_pthread_cond_broadcast \
@@ -535,6 +535,5 @@ libpthread {
ld {
GLIBC_PRIVATE {
__nptl_initial_report_events;
- __nptl_set_robust_list_avail;
}
}
@@ -631,14 +631,7 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
/* The robust mutex lists also need to be initialized
unconditionally because the cleanup for the previous stack owner
might have happened in the kernel. */
- pd->robust_head.futex_offset = (offsetof (pthread_mutex_t, __data.__lock)
- - offsetof (pthread_mutex_t,
- __data.__list.__next));
- pd->robust_head.list_op_pending = NULL;
-#if __PTHREAD_MUTEX_HAVE_PREV
- pd->robust_prev = &pd->robust_head;
-#endif
- pd->robust_head.list = &pd->robust_head;
+ robust_list_init (pd);
/* We place the thread descriptor at the end of the stack. */
*pdp = pd;
@@ -23,6 +23,7 @@
#include <setjmp.h>
#include <stdbool.h>
#include <sys/types.h>
+#include <atomic.h>
#include <hp-timing.h>
#include <list_t.h>
#include <lowlevellock.h>
@@ -458,6 +459,51 @@ cancel_enabled_and_canceled_and_async (int value)
== (CANCELTYPE_BITMASK | CANCELED_BITMASK);
}
+static inline void
+robust_list_init (struct pthread *pd)
+{
+ pd->robust_head.list_op_pending = NULL;
+#if __PTHREAD_MUTEX_HAVE_PREV
+ pd->robust_prev = &pd->robust_head;
+#endif
+ pd->robust_head.list = &pd->robust_head;
+ pd->robust_head.futex_offset = 0;
+}
+
+/* Set if the set_robust_list system call works. It is cleared the first
+ time the call fails, and never set again. */
+extern int __nptl_set_robust_list_avail attribute_hidden;
+
+extern bool __nptl_robust_setup (struct robust_list_head *robust_head)
+ attribute_hidden;
+
+/* Register PD's robust mutex list with the kernel unless that has already
+ been done, and return whether the list is registered.
+
+ robust_head.futex_offset works as the sentinel for this, where a zero
+ value means registration is already done for the thread (kernel requires
+ it to be non zero). */
+static inline bool
+robust_list_setup (struct pthread *pd)
+{
+ /* The current thread already registered its list. */
+ if (pd->robust_head.futex_offset != 0)
+ return true;
+
+ /* Avoid the futex_offset dance once set_robust_list is known to fail. */
+ if (!atomic_load_relaxed (&__nptl_set_robust_list_avail))
+ return false;
+
+ pd->robust_head.futex_offset = (offsetof (pthread_mutex_t, __data.__lock)
+ - offsetof (pthread_mutex_t,
+ __data.__list.__next));
+ if (__nptl_robust_setup (&pd->robust_head))
+ return true;
+
+ pd->robust_head.futex_offset = 0;
+ return false;
+}
+
/* This yields the pointer that TLS support code calls the thread pointer. */
#if TLS_TCB_AT_TP
# define TLS_TPADJ(pd) (pd)
new file mode 100644
@@ -0,0 +1,38 @@
+/* Linux robust mutex setup.
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <descr.h>
+#include <atomic.h>
+#include <pthreadP.h>
+
+int __nptl_set_robust_list_avail = 1;
+
+bool
+__nptl_robust_setup (struct robust_list_head *robust_head)
+{
+ if (atomic_load_relaxed (&__nptl_set_robust_list_avail))
+ {
+ int res = INTERNAL_SYSCALL_CALL (set_robust_list, robust_head,
+ sizeof (struct robust_list_head));
+ if (!INTERNAL_SYSCALL_ERROR_P (res))
+ return true;
+
+ atomic_store_relaxed (&__nptl_set_robust_list_avail, 0);
+ }
+ return false;
+}
@@ -401,14 +401,6 @@ start_thread (void *arg)
__libc_fatal ("Fatal glibc error: rseq registration failed\n");
}
- if (__nptl_set_robust_list_avail)
- {
- /* This call should never fail because the initial call in init.c
- succeeded. */
- INTERNAL_SYSCALL_CALL (set_robust_list, &pd->robust_head,
- sizeof (struct robust_list_head));
- }
-
/* This is where the try/finally block should be created. For
compilers without that support we do use setjmp. */
struct pthread_unwind_buf unwind_buf;
@@ -556,7 +548,7 @@ start_thread (void *arg)
__pthread_slist_t *robust = pd->robust_list.__next;
#endif
/* We let the kernel do the notification if it is able to do so. */
- if (!__nptl_set_robust_list_avail
+ if (!atomic_load_relaxed (&__nptl_set_robust_list_avail)
&& __glibc_unlikely (robust_list_has_entry (robust, &pd->robust_head)))
{
do
@@ -94,7 +94,7 @@ ___pthread_mutex_init (pthread_mutex_t *mutex,
if ((imutexattr->mutexkind & PTHREAD_MUTEXATTR_FLAG_ROBUST) != 0)
{
if ((imutexattr->mutexkind & PTHREAD_MUTEXATTR_FLAG_PSHARED) != 0
- && !__nptl_set_robust_list_avail)
+ && !robust_list_setup (THREAD_SELF))
return ENOTSUP;
mutex_kind |= PTHREAD_MUTEX_ROBUST_NORMAL_NP;
}
@@ -177,6 +177,7 @@ __pthread_mutex_lock_full (pthread_mutex_t *mutex)
case PTHREAD_MUTEX_ROBUST_ERRORCHECK_NP:
case PTHREAD_MUTEX_ROBUST_NORMAL_NP:
case PTHREAD_MUTEX_ROBUST_ADAPTIVE_NP:
+ robust_list_setup (THREAD_SELF);
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending,
&mutex->__data.__list.__next);
/* We need to set op_pending before starting the operation. Also
@@ -361,6 +362,7 @@ __pthread_mutex_lock_full (pthread_mutex_t *mutex)
if (robust)
{
+ robust_list_setup (THREAD_SELF);
/* Note: robust PI futexes are signaled by setting bit 0. */
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending,
(void *) (((uintptr_t) &mutex->__data.__list.__next)
@@ -111,6 +111,7 @@ __pthread_mutex_clocklock_common (pthread_mutex_t *mutex,
case PTHREAD_MUTEX_ROBUST_ERRORCHECK_NP:
case PTHREAD_MUTEX_ROBUST_NORMAL_NP:
case PTHREAD_MUTEX_ROBUST_ADAPTIVE_NP:
+ robust_list_setup (THREAD_SELF);
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending,
&mutex->__data.__list.__next);
/* We need to set op_pending before starting the operation. Also
@@ -295,6 +296,7 @@ __pthread_mutex_clocklock_common (pthread_mutex_t *mutex,
if (robust)
{
+ robust_list_setup (THREAD_SELF);
/* Note: robust PI futexes are signaled by setting bit 0. */
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending,
(void *) (((uintptr_t) &mutex->__data.__list.__next)
@@ -77,6 +77,7 @@ ___pthread_mutex_trylock (pthread_mutex_t *mutex)
case PTHREAD_MUTEX_ROBUST_ERRORCHECK_NP:
case PTHREAD_MUTEX_ROBUST_NORMAL_NP:
case PTHREAD_MUTEX_ROBUST_ADAPTIVE_NP:
+ robust_list_setup (THREAD_SELF);
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending,
&mutex->__data.__list.__next);
/* We need to set op_pending before starting the operation. Also
@@ -219,6 +220,7 @@ ___pthread_mutex_trylock (pthread_mutex_t *mutex)
if (robust)
{
+ robust_list_setup (THREAD_SELF);
/* Note: robust PI futexes are signaled by setting bit 0. */
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending,
(void *) (((uintptr_t) &mutex->__data.__list.__next)
@@ -24,6 +24,16 @@
#include <support/check.h>
#include <support/xthread.h>
+/* Lock the mutex and exit without unlocking it, so that the owner-died
+ notification has to be delivered to the next locker. */
+static void *
+owner_thread (void *arg)
+{
+ pthread_mutex_t *mutex = arg;
+ TEST_COMPARE (pthread_mutex_lock (mutex), 0);
+ return NULL;
+}
+
static int
do_test (void)
{
@@ -57,6 +67,24 @@ do_test (void)
xpthread_mutexattr_destroy (&attr);
}
+ /* Have a thread lock a robust mutex and exit without unlocking it. This
+ exercises the deferred robust_list_setup path in pthread_mutex_lock. */
+ {
+ pthread_mutexattr_t attr;
+ xpthread_mutexattr_init (&attr);
+ xpthread_mutexattr_setrobust (&attr, PTHREAD_MUTEX_ROBUST);
+ pthread_mutex_t mutex;
+ TEST_COMPARE (pthread_mutex_init (&mutex, &attr), 0);
+ xpthread_mutexattr_destroy (&attr);
+
+ xpthread_join (xpthread_create (NULL, owner_thread, &mutex));
+
+ TEST_COMPARE (pthread_mutex_lock (&mutex), EOWNERDEAD);
+ TEST_COMPARE (pthread_mutex_consistent (&mutex), 0);
+ TEST_COMPARE (pthread_mutex_unlock (&mutex), 0);
+ TEST_COMPARE (pthread_mutex_destroy (&mutex), 0);
+ }
+
return 0;
}
@@ -35,22 +35,22 @@ _Fork (void)
{
struct pthread *self = THREAD_SELF;
- /* Initialize the robust mutex list setting in the kernel which has
- been reset during the fork. We do not check for errors because if
- it fails here, it must have failed at process startup as well and
- nobody could have used robust mutexes.
- Before we do that, we have to clear the list of robust mutexes
- because we do not inherit ownership of mutexes from the parent.
- We do not have to set self->robust_head.futex_offset since we do
- inherit the correct value from the parent. We do not need to clear
- the pending operation because it must have been zero when fork was
- called. */
+ /* Clear the list of robust mutexes because we do not inherit ownership
+ of mutexes from the parent. We do not need to clear the pending
+ operation because it must have been zero when fork was called.
+ futex_offset is inherited from the parent unchanged. */
#if __PTHREAD_MUTEX_HAVE_PREV
self->robust_prev = &self->robust_head;
#endif
self->robust_head.list = &self->robust_head;
- INTERNAL_SYSCALL_CALL (set_robust_list, &self->robust_head,
- sizeof (struct robust_list_head));
+ /* Re-register the robust list with the kernel only if the parent had
+ already initialized it. futex_offset is the sentinel: zero means
+ lazy initialization has not happened yet, so there is nothing to
+ re-register and the first robust mutex lock in the child will call
+ set_robust_list itself. */
+ if (self->robust_head.futex_offset != 0)
+ INTERNAL_SYSCALL_CALL (set_robust_list, &self->robust_head,
+ sizeof (struct robust_list_head));
call_function_static_weak (__getrandom_fork_subprocess);
}
@@ -28,9 +28,6 @@
#define TUNABLE_NAMESPACE pthread
#include <dl-tunables.h>
-bool __nptl_set_robust_list_avail;
-rtld_hidden_data_def (__nptl_set_robust_list_avail)
-
bool __nptl_initial_report_events;
rtld_hidden_def (__nptl_initial_report_events)
@@ -82,19 +79,7 @@ __tls_init_tp (void)
THREAD_SETMEM (pd, report_events, __nptl_initial_report_events);
/* Initialize the robust mutex data. */
- {
-#if __PTHREAD_MUTEX_HAVE_PREV
- pd->robust_prev = &pd->robust_head;
-#endif
- pd->robust_head.list = &pd->robust_head;
- pd->robust_head.futex_offset = (offsetof (pthread_mutex_t, __data.__lock)
- - offsetof (pthread_mutex_t,
- __data.__list.__next));
- int res = INTERNAL_SYSCALL_CALL (set_robust_list, &pd->robust_head,
- sizeof (struct robust_list_head));
- if (!INTERNAL_SYSCALL_ERROR_P (res))
- __nptl_set_robust_list_avail = true;
- }
+ robust_list_init (pd);
{
/* If the registration fails or is disabled by tunable, the public
@@ -192,11 +192,6 @@ libc_hidden_proto (__pthread_keys)
extern unsigned int __nptl_nthreads;
libc_hidden_proto (__nptl_nthreads)
-/* True if the set_robust_list system call works. Initialized in
- __tls_init_tp. */
-extern bool __nptl_set_robust_list_avail;
-rtld_hidden_proto (__nptl_set_robust_list_avail)
-
/* Thread Priority Protection. */
extern int __sched_fifo_min_prio;
libc_hidden_proto (__sched_fifo_min_prio)