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