179894
commit f0419e6a10740a672b28e112c409ae24f5e890ab
179894
Author: Jakub Jelinek <jakub@redhat.com>
179894
Date:   Thu Mar 4 15:15:33 2021 +0100
179894
179894
    [PATCH] pthread_once hangs when init routine throws an exception [BZ #18435]
179894
    
179894
    This is another attempt at making pthread_once handle throwing exceptions
179894
    from the init routine callback.  As the new testcases show, just switching
179894
    to the cleanup attribute based cleanup does fix the tst-once5 test, but
179894
    breaks the new tst-oncey3 test.  That is because when throwing exceptions,
179894
    only the unwind info registered cleanups (i.e. C++ destructors or cleanup
179894
    attribute), when cancelling threads and there has been unwind info from the
179894
    cancellation point up to whatever needs cleanup both unwind info registered
179894
    cleanups and THREAD_SETMEM (self, cleanup, ...) registered cleanups are
179894
    invoked, but once we hit some frame with no unwind info, only the
179894
    THREAD_SETMEM (self, cleanup, ...) registered cleanups are invoked.
179894
    So, to stay fully backwards compatible (allow init routines without
179894
    unwind info which encounter cancellation points) and handle exception throwing
179894
    we actually need to register the pthread_once cleanups in both unwind info
179894
    and in the THREAD_SETMEM (self, cleanup, ...) way.
179894
    If an exception is thrown, only the former will happen and we in that case
179894
    need to also unregister the THREAD_SETMEM (self, cleanup, ...) registered
179894
    handler, because otherwise after catching the exception the user code could
179894
    call deeper into the stack some cancellation point, get cancelled and then
179894
    a stale cleanup handler would clobber stack and probably crash.
179894
    If a thread calling init routine is cancelled and unwind info ends before
179894
    the pthread_once frame, it will be cleaned up through self->cleanup as
179894
    before.  And if unwind info is present, unwind_stop first calls the
179894
    self->cleanup registered handler for the frame, then it will call the
179894
    unwind info registered handler but that will already see __do_it == 0
179894
    and do nothing.
179894
179894
# Conflicts:
179894
#	nptl/Makefile
179894
#	(The usual cleanups because they don't match.)
179894
#	sysdeps/pthread/Makefile
179894
#	(The usual cleanups because all the other tests aren't moved.)
179894
179894
diff --git a/nptl/Makefile b/nptl/Makefile
179894
index dcf3868869767015..70a3be23ecfcd9c9 100644
179894
--- a/nptl/Makefile
179894
+++ b/nptl/Makefile
179894
@@ -334,10 +334,6 @@ xtests = tst-setuid1 tst-setuid1-static tst-setuid2 \
179894
 	tst-mutexpp1 tst-mutexpp6 tst-mutexpp10
179894
 test-srcs = tst-oddstacklimit
179894
 
179894
-# Test expected to fail on most targets (except x86_64) due to bug
179894
-# 18435 - pthread_once hangs when init routine throws an exception.
179894
-test-xfail-tst-once5 = yes
179894
-
179894
 # Files which must not be linked with libpthread.
179894
 tests-nolibpthread = tst-unload
179894
 
179894
diff --git a/nptl/pthreadP.h b/nptl/pthreadP.h
179894
index a2d48b2015cd385c..7ddc166cf32414c4 100644
179894
--- a/nptl/pthreadP.h
179894
+++ b/nptl/pthreadP.h
179894
@@ -571,6 +571,67 @@ extern void __pthread_cleanup_pop (struct _pthread_cleanup_buffer *buffer,
179894
 # undef pthread_cleanup_pop
179894
 # define pthread_cleanup_pop(execute)                   \
179894
   __pthread_cleanup_pop (&_buffer, (execute)); }
179894
+
179894
+# if defined __EXCEPTIONS && !defined __cplusplus
179894
+/* Structure to hold the cleanup handler information.  */
179894
+struct __pthread_cleanup_combined_frame
179894
+{
179894
+  void (*__cancel_routine) (void *);
179894
+  void *__cancel_arg;
179894
+  int __do_it;
179894
+  struct _pthread_cleanup_buffer __buffer;
179894
+};
179894
+
179894
+/* Special cleanup macros which register cleanup both using
179894
+   __pthread_cleanup_{push,pop} and using cleanup attribute.  This is needed
179894
+   for pthread_once, so that it supports both throwing exceptions from the
179894
+   pthread_once callback (only cleanup attribute works there) and cancellation
179894
+   of the thread running the callback if the callback or some routines it
179894
+   calls don't have unwind information.  */
179894
+
179894
+static __always_inline void
179894
+__pthread_cleanup_combined_routine (struct __pthread_cleanup_combined_frame
179894
+				    *__frame)
179894
+{
179894
+  if (__frame->__do_it)
179894
+    {
179894
+      __frame->__cancel_routine (__frame->__cancel_arg);
179894
+      __frame->__do_it = 0;
179894
+      __pthread_cleanup_pop (&__frame->__buffer, 0);
179894
+    }
179894
+}
179894
+
179894
+static inline void
179894
+__pthread_cleanup_combined_routine_voidptr (void *__arg)
179894
+{
179894
+  struct __pthread_cleanup_combined_frame *__frame
179894
+    = (struct __pthread_cleanup_combined_frame *) __arg;
179894
+  if (__frame->__do_it)
179894
+    {
179894
+      __frame->__cancel_routine (__frame->__cancel_arg);
179894
+      __frame->__do_it = 0;
179894
+    }
179894
+}
179894
+
179894
+#  define pthread_cleanup_combined_push(routine, arg) \
179894
+  do {									      \
179894
+    void (*__cancel_routine) (void *) = (routine);			      \
179894
+    struct __pthread_cleanup_combined_frame __clframe			      \
179894
+      __attribute__ ((__cleanup__ (__pthread_cleanup_combined_routine)))      \
179894
+      = { .__cancel_routine = __cancel_routine, .__cancel_arg = (arg),	      \
179894
+	  .__do_it = 1 };						      \
179894
+    __pthread_cleanup_push (&__clframe.__buffer,			      \
179894
+			    __pthread_cleanup_combined_routine_voidptr,	      \
179894
+			    &__clframe);
179894
+
179894
+#  define pthread_cleanup_combined_pop(execute) \
179894
+    __pthread_cleanup_pop (&__clframe.__buffer, 0);			      \
179894
+    __clframe.__do_it = 0;						      \
179894
+    if (execute)							      \
179894
+      __cancel_routine (__clframe.__cancel_arg);			      \
179894
+  } while (0)
179894
+
179894
+# endif
179894
 #endif
179894
 
179894
 extern void __pthread_cleanup_push_defer (struct _pthread_cleanup_buffer *buffer,
179894
diff --git a/nptl/pthread_once.c b/nptl/pthread_once.c
179894
index 1653226286dc3539..45e965e8743d9412 100644
179894
--- a/nptl/pthread_once.c
179894
+++ b/nptl/pthread_once.c
179894
@@ -111,11 +111,11 @@ __pthread_once_slow (pthread_once_t *once_control, void (*init_routine) (void))
179894
       /* This thread is the first here.  Do the initialization.
179894
 	 Register a cleanup handler so that in case the thread gets
179894
 	 interrupted the initialization can be restarted.  */
179894
-      pthread_cleanup_push (clear_once_control, once_control);
179894
+      pthread_cleanup_combined_push (clear_once_control, once_control);
179894
 
179894
       init_routine ();
179894
 
179894
-      pthread_cleanup_pop (0);
179894
+      pthread_cleanup_combined_pop (0);
179894
 
179894
 
179894
       /* Mark *once_control as having finished the initialization.  We need
179894
diff --git a/nptl/tst-once5.cc b/nptl/tst-once5.cc
179894
index d232266c3ace89d9..dda18e610c9114bc 100644
179894
--- a/nptl/tst-once5.cc
179894
+++ b/nptl/tst-once5.cc
179894
@@ -59,7 +59,7 @@ do_test (void)
179894
                " throwing an exception", stderr);
179894
     }
179894
     catch (OnceException) {
179894
-      if (1 < niter)
179894
+      if (niter > 1)
179894
         fputs ("pthread_once unexpectedly threw", stderr);
179894
       result = 0;
179894
     }
179894
@@ -75,7 +75,5 @@ do_test (void)
179894
   return result;
179894
 }
179894
 
179894
-// The test currently hangs and is XFAILed.  Reduce the timeout.
179894
-#define TIMEOUT 1
179894
 #define TEST_FUNCTION do_test ()
179894
 #include "../test-skeleton.c"
179894
diff --git a/sysdeps/pthread/Makefile b/sysdeps/pthread/Makefile
179894
index 14ef04247cb84ad3..80a71f3f9f0e72ae 100644
179894
--- a/sysdeps/pthread/Makefile
179894
+++ b/sysdeps/pthread/Makefile
179894
@@ -35,7 +35,7 @@ tst-create1mod.so-no-z-defs = yes
179894
 
179894
 tests += tst-once1 tst-once2 tst-once3 tst-once4
179894
 
179894
-tests += tst-oncex3 tst-oncex4
179894
+tests += tst-oncex3 tst-oncex4 tst-oncey3 tst-oncey4
179894
 
179894
 ifeq ($(build-shared),yes)
179894
 # Build all the modules even when not actually running test programs.
179894
@@ -44,6 +44,8 @@ endif
179894
 
179894
 CFLAGS-tst-oncex3.c += -fexceptions
179894
 CFLAGS-tst-oncex4.c += -fexceptions
179894
+CFLAGS-tst-oncey3.c += -fno-exceptions -fno-asynchronous-unwind-tables
179894
+CFLAGS-tst-oncey4.c += -fno-exceptions -fno-asynchronous-unwind-tables
179894
 
179894
 modules-names += tst-create1mod
179894
 test-modules = $(addprefix $(objpfx),$(addsuffix .so,$(modules-names)))
179894
diff --git a/sysdeps/pthread/tst-oncey3.c b/sysdeps/pthread/tst-oncey3.c
179894
new file mode 100644
179894
index 0000000000000000..08225b88dc06b979
179894
--- /dev/null
179894
+++ b/sysdeps/pthread/tst-oncey3.c
179894
@@ -0,0 +1 @@
179894
+#include "tst-once3.c"
179894
diff --git a/sysdeps/pthread/tst-oncey4.c b/sysdeps/pthread/tst-oncey4.c
179894
new file mode 100644
179894
index 0000000000000000..9b4d98f3f13c265a
179894
--- /dev/null
179894
+++ b/sysdeps/pthread/tst-oncey4.c
179894
@@ -0,0 +1 @@
179894
+#include "tst-once4.c"