Ereshkigal
view release on metacpan or search on metacpan
lib/Ereshkigal.pm view on Meta::CPAN
# only ever wording.
#
# Returns nothing meaningful, an empty return.
#
# # not called directly... armed per child when it is spawned
# $kernel->sig_child( $wheel->PID, 'kur_reaped' );
sub _poe_kur_reaped {
my ( $self, $kernel, $pid, $exit ) = @_[ OBJECT, KERNEL, ARG1, ARG2 ];
my $name = delete( $self->{pid_to_kur}{$pid} );
if ( !defined($name) ) {
return;
}
my $entry = $self->{kurs}{$name};
if ( defined($entry) && defined( $entry->{wheel} ) ) {
delete( $self->{wheel_to_kur}{ $entry->{wheel}->ID } );
$entry->{wheel} = undef;
$entry->{pid} = undef;
}
# the raw wait status packs the signal that killed it into the low seven
# bits and the exit code into the next eight, so decode both rather than
# shifting blindly... a kur killed by signal 9 and one that exited 9 of
# its own accord are not the same event and should not read alike
my $signal = $exit & 127;
my $how = $signal ? 'was killed by signal ' . $signal : 'exited with ' . ( $exit >> 8 );
log_drek( 'info', 'kur "' . $name . '" PID ' . $pid . ' ' . $how );
if ( $self->{shutting_down} || !defined($entry) || !$entry->{enabled} ) {
return;
}
# it ran long enough to be considered to have started fine, so reset the backoff
if ( defined( $entry->{spawned} ) && ( time - $entry->{spawned} ) > 60 ) {
$entry->{delay} = 1;
}
my $delay = $entry->{delay};
$entry->{delay} = $delay * 2 > 60 ? 60 : $delay * 2;
$entry->{restarts}++;
# whether a kur comes back is the manager's call and only the manager's...
# a kur does not get a vote in it by choosing how it exits, so anything
# the manager did not itself ask for is restarted, tidily as it may have
# gone. The stops that were asked for never reach here, stop_all having
# set shutting_down and remove_kur having cleared enabled before either
# takes a kur down, so what is left is a kur that went away on its own.
# That is an error whatever status it managed to exit with, and is logged
# as one... the status only changes the wording, never the decision
log_drek( 'err', 'kur "' . $name . '" ' . $how . ', restarting in ' . $delay . ' seconds' );
$kernel->delay_set( 'restart_kur', $delay, $name );
return;
} ## end sub _poe_kur_reaped
# The manager session's remove_kur handler, which stops one kur and drops it
# from the registry for good. The actual removal has to happen in the manager
# session as destroying a POE::Wheel::Run from within another session leaves
# its watchers behind, keeping the manager session alive forever... which is
# why _cmd_remove_kur only marks the entry disabled and posts here rather
# than doing the work itself.
#
# A running kur is asked to stop over its own socket, so it checkpoints its
# tablets and tears its firewall setup down properly. Only if that fails is
# it sent a TERM, which gets the process gone but leaves whatever it was
# holding in the firewall. Either way the entry is then unwired... its wheel
# dropped from wheel_to_kur, its PID from pid_to_kur, and the registry entry
# deleted.
#
# Those two lookups are cleaned here rather than being left to the reap
# handler because the reap handler only cleans them while the registry entry
# is still around, and it is about to not be... without this every add and
# remove cycle would leak a wheel_to_kur entry.
#
# POE calling convention... invoked as an object state, and note it does not
# take KERNEL...
#
# $_[OBJECT] :: This Ereshkigal instance.
# $_[ARG0] :: The kur instance name to remove, as a plain string. A
# name that is no longer registered returns immediately, so
# a doubled post is harmless.
#
# Returns nothing meaningful, an empty return.
#
# # from _cmd_remove_kur, after marking the entry disabled
# $poe_kernel->post( 'ereshkigal_manager', 'remove_kur', $name );
sub _poe_remove_kur {
my ( $self, $name ) = @_[ OBJECT, ARG0 ];
my $entry = $self->{kurs}{$name};
if ( !defined($entry) ) {
return;
}
if ( defined( $entry->{pid} ) ) {
eval { $self->_kur_client($name)->call_ok('stop'); };
if ($@) {
log_drek( 'err', 'stopping kur "' . $name . '" via it\'s socket failed, sending TERM... ' . $@ );
if ( defined( $entry->{wheel} ) ) {
$entry->{wheel}->kill('TERM');
}
}
}
# the reaped handler only cleans these up while the registry entry is
# still around, which it is about to not be, so they are cleaned here...
# otherwise every add/remove cycle leaks a wheel_to_kur entry
if ( defined( $entry->{wheel} ) ) {
delete( $self->{wheel_to_kur}{ $entry->{wheel}->ID } );
}
if ( defined( $entry->{pid} ) ) {
delete( $self->{pid_to_kur}{ $entry->{pid} } );
}
delete( $self->{kurs}{$name} );
log_drek( 'info', 'removed kur "' . $name . '"' );
return;
lib/Ereshkigal.pm view on Meta::CPAN
# my $result = $self->_cmd_add_kur(
# {
# 'args' => {
# 'name' => 'dns',
# 'opts' => { 'backend' => 'pf', 'ports' => ['53'] },
# }
# }
# );
# # { added => 'dns' }
sub _cmd_add_kur {
my ( $self, $request ) = @_;
my $args = $request->{args};
if ( !defined($args) || !defined( $args->{name} ) ) {
die('args.name must be the name for the new kur instance');
}
my $name = $args->{name};
if ( defined( $self->{kurs}{$name} ) ) {
die( 'The kur instance "' . $name . '" already exists' );
}
$self->_check_kur_def( $name, $args->{opts}, 0 );
if ( defined( $args->{opts}{fan_out} ) ) {
$self->_check_fan_out_members( $name, $args->{opts}, 0 );
}
$self->{kurs}{$name} = {
'opts' => $args->{opts},
'wheel' => undef,
'pid' => undef,
'restarts' => 0,
'delay' => 1,
'enabled' => 1,
'spawned' => undef,
};
$poe_kernel->post( 'ereshkigal_manager', 'spawn_kur', $name );
log_drek( 'info', 'added kur "' . $name . '"' );
return { 'added' => $name };
} ## end sub _cmd_add_kur
# Handles the remove_kur command... refuses while args.name is still a
# fan_out member, otherwise disables it and has the manager session stop and
# drop it.
#
# The gate check is why this is not simply a post. Config load refuses a
# fan_out naming an unknown kur, so removal cannot be allowed to create that
# same dangling state at runtime... every registered kur is scanned for gates
# naming this one, and if any do the removal is refused naming them. Remove
# the gate first, or the member stays.
#
# Setting enabled to 0 before posting matters as well... it is what stops the
# reap handler restarting the kur when the stop it is about to be sent
# actually kills it.
#
# The real work is left to the manager session, as a POE::Wheel::Run must be
# destroyed in the session watching it or its watchers outlive it and keep
# the session alive forever.
#
# The config file is not rewritten, so a kur defined there returns at the
# next manager start.
#
# Note this takes no context and authorizes nothing of its own... the
# dispatch entry has already run _authorize with no kur names.
#
# Args...
#
# $request :: Required. The decoded request hash ref. Must carry args
# holding a name key naming the kur to remove, as a plain
# string.
#
# Returns a hash ref of { removed => name } once it is disabled and the
# removal posted. As with add_kur that is a promise the manager will do it,
# not that the process is already gone.
#
# Dies with a plain string when args.name is missing, names no registered
# kur, or is still a member of one or more gates, which names them.
#
# my $result = $self->_cmd_remove_kur( { 'args' => { 'name' => 'dns' } } );
# # { removed => 'dns' }
sub _cmd_remove_kur {
my ( $self, $request ) = @_;
my $args = $request->{args};
if ( !defined($args) || !defined( $args->{name} ) ) {
die('args.name must be the name of a kur instance');
}
my $name = $args->{name};
my $entry = $self->{kurs}{$name};
if ( !defined($entry) ) {
die( 'No such kur instance, "' . $name . '"' );
}
# config load refuses a fan_out naming an unknown kur, so removal cannot
# be allowed to create that same dangling state at runtime... remove the
# gate first, or the member stays
my @gates_using;
foreach my $other_name ( sort( keys( %{ $self->{kurs} } ) ) ) {
my $fan_out = $self->{kurs}{$other_name}{opts}{fan_out};
if ( defined($fan_out) && grep { $_ eq $name } @{$fan_out} ) {
push( @gates_using, $other_name );
}
}
if (@gates_using) {
die( 'The kur "'
. $name
. '" is a fan_out member of "'
. join( '", "', @gates_using )
. '"... remove the fan_out kur first' );
}
$entry->{enabled} = 0;
# the actual stop and removal happens in the manager session given the
# wheel has to be destroyed there
$poe_kernel->post( 'ereshkigal_manager', 'remove_kur', $name );
( run in 1.388 second using v1.01-cache-2.11-cpan-14f38c9f855 )