On 07/12/22(Wed) 22:17, Joel Knight wrote:
> Hi. As first mentioned on misc[1], I've identified a deadlock in libc
> when a process forks, the children are multi-threaded, and they set
> one or more atfork callbacks. The diff below causes ATFORK_UNLOCK() to
> release the lock even when the process isn't multi-threaded. This
> avoids the deadlock. With this patch applied, the test case I have for
> this issue succeeds and there are no new failures during a full 'make
> regress'.
> 
> Threading is outside my area of expertise so I've no idea if the fix
> proposed here is appropriate. I'm happy to take or test feedback.
> 
> The diff is below and a clean copy is here:
> https://www.packetmischief.ca/files/patches/atfork_on_fork.diff.

This sounds legit to me.  Anyone some time wants to take a look?

> .joel
> 
> 
> [1] https://marc.info/?l=openbsd-misc&m=166926508819790&w=2
> 
> 
> 
> Index: lib/libc/include/thread_private.h
> ===================================================================
> RCS file: /data/cvs-mirror/OpenBSD/src/lib/libc/include/thread_private.h,v
> retrieving revision 1.36
> diff -p -u -r1.36 thread_private.h
> --- lib/libc/include/thread_private.h 6 Jan 2021 19:54:17 -0000 1.36
> +++ lib/libc/include/thread_private.h 8 Dec 2022 04:28:45 -0000
> @@ -228,7 +228,7 @@ __END_HIDDEN_DECLS
>   } while (0)
>  #define _ATFORK_UNLOCK() \
>   do { \
> - if (__isthreaded) \
> + if (_thread_cb.tc_atfork_unlock != NULL) \
>   _thread_cb.tc_atfork_unlock(); \
>   } while (0)
> 
> Index: regress/lib/libpthread/pthread_atfork_on_fork/Makefile
> ===================================================================
> RCS file: regress/lib/libpthread/pthread_atfork_on_fork/Makefile
> diff -N regress/lib/libpthread/pthread_atfork_on_fork/Makefile
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ regress/lib/libpthread/pthread_atfork_on_fork/Makefile 7 Dec 2022
> 04:38:39 -0000
> @@ -0,0 +1,9 @@
> +# $OpenBSD$
> +
> +PROG= pthread_atfork_on_fork
> +
> +REGRESS_TARGETS= timeout
> +timeout:
> + timeout 10s ./${PROG}
> +
> +.include <bsd.regress.mk>
> Index: regress/lib/libpthread/pthread_atfork_on_fork/pthread_atfork_on_fork.c
> ===================================================================
> RCS file: 
> regress/lib/libpthread/pthread_atfork_on_fork/pthread_atfork_on_fork.c
> diff -N regress/lib/libpthread/pthread_atfork_on_fork/pthread_atfork_on_fork.c
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ regress/lib/libpthread/pthread_atfork_on_fork/pthread_atfork_on_fork.c
> 7 Dec 2022 04:59:10 -0000
> @@ -0,0 +1,94 @@
> +/* $OpenBSD$ */
> +
> +/*
> + * Copyright (c) 2022 Joel Knight <j...@openbsd.org>
> + *
> + * Permission to use, copy, modify, and distribute this software for any
> + * purpose with or without fee is hereby granted, provided that the above
> + * copyright notice and this permission notice appear in all copies.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
> + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
> + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
> + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
> + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
> + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
> + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
> + */
> +
> +/*
> + * This test exercises atfork lock/unlock through multiple generations of
> + * forked child processes where each child also becomes multi-threaded.
> + */
> +
> +#include <pthread.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +
> +#include <sys/wait.h>
> +
> +#define NUMBER_OF_GENERATIONS 4
> +#define NUMBER_OF_TEST_THREADS 2
> +
> +#define SAY(...) do { \
> +    fprintf(stderr, "pid %5i ", getpid()); \
> +    fprintf(stderr, __VA_ARGS__); \
> +} while (0)
> +
> +void
> +prepare(void)
> +{
> +    /* Do nothing */
> +}
> +
> +void *
> +thread(void *arg)
> +{
> +    return (NULL);
> +}
> +
> +int
> +test(int fork_level)
> +{
> +    pid_t       proc_pid;
> +    size_t      thread_index;
> +    pthread_t   threads[NUMBER_OF_TEST_THREADS];
> +
> +    proc_pid = fork();
> +    fork_level = fork_level - 1;
> +
> +    if (proc_pid == 0) {
> +        SAY("generation %i\n", fork_level);
> +        pthread_atfork(prepare, NULL, NULL);
> +
> +        for (thread_index = 0; thread_index < NUMBER_OF_TEST_THREADS;
> thread_index++) {
> +            pthread_create(&threads[thread_index], NULL, thread, NULL);
> +        }
> +
> +        for (thread_index = 0; thread_index < NUMBER_OF_TEST_THREADS;
> thread_index++) {
> +            pthread_join(threads[thread_index], NULL);
> +        }
> +
> +        if (fork_level > 0) {
> +            test(fork_level);
> +        }
> +
> +        SAY("exiting\n");
> +        exit(0);
> +    }
> +    else {
> +        SAY("parent waiting on child %i\n", proc_pid);
> +        waitpid(proc_pid, 0, 0);
> +    }
> +
> +    return (0);
> +}
> +
> +int
> +main(int argc, char *argv[])
> +{
> +    test(NUMBER_OF_GENERATIONS);
> +
> +    return (0);
> +}
> 

Reply via email to