App-Base

 view release on metacpan or  search on metacpan

lib/App/Base/Daemon.pm  view on Meta::CPAN

    sub options {

        # See App::Base::Script::Common
    }

    sub daemon_run {
        my $self = shift;
        while (1) {
            # do something
            sleep(1)
        }

        return 0;    # This will never be reached
    }

    sub handle_shutdown {
        my $self = shift;
        # do something
        return 0;
    }

    no Moose;
    __PACKAGE__->meta->make_immutable;
    1;

    exit App::Base::Daemon::example->new->run;

=head1 DESCRIPTION

App::Base::Daemon builds on App::Base::Script::Common and provides common infrastructure for writing daemons, including:

=over 4

=item -

Standardized logging techniques via syslog

=item -

Signal processing and graceful shutdown

=back

=head1 BUILT-IN OPTIONS

Every App::Base::Daemon-implementing class gets some daemon-specific options for
free, in addition to those provided by App::Base::Script::Common. They are:

=head2 --no-fork

Rather than double-forking and detaching from the console, the daemon
runs in the foreground (parent) process. Useful for debugging or
interactive invocations.

=head2 --pid-file

Writes PID of the daemon into specified file, by default writes pid into /var/run/__PACKAGE__.pid

=head2 --no-pid-file

Do not write pid file, and do not check if it is exist and locked.

=head2 --no-warn

Do not produce warnings, silent mode

=head1 REQUIRED SUBCLASS METHODS

=cut

use namespace::autoclean;
use Syntax::Keyword::Try;
use Path::Tiny;

=head2 daemon_run

The main loop that runs the daemon. Typically this will include while(1) or
something similar.  If this method returns, daemon exits.

=cut

requires 'daemon_run';

=head2 handle_shutdown

Called before the daemon shuts down in response to a shutdown signal. Should
clean up any resources in use by the daemon. The return value of
handle_shutdown is used as the exit status of the daemon.

=cut

requires 'handle_shutdown';

use Socket;
use IO::Handle;
use File::Flock::Tiny;
use POSIX qw();

=head1 ATTRIBUTES

=head2 shutdown_signals

An arrayref of signals that should result in termination of the daemon.
Defaults are: INT, QUIT, TERM.

=cut

has 'shutdown_signals' => (
    is      => 'ro',
    default => sub {
        [qw( INT QUIT TERM )];
    },
);

=head2 user

Run as specified user, note that it is only possible if daemon started as root

=cut

has user => (is => 'ro');

=head2 group

Run as specified group, note that it is only possible if daemon started as root

=cut

has group => (is => 'ro');

=head2 pid_file

Pid file name

=cut

has pid_file => (
    is      => 'ro',
    lazy    => 1,
    builder => '_build_pid_file',
);

sub _build_pid_file {
    my $self = shift;
    my $file = $self->getOption('pid-file');
    unless ($file) {
        my $class  = ref $self;
        my $piddir = $ENV{APP_BASE_DAEMON_PIDDIR} || '/var/run';
        $file = path($piddir)->child("$class.pid");
    }
    return "$file";
}

=head2 can_do_hot_reload

Should return true if implementation supports hot reloading

=cut

sub can_do_hot_reload { return }

around 'base_options' => sub {
    my $orig = shift;
    my $self = shift;
    return [
        @{$self->$orig},
        {
            name          => 'no-fork',
            documentation => "Do not detach and run in the background",
        },
        {
            name          => 'pid-file',
            option_type   => 'string',
            documentation => "Use specified file to save PID",
        },
        {
            name          => 'no-pid-file',
            documentation => "Do not check if pidfile exists and locked",
        },
        {
            name          => 'user',
            documentation => "User to run as",
        },
        {
            name          => 'group',
            documentation => "Group to run as",
        },
        {
            name          => 'no-warn',
            documentation => 'Do not produce warnings',
        },
    ];
};

=head1 METHODS

=cut

sub _signal_shutdown {
    my $self = shift;
    $self->handle_shutdown;
    exit 0;
}

sub __run {
    my $self = shift;

    my $pid;
    my $hot_reload = $ENV{APP_BASE_DAEMON_GEN}++ && $self->can_do_hot_reload;
    unless ($self->getOption('no-pid-file') or $hot_reload) {
        $pid = File::Flock::Tiny->trylock($self->pid_file);
        unless ($pid) {
            if ($self->can_do_hot_reload) {
                chomp(my $pid = eval { my $fh = path($self->pid_file)->openr; <$fh>; });
                if ($pid and kill USR2 => $pid) {
                    warn("Daemon is alredy running, initiated hot reload") unless $self->getOption('no-warn');
                    exit 0;
                } else {
                    $self->error("Neither could lock pid file nor send USR2 to already running daemon.");
                }
            } else {
                die("Couldn't lock " . $self->pid_file . ". Is another copy of this daemon already running?");
            }
        }
    }

    $SIG{PIPE} = 'IGNORE';    ## no critic (RequireLocalizedPunctuationVars)
    foreach my $signal (@{$self->shutdown_signals}) {
        $SIG{$signal} = sub { App::Base::Daemon::_signal_shutdown($self, @_) };    ## no critic (RequireLocalizedPunctuationVars)
    }

    # Daemonize unless specifically asked not to.
    unless ($self->getOption('no-fork') or $hot_reload) {
        my $child_pid = fork();
        if (!defined($child_pid)) {
            die("Can't fork child process: $!");
        } elsif ($child_pid == 0) {
            POSIX::setsid();



( run in 1.212 second using v1.01-cache-2.11-cpan-e1769b4cff6 )