On Fri, Oct 14, 2022 at 08:14:42AM -0400, Elad Lahav wrote: > Ah, multi-threaded fork, my arch-nemesis. > > A few points, though I'm not sure any of these are worth mentioning in the book: > > 1. It should perhaps be made clear that the child's copy of the lock > is, in fact, a separate lock. The statement that the lock is held by a > parent thread is somewhat inaccurate. That is, unless the lock is in > explicitly-shared memory (i.e., the result of mmap(MAP_SHARED)), in > which case the lock is indeed the same and is (presumably) meant to be > shared between the parent and child. > > 2. POSIX explicitly says that you can't execute any > non-async-signal-safe functions between fork() and exec(), which > pretty much eliminates any function that acquires a lock. > > 3. posix_spawn() is a safer alternative to the fork()/exec() pair > (though not to fork() by itself), at least with sensible > implementations that don't devolve to fork() and exec(). Good points! How about the following update? Thanx, Paul ------------------------------------------------------------------------ commit 069eb5ee2b570c9216b7f1244847a4bf2d2dfb47 Author: Paul E. McKenney <paulmck@xxxxxxxxxx> Date: Wed Oct 19 16:30:52 2022 -0700 locking: Expand on fork()/exec() locking issues Reported-by: Elad Lahav <e2lahav@xxxxxxxxx> Signed-off-by: Paul E. McKenney <paulmck@xxxxxxxxxx> diff --git a/bib/os.bib b/bib/os.bib index 3ae9b617..0846801e 100644 --- a/bib/os.bib +++ b/bib/os.bib @@ -1442,3 +1442,24 @@ Paul E. McKenney", note="\url{https://google.github.io/tcmalloc/overview.html}", lastchecked="January 24, 2020", } + +@unpublished{JoshTriplett2022io_uring_spawn, + author="Josh Triplett", + title="Spawning processes faster and easier with \co{io_uring}", + year="2022", + month="September", + day="12", + url={https://lpc.events/event/16/contributions/1213/}, + note="\url{https://www.youtube.com/watch?v=_h-kV8AYYqM&t=4074s}", + lastchecked="October 19, 2022", +} + +@unpublished{JakeEdge2022io_uring_spawn, + author="Jake Edge", + title="Introducing \co{io_uring_spawn}", + year="2022", + month="September", + day="20", + note="\url{https://lwn.net/Articles/908268/}", + lastchecked="October 19, 2022", +} diff --git a/locking/locking.tex b/locking/locking.tex index 6d3bb1c8..f74e0784 100644 --- a/locking/locking.tex +++ b/locking/locking.tex @@ -2436,11 +2436,11 @@ As noted earlier, if a thread executing a library function is holding a lock at the time that some other thread invokes \apipx{fork()}, the fact that the parent's memory is copied to create the child means that this lock will be born held in the child's context. -The thread that will release this lock is running in the parent, but not -in the child, which means that the child's copy of this lock will never -be released. +The thread that will release this lock is running in the parent, but +not in the child, which means that although the parent's copy of this +lock will be released, the child's copy never will be. Therefore, any attempt on the part of the child to invoke that same -library function will result in deadlock. +library function (thus acquiring that same lock) will result in deadlock. A pragmatic and straightforward way of solving this problem is to \co{fork()} a child process while the process is still single-threaded, @@ -2472,10 +2472,16 @@ parent before the \co{fork()}, one to be called by the parent after the \co{fork()}, and one to be called by the child after the \co{fork()}. Appropriate cleanups can then be carried out at these three points. -Be warned, however, that coding of \co{pthread_atfork()} handlers is quite subtle -in general. -The cases where \co{pthread_atfork()} works best are cases where the data structure -in question can simply be re-initialized by the child. +Be warned, however, that coding of \co{pthread_atfork()} handlers is +quite subtle in general. +The cases where \co{pthread_atfork()} works best are cases where the +data structure in question can simply be re-initialized by the child. +Which might be one reason why the POSIX standard forbids use of any +non-async-signal-safe functions between the \co{fork()} and the +\co{exec()}, which rules out acquisition of locks during that time. + +Other alternatives to \co{fork()}/\co{exec()} include \co{posix_spawn()} +and \co{io_uring_spawn()}~\cite{JoshTriplett2022io_uring_spawn,JakeEdge2022io_uring_spawn}. \subsubsection{Parallel Libraries: Discussion}