view release on metacpan or search on metacpan
{
"abstract" : "An yet another view to daemon processes",
"author" : [
"Serz Minus (Sergey Lepenkov) <abalama@cpan.org>"
],
"dynamic_config" : 1,
"generated_by" : "ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010",
"license" : [
"perl_5"
],
"meta-spec" : {
"url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
---
abstract: 'An yet another view to daemon processes'
author:
- 'Serz Minus (Sergey Lepenkov) <abalama@cpan.org>'
build_requires:
ExtUtils::MakeMaker: '6.6'
Test::More: '0.94'
configure_requires:
ExtUtils::MakeMaker: '0'
dynamic_config: 1
generated_by: 'ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010'
license: perl
[//]: # ( README.md Sun 03 Dec 2023 13:54:32 MSK )
# Acme::Ghost
An yet another view to daemon processes
eg/ghost_ae.pl view on Meta::CPAN
sub startup {
my $self = shift;
my $quit = AnyEvent->condvar;
my $i = 0;
# Create watcher timer
my $watcher = AnyEvent->timer (after => 1, interval => 1, cb => sub {
$quit->send unless $self->ok;
});
# Create process timer
my $timer = AnyEvent->timer(after => 3, interval => 3, cb => sub {
$self->log->info("Tick! " . ++$i);
$quit->send if $i >= 10;
});
$self->log->debug("Start AnyEvent");
$quit->recv; # Run!
$self->log->debug("Finish AnyEvent");
}
lib/Acme/Ghost.pm view on Meta::CPAN
package Acme::Ghost;
use warnings;
use strict;
use utf8;
=encoding utf-8
=head1 NAME
Acme::Ghost - An yet another view to daemon processes
=head1 SYNOPSIS
use Acme::Ghost
my $g = Acme::Ghost->new(
logfile => '/tmp/daemon.log',
pidfile => '/tmp/daemon.pid',
user => 'nobody',
group => 'nogroup',
);
$g->daemonize;
$g->log->info('Oops! I am Your Ghost');
=head1 DESCRIPTION
An yet another view to daemon processes
=head2 new
my $g = Acme::Ghost->new(
name => 'myDaemon',
user => 'nobody',
group => 'nogroup',
pidfile => '/var/run/myDaemon.pid',
logfile => '/var/log/myDaemon.log',
ident => 'myDaemon',
lib/Acme/Ghost.pm view on Meta::CPAN
This attribute sets facility for logging
See L<Acme::Ghost::Log/facility>
=head2 group
group => 'nogroup',
group => 65534,
This attribute sets group/gid for spawned process
=head2 ident
ident => 'myDaemon',
This attribute sets ident string for system log (syslog)
=head2 logfile
logfile => '/var/log/myDaemon.log',
lib/Acme/Ghost.pm view on Meta::CPAN
pidfile => '/var/run/myDaemon.pid',
This attribute sets PID file path. Default: ./<NAME>.pid
=head2 user
user => 'nobody',
user => 65534,
This attribute sets user/uid for spawned process
=head1 METHODS
This class implements the following methods
=head2 again
This method is called immediately after creating the instance and returns it
B<NOTE:> Internal use only for subclasses!
=head2 daemonize
$g = $g->daemonize;
Main routine for just daemonize.
This routine will check on the pid file, safely fork, create the pid file (storing the pid in the file),
become another user and group, close STDIN, STDOUT and STDERR, separate from the process group (become session leader),
and install $SIG{INT} to remove the pid file. In otherwords - daemonize.
All errors result in a die
=head2 filepid
my $filepid = $g->filepid;
This method returns L<Acme::Ghost::FilePid> object
=head2 flush
$self = $self->flush;
This internal method flush (resets) process counters to defaults. Please do not use this method in your inherits
=head2 is_daemonized
$g->is_daemonized or die "Your ghost process really is not a daemon"
This method returns status of daemon:
True - the process is an daemon;
False - the process is not daemon;
=head2 is_spirited
my $is_spirited = $g->is_spirited;
This method returns status of spirit:
True - the process is an spirit;
False - the process is not spirit;
=head2 log
my $log = $g->log;
This method returns L<Acme::Ghost::Log> object
=head2 ok
$g->ok or die "Interrupted!";
This method checks process state and returns boolean status of healthy.
If this status is false, then it is immediately to shut down Your process
as soon as possible, otherwise your process will be forcibly destroyed
within 7 seconds from the moment your process receives the corresponding signal
=head2 pid
print $g->pid;
This method returns PID of the daemon
=head2 set_gid
$g = $g->set_gid('1000 10001 10002');
lib/Acme/Ghost.pm view on Meta::CPAN
This method performs restarting the daemon and returns C<0> as successfully
exit code or C<1> in otherwise
=head2 start
my $exit_code = $g->start;
say "Running ". $g->pid;
exit $exit_code;
This method performs starting the daemon and returns C<0> as exit code.
The spawned process calls the startup handler and exits with status C<0>
as exit code without anything return
=head2 status
if (my $runned = $g->status) {
say "Running $runned";
} else {
say "Not running";
}
lib/Acme/Ghost.pm view on Meta::CPAN
sub startup {
my $self = shift;
my $quit = AnyEvent->condvar;
my $i = 0;
# Create watcher timer
my $watcher = AnyEvent->timer (after => 1, interval => 1, cb => sub {
$quit->send unless $self->ok;
});
# Create process timer
my $timer = AnyEvent->timer(after => 3, interval => 3, cb => sub {
$self->log->info("Tick! " . ++$i);
$quit->send if $i >= 10;
});
$self->log->debug("Start AnyEvent");
$quit->recv; # Run!
$self->log->debug("Finish AnyEvent");
}
lib/Acme/Ghost.pm view on Meta::CPAN
facility => $args->{facility},
logfile => $args->{logfile},
ident => $args->{ident} || $name,
logopt => $args->{logopt},
logger => $args->{logger},
loglevel => $args->{loglevel},
loghandle => $args->{loghandle},
_log => undef,
# Runtime
initpid => $$, # PID of root process
ppid => 0, # PID before daemonize
pid => 0, # PID daemonized process
daemonized => 0, # 0 - no daemonized; 1 - daemonized
spirited => 0, # 0 - is not spirit; 1 - is spirit (See ::Prefork)
# Manage
ok => 0, # 1 - Ok. Process is healthy (ok)
signo => 0, # The caught signal number
interrupt => 0, # The interrupt counter
}, $class;
return $self->again(%$args);
lib/Acme/Ghost.pm view on Meta::CPAN
POSIX::setgid($gid) || die "Setgid $gid failed - $!\n"; # Set first GID
if (! grep {$gid == $_} split /\s+/, $() { # look for any valid id in the list
die "Detected strange GID. Couldn't become GID \"$gid\": $!\n";
}
return $self;
}
sub daemonize {
my $self = shift;
my $safe = shift;
croak "This process is already daemonized (PID=$$)\n" if $self->{daemonized};
# Check PID
my $pid_file = $self->filepid->file; # PID File
if ( my $runned = $self->filepid->running ) {
die "Already running $runned\n";
}
# Store current PID to instance as Parent PID
$self->{ppid} = $$;
lib/Acme/Ghost.pm view on Meta::CPAN
$self->{_log} = undef; # Close log handlers before spawn
# Spawn
my $pid = _fork();
if ($pid) {
_debug("!! Spawned (PID=%s)", $pid);
if ($safe) { # For internal use only
$self->{pid} = $pid; # Store child PID to instance
return $self;
}
exit 0; # exit parent process
}
# Child
$self->{daemonized} = 1; # Set daemonized flag
$self->filepid->pid($$)->save; # Set new PID and Write PID file
chown($uid, $gid, $pid_file) if IS_ROOT && -e $pid_file;
# Set GID and UID
$self->set_gid->set_uid;
# Turn process into session leader, and ensure no controlling terminal
unless (DEBUG) {
die "Can't start a new session: $!" if POSIX::setsid() < 0;
}
# Init logger!
my $log = $self->log;
# Close all standart filehandles
unless (DEBUG) {
my $devnull = File::Spec->devnull;
lib/Acme/Ghost.pm view on Meta::CPAN
sub pid { shift->{pid} }
# Hooks
sub preinit { }
sub init { }
sub cleanup { } # 0 -- at destroy; 1 -- at interrupt
sub startup { }
sub hangup { }
# Process
sub flush { # Flush process counters
my $self = shift;
$self->{interrupt} = 0;
$self->{signo} = 0;
$self->{ok} = 1;
return $self;
}
sub ok {
my $self = shift;
return 0 unless defined $self->{ppid}; # No parent pid found (it is not a daemon?)
return $self->{ok} ? 1 : 0;
lib/Acme/Ghost.pm view on Meta::CPAN
# LSB Daemon Control Methods
# These methods can be used to control the daemon behavior.
# Every effort has been made to have these methods DWIM (Do What I Mean),
# so that you can focus on just writing the code for your daemon
sub _term {
my $self = shift;
my $signo = shift || 0;
$self->{ok} = 0; # Not Ok!
$self->{signo} = $signo;
$self->log->debug(sprintf("Request for terminate of ghost process %s received on signal %s", $self->pid, $signo));
if ($self->{interrupt} >= INT_TRIES) { # Forced terminate
POSIX::_exit(1) if $self->is_spirited;
$self->cleanup(1);
$self->log->fatal(sprintf("Ghost process %s forcefully terminated on signal %s", $self->pid, $signo));
$self->filepid->remove;
POSIX::_exit(1);
}
$self->{interrupt}++;
}
sub start {
my $self = shift;
$self->daemonize(1); # First daemonize and switch to child process
return 0 unless $self->is_daemonized; # Exit from parent process
# Signals Trapping for interruption
local $SIG{INT} = sub { $self->_term(SIGINT) }; # 2
local $SIG{TERM} = sub { $self->_term(SIGTERM) }; # 15
local $SIG{QUIT} = sub { $self->_term(SIGQUIT) }; # 3
$self->flush; # Flush process counters
$self->log->info(sprintf("Ghost process %s started", $self->pid));
$self->startup(); # Master hook
$self->log->info(sprintf("Ghost process %s stopped", $self->pid));
exit 0; # Exit code for child: ok
}
sub stop {
my $self = shift;
my $pid = $self->filepid->running;
$self->{pid} = $pid;
return 0 unless $pid; # Not running
# Try SIGQUIT ... 2s ... SIGTERM ... 4s ... SIGINT ... 3s ... SIGKILL ... 3s ... UNDEAD!
my $tsig = 0;
for ([SIGQUIT, 2], [SIGTERM, 2], [SIGTERM, 2], [SIGINT, 3], [SIGKILL, 3]) {
my ($signo, $timeout) = @$_;
kill $signo, $pid;
for (1 .. $timeout) { # abort early if the process is now stopped
unless ($self->filepid->running) {
$tsig = $signo;
last;
}
sleep 1;
}
last if $tsig;
}
if ($tsig) {
if( $tsig == SIGKILL ) {
$self->filepid->remove;
warn "Had to resort to 'kill -9' and it worked, wiping pidfile\n";
}
return $pid;
}
# The ghost process doesn't seem to want to die. It is still running...;
return -1 * $pid;
}
sub status {
my $self = shift;
return $self->{pid} = $self->filepid->running || 0;
}
sub restart {
my $self = shift;
my $runned = $self->stop;
return 1 if $runned && $runned < 0; # It is still running
lib/Acme/Ghost.pm view on Meta::CPAN
printf "Running %s\n", $self->pid;
} elsif ($cmd eq 'status') {
if (my $runned = $self->status) {
printf "Running %s\n", $runned;
} else {
print "Not running\n";
}
} elsif ($cmd eq 'stop') {
if (my $runned = $self->stop) {
if ($runned < 0) {
printf STDERR "The ghost process %s doesn't seem to want to die. It is still running...\n", $self->pid;
$exit_code = 1;
} else {
printf "Stopped %s\n", $runned;
}
} else {
print "Not running\n";
}
} elsif ($cmd eq 'restart') {
$exit_code = $self->restart;
if ($exit_code) {
lib/Acme/Ghost/FilePid.pm view on Meta::CPAN
my $fp = Acme::Ghost::FilePid->new (
file => '/some/file.pid',
auto => 1,
);
die "Already running" if $fp->running;
# . . .
=head1 DESCRIPTION
This software manages a pid file for you. It will create a pid file,
query the process within to discover if it's still running, and remove
the pid file.
=head2 new
my $fp = Acme::Ghost::FilePid->new;
my $fp = Acme::Ghost::FilePid->new(
file => '/var/run/daemon.pid',
);
lib/Acme/Ghost/FilePid.pm view on Meta::CPAN
Removes the pid file from disk. Returns true on success, false on
failure.
=head2 running
my $pid = $fp->running;
die "Service already running: $pid" if $pid;
Checks to see if the pricess identified in the pid file is still
running. If the process is still running, the pid is returned. Otherwise
C<undef> is returned.
=head2 save
$fp->save;
Writes the pid file to disk, inserting the pid inside the file.
On success, the object is returned. On failure, C<undef> is
returned.
lib/Acme/Ghost/Prefork.pm view on Meta::CPAN
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 C<2>
=head2 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 C<1>)
Defaults to C<4>
=head1 METHODS
This class inherits all methods from L<Acme::Ghost> and implements the following new ones
=head2 again
This method is called immediately after creating the instance and returns it
B<NOTE:> Internal use only!
=head2 healthy
my $healthy = $g->healthy;
This method returns the number of currently active live spirit processes (with a heartbeat)
=head2 startup
$prefork->startup;
This method starts preforked process (manager and spirits) and wait for L</"MANAGER SIGNALS">
=head2 tick
my $ok = $g->tick;
my $ok = $g->tick(1); # marks the finished status
This is B<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
=head1 MANAGER SIGNALS
The manager process can be controlled at runtime with the following signals
=head2 INT, TERM
Shut down server immediately
=head2 QUIT
Shut down server gracefully
=head2 TTIN
Increase spirit pool by one
=head2 TTOU
Decrease spirit pool by one
=head1 SPIRIT SIGNALS
The spirit processes can be controlled at runtime with the following signals
=head2 QUIT
Stop spirit gracefully
=head1 HOOKS
This class inherits all hooks from L<Acme::Ghost> and implements the following new ones
Any of the following methods may be implemented (overwriting) in your class
lib/Acme/Ghost/Prefork.pm view on Meta::CPAN
}
=head2 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");
}
=head2 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");
}
=head2 waitup
sub waitup {
lib/Acme/Ghost/Prefork.pm view on Meta::CPAN
sub waitup {
my $self = shift;
my $spirits = $prefork->{spirits};
$self->log->debug("Waiting for heartbeat messages from $spirits spirits");
}
=head2 spirit
B<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;
# . . .
}
=head1 EXAMPLES
=over 4
lib/Acme/Ghost/Prefork.pm view on Meta::CPAN
sub tick { # Spirit level
my $self = shift;
my $finished = shift || 0; # 0 - no finished; 1 - finished
$self->_heartbeat($finished);
return $self->ok;
}
# User hooks
sub finish { } # Emitted when the server shuts down
sub heartbeat { } # Emitted when a heartbeat message has been received from a spirit
sub reap { } # Emitted when a child process exited
sub spawn { } # Emitted when a spirit process is spawned
sub waitup { } # Emitted when the manager starts waiting for new heartbeat messages
sub spirit {
my $self = shift;
my $cb = $self->{spirit_cb};
return unless $cb;
return $self->$cb if ref($cb) eq 'CODE';
$self->log->error("Callback `spirit` is incorrect");
$self->tick(1);
}
lib/Acme/Ghost/Prefork.pm view on Meta::CPAN
}
}
sub _stop { # Manager level
my ($self, $graceful) = @_;
$self->log->debug(sprintf("> Received stop signal/command: %s",
$graceful ? 'graceful shutdown' : 'forced shutdown')) if DEBUG;
$self->finish($graceful);
$self->{finished} = 1;
$self->{gracefully_stop} = $graceful ? 1 : 0;
}
sub _stopped { # Manager level (Calls when a child process exited)
my $self = shift;
my $pid = shift;
$self->log->debug(sprintf("> Reap %s", $pid)) if DEBUG;
$self->reap($pid);
return unless my $w = delete $self->{pool}{$pid};
$self->log->info("Spirit $pid stopped");
unless ($w->{healthy}) {
$self->log->error("Spirit $pid stopped too early, shutting down");
$self->_stop;
t/02-daemon.t view on Meta::CPAN
# Set debug mode
$ENV{ACME_GHOST_DEBUG} //= 0;
my $g = Acme::Ghost->new(
logfile => 'daemon.log',
pidfile => 'daemon.pid',
);
#note explain $ghost;
ok !$g->is_daemonized, "Is not daemonized";
is $g->pid, 0, "No PID in ghost process";
#note $g->pid;
done_testing;
__END__
ACME_GHOST_DEBUG=1 prove -lv t/02-daemon.t
tail -f daemon.log | bell -s mush -v 36000 | ccze -A -p syslog
t/03-filepid.t view on Meta::CPAN
#
#########################################################################
use strict;
use Test::More;
use_ok qw/Acme::Ghost::FilePid/;
# Regular mode
{
my $fp = Acme::Ghost::FilePid->new(file => "test03.pid");
is $fp->pid, $$, 'current process by default';
ok $fp->save, 'writing file';
is $fp->running, $$, 'we are running';
ok $fp->remove, 'deleted file';
#note explain $fp;
}
# Autoremove mode
{
my $fp = Acme::Ghost::FilePid->new(file => "test03.tmp", autoremove => 1);
ok $fp->save, 'writing file';
t/03-filepid.t view on Meta::CPAN
}
# Fork mode
my $file = 'child03.tmp';
unlink $file if -e $file;
if (my $child = fork) { # Parent PID
sleep 1;
my $p = Acme::Ghost::FilePid->new(file => $file, autoremove => 1);
note sprintf "Parent PID: %s; Parent Owner: %s", $p->pid, $p->owner;
$p->save unless $p->running;
ok $p->running, 'child process is running';
#note explain $p;
waitpid $child, 0;
done_testing;
} else { # child process
my $p = Acme::Ghost::FilePid->new(file => $file, autoremove => 1); # hope for the best
unless ($p->running) {
$p->save;
note sprintf "Start child process (Child PID: %s; Child Owner: %s)", $p->pid, $p->owner;
sleep 3;
note sprintf "Finish child process (Child PID: %s; Child Owner: %s)", $p->pid, $p->owner;
}
#note 'parent is running' if $p->running;
}
__END__
prove -lv t/03-filepid.t