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