💾 Archived View for thrig.me › blog › 2023 › 08 › 27 › unexpected-loss-of-parent-process.gmi captured on 2023-09-08 at 15:59:20. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
Usually it is the parent process that monitors children with wait(2), though sometimes you may want a child process to detect when the parent has gone away. Perhaps the child is holding a lock, and needs to close that lock should the parent unexpectedly exit. Power users can be fast and loose with KILL signals, or the parent process could crash.
One solution would be to check the parent PID, though this has various problems. The parent PID might already be 1 if the process is run by init(8) so there would be nothing to reparent to, or on the portability front some operating systems will reparent to some other PID that is not 1, or anyways there is a race condition: the parent might already have crashed or have been killed by the time the child can call getppid(2). Hence the spoiler in the title for this section.
#!/usr/bin/perl # bad.pl - a not so good way to check if the parent has gone away use 5.36.0; # murder proc - kills the parent after a few seconds { my $pid = fork() // die "fork failed: $!"; if ( $pid == 0 ) { my $parent = getppid(); # NOTE race condition! sleep 3; warn "kill\t$parent\n"; kill( KILL => $parent ); exit; } } # a child process that reacts to parent being killed. maybe. my $pid = fork() // die "fork failed"; if ($pid) { sleep 99; # parent will not go gently into that good night } else { my $i = 5; while ( $i-- ) { my $parent = getppid(); # NOTE race condition! die "parent went away!\n" if $parent == 1; warn "parent $parent\n"; sleep 1; } print "\n"; }
$ perl bad.pl parent 89230 parent 89230 parent 89230 kill 89230 Killed $ parent went away!
Another method (used elsewhere for other tricks on this capsule) is to setup a pipe, and then the child can react when that pipe goes away. This does consume a file descriptor, but hopefully you are not running short on those.
#!/usr/bin/perl # pipewatch.pl - react to parent loss via a pipe use 5.36.0; # murder proc { my $parent = $; my $pid = fork() // die "fork failed: $!"; if ( $pid == 0 ) { sleep 3; warn "kill\t$parent\n"; kill( KILL => $parent ); exit; } } # a child that reacts to the pipe closing pipe( my $pipe_reader, my $pipe_writer ) or die "pipe failed: $!"; $pipe_reader->blocking(0); my $pid = fork // die "fork failed: $!"; my $i = 5; if ($pid) { close $pipe_reader; while ( $i-- ) { say "parent\t$"; sleep 1; } } else { close $pipe_writer; while ( $i-- ) { my $ret = sysread $pipe_reader, my $buf, 1; die "no parent\n" if defined $ret and $ret == 0; # EOF say "child\t$\t", getppid(); sleep 1; } print "\n"; }
$ perl pipewatch.pl parent 84976 child 20858 84976 parent 84976 child 20858 84976 parent 84976 child 20858 84976 kill 84976 Killed $ no parent
Downsides may involve systems that do not support pipes, in which case you may need to use a socket or a temporary file, or to instead stop using Windows. Or maybe instead design things so that a child process need not be aware of whether the parent or worse some ancestor is still around, but that may not always be possible.
Also, avoid using the KILL signal if at all possible. Unless that process is Firefox, though it is more rare these days that large and bad software needs to be gotten rid of that way.
tags #unix