]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
nptl: Make pthread_{clock, timed}join{_np} act on all cancellation (BZ 33717)
authorAdhemerval Zanella <adhemerval.zanella@linaro.org>
Thu, 11 Dec 2025 20:47:22 +0000 (17:47 -0300)
committerAdhemerval Zanella <adhemerval.zanella@linaro.org>
Fri, 19 Dec 2025 16:23:06 +0000 (13:23 -0300)
The pthread_join/pthread_timedjoin_np/pthread_clockjoin_np will not act
on cancellation if 1. some other thread is already waiting on the 'joinid'
or 2. If the thread has already exited.

On nptl/pthread_join_common.c, the 1. is due to the CAS doing an early
return:

 80   else if (__glibc_unlikely (atomic_compare_exchange_weak_acquire (&pd->joinid,
 81                                                                    &self,
 82                                                                    NULL)))
 83     /* There is already somebody waiting for the thread.  */
 84     return EINVAL;

And 2. is due to the pd->tid equal to 0:

 99       pid_t tid;
100       while ((tid = atomic_load_acquire (&pd->tid)) != 0)
101         {

The easiest solution would be to add an __pthread_testcancel () on
__pthread_clockjoin_ex () if 'cancel' is true.

Checked on aarch64-linux-gnu, arm-linux-gnueabihf, x86_64-linux-gnu,
and i686-linux-gnu.

Reviewed-by: Florian Weimer <fweimer@redhat.com>
nptl/Makefile
nptl/pthread_join_common.c
nptl/tst-cancel34.c [new file with mode: 0644]

index 963b32123c2378235f9dd41f75af765c95b7b57e..c7f2c2433991ccf4ff0b6760a29a72aa64c6f982 100644 (file)
@@ -278,6 +278,7 @@ tests = \
   tst-cancel24 \
   tst-cancel31 \
   tst-cancel33 \
+  tst-cancel34 \
   tst-cleanup5 \
   tst-cond26 \
   tst-context1 \
index 6cdb710bc92e1c381832b866d486e893bfc277b7..ddf10133c1f8ac3b7df6a8766c41f2d850b79caf 100644 (file)
@@ -28,6 +28,9 @@ __pthread_clockjoin_ex (pthread_t threadid, void **thread_return,
                         const struct __timespec64 *abstime,
                         bool cancel)
 {
+  if (cancel)
+    __pthread_testcancel ();
+
   struct pthread *pd = (struct pthread *) threadid;
 
   /* Make sure the clock and time specified are valid.  */
diff --git a/nptl/tst-cancel34.c b/nptl/tst-cancel34.c
new file mode 100644 (file)
index 0000000..ad050c1
--- /dev/null
@@ -0,0 +1,107 @@
+/* Check if pthread_join acts a cancellation entrypoiny (BZ 33717)
+   Copyright (C) 2025 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 <support/check.h>
+#include <support/xunistd.h>
+#include <support/xthread.h>
+#include <support/process_state.h>
+
+static pthread_barrier_t b1;
+static pthread_barrier_t b2;
+
+static void *
+thr_func2 (void *arg)
+{
+  xpthread_barrier_wait (&b2);
+  return NULL;
+}
+
+static void *
+thr_func1 (void *arg)
+{
+  int (*join_func)(pthread_t, void **) = arg;
+
+  xpthread_barrier_init (&b2, NULL, 2);
+
+  pthread_t thr = xpthread_create (NULL, thr_func2, NULL);
+
+  pid_t tid = pthread_gettid_np (thr);
+  /* Synchronize the inner thread so the pthread_gettid_np returns a valid
+     TID.  */
+  xpthread_barrier_wait (&b2);
+
+  support_thread_state_wait (tid, support_process_state_dead);
+
+  /* Synchronize with parent thread so the cancellation is issues after
+     the inner thread has finished.  */
+  xpthread_barrier_wait (&b1);
+
+  /* Wait for the cancellation.  */
+  xpthread_barrier_wait (&b1);
+
+  join_func (thr, NULL);
+
+  support_record_failure ();
+
+  return NULL;
+}
+
+static void
+do_test_common (int (*join_func)(pthread_t, void **))
+{
+  xpthread_barrier_init (&b1, NULL, 2);
+
+  pthread_t thr = xpthread_create (NULL, thr_func1, join_func);
+
+  /* Wait until the inner thread is created and terminated
+     (support_process_state_dead) before issuing the pthread_cancel.  */
+  xpthread_barrier_wait (&b1);
+
+  xpthread_cancel (thr);
+
+  /* The deferred cancellation is pending on the thread, issue the
+     routine to check.  */
+  xpthread_barrier_wait (&b1);
+
+  TEST_VERIFY (xpthread_join (thr) == PTHREAD_CANCELED);
+}
+
+static int
+pthread_timedjoin_np_wrapper (pthread_t thr, void **retval)
+{
+  return pthread_timedjoin_np (thr, retval, NULL);
+}
+
+static int
+pthread_clockjoin_np_wrapper (pthread_t thr, void **retval)
+{
+  return pthread_clockjoin_np (thr, retval, CLOCK_REALTIME, NULL);
+}
+
+static int
+do_test (void)
+{
+  do_test_common (pthread_join);
+  do_test_common (pthread_timedjoin_np_wrapper);
+  do_test_common (pthread_clockjoin_np_wrapper);
+
+  return 0;
+}
+
+#define TIMEOUT 3
+#include <support/test-driver.c>