[PATCH v2 3/3] nptl: Only initialize robust list at mutex usage

Adhemerval Zanella adhemerval.zanella@linaro.org
Wed Jul 29 17:46:38 GMT 2026


The set_robust_list syscall is called unconditionally at process startup
(__tls_init_tp) and on every thread creation (pthread_create), even in
programs that never use robust mutexes.

Defer the call to the first time a robust mutex is actually locked, or
initialized with PTHREAD_PROCESS_SHARED.  The new helper
robust_list_setup() performs the registration and is called from
pthread_mutex_init (for pshared+robust), pthread_mutex_lock,
pthread_mutex_trylock, and pthread_mutex_timedlock.

The robust_head.futex_offset field acts as the sentinel for whether
the list has been registered with the kernel.

_Fork re-registers the list with the kernel only when the parent had
already registered it.  If the parent never used robust mutexes, the
first robust mutex lock in the child calls robust_list_setup() itself.

The __nptl_set_robust_list_avail variable moves from ld.so to libc.so,
since it no longer needs to be set during pthread startup.

Checked on x86_64-linux-gnu and aarch64-linux-gnu.
---
 nptl/Makefile                  |  1 +
 nptl/Versions                  |  1 -
 nptl/allocatestack.c           |  9 +------
 nptl/descr.h                   | 46 ++++++++++++++++++++++++++++++++++
 nptl/nptl_robust_setup.c       | 38 ++++++++++++++++++++++++++++
 nptl/pthread_create.c          | 10 +-------
 nptl/pthread_mutex_init.c      |  2 +-
 nptl/pthread_mutex_lock.c      |  2 ++
 nptl/pthread_mutex_timedlock.c |  2 ++
 nptl/pthread_mutex_trylock.c   |  2 ++
 nptl/tst-robust-pshared.c      | 28 +++++++++++++++++++++
 sysdeps/nptl/_Fork.c           | 24 +++++++++---------
 sysdeps/nptl/dl-tls_init_tp.c  | 17 +------------
 sysdeps/nptl/pthreadP.h        |  5 ----
 14 files changed, 135 insertions(+), 52 deletions(-)
 create mode 100644 nptl/nptl_robust_setup.c

diff --git a/nptl/Makefile b/nptl/Makefile
index 01aa3619932..c9987d23384 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -50,6 +50,7 @@ routines = \
   nptl_deallocate_tsd \
   nptl_free_tcb \
   nptl_nthreads \
+  nptl_robust_setup \
   nptl_setxid \
   nptlfreeres \
   old_pthread_cond_broadcast \
diff --git a/nptl/Versions b/nptl/Versions
index b813b675b91..94a567bd609 100644
--- a/nptl/Versions
+++ b/nptl/Versions
@@ -535,6 +535,5 @@ libpthread {
 ld {
   GLIBC_PRIVATE {
      __nptl_initial_report_events;
-     __nptl_set_robust_list_avail;
   }
 }
diff --git a/nptl/allocatestack.c b/nptl/allocatestack.c
index b2ecb001136..fcccc75ddb8 100644
--- a/nptl/allocatestack.c
+++ b/nptl/allocatestack.c
@@ -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;
diff --git a/nptl/descr.h b/nptl/descr.h
index 627cc3980f0..da877d6b18d 100644
--- a/nptl/descr.h
+++ b/nptl/descr.h
@@ -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)
diff --git a/nptl/nptl_robust_setup.c b/nptl/nptl_robust_setup.c
new file mode 100644
index 00000000000..0c14432afb8
--- /dev/null
+++ b/nptl/nptl_robust_setup.c
@@ -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;
+}
diff --git a/nptl/pthread_create.c b/nptl/pthread_create.c
index f8220c22208..35202c0ef12 100644
--- a/nptl/pthread_create.c
+++ b/nptl/pthread_create.c
@@ -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
diff --git a/nptl/pthread_mutex_init.c b/nptl/pthread_mutex_init.c
index 9be08332f1f..2f0bb84c557 100644
--- a/nptl/pthread_mutex_init.c
+++ b/nptl/pthread_mutex_init.c
@@ -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;
     }
diff --git a/nptl/pthread_mutex_lock.c b/nptl/pthread_mutex_lock.c
index a697f2b6ca8..f649036d954 100644
--- a/nptl/pthread_mutex_lock.c
+++ b/nptl/pthread_mutex_lock.c
@@ -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)
diff --git a/nptl/pthread_mutex_timedlock.c b/nptl/pthread_mutex_timedlock.c
index 9efca2c7791..a52ed795df0 100644
--- a/nptl/pthread_mutex_timedlock.c
+++ b/nptl/pthread_mutex_timedlock.c
@@ -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)
diff --git a/nptl/pthread_mutex_trylock.c b/nptl/pthread_mutex_trylock.c
index 236b3228ddb..09c5dc983b2 100644
--- a/nptl/pthread_mutex_trylock.c
+++ b/nptl/pthread_mutex_trylock.c
@@ -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)
diff --git a/nptl/tst-robust-pshared.c b/nptl/tst-robust-pshared.c
index 3edbabadf91..01044f4e628 100644
--- a/nptl/tst-robust-pshared.c
+++ b/nptl/tst-robust-pshared.c
@@ -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;
 }
 
diff --git a/sysdeps/nptl/_Fork.c b/sysdeps/nptl/_Fork.c
index 907b3fef876..ba6912aa346 100644
--- a/sysdeps/nptl/_Fork.c
+++ b/sysdeps/nptl/_Fork.c
@@ -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);
     }
 
diff --git a/sysdeps/nptl/dl-tls_init_tp.c b/sysdeps/nptl/dl-tls_init_tp.c
index 75e3712a6d8..77f2e341822 100644
--- a/sysdeps/nptl/dl-tls_init_tp.c
+++ b/sysdeps/nptl/dl-tls_init_tp.c
@@ -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
diff --git a/sysdeps/nptl/pthreadP.h b/sysdeps/nptl/pthreadP.h
index c62c8982905..7d4dd077c74 100644
--- a/sysdeps/nptl/pthreadP.h
+++ b/sysdeps/nptl/pthreadP.h
@@ -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)
-- 
2.53.0



More information about the Libc-alpha mailing list