From: Adhemerval Zanella <adhemerval.zanella@linaro.org>
To: Arjun Shankar <arjun@redhat.com>, libc-alpha@sourceware.org
Cc: Florian Weimer <fweimer@redhat.com>,
Siddhesh Poyarekar <siddhesh@sourceware.org>
Subject: Re: [PATCH v2] Fix deadlock when pthread_atfork handler calls pthread_atfork or dlclose
Date: Tue, 10 May 2022 10:55:33 -0300 [thread overview]
Message-ID: <21f244b3-2b8c-891a-bb8b-66a7937e8394@linaro.org> (raw)
In-Reply-To: <20220427134625.3759831-1-arjun@redhat.com>
On 27/04/2022 10:46, Arjun Shankar wrote:
> In multi-threaded programs, registering via pthread_atfork,
> de-registering implicitly via dlclose, or running pthread_atfork
> handlers during fork was protected by an internal lock. This meant
> that a pthread_atfork handler attempting to register another handler or
> dlclose a dynamically loaded library would lead to a deadlock.
>
> This commit fixes the deadlock in the following way:
>
> During the execution of handlers at fork time, the atfork lock is
> released prior to the execution of each handler and taken again upon its
> return. Any handler registrations or de-registrations that occurred
> during the execution of the handler are accounted for before proceeding
> with further handler execution.
>
> If a handler that hasn't been executed yet gets de-registered by another
> handler during fork, it will not be executed. If a handler gets
> registered by another handler during fork, it will not be executed
> during that particular fork.
>
> The possibility that handlers may now be registered or deregistered
> during handler execution means that identifying the next handler to be
> run after a given hander may register/de-register others requires some
s/hander/handler.
> bookkeeping. The fork_handler struct has an additional field, 'id',
> which is assigned sequentially during registration. After the execution
> of a handler corresponding to ID, the next handler to be executed should
> have an id either < ID during 'prepare', or > ID during parent/child
Maybe just use 'higher' and 'lower' here.
> execution after the fork.
>
> Two tests are included:
>
> * tst-atfork3: Adhemerval Zanella <adhemerval.zanella@linaro.org>
> This test exercises calling dlclose from prepare, parent, and child
> handlers.
>
> * tst-atfork4: This test exercises calling pthread_atfork and dlclose
> from the prepare handler.
>
> [BZ #24595, BZ #27054]
> ---
> Tested on x86_64 and i686.
>
> I will shortly follow up with manual changes describing support for
> registering/de-registering atfork handlers from within a handler.
>
> v1: https://sourceware.org/pipermail/libc-alpha/2022-April/138026.html
>
> Changes in v2:
>
> * Added a new test, copied from Adhemerval's 2019 patch as tst-atfork3:
> https://sourceware.org/pipermail/libc-alpha/2019-August/105590.html
> and renamed the test included in v1 to tst-atfork4.
>
> * Fixed a bug in __run_postfork_handlers where handler list length was
> not updated after calling a handler. This was caught by Adhemerval's
> test when I ran it against v1.
>
> * Adjusted/corrected some comments in register-atfork.c.
>
> * Fixed a bug in tst-atfork4 where it was calling dlclose twice.
Patch looks ok, some comments below.
> ---
> include/register-atfork.h | 26 +++---
> posix/fork.c | 7 +-
> posix/register-atfork.c | 144 ++++++++++++++++++++++++-------
> sysdeps/pthread/Makefile | 17 +++-
> sysdeps/pthread/tst-atfork3.c | 120 ++++++++++++++++++++++++++
> sysdeps/pthread/tst-atfork3mod.c | 44 ++++++++++
> sysdeps/pthread/tst-atfork4.c | 134 ++++++++++++++++++++++++++++
> sysdeps/pthread/tst-atfork4mod.c | 48 +++++++++++
> 8 files changed, 492 insertions(+), 48 deletions(-)
> create mode 100644 sysdeps/pthread/tst-atfork3.c
> create mode 100644 sysdeps/pthread/tst-atfork3mod.c
> create mode 100644 sysdeps/pthread/tst-atfork4.c
> create mode 100644 sysdeps/pthread/tst-atfork4mod.c
>
> diff --git a/include/register-atfork.h b/include/register-atfork.h
> index be631137b6..1852239b30 100644
> --- a/include/register-atfork.h
> +++ b/include/register-atfork.h
> @@ -26,6 +26,7 @@ struct fork_handler
> void (*parent_handler) (void);
> void (*child_handler) (void);
> void *dso_handle;
> + uint64_t id;
> };
>
> /* Function to call to unregister fork handlers. */
> @@ -39,19 +40,18 @@ enum __run_fork_handler_type
> atfork_run_parent
> };
>
> -/* Run the atfork handlers and lock/unlock the internal lock depending
> - of the WHO argument:
> -
> - - atfork_run_prepare: run all the PREPARE_HANDLER in reverse order of
> - insertion and locks the internal lock.
> - - atfork_run_child: run all the CHILD_HANDLER and unlocks the internal
> - lock.
> - - atfork_run_parent: run all the PARENT_HANDLER and unlocks the internal
> - lock.
> -
> - Perform locking only if DO_LOCKING. */
> -extern void __run_fork_handlers (enum __run_fork_handler_type who,
> - _Bool do_locking) attribute_hidden;
> +/* Run the atfork prepare handlers in the reverse order of registration and
> +return the ID of the last registered handler. If DO_LOCKING is true, the
> +internal lock is held locked upon return. */
> +extern uint64_t __run_prefork_handlers (_Bool do_locking) attribute_hidden;
> +
> +/* Given a handler type (parent or child), run all the atfork handlers in
> +the order of registration up to and including the handler with id equal to
> +LASTRUN. If DO_LOCKING is true, the internal lock is unlocked prior to
> +return. */
> +extern void __run_postfork_handlers (enum __run_fork_handler_type who,
> + _Bool do_locking,
> + uint64_t lastrun) attribute_hidden;
>
Maybe align the comment with the start to the phrase:
/* Run ....
continuing... */
> /* C library side function to register new fork handlers. */
> extern int __register_atfork (void (*__prepare) (void),
> diff --git a/posix/fork.c b/posix/fork.c
> index 6b50c091f9..e1be3422ea 100644
> --- a/posix/fork.c
> +++ b/posix/fork.c
> @@ -46,8 +46,9 @@ __libc_fork (void)
> best effort to make is async-signal-safe at least for single-thread
> case. */
> bool multiple_threads = __libc_single_threaded == 0;
> + uint64_t lastrun;
>
> - __run_fork_handlers (atfork_run_prepare, multiple_threads);
> + lastrun = __run_prefork_handlers (multiple_threads);
>
> struct nss_database_data nss_database_data;
>
> @@ -105,7 +106,7 @@ __libc_fork (void)
> reclaim_stacks ();
>
> /* Run the handlers registered for the child. */
> - __run_fork_handlers (atfork_run_child, multiple_threads);
> + __run_postfork_handlers (atfork_run_child, multiple_threads, lastrun);
> }
> else
> {
> @@ -123,7 +124,7 @@ __libc_fork (void)
> }
>
> /* Run the handlers registered for the parent. */
> - __run_fork_handlers (atfork_run_parent, multiple_threads);
> + __run_postfork_handlers (atfork_run_parent, multiple_threads, lastrun);
>
> if (pid < 0)
> __set_errno (save_errno);
Ok.
> diff --git a/posix/register-atfork.c b/posix/register-atfork.c
> index 74b1b58404..2271682cfa 100644
> --- a/posix/register-atfork.c
> +++ b/posix/register-atfork.c
> @@ -26,7 +26,7 @@
> #include <malloc/dynarray-skeleton.c>
>
> static struct fork_handler_list fork_handlers;
> -static bool fork_handler_init = false;
> +static uint64_t fork_handler_counter = 0;
>
> static int atfork_lock = LLL_LOCK_INITIALIZER;
>
> @@ -36,11 +36,8 @@ __register_atfork (void (*prepare) (void), void (*parent) (void),
> {
> lll_lock (atfork_lock, LLL_PRIVATE);
>
> - if (!fork_handler_init)
> - {
> + if (fork_handler_counter == 0)
> fork_handler_list_init (&fork_handlers);
Indentation seems off here (4 spaces instead of 2).
> - fork_handler_init = true;
> - }
>
> struct fork_handler *newp = fork_handler_list_emplace (&fork_handlers);
> if (newp != NULL)
> @@ -49,6 +46,11 @@ __register_atfork (void (*prepare) (void), void (*parent) (void),
> newp->parent_handler = parent;
> newp->child_handler = child;
> newp->dso_handle = dso_handle;
> +
> + /* IDs assigned to handlers start at 1 and increment with handler
> + registration. Un-registering a handlers discards the corresponding
> + ID. It is not reused in future registrations. */
Maybe also align the comment here.
> + newp->id = ++fork_handler_counter;
Although highly unlikely I think it would be good to abort in case of overflow:
if (INT_ADD_OVERFLOW (fork_handler_counter, 1))
__libc_fatal ("fork handler counter overflow");
newp->id = ++fork_handler_counter;
> }
>
> /* Release the lock. */
> @@ -103,37 +105,119 @@ __unregister_atfork (void *dso_handle)
> lll_unlock (atfork_lock, LLL_PRIVATE);
> }
>
Ok.
> -void
> -__run_fork_handlers (enum __run_fork_handler_type who, _Bool do_locking)
> +uint64_t
> +__run_prefork_handlers (_Bool do_locking)
> {
> - struct fork_handler *runp;
> + uint64_t lastrun;
>
> - if (who == atfork_run_prepare)
> + if (do_locking)
> + lll_lock (atfork_lock, LLL_PRIVATE);
> +
> + /* We run `prepare' handlers from last to first. After fork, only
I think we now avoid marking backtick, maybe just putting in uppercase
should suffice.
> + handlers up to the last handler found here (pre-fork) will be run.
> + Handlers registered during __run_prefork_handlers or
> + __run_postfork_handlers will be positioned after this last handler, and
> + since their prepare handlers won't be run now, their parent/child
> + handlers should also be ignored. */
> + lastrun = fork_handler_counter;
Maybe also align the comment here.
> +
> + size_t sl = fork_handler_list_size (&fork_handlers);
> + for (size_t i = sl; i > 0;)
> {
> - if (do_locking)
> - lll_lock (atfork_lock, LLL_PRIVATE);
> - size_t sl = fork_handler_list_size (&fork_handlers);
> - for (size_t i = sl; i > 0; i--)
> - {
> - runp = fork_handler_list_at (&fork_handlers, i - 1);
> - if (runp->prepare_handler != NULL)
> - runp->prepare_handler ();
> - }
> + struct fork_handler *runp
> + = fork_handler_list_at (&fork_handlers, i - 1);
> +
> + uint64_t id = runp->id;
> +
> + if (runp->prepare_handler != NULL)
> + {
> + if (do_locking)
> + lll_unlock (atfork_lock, LLL_PRIVATE);
> +
> + runp->prepare_handler ();
> +
> + if (do_locking)
> + lll_lock (atfork_lock, LLL_PRIVATE);
> + }
> +
> + /* We unlocked, ran the handler, and locked again. In the
> + meanwhile, one or more deregistrations could have occurred leading
> + to the current (just run) handler being moved up the list or even
> + removed from the list itself. Since handler IDs are guaranteed to
> + to be in increasing order, the next handler has to have: */
> +
> + /* A. An earlier position than the current one has. */
> + i--;
> +
> + /* B. A lower ID than the current one does. */
Maybe add the code below skips newly added handlers.
> + while (i > 0
> + && fork_handler_list_at (&fork_handlers, i - 1)->id >= id)
> + i--;
> }
> - else
> +
> + return lastrun;
> +}
> +
> +void
> +__run_postfork_handlers (enum __run_fork_handler_type who, _Bool do_locking,
> + uint64_t lastrun)
> +{
> + size_t sl = fork_handler_list_size (&fork_handlers);
> + for (size_t i = 0; i < sl;)
> {
> - size_t sl = fork_handler_list_size (&fork_handlers);
> - for (size_t i = 0; i < sl; i++)
> - {
> - runp = fork_handler_list_at (&fork_handlers, i);
> - if (who == atfork_run_child && runp->child_handler)
> - runp->child_handler ();
> - else if (who == atfork_run_parent && runp->parent_handler)
> - runp->parent_handler ();
> - }
> - if (do_locking)
> - lll_unlock (atfork_lock, LLL_PRIVATE);
> + struct fork_handler *runp = fork_handler_list_at (&fork_handlers, i);
> + uint64_t id = runp->id;
> +
> + /* prepare handlers were not run for handlers with ID > LASTRUN.
> + Thus, parent/child handlers will also not be run. */
Maybe 'will also not run'.
> + if (id > lastrun)
> + break;
> +
> + if (who == atfork_run_child && runp->child_handler)
> + {
> + if (do_locking)
> + lll_unlock (atfork_lock, LLL_PRIVATE);
> +
> + runp->child_handler ();
> +
> + if (do_locking)
> + lll_lock (atfork_lock, LLL_PRIVATE);
> + }
> + else if (who == atfork_run_parent && runp->parent_handler)
> + {
> + if (do_locking)
> + lll_unlock (atfork_lock, LLL_PRIVATE);
> +
> + runp->parent_handler ();
> +
> + if (do_locking)
> + lll_lock (atfork_lock, LLL_PRIVATE);
> + }
I think you can simplify with:
assert (who != atfork_run_prepare);
[...]
if (do_locking)
lll_unlock (atfork_lock, LLL_PRIVATE);
if (who == atfork_run_child && runp->child_handler)
runp->child_handler ();
else if (who == atfork_run_parent && runp->parent_handler)
runp->parent_handler ();
if (do_locking)
ll_lock (atfork_lock, LLL_PRIVATE);
> +
> + /* We unlocked, ran the handler, and locked again. In the meanwhile,
> + one or more [de]registrations could have occurred. Due to this, the
> + list size must be updated. */
> + sl = fork_handler_list_size (&fork_handlers);
> +
> + /* The just-run handler could also have moved up the list. */
> +
> + if (sl > i && fork_handler_list_at (&fork_handlers, i)->id == id)
> + /* The position of the recently run handler hasn't changed. The
> + next handler to be run is an easy increment away. */
> + i++;
> + else
> + {
> + /* The next handler to be run is the first handler in the list
> + to have an ID higher than the current one. */
> + for (i = 0; i < sl; i++)
> + {
> + if (fork_handler_list_at (&fork_handlers, i)->id > id)
> + break;
> + }
> + }> }
> + if (do_locking)
> + lll_unlock (atfork_lock, LLL_PRIVATE);
Indentation seems ok here.
> }
>
>
> diff --git a/sysdeps/pthread/Makefile b/sysdeps/pthread/Makefile
> index e901c51df0..abe6504976 100644
> --- a/sysdeps/pthread/Makefile
> +++ b/sysdeps/pthread/Makefile
> @@ -154,16 +154,19 @@ tests += tst-cancelx2 tst-cancelx3 tst-cancelx6 tst-cancelx8 tst-cancelx9 \
> tst-cleanupx0 tst-cleanupx1 tst-cleanupx2 tst-cleanupx3
>
> ifeq ($(build-shared),yes)
> -tests += tst-atfork2 tst-pt-tls4 tst-_res1 tst-fini1 tst-create1
> +tests += tst-atfork2 tst-pt-tls4 tst-_res1 tst-fini1 tst-create1 tst-atfork3 \
> + tst-atfork4
Could you change to add one test per line?
tests += \
tst-atfork2 \
...
# tests
> tests-nolibpthread += tst-fini1
> endif
>
> modules-names += tst-atfork2mod tst-tls4moda tst-tls4modb \
> tst-_res1mod1 tst-_res1mod2 tst-fini1mod \
> - tst-create1mod
> + tst-create1mod tst-atfork3mod tst-atfork4mod
Same here.
> test-modules = $(addprefix $(objpfx),$(addsuffix .so,$(modules-names)))
>
> tst-atfork2mod.so-no-z-defs = yes
> +tst-atfork3mod.so-no-z-defs = yes
> +tst-atfork4mod.so-no-z-defs = yes
> tst-create1mod.so-no-z-defs = yes
>
> ifeq ($(build-shared),yes)
> @@ -226,8 +229,18 @@ tst-atfork2-ENV = MALLOC_TRACE=$(objpfx)tst-atfork2.mtrace \
> LD_PRELOAD=$(common-objpfx)/malloc/libc_malloc_debug.so
> $(objpfx)tst-atfork2mod.so: $(shared-thread-library)
>
> +$(objpfx)tst-atfork3: $(shared-thread-library)
> +LDFLAGS-tst-atfork3 = -rdynamic
> +$(objpfx)tst-atfork3mod.so: $(shared-thread-library)
> +
> +$(objpfx)tst-atfork4: $(shared-thread-library)
> +LDFLAGS-tst-atfork4 = -rdynamic
> +$(objpfx)tst-atfork4mod.so: $(shared-thread-library)
> +
> ifeq ($(build-shared),yes)
> $(objpfx)tst-atfork2.out: $(objpfx)tst-atfork2mod.so
> +$(objpfx)tst-atfork3.out: $(objpfx)tst-atfork3mod.so
> +$(objpfx)tst-atfork4.out: $(objpfx)tst-atfork4mod.so
> endif
>
> ifeq ($(build-shared),yes)
> diff --git a/sysdeps/pthread/tst-atfork3.c b/sysdeps/pthread/tst-atfork3.c
> new file mode 100644
> index 0000000000..c767fcd69a
> --- /dev/null
> +++ b/sysdeps/pthread/tst-atfork3.c
> @@ -0,0 +1,120 @@
> +/* Check if pthread_atfork handler can call dlclose (BZ#24595)
> + Copyright (C) 2022 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
> + <http://www.gnu.org/licenses/>. */
> +
> +#include <stdio.h>
> +#include <dlfcn.h>
> +#include <pthread.h>
> +#include <unistd.h>
> +#include <stdlib.h>
> +#include <stdbool.h>
> +
> +#include <support/check.h>
> +#include <support/xthread.h>
> +#include <support/capture_subprocess.h>
> +
> +/* Check if pthread_atfork handlers do not deadlock when calling a function
> + that might alter the internal fork handle list, such as dlclose.
> +
> + The test registers a callback set with pthread_atfork(), dlopen() a shared
> + library (nptl/tst-atfork3mod.c), calls an exported symbol from the library
> + (which in turn also registers atfork handlers), and calls fork to trigger
> + the callbacks. */
> +
> +static void *handler;
> +static bool run_dlclose_prepare;
> +static bool run_dlclose_parent;
> +static bool run_dlclose_child;
> +
> +static void
> +prepare (void)
> +{
> + if (run_dlclose_prepare)
> + TEST_COMPARE (dlclose (handler), 0);
> +}
> +
> +static void
> +parent (void)
> +{
> + if (run_dlclose_parent)
> + TEST_COMPARE (dlclose (handler), 0);
> +}
> +
> +static void
> +child (void)
> +{
> + if (run_dlclose_child)
> + TEST_COMPARE (dlclose (handler), 0);
> +}
> +
> +static void
> +proc_func (void *closure)
> +{
> +}
> +
> +static void
> +do_test_generic (bool dlclose_prepare, bool dlclose_parent, bool dlclose_child)
> +{
> + run_dlclose_prepare = dlclose_prepare;
> + run_dlclose_parent = dlclose_parent;
> + run_dlclose_child = dlclose_child;
> +
> + handler = dlopen ("tst-atfork3mod.so", RTLD_NOW);
> + TEST_VERIFY (handler != NULL);
Use xdlopen.
> +
> + int (*atfork3mod_func)(void);
> + atfork3mod_func = dlsym (handler, "atfork3mod_func");
> + TEST_VERIFY (atfork3mod_func != NULL);
And xdlsym here.
> +
> + atfork3mod_func ();
> +
> + struct support_capture_subprocess proc
> + = support_capture_subprocess (proc_func, NULL);
> + support_capture_subprocess_check (&proc, "tst-atfork3", 0, sc_allow_none);
> +
> + handler = atfork3mod_func = NULL;
> +
> + support_capture_subprocess_free (&proc);
> +}
> +
> +static void *
> +thread_func (void *closure)
> +{
> + return NULL;
> +}
> +
> +static int
> +do_test (void)
> +{
> + {
> + /* Make the process acts as multithread. */
> + pthread_attr_t attr;
> + xpthread_attr_init (&attr);
> + xpthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
> + xpthread_create (&attr, thread_func, NULL);
> + }
> +
> + TEST_COMPARE (pthread_atfork (prepare, parent, child), 0);
> +
> + do_test_generic (true /* prepare */, false /* parent */, false /* child */);
> + do_test_generic (false /* prepare */, true /* parent */, false /* child */);
> + do_test_generic (false /* prepare */, false /* parent */, true /* child */);
> +
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/sysdeps/pthread/tst-atfork3mod.c b/sysdeps/pthread/tst-atfork3mod.c
> new file mode 100644
> index 0000000000..6d0658cb9e
> --- /dev/null
> +++ b/sysdeps/pthread/tst-atfork3mod.c
> @@ -0,0 +1,44 @@
> +/* Copyright (C) 2022 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
> + <http://www.gnu.org/licenses/>. */
> +
> +#include <unistd.h>
> +#include <stdlib.h>
> +#include <pthread.h>
> +
> +#include <support/check.h>
> +
> +static void
> +mod_prepare (void)
> +{
> +}
> +
> +static void
> +mod_parent (void)
> +{
> +}
> +
> +static void
> +mod_child (void)
> +{
> +}
> +
> +int atfork3mod_func (void)
> +{
> + TEST_COMPARE (pthread_atfork (mod_prepare, mod_parent, mod_child), 0);
> +
> + return 0;
> +}
> diff --git a/sysdeps/pthread/tst-atfork4.c b/sysdeps/pthread/tst-atfork4.c
> new file mode 100644
> index 0000000000..6df7b7dbeb
> --- /dev/null
> +++ b/sysdeps/pthread/tst-atfork4.c
> @@ -0,0 +1,134 @@
> +/* pthread_atfork supports handlers that call pthread_atfork or dlclose.
> + Copyright (C) 2022 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 <dlfcn.h>
> +#include <stdio.h>
> +#include <pthread.h>
> +#include <sys/types.h>
> +#include <sys/wait.h>
> +#include <unistd.h>
> +#include <support/check.h>
> +#include <stdlib.h>
> +
> +static void *
> +thread_func (void *x)
> +{
> + return NULL;
> +}
> +
> +static unsigned int second_atfork_handler_runcount = 0;
> +
> +static void
> +second_atfork_handler (void)
> +{
> + second_atfork_handler_runcount++;
> +}
> +
> +static void *h = NULL;
> +
> +static unsigned int atfork_handler_runcount = 0;
> +
> +static void
> +prepare (void)
> +{
> + /* These atfork handlers are registered while atfork handlers are being
> + executed and thus will not be executed during the corresponding fork. */
> + pthread_atfork (second_atfork_handler,
> + second_atfork_handler,
> + second_atfork_handler);
Check pthread_atfork return code.
> +
> + /* This will de-register the atfork handlers registered by the dlopen'd
> + library and so they will not be executed. */
> + if (h != NULL)
> + {
> + dlclose (h);
Use xdlclose.
> + h = NULL;
> + }
> +
> + atfork_handler_runcount++;
> +}
> +
> +static void
> +after (void)
> +{
> + atfork_handler_runcount++;
> +}
> +
> +static int
> +do_test (void)
> +{
> + /* Make sure __libc_single_threaded is 0. */
> + pthread_t t;
> + pthread_attr_t attr;
> + TEST_VERIFY_EXIT (pthread_attr_init (&attr) == 0);
Use xpthread_attr_init.
> + TEST_VERIFY_EXIT (pthread_attr_setdetachstate (&attr,
> + PTHREAD_CREATE_DETACHED)
And xpthread_attr_setdetachstate.
> + == 0);
> + TEST_VERIFY_EXIT (pthread_create (&t, &attr, thread_func, NULL) == 0);
And xpthread_create.
> +
> + void (*reg_atfork_handlers) (void);
> +
> + h = dlopen ("tst-atfork4mod.so", RTLD_LAZY);
> + TEST_VERIFY_EXIT (h != NULL);
Adn xdlopen;
> +
> + * (void **) (®_atfork_handlers) = dlsym (h, "reg_atfork_handlers");
> + TEST_VERIFY_EXIT (reg_atfork_handlers != NULL);
There is no need to this double cast, just use:
void (*reg_atfork_handlers) (void) = xdlsym (h, "reg_atfork_handlers");
> +
> + reg_atfork_handlers ();
> +
> + /* We register our atfork handlers *after* loading the module so that our
> + prepare handler is called first at fork, where we then dlclose the module
> + before its prepare handler has a chance to be called. */
> + pthread_atfork (prepare, after, after);
> +
> + pid_t pid = fork ();
> + TEST_VERIFY_EXIT (pid >= 0);
Use xfork, same as bellow.
> +
> + /* Both the parent and the child processes should observe this. */
> + TEST_VERIFY_EXIT (atfork_handler_runcount == 2);
> + TEST_VERIFY_EXIT (second_atfork_handler_runcount == 0);
> +
> + if (pid > 0)
> + {
> + int childstat;
> +
> + wait (&childstat);
Use xwaitpid, same as bellow.
> + TEST_VERIFY_EXIT (WIFEXITED (childstat)
> + && WEXITSTATUS (childstat) == 0);
> +
> + /* This time, the second set of atfork handlers should also be called
> + since the handlers are already in place before fork is called. */
> +
> + pid = fork ();
> + TEST_VERIFY_EXIT (pid >= 0);
> +
> + TEST_VERIFY_EXIT (atfork_handler_runcount == 4);
> + TEST_VERIFY_EXIT (second_atfork_handler_runcount == 2);
> +
> + if (pid > 0)
> + {
> + wait (&childstat);
> + TEST_VERIFY_EXIT (WIFEXITED (childstat)
> + && WEXITSTATUS (childstat) == 0);
> + }
> + }
> +
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/sysdeps/pthread/tst-atfork4mod.c b/sysdeps/pthread/tst-atfork4mod.c
> new file mode 100644
> index 0000000000..78e2a06f11
> --- /dev/null
> +++ b/sysdeps/pthread/tst-atfork4mod.c
> @@ -0,0 +1,48 @@
> +/* pthread_atfork supports handlers that call pthread_atfork or dlclose.
> + Copyright (C) 2022 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 <pthread.h>
> +#include <stdlib.h>
> +
> +/* This dynamically loaded library simply registers its atfork handlers when
> +asked to. The atfork handlers should never be executed because the library
> +is unloaded before fork is called by the test program. */
> +
> +static void
> +prepare (void)
> +{
> + abort ();
> +}
> +
> +static void
> +parent (void)
> +{
> + abort ();
> +}
> +
> +static void
> +child (void)
> +{
> + abort ();
> +}
> +
> +void
> +reg_atfork_handlers (void)
> +{
> + pthread_atfork (prepare, parent, child);
> +}
Ok.
prev parent reply other threads:[~2022-05-10 13:55 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-04-27 13:46 Arjun Shankar
2022-05-10 13:55 ` Adhemerval Zanella [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=21f244b3-2b8c-891a-bb8b-66a7937e8394@linaro.org \
--to=adhemerval.zanella@linaro.org \
--cc=arjun@redhat.com \
--cc=fweimer@redhat.com \
--cc=libc-alpha@sourceware.org \
--cc=siddhesh@sourceware.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).