DBIx-QuickDB
view release on metacpan or search on metacpan
lib/DBIx/QuickDB/Watcher.pm view on Meta::CPAN
package DBIx::QuickDB::Watcher;
use strict;
use warnings;
our $VERSION = '0.000065';
use Carp qw/croak/;
use Cwd qw/abs_path/;
use Errno qw/ESRCH/;
use POSIX qw/:sys_wait_h/;
use Time::HiRes qw/sleep time/;
use Scalar::Util qw/weaken/;
use File::Path qw/remove_tree/;
# Resolved at load time, not at spawn time: $INC{} can be relative, and an
# application may chdir before starting a database, which would resolve it
# against the wrong directory and hand the watcher a different Watcher.pm.
my $LOADED_FROM = do {
my $file = $INC{'DBIx/QuickDB/Watcher.pm'};
my $dir;
if (defined($file) && length($file)) {
my $root = $file;
$root =~ s{[\\/]?\QDBIx/QuickDB/Watcher.pm\E$}{};
$dir = abs_path(length($root) ? $root : '.');
}
(defined($dir) && length($dir) && -d $dir) ? $dir : undef;
};
use DBIx::QuickDB::Util::HashBase qw{
<db <args
<server_pid
<watcher_pid
<master_pid
<log_file
<data_dir
<stopped
<eliminated
<detached
<delete_data
<server_reaped
};
# The watch loop records its reap inside the data dir, which disposable teardown
# then deletes -- so the proof is gone by the time wait() wants it. Latch it
# before signalling, while the dir is still there.
sub _latch_server_reaped {
my $self = shift;
return if $self->{+SERVER_REAPED};
my $dir = $self->{+DATA_DIR} or return;
$self->{+SERVER_REAPED} = 1 if -f $self->server_exit_status_file($dir);
return;
}
sub init {
my $self = shift;
$self->{+MASTER_PID} ||= $$;
# Captured up front: wait() needs it after teardown, when the DB reference
# has been weakened and may already be gone.
$self->{+DATA_DIR} //= $self->{+DB}->dir;
$self->{+LOG_FILE} = $self->{+DB}->gen_log;
$self->start();
weaken($self->{+DB}) if $self->{+MASTER_PID} == $$;
}
sub start {
my $self = shift;
return if $self->{+SERVER_PID};
my ($rh, $wh);
pipe($rh, $wh) or die "Could not open pipe: $!";
my $pid = fork;
die "Could not fork: $!" unless defined $pid;
if ($pid) {
close($wh);
waitpid($pid, 0);
chomp($self->{+WATCHER_PID} = <$rh>);
chomp($self->{+SERVER_PID} = <$rh>);
close($rh);
die "Did not get watcher pid!" unless $self->{+WATCHER_PID};
die "Did not get server pid!" unless $self->{+SERVER_PID};
return;
}
close($rh);
POSIX::setsid();
setpgrp(0, 0);
$pid = fork;
die "Could not fork: $!" unless defined $pid;
POSIX::_exit(0) if $pid;
$wh->autoflush(1);
print $wh "$$\n";
# In watcher now
eval { $self->watch($wh); 1 } or POSIX::_exit(1);
POSIX::_exit(0);
}
# -I args for the re-exec'd watcher: the directory this module was loaded from,
# then the rest of @INC. The watcher must load the same Watcher.pm its parent
# did, so the parent's own resolution is forwarded rather than guessed.
#
# -I rather than PERL5LIB, which the MySQLCom and MariaDB drivers deliberately
# blank so vendor scripts cannot reach application modules.
#
# $root is only passed by tests; it defaults to the load-time capture.
sub _watcher_inc_args {
my ($root) = @_ ? @_ : ($LOADED_FROM);
my @dirs;
push @dirs => $root if defined $root;
# @INC hooks (refs) cannot be expressed as -I.
push @dirs => grep { !ref($_) && defined($_) && length($_) } @INC;
# Absolutizing the rest is hygiene only -- this runs in the forked watcher,
# which shares the caller's cwd, so a relative entry would resolve the same
# either way. $LOADED_FROM above is what actually pins the module.
my (@out, %seen);
for my $dir (@dirs) {
( run in 1.045 second using v1.01-cache-2.11-cpan-364913b4093 )