The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Acme::Ghost::Prefork - Pre-forking ghost daemon

SYNOPSIS

    use Acme::Ghost::Prefork;

    my $g = Acme::Ghost::Prefork->new(
        logfile => '/tmp/daemon.log',
        pidfile => '/tmp/daemon.pid',
        spirit => sub {
            my $self = shift;
            my $max = 10;
            my $i = 0;
            while ($self->tick) {
                $i++;
                sleep 1;
                $self->log->debug(sprintf("$$> %d/%d", $i, $max));
                last if $i >= $max;
            }
        },
    );

    exit $g->ctrl(shift(@ARGV) // '');

DESCRIPTION

Pre-forking ghost daemon (server)

ATTRIBUTES

This class inherits all attributes from Acme::Ghost and implements the following new ones

graceful_timeout

    graceful_timeout => 120

The maximum amount of time in seconds stopping a spirit gracefully may take before being forced to stop

Note that this value should usually be a little larger than the maximum amount of time you expect any one request to take

Defaults to 120

heartbeat_interval

    heartbeat_interval => 5

Heartbeat interval in seconds, defaults to 5

heartbeat_timeout

    heartbeat_timeout => 50

Maximum amount of time in seconds before a spirit without a heartbeat will be stopped gracefully

Note that this value should usually be a little larger than the maximum amount of time you expect any one operation to block the event loop

Defaults to 50

spare

    spare => 2

Temporarily spawn up to this number of additional spirits if there is a need

This allows for new spirits to be started while old ones are still shutting down gracefully, drastically reducing the performance cost of spirit restarts.

Defaults to 2

spirits, workers

    spirits => 4

Number of spirit processes.

A good rule of thumb is two spirit processes per CPU core for applications that perform mostly non-blocking operations. Blocking operations often require more amount of spirits and benefit from decreasing concurrency (often as low as 1)

Defaults to 4

METHODS

This class inherits all methods from Acme::Ghost and implements the following new ones

again

This method is called immediately after creating the instance and returns it

NOTE: Internal use only!

healthy

    my $healthy = $g->healthy;

This method returns the number of currently active live spirit processes (with a heartbeat)

startup

    $prefork->startup;

This method starts preforked process (manager and spirits) and wait for "MANAGER SIGNALS"

tick

    my $ok = $g->tick;
    my $ok = $g->tick(1); # marks the finished status

This is required method of spirit main process that sends heartbeat message to process manager and returns the status of the running server via the 'ok' attribute

MANAGER SIGNALS

The manager process can be controlled at runtime with the following signals

INT, TERM

Shut down server immediately

QUIT

Shut down server gracefully

TTIN

Increase spirit pool by one

TTOU

Decrease spirit pool by one

SPIRIT SIGNALS

The spirit processes can be controlled at runtime with the following signals

QUIT

Stop spirit gracefully

HOOKS

This class inherits all hooks from Acme::Ghost and implements the following new ones

Any of the following methods may be implemented (overwriting) in your class

finish

    sub finish {
        my $self = shift;
        my $graceful = shift;
        # . . .
    }

Is called when the server shuts down

    sub finish {
        my $self = shift;
        my $graceful = shift;
        $self->log->debug($graceful ? 'Graceful server shutdown' : 'Server shutdown');
    }

heartbeat

    sub heartbeat {
        my $self = shift;
        my $pid = shift;
        # . . .
    }

Is called when a heartbeat message has been received from a spirit

    sub heartbeat {
        my $self = shift;
        my $pid = shift;
        $self->log->debug("Spirit $pid has a heartbeat");
    }

reap

    sub reap {
        my $self = shift;
        my $pid = shift;
        # . . .
    }

Is called when a child process (spirit) finished

    sub reap {
        my $self = shift;
        my $pid = shift;
        $self->log->debug("Spirit $pid stopped");
    }

spawn

    sub spawn {
        my $self = shift;
        my $pid = shift;
        # . . .
    }

Is called when a spirit process is spawned

    sub spawn {
        my $self = shift;
        my $pid = shift;
        $self->log->debug("Spirit $pid started");
    }

waitup

    sub waitup {
        my $self = shift;
        # . . .
    }

Is called when the manager starts waiting for new heartbeat messages

    sub waitup {
        my $self = shift;
        my $spirits = $prefork->{spirits};
        $self->log->debug("Waiting for heartbeat messages from $spirits spirits");
    }

spirit

The spirit body

This hook is called when the spirit process has started and is ready to run in isolation. This is main hook that MUST BE implement to in user subclass

    sub spirit {
        my $self = shift;
        # . . .
    }

EXAMPLES

prefork_acme.pl

Prefork acme example of daemon with reloading demonstration

    my $g = MyGhost->new(
        logfile => 'daemon.log',
        pidfile => 'daemon.pid',
    );
    exit $g->ctrl(shift(@ARGV) // 'start');

    1;

    package MyGhost;

    use parent 'Acme::Ghost::Prefork';
    use Data::Dumper qw/Dumper/;

    sub init {
        my $self = shift;
        $SIG{HUP} = sub { $self->hangup };
    }
    sub hangup {
        my $self = shift;
        $self->log->debug(Dumper($self->{pool}));
    }
    sub spirit {
        my $self = shift;
        my $max = 10;
        my $i = 0;
        while ($self->tick) {
            $i++;
            sleep 1;
            $self->log->debug(sprintf("$$> %d/%d", $i, $max));
            last if $i >= $max;
        }
    }

    1;
prefork_ioloop.pl

Mojo::IOLoop example

    my $g = MyGhost->new(
        logfile => 'daemon.log',
        pidfile => 'daemon.pid',
    );
    exit $g->ctrl(shift(@ARGV) // 'start');

    1;

    package MyGhost;

    use parent 'Acme::Ghost::Prefork';
    use Mojo::IOLoop;
    use Data::Dumper qw/Dumper/;

    sub init {
        my $self = shift;
        $self->{loop} = Mojo::IOLoop->new;
    }
    sub spirit {
        my $self = shift;
        my $loop = $self->{loop};
        my $max = 10;
        my $i = 0;

        # Add a timers
        my $timer = $loop->timer(5 => sub {
            my $l = shift; # loop
            $self->log->info("Timer!");
        });

        my $recur = $loop->recurring(1 => sub {
            my $l = shift; # loop
            $l->stop unless $self->tick;
            $self->log->debug(sprintf("$$> %d/%d", ++$i, $max));
            $l->stop if $i >= $max;
        });

        $self->log->debug("Start IOLoop");

        # Start event loop if necessary
        $loop->start unless $loop->is_running;

        $self->log->debug("Finish IOLoop");
    }

    1;

TO DO

See TODO file

SEE ALSO

Acme::Ghost, Mojo::Server::Prefork

AUTHOR

Serż Minus (Sergey Lepenkov) https://www.serzik.com <abalama@cpan.org>

COPYRIGHT

Copyright (C) 1998-2024 D&D Corporation. All Rights Reserved

LICENSE

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

See LICENSE file and https://dev.perl.org/licenses/