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 )