La séptima vida

...o el gato así lo espera/teme

AnyEvent child watchers and timers

Imagine a ftp client that is launched every half an hour as a cron job. The server goes down, and so the client we just launched tries to reconnect every minute or so. After a few days of creating such clients, we end up with hundreds of clients that try to reconnect to the broken server. Sad but true.

The following program tries to help in this situation. It will only fork a certain time after the previous child has exited, thus maintaining a single instance of the child program alive.

It is another of my experiments to learn about AnyEvent.

#! /usr/bin/env perl

use AnyEvent;
use POSIX ":sys_wait_h";
use strict;
use warnings;
use v5.14;

my $cv = AnyEvent->condvar;
my $child_watcher;
my $timer;

# Launches a child process (forks). For the parent, returns the
# child's PID. For children, sleeps and then finishes the program
sub launch {
    my $pid = fork;
    die "Unable to fork: $!" unless defined $pid;

    if ($pid) {
        say "Child pid is $pid";
    }
    else {
        sleep 3;
        $cv->send(1);
    }
    return $pid;
}

# Takes the pid of the newly created child and returns a child watcher.
# When the child terminates, the CB deletes the current watcher,
# and starts a timer. The timer will then launch a new child and
# it will also create a new child watcher.
# The child watcher and the timer, both remain in scope using the
# file-level varialbles $child_watcher and $timer.
sub child_watcher {
    my $pid = shift;
    return AnyEvent->child(
        pid => $pid,
        cb  => sub {
            my ($pid, $status) = @_;
            say "Process finished: $pid";
            undef $child_watcher;

            $timer = AnyEvent->timer(
                after => 3,
                cb    => sub {
                    say "Timer executed.";
                    my $new_pid    = launch();
                    $child_watcher = child_watcher($new_pid);
                    undef $timer;
                }
            );
        }
    );
}

# Launch the child process and start the child watcher.
$child_watcher = child_watcher(launch());

# So that ctrl-C ends everything
my $exit = AnyEvent->signal(
    signal => 'INT',
    cb     => sub {
        $cv->send('OK');
    }
);

# We loop until the condvar receives a message. Children send 1,
# the parent sends OK
my $msg = $cv->recv;
wait;
say "Exit: $$ $msg";

And a sample output:

julio@julio-lap$ perl fork.pl 
Child pid is 7788
Exit: 7788 1
Process finished: 7788
Timer executed.
Child pid is 7789
Exit: 7789 1
Process finished: 7789
Timer executed.
Child pid is 7790
Exit: 7790 1
Process finished: 7790
Timer executed.
Child pid is 7791
^CExit: 7791 1
Exit: 7787 OK