Developer-Dashboard
view release on metacpan or search on metacpan
lib/Developer/Dashboard/PageRuntime.pm view on Meta::CPAN
$self->_cleanup_saved_ajax_temp_files(@temp_files);
die $@;
}
close $stdin;
my $select = IO::Select->new( $stdout, $stderr );
my $stream_error = '';
my $disconnected = 0;
eval {
while (1) {
my @ready = $select->can_read(0.25);
last if !@ready && !$select->count;
for my $fh (@ready) {
my $continued = $self->_drain_saved_ajax_ready_handle(
fh => $fh,
path => $path,
select => $select,
stdout => $stdout,
stdout_writer => $stdout_writer,
stderr_writer => $stderr_writer,
);
if ( !$continued ) {
$disconnected = 1;
die "__DD_AJAX_STREAM_DISCONNECTED__\n";
}
}
}
1;
} or do {
$stream_error = $@ || "Saved ajax stream failed\n";
};
$self->_close_saved_ajax_streams( $select, $stdout, $stderr );
my $fatal_error = '';
if ($disconnected) {
$self->_terminate_saved_ajax_process($pid);
}
elsif ( $stream_error ne '' ) {
$self->_terminate_saved_ajax_process($pid);
$fatal_error = $stream_error if !$self->_looks_like_stream_disconnect_error($stream_error);
}
waitpid( $pid, 0 );
$self->_cleanup_saved_ajax_temp_files(@temp_files);
die $fatal_error if $fatal_error ne '';
return {
disconnected => $disconnected ? 1 : 0,
exit_code => $? >> 8,
status => $?,
};
}
# _noop_writer(@parts)
# Accepts streamed output chunks when the caller does not need them.
# Input: zero or more ignored chunk parts.
# Output: empty string.
sub _noop_writer { return '' }
# _drain_saved_ajax_ready_handle(%args)
# Reads one ready saved-Ajax process pipe handle and forwards the chunk or error to the right writer.
# Input: ready fh, active select set, stdout fh, saved file path, and writer callbacks.
# Output: true value when streaming should continue, otherwise false when the client disconnected.
sub _drain_saved_ajax_ready_handle {
my ( $self, %args ) = @_;
my $fh = $args{fh} || die 'Missing ready handle';
my $path = $args{path} || '';
my $select = $args{select} || die 'Missing select set';
my $stdout = $args{stdout} || die 'Missing stdout handle';
my $stdout_writer = $args{stdout_writer} || \&_noop_writer;
my $stderr_writer = $args{stderr_writer} || \&_noop_writer;
my $chunk = '';
my $bytes = $self->_stream_sysread( $fh, \$chunk );
if ( !defined $bytes ) {
return 1 if $!{EINTR};
$stderr_writer->("Unable to read ajax stream for $path: $!\n");
$select->remove($fh);
close $fh;
return 1;
}
if ( $bytes == 0 ) {
$select->remove($fh);
close $fh;
return 1;
}
my $ready_fileno = fileno($fh);
my $stdout_fileno = fileno($stdout);
if ( defined $ready_fileno && defined $stdout_fileno && $ready_fileno == $stdout_fileno ) {
my $continued = $stdout_writer->($chunk);
return defined $continued ? $continued : 1;
}
my $continued = $stderr_writer->($chunk);
return defined $continued ? $continued : 1;
}
# _close_saved_ajax_streams($select, @handles)
# Closes the saved-Ajax select set and any remaining pipe handles after streaming stops.
# Input: IO::Select object plus zero or more pipe handles.
# Output: true value.
sub _close_saved_ajax_streams {
my ( $self, $select, @handles ) = @_;
if ( $select && eval { $select->can('handles') } ) {
for my $fh ( $select->handles ) {
next if !defined fileno($fh);
$select->remove($fh);
close $fh;
}
}
for my $fh (@handles) {
next if !defined $fh;
next if !defined fileno($fh);
close $fh;
}
return 1;
}
# _terminate_saved_ajax_process($pid)
# Stops one saved-Ajax worker process after stream cancellation or writer failure.
# Input: child process id integer.
# Output: true value.
sub _terminate_saved_ajax_process {
my ( $self, $pid ) = @_;
return 1 if !$pid;
return 1 if !kill 0, $pid;
kill 15, $pid;
for ( 1 .. 20 ) {
return 1 if !kill 0, $pid;
sleep 0.05;
}
kill 9, $pid if kill 0, $pid;
return 1;
}
# _looks_like_stream_disconnect_error($error)
# Detects writer failures that mean the browser stream was closed and the worker should just be stopped.
# Input: raw exception text from one writer callback.
# Output: boolean true when the error matches a disconnect or closed-stream condition.
sub _looks_like_stream_disconnect_error {
my ( $self, $error ) = @_;
return 1 if !defined $error || $error eq '';
return 1 if $error =~ /^__DD_AJAX_STREAM_DISCONNECTED__/;
return $error =~ /(broken pipe|client disconnected|connection reset|stream closed|connection aborted|write failed|closed handle)/i ? 1 : 0;
}
# _stream_sysread($fh, $chunk_ref)
# Reads one chunk from a saved-Ajax process pipe.
# Input: fh and scalar reference that receives the read chunk.
# Output: byte count or undef on read error.
sub _stream_sysread {
my ( $self, $fh, $chunk_ref ) = @_;
return sysread( $fh, ${$chunk_ref}, 8192 );
}
# _saved_ajax_command(%args)
# Resolves the process command used to execute one saved Ajax file.
# Input: saved file path.
# Output: command list suitable for open3.
( run in 0.868 second using v1.01-cache-2.11-cpan-39bf76dae61 )