-- Leo's gemini proxy

-- Connecting to thrig.me:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini

Unexpected Loss Of Parent Process


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.


A Bad Script


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";
    }

bad.pl


    $ perl bad.pl
    parent 89230
    parent 89230
    parent 89230
    kill    89230
    Killed
    $ parent went away!

Yet Another Use For A Pipe


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";
    }

pipewatch.pl


    $ 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

-- Response ended

-- Page fetched on Tue May 21 19:04:46 2024