App-karr
view release on metacpan or search on metacpan
lib/App/karr/Foundation/Runner.pm view on Meta::CPAN
unless (@ready) {
# Spurious wakeup (signal) or genuine deadline. SIGALRM would have set
# the flag, but the deadline could also be reached by wall clock if a
# signal reset the alarm â check both and end the loop either way.
next unless $max_runtime > 0;
last if time - $started >= $max_runtime;
next;
}
my $chunk;
my $n = sysread( $reader, $chunk, 65536 );
last if !defined $n; # read error (or SIGALRM closing the fd)
last if $n == 0; # EOF â the command closed its output
print {$log_fh} $chunk;
if ($stream_terms) {
$pending .= $chunk;
print Encode::decode( 'UTF-8', $pending, Encode::FB_QUIET );
}
$output .= $chunk;
}
# Disarm the alarm before reap: a waitpid that takes longer than max_runtime
# would otherwise be cut short by SIGALRM (no handler anymore â the default
# action is to die, and Foundation is the parent). $max_runtime == 0 already
# never armed.
alarm 0;
$SIG{ALRM} = 'DEFAULT' if $max_runtime > 0;
my $exit_code;
if ($timed_out) {
my $elapsed = time - $started;
# The one call that has to happen here rather than after the kill: it is the
# only record of why the agent was stopped, and the kill/waitpid pair below
# can block for as long as the child stays unkillable. So it runs
# best-effort â a log the OS took away mid-run (#147) must not cost us the
# SIGTERM/SIGKILL and the reap, which are all that stop a hung agent. The
# failure is reported once the child is safely gone, and the END line below
# raises it for real if the log is still unwritable by then.
my $log_err;
eval {
$self->foundation->_append_log( $repo,
"TIMEOUT after ${elapsed}s \x{2014} sending SIGTERM to $pid (group -$pid)" );
1;
} or $log_err = clean_error($@);
# Negative pid = process group (kill(2) group semantics, #148). The shell,
# the agent, any grandchildren the agent backgrounded, all receive the
# signal. SIGTERM is catchable, so we wait up to 2s before escalating.
kill 'TERM', -$pid;
my $deadline = time + 2;
while ( time < $deadline ) {
last if kill( 0, $pid ) == 0;
select undef, undef, undef, 0.05;
}
kill 'KILL', -$pid;
waitpid( $pid, 0 );
warn "karr-foundation: cannot write $log_file: $log_err\n" if $log_err;
# 128 + SIGTERM(15) = 143 â same convention as shells, distinct from a
# clean non-zero exit, and surfaces in cooldown/last_error so an agent
# that exceeded max_runtime triggers the backoff (#164 / #161).
$exit_code = 128 + SIGTERM;
} else {
# The child may still be alive after the loop ended on EOF â a command
# whose stdout is closed while it keeps running (the classic
# `exec >/dev/null 2>&1; sleep N`, #161). Reap it with a wait loop that
# checks the wall-clock deadline: if the loop ended on EOF before
# max_runtime expired, this blocks until the child exits on its own or
# until the deadline arrives and we kill it via the timed_out path. The
# loop uses WNOHANG to keep checking; the deadline path is identical to
# the SIGALRM path above.
my $deadline;
if ( $max_runtime > 0 ) {
$deadline = $started + $max_runtime;
while (1) {
my $w = waitpid( $pid, WNOHANG );
last if $w > 0 || $w < 0;
if ( time >= $deadline ) {
$timed_out = 1;
kill 'TERM', -$pid;
my $term_deadline = time + 2;
while ( time < $term_deadline ) {
last if kill( 0, $pid ) == 0;
select undef, undef, undef, 0.05;
}
kill 'KILL', -$pid;
waitpid( $pid, 0 );
last;
}
select undef, undef, undef, 0.05;
}
} else {
waitpid( $pid, 0 );
}
$exit_code = _classify_exit($?);
$exit_code = 128 + SIGTERM if $timed_out && $exit_code == 0;
}
close $reader if defined fileno $reader;
close $log_fh;
# Clear the live-agent handle: the SIGTERM handler must not see this agent
# after we have reaped it. The next iteration of the drain (or the next
# repo) installs its own.
$self->foundation->_live_agent( undef );
my $elapsed = time - $started;
$self->foundation->_append_log( $repo, "END elapsed=${elapsed}s exit=$exit_code" );
return ( $exit_code, $output );
}
# Translate the raw $? from waitpid(2) into the exit code the drain sees. A
# child that exited normally: the high 8 bits are the status. A child that
# died from a signal: the low 7 bits are the signal number and the high 8
# bits are 0 â `$? >> 8` was 0 here, which is the bug #164 pins: the runner
# reported the OOM-killed / SIGTERM'd / SIGSEGV'd agent as exit 0, the drain
# read it as a clean run, and the cooldown that was supposed to catch a
# machine-killing agent never engaged. Surface the signal as 128 + signum so
# it is distinguishable from any real exit code (shells do the same), and
# fall through to the normal high-bits path otherwise.
#
# Accept both forms: the runner calls this as a function
# (`_classify_exit($?)`) and tests call it as a method (`$r->_classify_exit($?)`).
# `use Moo;` turns every sub in the package into a method, so the method
( run in 1.651 second using v1.01-cache-2.11-cpan-14f38c9f855 )