Skip to content

Fork pool with hang cleanup

Track worker PIDs and thread IDs, and kill children that exceed PATIENCE.

18th August 2026

use strict;
use warnings;

use Parallel::ForkManager;

use constant PATIENCE => 50;

my %workers = ();
my %tids    = ();
my %pids    = ();

my $max_forks   = 3;
my $total_forks = 10;

sub actually_do_something {
    my ($c) = @_;
    print "Fork ID number: $c\n";
    sleep 3;
}

sub cleanup_by_pid {
    my ($pid) = @_;
    my $tid = $pids{$pid};
    delete $tids{$tid} if defined $tid;
    delete $pids{$pid};
    delete $workers{$pid};
}

sub cleanup_by_threadid {
    my ($tid) = @_;
    my $pid = $tids{$tid};
    delete $tids{$tid};
    delete $pids{$pid} if defined $pid;
    delete $workers{$pid} if defined $pid;
}

sub dismiss_hung_workers {
    while (my ($pid, $started_at) = each %workers) {
        next unless time() - $started_at > PATIENCE;
        kill TERM => $pid;
        cleanup_by_pid($pid);
    }
}

sub main {
    my $pm = Parallel::ForkManager->new($max_forks);

    $pm->run_on_wait(\&dismiss_hung_workers, 1);
    $pm->run_on_finish(sub {
        my ($pid, $tid) = @_;
        cleanup_by_threadid($tid);
    });

    for my $i (1 .. $total_forks) {
        if (my $pid = $pm->start) {
            $tids{$i}      = $pid;
            $pids{$pid}    = $i;
            $workers{$pid} = time();
            next;
        }
        actually_do_something($i);
        $pm->finish($i);
    }
    $pm->wait_all_children;
}

Caveats

Needs Parallel::ForkManager.