Developer-Dashboard

 view release on metacpan or  search on metacpan

lib/Developer/Dashboard/PageRuntime.pm  view on Meta::CPAN

    }
    my $ready_fileno  = fileno($fh);
    my $stdout_fileno = fileno($stdout);
    if ( defined $ready_fileno && defined $stdout_fileno && $ready_fileno == $stdout_fileno ) {    # uncoverable condition left
        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, $process_group, $status_ref)
# Stops one saved-Ajax worker and its owned POSIX process group after stream
# cancellation or writer failure, retaining direct-pid behavior on Windows.
# Input: child process id integer, optional positive POSIX process-group id, and
# an optional scalar reference that receives the worker wait status when the
# worker exited inside the grace window.
# Output: true value.
sub _terminate_saved_ajax_process {
    my ( $self, $pid, $process_group, $status_ref ) = @_;
    return 1 if !$pid;
    my $owns_group = !is_windows() && defined $process_group && $process_group =~ /^\d+$/ && $process_group > 0;
    return 1 if !$owns_group && !kill 0, $pid;
    kill 15, -$process_group if $owns_group;
    kill 15, $pid;
    my $drained = $self->_await_saved_ajax_exit( $pid, ( $owns_group ? $process_group : undef ), $status_ref );
    kill 9, -$process_group if $owns_group && !$drained;

    # Only signal the worker again when the window really expired: a drained
    # wait has already reaped it, and a reaped pid can be reissued by the OS.
    kill 9, $pid if !$drained && kill 0, $pid;
    return 1;
}

# _await_saved_ajax_exit($pid, $process_group, $status_ref)
# Waits out the SIGTERM grace window after a saved-Ajax worker was signalled,
# reaping the worker the moment it exits so its own TERM handler is never cut
# short and the caller does not block on a second wait. Elapsed wall-clock time
# bounds the wait, so the escalation is deterministic instead of depending on a
# fixed number of poll iterations.
# Input: worker pid, owned POSIX process-group id or undef for direct-pid
# cleanup, and an optional scalar reference that receives the reaped wait status.
# Output: true when the worker and any owned group went away inside the window,
# false when the window expired with something still alive.
sub _await_saved_ajax_exit {
    my ( $self, $pid, $process_group, $status_ref ) = @_;
    my $deadline = Time::HiRes::time() + $SAVED_AJAX_TERM_GRACE_SECONDS;
    my $reaped   = 0;
    while (1) {
        if ( !$reaped ) {
            my ( $exited, $status ) = $self->_saved_ajax_child_exited($pid);
            if ($exited) {
                $reaped = 1;
                ${$status_ref} = $status if ref($status_ref) eq 'SCALAR';
            }
        }

        # A reaped worker can still have live descendants in the group it led,
        # and those descendants are exactly what the grace window exists for.
        my $worker_gone = $reaped || !kill 0, $pid;
        my $group_alive = defined $process_group && kill 0, -$process_group;
        return 1 if $worker_gone && !$group_alive;
        return 0 if Time::HiRes::time() >= $deadline;
        Time::HiRes::sleep($SAVED_AJAX_TERM_POLL_SECONDS);
    }
}

# _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.
sub _saved_ajax_command {
    my ( $self, %args ) = @_;
    my $path = $args{path} || die 'Missing saved ajax file path';
    return ( $^X, '-MDeveloper::Dashboard::PageRuntime', '-e', 'Developer::Dashboard::PageRuntime->_run_saved_ajax_perl_file(shift @ARGV)', $path ) if $path =~ /\.pl\z/i;
    return command_argv_for_path($path) if $path =~ /\.(?:ps1|cmd|bat|sh|bash)\z/i;
    return ( command_in_path('python3') || command_in_path('python') || 'python3', $path ) if $path =~ /\.py\z/i;
    open my $fh, '<', $path or die "Unable to read saved ajax file $path: $!";
    my $first_line = <$fh>;
    close $fh;



( run in 1.426 second using v1.01-cache-2.11-cpan-4ab04211f4c )