DBIx-QuickDB

 view release on metacpan or  search on metacpan

lib/DBIx/QuickDB/Driver.pm  view on Meta::CPAN

    }

    return "exit value " . ($status >> 8);
}

sub started {
    my $self = shift;

    my $socket = $self->socket;
    return 1 if $self->{+WATCHER} || -S $socket;
    return 0;
}

sub start {
    my $self = shift;
    my @args = @_;

    my $dir = $self->{+DIR};
    my $socket = $self->socket;

    return if $self->{+WATCHER} || -S $socket;

    # Drop a previous server's recorded crash, or a legitimate restart would
    # fail instantly for it. Before the watcher exists, so nothing is writing.
    unlink(DBIx::QuickDB::Watcher->server_exit_status_file($dir));

    my $watcher = $self->{+WATCHER} = DBIx::QuickDB::Watcher->new(db => $self, args => \@args);

    # Defaults to 10s; tunable via QDB_START_TIMEOUT for slow hosts that need
    # longer to bring a server up (e.g. a clone doing crash recovery).
    my $timeout = env_timeout(QDB_START_TIMEOUT => 10);

    my $start = time;
    until (-S $socket) {
        my $waited = time - $start;

        # A server that aborts during startup is dead in milliseconds; there is
        # nothing left to wait for. Re-check the socket first so one that came
        # up and then exited still counts as started.
        if (my $status = $self->_server_exit_status) {
            last if -S $socket;

            my $launch_log = $self->_read_file($watcher->log_file);
            my $error_log  = $self->read_error_log;

            $watcher->eliminate();

            my $msg = "Server exited during startup after ${\ sprintf('%.2f', $waited)}s ($status).";
            $msg .= "\n=== server launch log ===\n$launch_log" if length $launch_log;
            $msg .= "\n=== error log ===\n$error_log"          if length $error_log;
            confess $msg;
        }

        if ($waited > $timeout) {
            # Capture diagnostics BEFORE eliminate() removes the data dir (which
            # holds both the error log and the watcher's launch log). The server
            # process's own stdout/stderr go to the watcher's log_file, not the
            # driver's error_log, so a server that died (or never launched)
            # before writing error_log leaves error_log showing only inherited
            # template history -- the real failure is in the launch log. Also
            # report whether the server pid is still alive: "not running" points
            # at a launch/early-exit failure, "alive" at a slow or hung startup.
            # Recorded status before kill(0), which succeeds on a zombie and so
            # mislabelled a crashed server as a slow start.
            my $spid   = $watcher->server_pid;
            my $status = $self->_server_exit_status;
            my $alive
                = $status                  ? "not running ($status)"
                : ($spid && kill(0, $spid)) ? "alive (pid $spid)"
                :                             "not running";
            my $error_log  = $self->read_error_log;
            my $launch_log = $self->_read_file($watcher->log_file);

            $watcher->eliminate();

            my $msg = "Timed out waiting for server to start after ${timeout}s; server process is $alive.";
            $msg .= "\n=== server launch log ===\n$launch_log" if length $launch_log;
            $msg .= "\n=== error log ===\n$error_log"          if length $error_log;
            confess $msg;
        }

        sleep 0.01;
    }

    return;
}

# Disconnect this process's DBI handles to this instance's server. Must run
# before any teardown signal: a connected client stalls a graceful shutdown
# (PostgreSQL's smart shutdown waits for clients INDEFINITELY, mysqld several
# seconds), pinning the stop at the full QDB_STOP_GRACE before the watcher
# escalates. It also keeps this process from retaining broken handles that
# later report "server has gone away". Skipped during global destruction:
# DBI's own structures are already being torn down (DBI->visit_handles dies
# with "Can't call method visit_child_handles on an undefined value"), and
# retaining broken handles no longer matters because the process is exiting.
#
# Skipped entirely when the no_disconnect_handles attribute is set: some callers
# (e.g. a test framework running teardown queries) hold their OWN active handles
# to this same db, and ripping those out mid-query surfaces as "Lost connection
# ... during query". MySQL/MariaDB shut down fine with clients still connected,
# so such callers can opt out. PostgreSQL's smart shutdown waits for clients
# indefinitely, so opting out there risks a stalled stop -- caller's choice.
sub _disconnect_handles {
    my $self = shift;

    return if $self->{+NO_DISCONNECT_HANDLES};

    disconnect_dbi_handles($self->{+DIR});

    return;
}

sub stop {
    my $self = shift;
    my %params = @_;

    my $watcher = delete $self->{+WATCHER} or return;

    # Flush a durable checkpoint while the server is still running. If shutdown
    # is slow enough to be SIGKILLed, this ensures the on-disk state is already
    # consistent so a later clone does not crash-recover and jump SERIAL
    # sequences forward (PostgreSQL SEQ_LOG_VALS=32), corrupting the clone.
    # No-op for drivers that do not need it.
    $self->checkpoint;

    $self->_disconnect_handles;

    $watcher->stop();

    unless ($params{no_wait}) {
        # Blocks until the watcher exits, having done everything it could to
        # stop the server. If the server somehow survived, wait() spends its
        # timeout confirming that and then warns. Do not poll the stored server
        # pid instead: once the watcher is gone that pid may have been recycled.
        $watcher->wait();



( run in 1.382 second using v1.01-cache-2.11-cpan-bbc515a03b3 )