Support concurrent calls to exit

And lock all stdio streams during exit

Resolves: RHEL-65358
This commit is contained in:
Frédéric Bérat 2025-01-10 18:53:42 +01:00
parent bd7cb93842
commit 19df5781cb
6 changed files with 927 additions and 1 deletions

106
glibc-RHEL-65358-1.patch Normal file
View 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
View 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
View 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
View 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
View 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"

View File

@ -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)