Support concurrent calls to exit
And lock all stdio streams during exit Resolves: RHEL-65358
This commit is contained in:
parent
bd7cb93842
commit
19df5781cb
106
glibc-RHEL-65358-1.patch
Normal file
106
glibc-RHEL-65358-1.patch
Normal file
@ -0,0 +1,106 @@
|
||||
commit 047703fbb88eb38fbe973f3abedb279382f181d0
|
||||
Author: Florian Weimer <fweimer@redhat.com>
|
||||
Date: Tue Jun 6 11:37:30 2023 +0200
|
||||
|
||||
support: Add delayed__exit (with two underscores)
|
||||
|
||||
It calls _exit instead of exit once the timeout expires.
|
||||
|
||||
Conflicts:
|
||||
support/delayed_exit.c (fixup context)
|
||||
support/xthread.h (fixup context)
|
||||
|
||||
diff --git a/support/delayed_exit.c b/support/delayed_exit.c
|
||||
index 450860c5953257be..9242d4a1236e94ee 100644
|
||||
--- a/support/delayed_exit.c
|
||||
+++ b/support/delayed_exit.c
|
||||
@@ -23,33 +23,58 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <support/check.h>
|
||||
+#include <support/support.h>
|
||||
#include <time.h>
|
||||
+#include <unistd.h>
|
||||
+
|
||||
+struct delayed_exit_request
|
||||
+{
|
||||
+ void (*exitfunc) (int);
|
||||
+ int seconds;
|
||||
+};
|
||||
|
||||
static void *
|
||||
-delayed_exit_thread (void *seconds_as_ptr)
|
||||
+delayed_exit_thread (void *closure)
|
||||
{
|
||||
- int seconds = (uintptr_t) seconds_as_ptr;
|
||||
- struct timespec delay = { seconds, 0 };
|
||||
+ struct delayed_exit_request *request = closure;
|
||||
+ void (*exitfunc) (int) = request->exitfunc;
|
||||
+ struct timespec delay = { request->seconds, 0 };
|
||||
struct timespec remaining = { 0 };
|
||||
+ free (request);
|
||||
+
|
||||
if (nanosleep (&delay, &remaining) != 0)
|
||||
FAIL_EXIT1 ("nanosleep: %m");
|
||||
- /* Exit the process sucessfully. */
|
||||
- exit (0);
|
||||
+ /* Exit the process successfully. */
|
||||
+ exitfunc (0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
-void
|
||||
-delayed_exit (int seconds)
|
||||
+static void
|
||||
+delayed_exit_1 (int seconds, void (*exitfunc) (int))
|
||||
{
|
||||
/* Create the new thread with all signals blocked. */
|
||||
sigset_t all_blocked;
|
||||
sigfillset (&all_blocked);
|
||||
sigset_t old_set;
|
||||
xpthread_sigmask (SIG_SETMASK, &all_blocked, &old_set);
|
||||
+ struct delayed_exit_request *request = xmalloc (sizeof (*request));
|
||||
+ request->seconds = seconds;
|
||||
+ request->exitfunc = exitfunc;
|
||||
/* Create a detached thread. */
|
||||
- pthread_t thr = xpthread_create
|
||||
- (NULL, delayed_exit_thread, (void *) (uintptr_t) seconds);
|
||||
+ pthread_t thr = xpthread_create (NULL, delayed_exit_thread, request);
|
||||
xpthread_detach (thr);
|
||||
/* Restore the original signal mask. */
|
||||
xpthread_sigmask (SIG_SETMASK, &old_set, NULL);
|
||||
}
|
||||
+
|
||||
+void
|
||||
+delayed_exit (int seconds)
|
||||
+{
|
||||
+ delayed_exit_1 (seconds, exit);
|
||||
+}
|
||||
+
|
||||
+void
|
||||
+delayed__exit (int seconds)
|
||||
+{
|
||||
+ delayed_exit_1 (seconds, _exit);
|
||||
+}
|
||||
diff --git a/support/xthread.h b/support/xthread.h
|
||||
index 1a39b1c0ddda9725..5c6b57e8829a4ee9 100644
|
||||
--- a/support/xthread.h
|
||||
+++ b/support/xthread.h
|
||||
@@ -24,11 +24,14 @@
|
||||
|
||||
__BEGIN_DECLS
|
||||
|
||||
-/* Terminate the process (with exit status 0) after SECONDS have
|
||||
- elapsed, from a helper thread. The process is terminated with the
|
||||
- exit function, so atexit handlers are executed. */
|
||||
+/* Terminate the process (with exit (0)) after SECONDS have elapsed,
|
||||
+ from a helper thread. The process is terminated with the exit
|
||||
+ function, so atexit handlers are executed. */
|
||||
void delayed_exit (int seconds);
|
||||
|
||||
+/* Like delayed_exit, but use _exit (0). */
|
||||
+void delayed__exit (int seconds);
|
||||
+
|
||||
/* Terminate the process (with exit status 1) if VALUE is not zero.
|
||||
In that case, print a failure message to standard output mentioning
|
||||
FUNCTION. The process is terminated with the exit function, so
|
23
glibc-RHEL-65358-2.patch
Normal file
23
glibc-RHEL-65358-2.patch
Normal file
@ -0,0 +1,23 @@
|
||||
commit 7d421209287a07db5e926552ae5fbe9d8abb50dc
|
||||
Author: Florian Weimer <fweimer@redhat.com>
|
||||
Date: Tue Jun 6 11:39:06 2023 +0200
|
||||
|
||||
pthreads: Use _exit to terminate the tst-stdio1 test
|
||||
|
||||
Previously, the exit function was used, but this causes the test to
|
||||
block (until the timeout) once exit is changed to lock stdio streams
|
||||
during flush.
|
||||
|
||||
diff --git a/sysdeps/pthread/tst-stdio1.c b/sysdeps/pthread/tst-stdio1.c
|
||||
index 80fb59c4e42ca550..a2cc71d67f0761f6 100644
|
||||
--- a/sysdeps/pthread/tst-stdio1.c
|
||||
+++ b/sysdeps/pthread/tst-stdio1.c
|
||||
@@ -47,7 +47,7 @@ do_test (void)
|
||||
_exit (1);
|
||||
}
|
||||
|
||||
- delayed_exit (1);
|
||||
+ delayed__exit (1);
|
||||
xpthread_join (th);
|
||||
|
||||
puts ("join returned");
|
131
glibc-RHEL-65358-3.patch
Normal file
131
glibc-RHEL-65358-3.patch
Normal file
@ -0,0 +1,131 @@
|
||||
commit af130d27099651e0d27b2cf2cfb44dafd6fe8a26
|
||||
Author: Andreas Schwab <schwab@suse.de>
|
||||
Date: Tue Jan 30 10:16:00 2018 +0100
|
||||
|
||||
Always do locking when accessing streams (bug 15142, bug 14697)
|
||||
|
||||
Now that abort no longer calls fflush there is no reason to avoid locking
|
||||
the stdio streams anywhere. This fixes a conformance issue and potential
|
||||
heap corruption during exit.
|
||||
|
||||
diff --git a/libio/genops.c b/libio/genops.c
|
||||
index b964c50657d7fbe9..a82c1b96767e14e0 100644
|
||||
--- a/libio/genops.c
|
||||
+++ b/libio/genops.c
|
||||
@@ -683,7 +683,7 @@ _IO_adjust_column (unsigned start, const char *line, int count)
|
||||
libc_hidden_def (_IO_adjust_column)
|
||||
|
||||
int
|
||||
-_IO_flush_all_lockp (int do_lock)
|
||||
+_IO_flush_all (void)
|
||||
{
|
||||
int result = 0;
|
||||
FILE *fp;
|
||||
@@ -696,8 +696,7 @@ _IO_flush_all_lockp (int do_lock)
|
||||
for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)
|
||||
{
|
||||
run_fp = fp;
|
||||
- if (do_lock)
|
||||
- _IO_flockfile (fp);
|
||||
+ _IO_flockfile (fp);
|
||||
|
||||
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
|
||||
|| (_IO_vtable_offset (fp) == 0
|
||||
@@ -707,8 +706,7 @@ _IO_flush_all_lockp (int do_lock)
|
||||
&& _IO_OVERFLOW (fp, EOF) == EOF)
|
||||
result = EOF;
|
||||
|
||||
- if (do_lock)
|
||||
- _IO_funlockfile (fp);
|
||||
+ _IO_funlockfile (fp);
|
||||
run_fp = NULL;
|
||||
}
|
||||
|
||||
@@ -719,14 +717,6 @@ _IO_flush_all_lockp (int do_lock)
|
||||
|
||||
return result;
|
||||
}
|
||||
-
|
||||
-
|
||||
-int
|
||||
-_IO_flush_all (void)
|
||||
-{
|
||||
- /* We want locking. */
|
||||
- return _IO_flush_all_lockp (1);
|
||||
-}
|
||||
libc_hidden_def (_IO_flush_all)
|
||||
|
||||
void
|
||||
@@ -792,6 +782,9 @@ _IO_unbuffer_all (void)
|
||||
{
|
||||
int legacy = 0;
|
||||
|
||||
+ run_fp = fp;
|
||||
+ _IO_flockfile (fp);
|
||||
+
|
||||
#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1)
|
||||
if (__glibc_unlikely (_IO_vtable_offset (fp) != 0))
|
||||
legacy = 1;
|
||||
@@ -807,18 +800,6 @@ _IO_unbuffer_all (void)
|
||||
/* Iff stream is un-orientated, it wasn't used. */
|
||||
&& (legacy || fp->_mode != 0))
|
||||
{
|
||||
-#ifdef _IO_MTSAFE_IO
|
||||
- int cnt;
|
||||
-#define MAXTRIES 2
|
||||
- for (cnt = 0; cnt < MAXTRIES; ++cnt)
|
||||
- if (fp->_lock == NULL || _IO_lock_trylock (*fp->_lock) == 0)
|
||||
- break;
|
||||
- else
|
||||
- /* Give the other thread time to finish up its use of the
|
||||
- stream. */
|
||||
- __sched_yield ();
|
||||
-#endif
|
||||
-
|
||||
if (! legacy && ! dealloc_buffers && !(fp->_flags & _IO_USER_BUF))
|
||||
{
|
||||
fp->_flags |= _IO_USER_BUF;
|
||||
@@ -832,17 +813,15 @@ _IO_unbuffer_all (void)
|
||||
|
||||
if (! legacy && fp->_mode > 0)
|
||||
_IO_wsetb (fp, NULL, NULL, 0);
|
||||
-
|
||||
-#ifdef _IO_MTSAFE_IO
|
||||
- if (cnt < MAXTRIES && fp->_lock != NULL)
|
||||
- _IO_lock_unlock (*fp->_lock);
|
||||
-#endif
|
||||
}
|
||||
|
||||
/* Make sure that never again the wide char functions can be
|
||||
used. */
|
||||
if (! legacy)
|
||||
fp->_mode = -1;
|
||||
+
|
||||
+ _IO_funlockfile (fp);
|
||||
+ run_fp = NULL;
|
||||
}
|
||||
|
||||
#ifdef _IO_MTSAFE_IO
|
||||
@@ -868,9 +847,7 @@ libc_freeres_fn (buffer_free)
|
||||
int
|
||||
_IO_cleanup (void)
|
||||
{
|
||||
- /* We do *not* want locking. Some threads might use streams but
|
||||
- that is their problem, we flush them underneath them. */
|
||||
- int result = _IO_flush_all_lockp (0);
|
||||
+ int result = _IO_flush_all ();
|
||||
|
||||
/* We currently don't have a reliable mechanism for making sure that
|
||||
C++ static destructors are executed in the correct order.
|
||||
diff --git a/libio/libioP.h b/libio/libioP.h
|
||||
index 811e9c919bbc2ce1..fbe58fc10fb694d0 100644
|
||||
--- a/libio/libioP.h
|
||||
+++ b/libio/libioP.h
|
||||
@@ -488,7 +488,6 @@ extern int _IO_new_do_write (FILE *, const char *, size_t);
|
||||
extern int _IO_old_do_write (FILE *, const char *, size_t);
|
||||
extern int _IO_wdo_write (FILE *, const wchar_t *, size_t);
|
||||
libc_hidden_proto (_IO_wdo_write)
|
||||
-extern int _IO_flush_all_lockp (int);
|
||||
extern int _IO_flush_all (void);
|
||||
libc_hidden_proto (_IO_flush_all)
|
||||
extern int _IO_cleanup (void);
|
230
glibc-RHEL-65358-4.patch
Normal file
230
glibc-RHEL-65358-4.patch
Normal file
@ -0,0 +1,230 @@
|
||||
commit f6ba993e0cda0ca5554fd47b00e6a87be5fdf05e
|
||||
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
|
||||
Date: Thu Jul 25 15:41:44 2024 -0300
|
||||
|
||||
stdlib: Allow concurrent exit (BZ 31997)
|
||||
|
||||
Even if C/POSIX standard states that exit is not formally thread-unsafe,
|
||||
calling it more than once is UB. The glibc already supports
|
||||
it for the single-thread, and both elf/nodelete2.c and tst-rseq-disable.c
|
||||
call exit from a DSO destructor (which is called by _dl_fini, registered
|
||||
at program startup with __cxa_atexit).
|
||||
|
||||
However, there are still race issues when it is called more than once
|
||||
concurrently by multiple threads. A recent Rust PR triggered this
|
||||
issue [1], which resulted in an Austin Group ask for clarification [2].
|
||||
Besides it, there is a discussion to make concurrent calling not UB [3],
|
||||
wtih a defined semantic where any remaining callers block until the first
|
||||
call to exit has finished (reentrant calls, leaving through longjmp, and
|
||||
exceptions are still undefined).
|
||||
|
||||
For glibc, at least reentrant calls are required to be supported to avoid
|
||||
changing the current behaviour. This requires locking using a recursive
|
||||
lock, where any exit called by atexit() handlers resumes at the point of
|
||||
the current handler (thus avoiding calling the current handle multiple
|
||||
times).
|
||||
|
||||
Checked on x86_64-linux-gnu and aarch64-linux-gnu.
|
||||
|
||||
[1] https://github.com/rust-lang/rust/issues/126600
|
||||
[2] https://austingroupbugs.net/view.php?id=1845
|
||||
[3] https://www.openwall.com/lists/libc-coord/2024/07/24/4
|
||||
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
|
||||
|
||||
diff --git a/stdlib/Makefile b/stdlib/Makefile
|
||||
index 603a330b1e8f1ba2..865d804ef2642cb5 100644
|
||||
--- a/stdlib/Makefile
|
||||
+++ b/stdlib/Makefile
|
||||
@@ -93,6 +93,7 @@ tests := \
|
||||
tst-bsearch \
|
||||
tst-bz20544 \
|
||||
tst-canon-bz26341 \
|
||||
+ tst-concurrent-exit \
|
||||
tst-cxa_atexit \
|
||||
tst-environ \
|
||||
tst-getenv-signal \
|
||||
diff --git a/stdlib/exit.c b/stdlib/exit.c
|
||||
index 546343f7d4b74773..7d536098623d47ff 100644
|
||||
--- a/stdlib/exit.c
|
||||
+++ b/stdlib/exit.c
|
||||
@@ -140,9 +140,17 @@ __run_exit_handlers (int status, struct exit_function_list **listp,
|
||||
}
|
||||
|
||||
|
||||
+/* The lock handles concurrent exit(), even though the C/POSIX standard states
|
||||
+ that calling exit() more than once is UB. The recursive lock allows
|
||||
+ atexit() handlers or destructors to call exit() itself. In this case, the
|
||||
+ handler list execution will resume at the point of the current handler. */
|
||||
+__libc_lock_define_initialized_recursive (static, __exit_lock)
|
||||
+
|
||||
void
|
||||
exit (int status)
|
||||
{
|
||||
+ /* The exit should never return, so there is no need to unlock it. */
|
||||
+ __libc_lock_lock_recursive (__exit_lock);
|
||||
__run_exit_handlers (status, &__exit_funcs, true, true);
|
||||
}
|
||||
libc_hidden_def (exit)
|
||||
diff --git a/stdlib/tst-concurrent-exit.c b/stdlib/tst-concurrent-exit.c
|
||||
new file mode 100644
|
||||
index 0000000000000000..1141130f87fde20f
|
||||
--- /dev/null
|
||||
+++ b/stdlib/tst-concurrent-exit.c
|
||||
@@ -0,0 +1,157 @@
|
||||
+/* Check if exit can be called concurrently by multiple threads.
|
||||
+ Copyright (C) 2024 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 <array_length.h>
|
||||
+#include <stdlib.h>
|
||||
+#include <support/check.h>
|
||||
+#include <support/xthread.h>
|
||||
+#include <stdio.h>
|
||||
+#include <support/xunistd.h>
|
||||
+#include <string.h>
|
||||
+
|
||||
+#define MAX_atexit 32
|
||||
+
|
||||
+static pthread_barrier_t barrier;
|
||||
+
|
||||
+static void *
|
||||
+tf (void *closure)
|
||||
+{
|
||||
+ xpthread_barrier_wait (&barrier);
|
||||
+ exit (0);
|
||||
+
|
||||
+ return NULL;
|
||||
+}
|
||||
+
|
||||
+static const char expected[] = "00000000000000000000000003021121130211";
|
||||
+static char crumbs[sizeof (expected)];
|
||||
+static int next_slot = 0;
|
||||
+
|
||||
+static void
|
||||
+exit_with_flush (int code)
|
||||
+{
|
||||
+ fflush (stdout);
|
||||
+ /* glibc allows recursive exit, the atexit handlers execution will be
|
||||
+ resumed from the where the previous exit was interrupted. */
|
||||
+ exit (code);
|
||||
+}
|
||||
+
|
||||
+/* Take some time, so another thread potentially issue exit. */
|
||||
+#define SETUP_NANOSLEEP \
|
||||
+ if (nanosleep (&(struct timespec) { .tv_sec = 0, .tv_nsec = 1000L }, \
|
||||
+ NULL) != 0) \
|
||||
+ FAIL_EXIT1 ("nanosleep: %m")
|
||||
+
|
||||
+static void
|
||||
+fn0 (void)
|
||||
+{
|
||||
+ crumbs[next_slot++] = '0';
|
||||
+ SETUP_NANOSLEEP;
|
||||
+}
|
||||
+
|
||||
+static void
|
||||
+fn1 (void)
|
||||
+{
|
||||
+ crumbs[next_slot++] = '1';
|
||||
+ SETUP_NANOSLEEP;
|
||||
+}
|
||||
+
|
||||
+static void
|
||||
+fn2 (void)
|
||||
+{
|
||||
+ crumbs[next_slot++] = '2';
|
||||
+ atexit (fn1);
|
||||
+ SETUP_NANOSLEEP;
|
||||
+}
|
||||
+
|
||||
+static void
|
||||
+fn3 (void)
|
||||
+{
|
||||
+ crumbs[next_slot++] = '3';
|
||||
+ atexit (fn2);
|
||||
+ atexit (fn0);
|
||||
+ SETUP_NANOSLEEP;
|
||||
+}
|
||||
+
|
||||
+static void
|
||||
+fn_final (void)
|
||||
+{
|
||||
+ TEST_COMPARE_STRING (crumbs, expected);
|
||||
+ exit_with_flush (0);
|
||||
+}
|
||||
+
|
||||
+_Noreturn static void
|
||||
+child (void)
|
||||
+{
|
||||
+ enum { nthreads = 8 };
|
||||
+
|
||||
+ xpthread_barrier_init (&barrier, NULL, nthreads + 1);
|
||||
+
|
||||
+ pthread_t thr[nthreads];
|
||||
+ for (int i = 0; i < nthreads; i++)
|
||||
+ thr[i] = xpthread_create (NULL, tf, NULL);
|
||||
+
|
||||
+ xpthread_barrier_wait (&barrier);
|
||||
+
|
||||
+ for (int i = 0; i < nthreads; i++)
|
||||
+ {
|
||||
+ pthread_join (thr[i], NULL);
|
||||
+ /* It should not be reached, it means that thread did not exit for
|
||||
+ some reason. */
|
||||
+ support_record_failure ();
|
||||
+ }
|
||||
+
|
||||
+ exit (2);
|
||||
+}
|
||||
+
|
||||
+static int
|
||||
+do_test (void)
|
||||
+{
|
||||
+ /* Register a large number of handler that will trigger a heap allocation
|
||||
+ for the handle state. On exit, each block will be freed after the
|
||||
+ handle is processed. */
|
||||
+ int slots_remaining = MAX_atexit;
|
||||
+
|
||||
+ /* Register this first so it can verify expected order of the rest. */
|
||||
+ atexit (fn_final); --slots_remaining;
|
||||
+
|
||||
+ TEST_VERIFY_EXIT (atexit (fn1) == 0); --slots_remaining;
|
||||
+ TEST_VERIFY_EXIT (atexit (fn3) == 0); --slots_remaining;
|
||||
+ TEST_VERIFY_EXIT (atexit (fn1) == 0); --slots_remaining;
|
||||
+ TEST_VERIFY_EXIT (atexit (fn2) == 0); --slots_remaining;
|
||||
+ TEST_VERIFY_EXIT (atexit (fn1) == 0); --slots_remaining;
|
||||
+ TEST_VERIFY_EXIT (atexit (fn3) == 0); --slots_remaining;
|
||||
+
|
||||
+ while (slots_remaining > 0)
|
||||
+ {
|
||||
+ TEST_VERIFY_EXIT (atexit (fn0) == 0); --slots_remaining;
|
||||
+ }
|
||||
+
|
||||
+ pid_t pid = xfork ();
|
||||
+ if (pid != 0)
|
||||
+ {
|
||||
+ int status;
|
||||
+ xwaitpid (pid, &status, 0);
|
||||
+ TEST_VERIFY (WIFEXITED (status));
|
||||
+ }
|
||||
+ else
|
||||
+ child ();
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+#include <support/test-driver.c>
|
427
glibc-RHEL-65358-5.patch
Normal file
427
glibc-RHEL-65358-5.patch
Normal file
@ -0,0 +1,427 @@
|
||||
commit c6af8a9a3ce137a9704825d173be22a2b2d9cb49
|
||||
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
|
||||
Date: Mon Aug 5 11:27:35 2024 -0300
|
||||
|
||||
stdlib: Allow concurrent quick_exit (BZ 31997)
|
||||
|
||||
As for exit, also allows concurrent quick_exit to avoid race
|
||||
conditions when it is called concurrently. Since it uses the same
|
||||
internal function as exit, the __exit_lock lock is moved to
|
||||
__run_exit_handlers. It also solved a potential concurrent when
|
||||
calling exit and quick_exit concurrently.
|
||||
|
||||
The test case 'expected' is expanded to a value larger than the
|
||||
minimum required by C/POSIX (32 entries) so at_quick_exit() will
|
||||
require libc to allocate a new block. This makes the test mre likely to
|
||||
trigger concurrent issues (through free() at __run_exit_handlers)
|
||||
if quick_exit() interacts with the at_quick_exit list concurrently.
|
||||
|
||||
This is also the latest interpretation of the Austin Ticket [1].
|
||||
|
||||
Checked on x86_64-linux-gnu.
|
||||
|
||||
[1] https://austingroupbugs.net/view.php?id=1845
|
||||
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
|
||||
|
||||
diff --git a/stdlib/Makefile b/stdlib/Makefile
|
||||
index 865d804ef2642cb5..4cbf47d215353681 100644
|
||||
--- a/stdlib/Makefile
|
||||
+++ b/stdlib/Makefile
|
||||
@@ -94,6 +94,7 @@ tests := \
|
||||
tst-bz20544 \
|
||||
tst-canon-bz26341 \
|
||||
tst-concurrent-exit \
|
||||
+ tst-concurrent-quick_exit \
|
||||
tst-cxa_atexit \
|
||||
tst-environ \
|
||||
tst-getenv-signal \
|
||||
diff --git a/stdlib/exit.c b/stdlib/exit.c
|
||||
index 7d536098623d47ff..1719f88c7aca5397 100644
|
||||
--- a/stdlib/exit.c
|
||||
+++ b/stdlib/exit.c
|
||||
@@ -30,6 +30,13 @@ DEFINE_HOOK (__libc_atexit, (void))
|
||||
__exit_funcs_lock is declared. */
|
||||
bool __exit_funcs_done = false;
|
||||
|
||||
+/* The lock handles concurrent exit() and quick_exit(), even though the
|
||||
+ C/POSIX standard states that calling exit() more than once is UB. The
|
||||
+ recursive lock allows atexit() handlers or destructors to call exit()
|
||||
+ itself. In this case, the handler list execution will resume at the
|
||||
+ point of the current handler. */
|
||||
+__libc_lock_define_initialized_recursive (static, __exit_lock)
|
||||
+
|
||||
/* Call all functions registered with `atexit' and `on_exit',
|
||||
in the reverse of the order in which they were registered
|
||||
perform stdio cleanup, and terminate program execution with STATUS. */
|
||||
@@ -38,6 +45,9 @@ attribute_hidden
|
||||
__run_exit_handlers (int status, struct exit_function_list **listp,
|
||||
bool run_list_atexit, bool run_dtors)
|
||||
{
|
||||
+ /* The exit should never return, so there is no need to unlock it. */
|
||||
+ __libc_lock_lock_recursive (__exit_lock);
|
||||
+
|
||||
/* First, call the TLS destructors. */
|
||||
#ifndef SHARED
|
||||
if (&__call_tls_dtors != NULL)
|
||||
@@ -140,17 +150,9 @@ __run_exit_handlers (int status, struct exit_function_list **listp,
|
||||
}
|
||||
|
||||
|
||||
-/* The lock handles concurrent exit(), even though the C/POSIX standard states
|
||||
- that calling exit() more than once is UB. The recursive lock allows
|
||||
- atexit() handlers or destructors to call exit() itself. In this case, the
|
||||
- handler list execution will resume at the point of the current handler. */
|
||||
-__libc_lock_define_initialized_recursive (static, __exit_lock)
|
||||
-
|
||||
void
|
||||
exit (int status)
|
||||
{
|
||||
- /* The exit should never return, so there is no need to unlock it. */
|
||||
- __libc_lock_lock_recursive (__exit_lock);
|
||||
__run_exit_handlers (status, &__exit_funcs, true, true);
|
||||
}
|
||||
libc_hidden_def (exit)
|
||||
diff --git a/stdlib/tst-concurrent-exit-skeleton.c b/stdlib/tst-concurrent-exit-skeleton.c
|
||||
new file mode 100644
|
||||
index 0000000000000000..cfd5140466e1a730
|
||||
--- /dev/null
|
||||
+++ b/stdlib/tst-concurrent-exit-skeleton.c
|
||||
@@ -0,0 +1,160 @@
|
||||
+/* Check if exit/quick_exit can be called concurrently by multiple threads.
|
||||
+ Copyright (C) 2024 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 <array_length.h>
|
||||
+#include <stdlib.h>
|
||||
+#include <support/check.h>
|
||||
+#include <support/xthread.h>
|
||||
+#include <stdio.h>
|
||||
+#include <support/xunistd.h>
|
||||
+#include <string.h>
|
||||
+
|
||||
+/* A value larger than the minimum required by C/POSIX (32), to trigger a
|
||||
+ new block memory allocation. */
|
||||
+#define MAX_atexit 64
|
||||
+
|
||||
+static pthread_barrier_t barrier;
|
||||
+
|
||||
+static void *
|
||||
+tf (void *closure)
|
||||
+{
|
||||
+ xpthread_barrier_wait (&barrier);
|
||||
+ EXIT (0);
|
||||
+
|
||||
+ return NULL;
|
||||
+}
|
||||
+
|
||||
+static const char expected[] = "00000000000000000000000000000000000"
|
||||
+ "00000000000000000000003021121130211";
|
||||
+static char crumbs[sizeof (expected)];
|
||||
+static int next_slot = 0;
|
||||
+
|
||||
+static void
|
||||
+exit_with_flush (int code)
|
||||
+{
|
||||
+ fflush (stdout);
|
||||
+ /* glibc allows recursive EXIT, the ATEXIT handlers execution will be
|
||||
+ resumed from the where the previous EXIT was interrupted. */
|
||||
+ EXIT (code);
|
||||
+}
|
||||
+
|
||||
+/* Take some time, so another thread potentially issue EXIT. */
|
||||
+#define SETUP_NANOSLEEP \
|
||||
+ if (nanosleep (&(struct timespec) { .tv_sec = 0, .tv_nsec = 1000L }, \
|
||||
+ NULL) != 0) \
|
||||
+ FAIL_EXIT1 ("nanosleep: %m")
|
||||
+
|
||||
+static void
|
||||
+fn0 (void)
|
||||
+{
|
||||
+ crumbs[next_slot++] = '0';
|
||||
+ SETUP_NANOSLEEP;
|
||||
+}
|
||||
+
|
||||
+static void
|
||||
+fn1 (void)
|
||||
+{
|
||||
+ crumbs[next_slot++] = '1';
|
||||
+ SETUP_NANOSLEEP;
|
||||
+}
|
||||
+
|
||||
+static void
|
||||
+fn2 (void)
|
||||
+{
|
||||
+ crumbs[next_slot++] = '2';
|
||||
+ ATEXIT (fn1);
|
||||
+ SETUP_NANOSLEEP;
|
||||
+}
|
||||
+
|
||||
+static void
|
||||
+fn3 (void)
|
||||
+{
|
||||
+ crumbs[next_slot++] = '3';
|
||||
+ ATEXIT (fn2);
|
||||
+ ATEXIT (fn0);
|
||||
+ SETUP_NANOSLEEP;
|
||||
+}
|
||||
+
|
||||
+static void
|
||||
+fn_final (void)
|
||||
+{
|
||||
+ TEST_COMPARE_STRING (crumbs, expected);
|
||||
+ exit_with_flush (0);
|
||||
+}
|
||||
+
|
||||
+_Noreturn static void
|
||||
+child (void)
|
||||
+{
|
||||
+ enum { nthreads = 8 };
|
||||
+
|
||||
+ xpthread_barrier_init (&barrier, NULL, nthreads + 1);
|
||||
+
|
||||
+ pthread_t thr[nthreads];
|
||||
+ for (int i = 0; i < nthreads; i++)
|
||||
+ thr[i] = xpthread_create (NULL, tf, NULL);
|
||||
+
|
||||
+ xpthread_barrier_wait (&barrier);
|
||||
+
|
||||
+ for (int i = 0; i < nthreads; i++)
|
||||
+ {
|
||||
+ pthread_join (thr[i], NULL);
|
||||
+ /* It should not be reached, it means that thread did not exit for
|
||||
+ some reason. */
|
||||
+ support_record_failure ();
|
||||
+ }
|
||||
+
|
||||
+ EXIT (2);
|
||||
+}
|
||||
+
|
||||
+static int
|
||||
+do_test (void)
|
||||
+{
|
||||
+ /* Register a large number of handler that will trigger a heap allocation
|
||||
+ for the handle state. On EXIT, each block will be freed after the
|
||||
+ handle is processed. */
|
||||
+ int slots_remaining = MAX_atexit;
|
||||
+
|
||||
+ /* Register this first so it can verify expected order of the rest. */
|
||||
+ ATEXIT (fn_final); --slots_remaining;
|
||||
+
|
||||
+ TEST_VERIFY_EXIT (ATEXIT (fn1) == 0); --slots_remaining;
|
||||
+ TEST_VERIFY_EXIT (ATEXIT (fn3) == 0); --slots_remaining;
|
||||
+ TEST_VERIFY_EXIT (ATEXIT (fn1) == 0); --slots_remaining;
|
||||
+ TEST_VERIFY_EXIT (ATEXIT (fn2) == 0); --slots_remaining;
|
||||
+ TEST_VERIFY_EXIT (ATEXIT (fn1) == 0); --slots_remaining;
|
||||
+ TEST_VERIFY_EXIT (ATEXIT (fn3) == 0); --slots_remaining;
|
||||
+
|
||||
+ while (slots_remaining > 0)
|
||||
+ {
|
||||
+ TEST_VERIFY_EXIT (ATEXIT (fn0) == 0); --slots_remaining;
|
||||
+ }
|
||||
+
|
||||
+ pid_t pid = xfork ();
|
||||
+ if (pid != 0)
|
||||
+ {
|
||||
+ int status;
|
||||
+ xwaitpid (pid, &status, 0);
|
||||
+ TEST_VERIFY (WIFEXITED (status));
|
||||
+ }
|
||||
+ else
|
||||
+ child ();
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+#include <support/test-driver.c>
|
||||
diff --git a/stdlib/tst-concurrent-exit.c b/stdlib/tst-concurrent-exit.c
|
||||
index 1141130f87fde20f..421c39d63126246d 100644
|
||||
--- a/stdlib/tst-concurrent-exit.c
|
||||
+++ b/stdlib/tst-concurrent-exit.c
|
||||
@@ -16,142 +16,7 @@
|
||||
License along with the GNU C Library; if not, see
|
||||
<https://www.gnu.org/licenses/>. */
|
||||
|
||||
-#include <array_length.h>
|
||||
-#include <stdlib.h>
|
||||
-#include <support/check.h>
|
||||
-#include <support/xthread.h>
|
||||
-#include <stdio.h>
|
||||
-#include <support/xunistd.h>
|
||||
-#include <string.h>
|
||||
+#define EXIT(__r) exit (__r)
|
||||
+#define ATEXIT(__f) atexit (__f)
|
||||
|
||||
-#define MAX_atexit 32
|
||||
-
|
||||
-static pthread_barrier_t barrier;
|
||||
-
|
||||
-static void *
|
||||
-tf (void *closure)
|
||||
-{
|
||||
- xpthread_barrier_wait (&barrier);
|
||||
- exit (0);
|
||||
-
|
||||
- return NULL;
|
||||
-}
|
||||
-
|
||||
-static const char expected[] = "00000000000000000000000003021121130211";
|
||||
-static char crumbs[sizeof (expected)];
|
||||
-static int next_slot = 0;
|
||||
-
|
||||
-static void
|
||||
-exit_with_flush (int code)
|
||||
-{
|
||||
- fflush (stdout);
|
||||
- /* glibc allows recursive exit, the atexit handlers execution will be
|
||||
- resumed from the where the previous exit was interrupted. */
|
||||
- exit (code);
|
||||
-}
|
||||
-
|
||||
-/* Take some time, so another thread potentially issue exit. */
|
||||
-#define SETUP_NANOSLEEP \
|
||||
- if (nanosleep (&(struct timespec) { .tv_sec = 0, .tv_nsec = 1000L }, \
|
||||
- NULL) != 0) \
|
||||
- FAIL_EXIT1 ("nanosleep: %m")
|
||||
-
|
||||
-static void
|
||||
-fn0 (void)
|
||||
-{
|
||||
- crumbs[next_slot++] = '0';
|
||||
- SETUP_NANOSLEEP;
|
||||
-}
|
||||
-
|
||||
-static void
|
||||
-fn1 (void)
|
||||
-{
|
||||
- crumbs[next_slot++] = '1';
|
||||
- SETUP_NANOSLEEP;
|
||||
-}
|
||||
-
|
||||
-static void
|
||||
-fn2 (void)
|
||||
-{
|
||||
- crumbs[next_slot++] = '2';
|
||||
- atexit (fn1);
|
||||
- SETUP_NANOSLEEP;
|
||||
-}
|
||||
-
|
||||
-static void
|
||||
-fn3 (void)
|
||||
-{
|
||||
- crumbs[next_slot++] = '3';
|
||||
- atexit (fn2);
|
||||
- atexit (fn0);
|
||||
- SETUP_NANOSLEEP;
|
||||
-}
|
||||
-
|
||||
-static void
|
||||
-fn_final (void)
|
||||
-{
|
||||
- TEST_COMPARE_STRING (crumbs, expected);
|
||||
- exit_with_flush (0);
|
||||
-}
|
||||
-
|
||||
-_Noreturn static void
|
||||
-child (void)
|
||||
-{
|
||||
- enum { nthreads = 8 };
|
||||
-
|
||||
- xpthread_barrier_init (&barrier, NULL, nthreads + 1);
|
||||
-
|
||||
- pthread_t thr[nthreads];
|
||||
- for (int i = 0; i < nthreads; i++)
|
||||
- thr[i] = xpthread_create (NULL, tf, NULL);
|
||||
-
|
||||
- xpthread_barrier_wait (&barrier);
|
||||
-
|
||||
- for (int i = 0; i < nthreads; i++)
|
||||
- {
|
||||
- pthread_join (thr[i], NULL);
|
||||
- /* It should not be reached, it means that thread did not exit for
|
||||
- some reason. */
|
||||
- support_record_failure ();
|
||||
- }
|
||||
-
|
||||
- exit (2);
|
||||
-}
|
||||
-
|
||||
-static int
|
||||
-do_test (void)
|
||||
-{
|
||||
- /* Register a large number of handler that will trigger a heap allocation
|
||||
- for the handle state. On exit, each block will be freed after the
|
||||
- handle is processed. */
|
||||
- int slots_remaining = MAX_atexit;
|
||||
-
|
||||
- /* Register this first so it can verify expected order of the rest. */
|
||||
- atexit (fn_final); --slots_remaining;
|
||||
-
|
||||
- TEST_VERIFY_EXIT (atexit (fn1) == 0); --slots_remaining;
|
||||
- TEST_VERIFY_EXIT (atexit (fn3) == 0); --slots_remaining;
|
||||
- TEST_VERIFY_EXIT (atexit (fn1) == 0); --slots_remaining;
|
||||
- TEST_VERIFY_EXIT (atexit (fn2) == 0); --slots_remaining;
|
||||
- TEST_VERIFY_EXIT (atexit (fn1) == 0); --slots_remaining;
|
||||
- TEST_VERIFY_EXIT (atexit (fn3) == 0); --slots_remaining;
|
||||
-
|
||||
- while (slots_remaining > 0)
|
||||
- {
|
||||
- TEST_VERIFY_EXIT (atexit (fn0) == 0); --slots_remaining;
|
||||
- }
|
||||
-
|
||||
- pid_t pid = xfork ();
|
||||
- if (pid != 0)
|
||||
- {
|
||||
- int status;
|
||||
- xwaitpid (pid, &status, 0);
|
||||
- TEST_VERIFY (WIFEXITED (status));
|
||||
- }
|
||||
- else
|
||||
- child ();
|
||||
-
|
||||
- return 0;
|
||||
-}
|
||||
-
|
||||
-#include <support/test-driver.c>
|
||||
+#include "tst-concurrent-exit-skeleton.c"
|
||||
diff --git a/stdlib/tst-concurrent-quick_exit.c b/stdlib/tst-concurrent-quick_exit.c
|
||||
new file mode 100644
|
||||
index 0000000000000000..3f321668d6b8d536
|
||||
--- /dev/null
|
||||
+++ b/stdlib/tst-concurrent-quick_exit.c
|
||||
@@ -0,0 +1,22 @@
|
||||
+/* Check if quick_exit can be called concurrently by multiple threads.
|
||||
+ Copyright (C) 2024 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/>. */
|
||||
+
|
||||
+#define EXIT(__r) quick_exit (__r)
|
||||
+#define ATEXIT(__f) at_quick_exit (__f)
|
||||
+
|
||||
+#include "tst-concurrent-exit-skeleton.c"
|
11
glibc.spec
11
glibc.spec
@ -157,7 +157,7 @@ end \
|
||||
Summary: The GNU libc libraries
|
||||
Name: glibc
|
||||
Version: %{glibcversion}
|
||||
Release: 150%{?dist}
|
||||
Release: 151%{?dist}
|
||||
|
||||
# In general, GPLv2+ is used by programs, LGPLv2+ is used for
|
||||
# libraries.
|
||||
@ -1062,6 +1062,11 @@ Patch754: glibc-RHEL-56542-6.patch
|
||||
Patch755: glibc-RHEL-56542-7.patch
|
||||
Patch756: glibc-RHEL-56542-8.patch
|
||||
Patch757: glibc-RHEL-56542-9.patch
|
||||
Patch758: glibc-RHEL-65358-1.patch
|
||||
Patch759: glibc-RHEL-65358-2.patch
|
||||
Patch760: glibc-RHEL-65358-3.patch
|
||||
Patch761: glibc-RHEL-65358-4.patch
|
||||
Patch762: glibc-RHEL-65358-5.patch
|
||||
|
||||
##############################################################################
|
||||
# Continued list of core "glibc" package information:
|
||||
@ -3055,6 +3060,10 @@ update_gconv_modules_cache ()
|
||||
%endif
|
||||
|
||||
%changelog
|
||||
* Fri Jan 10 2025 Frédéric Bérat <fberat@redhat.com> - 2.34-151
|
||||
- Lock all stdio streams during exit
|
||||
- Support concurrent calls to exit (RHEL-65358)
|
||||
|
||||
* Fri Jan 10 2025 Frédéric Bérat <fberat@redhat.com> - 2.34-150
|
||||
- Backport test implementation to verify readdir behavior (RHEL-56542)
|
||||
- Backport the deallocation attributes commit for opendir/fdopendir (RHEL-56543)
|
||||
|
Loading…
Reference in New Issue
Block a user