mirror of
https://github.com/percona/percona-toolkit.git
synced 2025-09-11 13:40:07 +00:00
Rewrite Daemon.pm: don't require an OptionParser, one public func, run(), that does the whole daemonize process or just check the PID file.
This commit is contained in:
275
bin/pt-agent
275
bin/pt-agent
@@ -4091,59 +4091,86 @@ package Daemon;
|
||||
use strict;
|
||||
use warnings FATAL => 'all';
|
||||
use English qw(-no_match_vars);
|
||||
|
||||
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
|
||||
|
||||
use POSIX qw(setsid);
|
||||
use Fcntl qw(:DEFAULT);
|
||||
|
||||
sub new {
|
||||
my ($class, %args) = @_;
|
||||
foreach my $arg ( qw(o) ) {
|
||||
die "I need a $arg argument" unless $args{$arg};
|
||||
}
|
||||
my $o = $args{o};
|
||||
my $self = {
|
||||
o => $o,
|
||||
log_file => $o->has('log') ? $o->get('log') : undef,
|
||||
PID_file => $o->has('pid') ? $o->get('pid') : undef,
|
||||
log_file => $args{log_file},
|
||||
pid_file => $args{pid_file},
|
||||
daemonize => $args{daemonize},
|
||||
};
|
||||
|
||||
check_PID_file(undef, $self->{PID_file});
|
||||
|
||||
PTDEBUG && _d('Daemonized child will log to', $self->{log_file});
|
||||
return bless $self, $class;
|
||||
}
|
||||
|
||||
sub daemonize {
|
||||
my ( $self ) = @_;
|
||||
sub run {
|
||||
my ($self, %args) = @_;
|
||||
my $pid ||= $PID;
|
||||
my $pid_file ||= $self->{pid_file};
|
||||
my $log_file ||= $self->{log_file};
|
||||
|
||||
PTDEBUG && _d('About to fork and daemonize');
|
||||
defined (my $pid = fork()) or die "Cannot fork: $OS_ERROR";
|
||||
if ( $pid ) {
|
||||
PTDEBUG && _d('Parent PID', $PID, 'exiting after forking child PID',$pid);
|
||||
exit;
|
||||
if ( $self->{daemonize} ) {
|
||||
$self->_daemonize(
|
||||
pid => $pid,
|
||||
pid_file => $pid_file,
|
||||
log_file => $log_file,
|
||||
);
|
||||
}
|
||||
elsif ( $pid_file ) {
|
||||
$self->_make_pid_file(
|
||||
pid => $pid,
|
||||
pid_file => $pid_file,
|
||||
);
|
||||
$self->{pid_file_owner} = $pid;
|
||||
}
|
||||
else {
|
||||
PTDEBUG && _d('Neither --daemonize nor --pid was specified');
|
||||
}
|
||||
|
||||
PTDEBUG && _d('Daemonizing child PID', $PID);
|
||||
$self->{PID_owner} = $PID;
|
||||
$self->{child} = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
POSIX::setsid() or die "Cannot start a new session: $OS_ERROR";
|
||||
chdir '/' or die "Cannot chdir to /: $OS_ERROR";
|
||||
sub _daemonize {
|
||||
my ($self, %args) = @_;
|
||||
my $pid = $args{pid};
|
||||
my $pid_file = $args{pid_file};
|
||||
my $log_file = $args{log_file};
|
||||
|
||||
$self->_make_PID_file();
|
||||
PTDEBUG && _d('Daemonizing');
|
||||
|
||||
if ( $pid_file ) {
|
||||
eval {
|
||||
$self->_make_pid_file(
|
||||
pid => $pid, # parent's pid
|
||||
pid_file => $pid_file,
|
||||
);
|
||||
};
|
||||
if ( $EVAL_ERROR ) {
|
||||
die "Cannot daemonize: $EVAL_ERROR\n";
|
||||
}
|
||||
}
|
||||
|
||||
defined (my $child_pid = fork())
|
||||
or die "Cannot fork: $OS_ERROR";
|
||||
if ( $child_pid ) {
|
||||
PTDEBUG && _d('Forked child', $child_pid);
|
||||
exit 0;
|
||||
}
|
||||
|
||||
$OUTPUT_AUTOFLUSH = 1;
|
||||
|
||||
PTDEBUG && _d('Redirecting STDIN to /dev/null');
|
||||
close STDIN;
|
||||
open STDIN, '/dev/null'
|
||||
or die "Cannot reopen STDIN to /dev/null: $OS_ERROR";
|
||||
|
||||
if ( $self->{log_file} ) {
|
||||
PTDEBUG && _d('Redirecting STDOUT and STDERR to', $self->{log_file});
|
||||
if ( $log_file ) {
|
||||
PTDEBUG && _d('Redirecting STDOUT and STDERR to', $log_file);
|
||||
close STDOUT;
|
||||
open STDOUT, '>>', $self->{log_file}
|
||||
or die "Cannot open log file $self->{log_file}: $OS_ERROR";
|
||||
open STDOUT, '>>', $log_file
|
||||
or die "Cannot open log file $log_file: $OS_ERROR";
|
||||
|
||||
close STDERR;
|
||||
open STDERR, ">&STDOUT"
|
||||
@@ -4166,82 +4193,123 @@ sub daemonize {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PTDEBUG && _d('I am child', $PID);
|
||||
|
||||
if ( $pid_file ) {
|
||||
$self->_update_pid_file(
|
||||
pid => $PID, # child's pid
|
||||
pid_file => $pid_file,
|
||||
);
|
||||
$self->{pid_file_owner} = $PID;
|
||||
}
|
||||
|
||||
POSIX::setsid() or die "Cannot start a new session: $OS_ERROR";
|
||||
chdir '/' or die "Cannot chdir to /: $OS_ERROR";
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub check_PID_file {
|
||||
my ( $self, $file ) = @_;
|
||||
my $PID_file = $self ? $self->{PID_file} : $file;
|
||||
PTDEBUG && _d('Checking PID file', $PID_file);
|
||||
if ( $PID_file && -f $PID_file ) {
|
||||
my $pid;
|
||||
eval {
|
||||
chomp($pid = (slurp_file($PID_file) || ''));
|
||||
|
||||
sub _make_pid_file {
|
||||
my ($self, %args) = @_;
|
||||
my @required_args = qw(pid pid_file);
|
||||
foreach my $arg ( @required_args ) {
|
||||
die "I need a $arg argument" unless $args{$arg};
|
||||
};
|
||||
if ( $EVAL_ERROR ) {
|
||||
die "The PID file $PID_file already exists but it cannot be read: "
|
||||
. $EVAL_ERROR;
|
||||
my $pid = $args{pid};
|
||||
my $pid_file = $args{pid_file};
|
||||
|
||||
eval {
|
||||
sysopen(PID_FH, $pid_file, O_RDWR|O_CREAT|O_EXCL) or die $OS_ERROR;
|
||||
print PID_FH $PID, "\n";
|
||||
close PID_FH;
|
||||
};
|
||||
if ( my $e = $EVAL_ERROR ) {
|
||||
if ( $e =~ m/file exists/i ) {
|
||||
my $old_pid = $self->_check_pid_file(
|
||||
pid_file => $pid_file,
|
||||
);
|
||||
if ( $old_pid ) {
|
||||
warn "Overwriting PID file $pid_file because PID $old_pid "
|
||||
. "is not running.\n";
|
||||
}
|
||||
PTDEBUG && _d('PID file exists; it contains PID', $pid);
|
||||
$self->_update_pid_file(
|
||||
pid => $PID,
|
||||
pid_file => $pid_file
|
||||
);
|
||||
}
|
||||
else {
|
||||
die "Error creating PID file $pid_file: $e\n";
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub _check_pid_file {
|
||||
my ($self, %args) = @_;
|
||||
my @required_args = qw(pid_file);
|
||||
foreach my $arg ( @required_args ) {
|
||||
die "I need a $arg argument" unless $args{$arg};
|
||||
};
|
||||
my $pid_file = $args{pid_file};
|
||||
|
||||
PTDEBUG && _d('Checking if PID in', $pid_file, 'is running');
|
||||
|
||||
if ( ! -f $pid_file ) {
|
||||
PTDEBUG && _d('PID file', $pid_file, 'does not exist');
|
||||
return;
|
||||
}
|
||||
|
||||
open my $fh, '<', $pid_file
|
||||
or die "Error opening $pid_file: $OS_ERROR";
|
||||
my $pid = do { local $/; <$fh> };
|
||||
chomp($pid) if $pid;
|
||||
close $fh
|
||||
or die "Error closing $pid_file: $OS_ERROR";
|
||||
|
||||
if ( $pid ) {
|
||||
PTDEBUG && _d('Checking if PID', $pid, 'is running');
|
||||
my $pid_is_alive = kill 0, $pid;
|
||||
if ( $pid_is_alive ) {
|
||||
die "The PID file $PID_file already exists "
|
||||
. " and the PID that it contains, $pid, is running";
|
||||
}
|
||||
else {
|
||||
warn "Overwriting PID file $PID_file because the PID that it "
|
||||
. "contains, $pid, is not running";
|
||||
die "PID file $pid_file exists and PID $pid is running\n";
|
||||
}
|
||||
}
|
||||
else {
|
||||
die "The PID file $PID_file already exists but it does not "
|
||||
. "contain a PID";
|
||||
die "PID file $pid_file exists but it is empty. Remove the file "
|
||||
. "if the process is no longer running.\n";
|
||||
}
|
||||
|
||||
return $pid;
|
||||
}
|
||||
else {
|
||||
PTDEBUG && _d('No PID file');
|
||||
}
|
||||
|
||||
sub _update_pid_file {
|
||||
my ($self, %args) = @_;
|
||||
my @required_args = qw(pid pid_file);
|
||||
foreach my $arg ( @required_args ) {
|
||||
die "I need a $arg argument" unless $args{$arg};
|
||||
};
|
||||
my $pid = $args{pid};
|
||||
my $pid_file = $args{pid_file};
|
||||
|
||||
open my $fh, '>', $pid_file
|
||||
or die "Cannot open $pid_file: $OS_ERROR";
|
||||
print { $fh } $pid, "\n"
|
||||
or die "Cannot print to $pid_file: $OS_ERROR";
|
||||
close $fh
|
||||
or warn "Cannot close $pid_file: $OS_ERROR";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub make_PID_file {
|
||||
my ( $self ) = @_;
|
||||
if ( exists $self->{child} ) {
|
||||
die "Do not call Daemon::make_PID_file() for daemonized scripts";
|
||||
}
|
||||
$self->_make_PID_file();
|
||||
$self->{PID_owner} = $PID;
|
||||
return;
|
||||
}
|
||||
|
||||
sub _make_PID_file {
|
||||
my ( $self ) = @_;
|
||||
|
||||
my $PID_file = $self->{PID_file};
|
||||
if ( !$PID_file ) {
|
||||
PTDEBUG && _d('No PID file to create');
|
||||
return;
|
||||
}
|
||||
|
||||
$self->check_PID_file();
|
||||
|
||||
open my $PID_FH, '>', $PID_file
|
||||
or die "Cannot open PID file $PID_file: $OS_ERROR";
|
||||
print $PID_FH $PID
|
||||
or die "Cannot print to PID file $PID_file: $OS_ERROR";
|
||||
close $PID_FH
|
||||
or die "Cannot close PID file $PID_file: $OS_ERROR";
|
||||
|
||||
PTDEBUG && _d('Created PID file:', $self->{PID_file});
|
||||
return;
|
||||
}
|
||||
|
||||
sub _remove_PID_file {
|
||||
my ( $self ) = @_;
|
||||
if ( $self->{PID_file} && -f $self->{PID_file} ) {
|
||||
unlink $self->{PID_file}
|
||||
or warn "Cannot remove PID file $self->{PID_file}: $OS_ERROR";
|
||||
sub remove_pid_file {
|
||||
my ($self, $pid_file) = @_;
|
||||
$pid_file ||= $self->{pid_file};
|
||||
if ( $pid_file && -f $pid_file ) {
|
||||
unlink $self->{pid_file}
|
||||
or warn "Cannot remove PID file $pid_file: $OS_ERROR";
|
||||
PTDEBUG && _d('Removed PID file');
|
||||
}
|
||||
else {
|
||||
@@ -4253,16 +4321,11 @@ sub _remove_PID_file {
|
||||
sub DESTROY {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->_remove_PID_file() if ($self->{PID_owner} || 0) == $PID;
|
||||
|
||||
return;
|
||||
if ( ($self->{pid_file_owner} || 0) == $PID ) {
|
||||
$self->remove_pid_file();
|
||||
}
|
||||
|
||||
sub slurp_file {
|
||||
my ($file) = @_;
|
||||
return unless $file;
|
||||
open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
|
||||
return do { local $/; <$fh> };
|
||||
return;
|
||||
}
|
||||
|
||||
sub _d {
|
||||
@@ -4795,17 +4858,15 @@ sub main {
|
||||
# ########################################################################
|
||||
# Daemonize first so all output goes to the --log.
|
||||
# ########################################################################
|
||||
my $daemon;
|
||||
my $daemon = Daemon->new(
|
||||
daemonize => $o->get('daemonize'),
|
||||
pid_file => $o->get('pid'),
|
||||
log_file => $o->get('log'),
|
||||
);
|
||||
if ( !$o->get('send-data') ) {
|
||||
if ( $o->get('daemonize') ) {
|
||||
$daemon = new Daemon(o=>$o);
|
||||
$daemon->daemonize();
|
||||
$daemon->run();
|
||||
PTDEBUG && _d('I am a daemon now');
|
||||
}
|
||||
elsif ( $o->get('pid') ) {
|
||||
$daemon = new Daemon(o=>$o);
|
||||
$daemon->make_PID_file();
|
||||
}
|
||||
|
||||
# If we daemonized, the parent has already exited and we're the child.
|
||||
# We shared a copy of every Cxn with the parent, and the parent's copies
|
||||
# were destroyed but the dbhs were not disconnected because the parent
|
||||
|
303
lib/Daemon.pm
303
lib/Daemon.pm
@@ -1,4 +1,4 @@
|
||||
# This program is copyright 2008-2011 Percona Ireland Ltd.
|
||||
# This program is copyright 2008-2013 Percona Ireland Ltd.
|
||||
# Feedback and improvements are welcome.
|
||||
#
|
||||
# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
|
||||
@@ -17,59 +17,91 @@
|
||||
# ###########################################################################
|
||||
# Daemon package
|
||||
# ###########################################################################
|
||||
{
|
||||
# Package: Daemon
|
||||
# Daemon daemonizes the caller and handles daemon-related tasks like PID files.
|
||||
package Daemon;
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => 'all';
|
||||
use English qw(-no_match_vars);
|
||||
|
||||
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
|
||||
|
||||
use POSIX qw(setsid);
|
||||
use Fcntl qw(:DEFAULT);
|
||||
|
||||
# The required o arg is an OptionParser object.
|
||||
sub new {
|
||||
my ($class, %args) = @_;
|
||||
foreach my $arg ( qw(o) ) {
|
||||
die "I need a $arg argument" unless $args{$arg};
|
||||
}
|
||||
my $o = $args{o};
|
||||
my $self = {
|
||||
o => $o,
|
||||
log_file => $o->has('log') ? $o->get('log') : undef,
|
||||
PID_file => $o->has('pid') ? $o->get('pid') : undef,
|
||||
log_file => $args{log_file},
|
||||
pid_file => $args{pid_file},
|
||||
daemonize => $args{daemonize},
|
||||
};
|
||||
|
||||
# undef because we can't call like $self->check_PID_file() yet.
|
||||
check_PID_file(undef, $self->{PID_file});
|
||||
|
||||
PTDEBUG && _d('Daemonized child will log to', $self->{log_file});
|
||||
return bless $self, $class;
|
||||
}
|
||||
|
||||
sub daemonize {
|
||||
my ( $self ) = @_;
|
||||
sub run {
|
||||
my ($self, %args) = @_;
|
||||
my $pid ||= $PID;
|
||||
my $pid_file ||= $self->{pid_file};
|
||||
my $log_file ||= $self->{log_file};
|
||||
|
||||
PTDEBUG && _d('About to fork and daemonize');
|
||||
defined (my $pid = fork()) or die "Cannot fork: $OS_ERROR";
|
||||
if ( $pid ) {
|
||||
PTDEBUG && _d('Parent PID', $PID, 'exiting after forking child PID',$pid);
|
||||
exit;
|
||||
if ( $self->{daemonize} ) {
|
||||
$self->_daemonize(
|
||||
pid => $pid,
|
||||
pid_file => $pid_file,
|
||||
log_file => $log_file,
|
||||
);
|
||||
}
|
||||
elsif ( $pid_file ) {
|
||||
$self->_make_pid_file(
|
||||
pid => $pid,
|
||||
pid_file => $pid_file,
|
||||
);
|
||||
$self->{pid_file_owner} = $pid;
|
||||
}
|
||||
else {
|
||||
PTDEBUG && _d('Neither --daemonize nor --pid was specified');
|
||||
}
|
||||
|
||||
# I'm daemonized now.
|
||||
PTDEBUG && _d('Daemonizing child PID', $PID);
|
||||
$self->{PID_owner} = $PID;
|
||||
$self->{child} = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
POSIX::setsid() or die "Cannot start a new session: $OS_ERROR";
|
||||
chdir '/' or die "Cannot chdir to /: $OS_ERROR";
|
||||
sub _daemonize {
|
||||
my ($self, %args) = @_;
|
||||
my $pid = $args{pid};
|
||||
my $pid_file = $args{pid_file};
|
||||
my $log_file = $args{log_file};
|
||||
|
||||
$self->_make_PID_file();
|
||||
PTDEBUG && _d('Daemonizing');
|
||||
|
||||
$OUTPUT_AUTOFLUSH = 1;
|
||||
# First obtain the pid file or die trying. NOTE: we're still the parent
|
||||
# so the pid file will contain the parent's pid at first. This is done
|
||||
# to avoid a race condition between the parent checking for the pid file,
|
||||
# forking, and the child actually obtaining the pid file. This way, if
|
||||
# the parent obtains the pid file, the child is guaranteed to be the only
|
||||
# process running.
|
||||
if ( $pid_file ) {
|
||||
eval {
|
||||
$self->_make_pid_file(
|
||||
pid => $pid, # parent's pid
|
||||
pid_file => $pid_file,
|
||||
);
|
||||
};
|
||||
if ( $EVAL_ERROR ) {
|
||||
die "Cannot daemonize: $EVAL_ERROR\n";
|
||||
}
|
||||
}
|
||||
|
||||
# Fork, exit parent, continue as child process.
|
||||
defined (my $child_pid = fork())
|
||||
or die "Cannot fork: $OS_ERROR";
|
||||
if ( $child_pid ) {
|
||||
# I'm the parent.
|
||||
PTDEBUG && _d('Forked child', $child_pid);
|
||||
exit 0;
|
||||
}
|
||||
|
||||
# I'm the child. First, open the log file, if any. Do this first
|
||||
# so that all daemon/child output goes there.
|
||||
|
||||
# We used to only reopen STDIN to /dev/null if it's a tty because
|
||||
# otherwise it may be a pipe, in which case we didn't want to break
|
||||
@@ -82,12 +114,11 @@ sub daemonize {
|
||||
close STDIN;
|
||||
open STDIN, '/dev/null'
|
||||
or die "Cannot reopen STDIN to /dev/null: $OS_ERROR";
|
||||
|
||||
if ( $self->{log_file} ) {
|
||||
PTDEBUG && _d('Redirecting STDOUT and STDERR to', $self->{log_file});
|
||||
if ( $log_file ) {
|
||||
PTDEBUG && _d('Redirecting STDOUT and STDERR to', $log_file);
|
||||
close STDOUT;
|
||||
open STDOUT, '>>', $self->{log_file}
|
||||
or die "Cannot open log file $self->{log_file}: $OS_ERROR";
|
||||
open STDOUT, '>>', $log_file
|
||||
or die "Cannot open log file $log_file: $OS_ERROR";
|
||||
|
||||
# If we don't close STDERR explicitly, then prove Daemon.t fails
|
||||
# because STDERR gets written before STDOUT even though we print
|
||||
@@ -114,94 +145,138 @@ sub daemonize {
|
||||
}
|
||||
}
|
||||
|
||||
# XXX: I don't think we need this?
|
||||
# $OUTPUT_AUTOFLUSH = 1;
|
||||
|
||||
PTDEBUG && _d('I am child', $PID);
|
||||
|
||||
# Now update the pid file to contain the correct pid, i.e. the child's pid.
|
||||
if ( $pid_file ) {
|
||||
$self->_update_pid_file(
|
||||
pid => $PID, # child's pid
|
||||
pid_file => $pid_file,
|
||||
);
|
||||
$self->{pid_file_owner} = $PID;
|
||||
}
|
||||
|
||||
# Last: other misc daemon stuff.
|
||||
POSIX::setsid() or die "Cannot start a new session: $OS_ERROR";
|
||||
chdir '/' or die "Cannot chdir to /: $OS_ERROR";
|
||||
|
||||
# We're not fully daemonized.
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
# The file arg is optional. It's used when new() calls this sub
|
||||
# because $self hasn't been created yet.
|
||||
sub check_PID_file {
|
||||
my ( $self, $file ) = @_;
|
||||
my $PID_file = $self ? $self->{PID_file} : $file;
|
||||
PTDEBUG && _d('Checking PID file', $PID_file);
|
||||
if ( $PID_file && -f $PID_file ) {
|
||||
my $pid;
|
||||
eval {
|
||||
chomp($pid = (slurp_file($PID_file) || ''));
|
||||
};
|
||||
if ( $EVAL_ERROR ) {
|
||||
# Be safe and die if we can't check that a process is
|
||||
# or is not already running.
|
||||
die "The PID file $PID_file already exists but it cannot be read: "
|
||||
. $EVAL_ERROR;
|
||||
}
|
||||
PTDEBUG && _d('PID file exists; it contains PID', $pid);
|
||||
if ( $pid ) {
|
||||
my $pid_is_alive = kill 0, $pid;
|
||||
if ( $pid_is_alive ) {
|
||||
die "The PID file $PID_file already exists "
|
||||
. " and the PID that it contains, $pid, is running";
|
||||
}
|
||||
else {
|
||||
warn "Overwriting PID file $PID_file because the PID that it "
|
||||
. "contains, $pid, is not running";
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Be safe and die if we can't check that a process is
|
||||
# or is not already running.
|
||||
die "The PID file $PID_file already exists but it does not "
|
||||
. "contain a PID";
|
||||
}
|
||||
}
|
||||
else {
|
||||
PTDEBUG && _d('No PID file');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
# Call this for non-daemonized scripts to make a PID file.
|
||||
sub make_PID_file {
|
||||
my ( $self ) = @_;
|
||||
if ( exists $self->{child} ) {
|
||||
die "Do not call Daemon::make_PID_file() for daemonized scripts";
|
||||
sub _make_pid_file {
|
||||
my ($self, %args) = @_;
|
||||
my @required_args = qw(pid pid_file);
|
||||
foreach my $arg ( @required_args ) {
|
||||
die "I need a $arg argument" unless $args{$arg};
|
||||
};
|
||||
my $pid = $args{pid};
|
||||
my $pid_file = $args{pid_file};
|
||||
|
||||
# "If O_CREAT and O_EXCL are set, open() shall fail if the file exists.
|
||||
# The check for the existence of the file and the creation of the file
|
||||
# if it does not exist shall be atomic with respect to other threads
|
||||
# executing open() naming the same filename in the same directory with
|
||||
# O_EXCL and O_CREAT set.
|
||||
eval {
|
||||
sysopen(PID_FH, $pid_file, O_RDWR|O_CREAT|O_EXCL) or die $OS_ERROR;
|
||||
print PID_FH $PID, "\n";
|
||||
close PID_FH;
|
||||
};
|
||||
if ( my $e = $EVAL_ERROR ) {
|
||||
if ( $e =~ m/file exists/i ) {
|
||||
# Check if the existing pid is running. If yes, then die,
|
||||
# else this returns and we overwrite the pid file.
|
||||
my $old_pid = $self->_check_pid_file(
|
||||
pid_file => $pid_file,
|
||||
);
|
||||
if ( $old_pid ) {
|
||||
warn "Overwriting PID file $pid_file because PID $old_pid "
|
||||
. "is not running.\n";
|
||||
}
|
||||
$self->_make_PID_file();
|
||||
# This causes the PID file to be auto-removed when this obj is destroyed.
|
||||
$self->{PID_owner} = $PID;
|
||||
$self->_update_pid_file(
|
||||
pid => $PID,
|
||||
pid_file => $pid_file
|
||||
);
|
||||
}
|
||||
else {
|
||||
die "Error creating PID file $pid_file: $e\n";
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
# Do not call this sub directly. For daemonized scripts, it's called
|
||||
# automatically from daemonize() if there's a --pid opt. For non-daemonized
|
||||
# scripts, call make_PID_file().
|
||||
sub _make_PID_file {
|
||||
my ( $self ) = @_;
|
||||
sub _check_pid_file {
|
||||
my ($self, %args) = @_;
|
||||
my @required_args = qw(pid_file);
|
||||
foreach my $arg ( @required_args ) {
|
||||
die "I need a $arg argument" unless $args{$arg};
|
||||
};
|
||||
my $pid_file = $args{pid_file};
|
||||
|
||||
my $PID_file = $self->{PID_file};
|
||||
if ( !$PID_file ) {
|
||||
PTDEBUG && _d('No PID file to create');
|
||||
PTDEBUG && _d('Checking if PID in', $pid_file, 'is running');
|
||||
|
||||
if ( ! -f $pid_file ) {
|
||||
PTDEBUG && _d('PID file', $pid_file, 'does not exist');
|
||||
return;
|
||||
}
|
||||
|
||||
# We checked this in new() but we'll double check here.
|
||||
$self->check_PID_file();
|
||||
open my $fh, '<', $pid_file
|
||||
or die "Error opening $pid_file: $OS_ERROR";
|
||||
my $pid = do { local $/; <$fh> };
|
||||
chomp($pid) if $pid;
|
||||
close $fh
|
||||
or die "Error closing $pid_file: $OS_ERROR";
|
||||
|
||||
open my $PID_FH, '>', $PID_file
|
||||
or die "Cannot open PID file $PID_file: $OS_ERROR";
|
||||
print $PID_FH $PID
|
||||
or die "Cannot print to PID file $PID_file: $OS_ERROR";
|
||||
close $PID_FH
|
||||
or die "Cannot close PID file $PID_file: $OS_ERROR";
|
||||
if ( $pid ) {
|
||||
PTDEBUG && _d('Checking if PID', $pid, 'is running');
|
||||
my $pid_is_alive = kill 0, $pid;
|
||||
if ( $pid_is_alive ) {
|
||||
die "PID file $pid_file exists and PID $pid is running\n";
|
||||
}
|
||||
}
|
||||
else {
|
||||
# PID file but no PID: not sure what to do, so be safe and die;
|
||||
# let the user figure it out (i.e. rm the pid file).
|
||||
die "PID file $pid_file exists but it is empty. Remove the file "
|
||||
. "if the process is no longer running.\n";
|
||||
}
|
||||
|
||||
return $pid;
|
||||
}
|
||||
|
||||
sub _update_pid_file {
|
||||
my ($self, %args) = @_;
|
||||
my @required_args = qw(pid pid_file);
|
||||
foreach my $arg ( @required_args ) {
|
||||
die "I need a $arg argument" unless $args{$arg};
|
||||
};
|
||||
my $pid = $args{pid};
|
||||
my $pid_file = $args{pid_file};
|
||||
|
||||
open my $fh, '>', $pid_file
|
||||
or die "Cannot open $pid_file: $OS_ERROR";
|
||||
print { $fh } $pid, "\n"
|
||||
or die "Cannot print to $pid_file: $OS_ERROR";
|
||||
close $fh
|
||||
or warn "Cannot close $pid_file: $OS_ERROR";
|
||||
|
||||
PTDEBUG && _d('Created PID file:', $self->{PID_file});
|
||||
return;
|
||||
}
|
||||
|
||||
sub _remove_PID_file {
|
||||
my ( $self ) = @_;
|
||||
if ( $self->{PID_file} && -f $self->{PID_file} ) {
|
||||
unlink $self->{PID_file}
|
||||
or warn "Cannot remove PID file $self->{PID_file}: $OS_ERROR";
|
||||
sub remove_pid_file {
|
||||
my ($self, $pid_file) = @_;
|
||||
$pid_file ||= $self->{pid_file};
|
||||
if ( $pid_file && -f $pid_file ) {
|
||||
unlink $self->{pid_file}
|
||||
or warn "Cannot remove PID file $pid_file: $OS_ERROR";
|
||||
PTDEBUG && _d('Removed PID file');
|
||||
}
|
||||
else {
|
||||
@@ -220,19 +295,14 @@ sub DESTROY {
|
||||
# have it. 2) When daemonized code forks its children get copies of
|
||||
# the Daemon obj which will also call this sub when they exit. We
|
||||
# don't remove it then because the daemonized parent code won't have it.
|
||||
# This trick works because $self->{PID_owner}=$PID is set once to the
|
||||
# This trick works because $self->{pid_file_owner}=$PID is set once to the
|
||||
# owner's $PID then this value is copied on fork. But the "== $PID"
|
||||
# here is the forked copy's PID which won't match the owner's PID.
|
||||
$self->_remove_PID_file() if ($self->{PID_owner} || 0) == $PID;
|
||||
|
||||
return;
|
||||
if ( ($self->{pid_file_owner} || 0) == $PID ) {
|
||||
$self->remove_pid_file();
|
||||
}
|
||||
|
||||
sub slurp_file {
|
||||
my ($file) = @_;
|
||||
return unless $file;
|
||||
open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
|
||||
return do { local $/; <$fh> };
|
||||
return;
|
||||
}
|
||||
|
||||
sub _d {
|
||||
@@ -244,7 +314,6 @@ sub _d {
|
||||
}
|
||||
|
||||
1;
|
||||
}
|
||||
# ###########################################################################
|
||||
# End Daemon package
|
||||
# ###########################################################################
|
||||
|
103
t/lib/Daemon.t
103
t/lib/Daemon.t
@@ -9,31 +9,28 @@ BEGIN {
|
||||
use strict;
|
||||
use warnings FATAL => 'all';
|
||||
use English qw(-no_match_vars);
|
||||
|
||||
use Test::More;
|
||||
use Time::HiRes qw(sleep);
|
||||
use File::Temp qw(tempfile);
|
||||
|
||||
use Daemon;
|
||||
use OptionParser;
|
||||
use PerconaTest;
|
||||
#plan skip_all => "Hm";
|
||||
|
||||
use constant PTDEVDEBUG => $ENV{PTDEVDEBUG} || 0;
|
||||
|
||||
my $o = new OptionParser(file => "$trunk/t/lib/samples/daemonizes.pl");
|
||||
my $d = new Daemon(o=>$o);
|
||||
|
||||
my $pid_file = '/tmp/daemonizes.pl.pid';
|
||||
my $log_file = '/tmp/daemonizes.output';
|
||||
my $cmd = "$trunk/t/lib/samples/daemonizes.pl";
|
||||
my $pid_file = "/tmp/pt-daemon-test.pid.$PID";
|
||||
my $log_file = "/tmp/pt-daemon-test.log.$PID";
|
||||
sub rm_tmp_files() {
|
||||
-e $pid_file && (unlink $pid_file || die "Error removing $pid_file");
|
||||
-e $log_file && (unlink $log_file || die "Error removing $log_file");
|
||||
-f $pid_file && (unlink $pid_file || die "Error removing $pid_file");
|
||||
-f $log_file && (unlink $log_file || die "Error removing $log_file");
|
||||
}
|
||||
|
||||
# ############################################################################
|
||||
# Test that it daemonizes, creates a PID file, and removes that PID file.
|
||||
# ############################################################################
|
||||
rm_tmp_files();
|
||||
|
||||
my $cmd = "$trunk/t/lib/samples/daemonizes.pl";
|
||||
my $ret_val = system("$cmd 5 --daemonize --pid $pid_file >/dev/null 2>&1");
|
||||
die 'Cannot test Daemon.pm because t/daemonizes.pl is not working'
|
||||
unless $ret_val == 0;
|
||||
@@ -41,16 +38,34 @@ die 'Cannot test Daemon.pm because t/daemonizes.pl is not working'
|
||||
PerconaTest::wait_for_files($pid_file);
|
||||
|
||||
my $output = `ps wx | grep '$cmd 5' | grep -v grep`;
|
||||
like($output, qr/$cmd/, 'Daemonizes');
|
||||
ok(-f $pid_file, 'Creates PID file');
|
||||
|
||||
my ($pid) = $output =~ /\s*(\d+)\s+/;
|
||||
like(
|
||||
$output,
|
||||
qr/$cmd/,
|
||||
'Daemonizes'
|
||||
);
|
||||
|
||||
ok(
|
||||
-f $pid_file,
|
||||
'Creates PID file'
|
||||
);
|
||||
|
||||
my ($pid) = $output =~ /^\s*(\d+)\s+/;
|
||||
$output = slurp_file($pid_file);
|
||||
is($output, $pid, 'PID file has correct PID');
|
||||
chomp($output) if $output;
|
||||
|
||||
is(
|
||||
$output,
|
||||
$pid,
|
||||
'PID file has correct PID'
|
||||
);
|
||||
|
||||
# Wait until the process goes away
|
||||
PerconaTest::wait_until(sub { !kill(0, $pid) });
|
||||
ok(! -f $pid_file, 'Removes PID file upon exit');
|
||||
ok(
|
||||
! -f $pid_file,
|
||||
'Removes PID file upon exit'
|
||||
);
|
||||
|
||||
# ############################################################################
|
||||
# Check that STDOUT can be redirected
|
||||
@@ -59,10 +74,19 @@ rm_tmp_files();
|
||||
|
||||
system("$cmd 0 --daemonize --log $log_file");
|
||||
PerconaTest::wait_for_files($log_file);
|
||||
ok(-f $log_file, 'Log file exists');
|
||||
|
||||
ok(
|
||||
-f $log_file,
|
||||
'Log file exists'
|
||||
);
|
||||
|
||||
$output = slurp_file($log_file);
|
||||
like($output, qr/STDOUT\nSTDERR\n/, 'STDOUT and STDERR went to log file');
|
||||
|
||||
like(
|
||||
$output,
|
||||
qr/STDOUT\nSTDERR\n/,
|
||||
'STDOUT and STDERR went to log file'
|
||||
);
|
||||
|
||||
my $log_size = -s $log_file;
|
||||
PTDEVDEBUG && PerconaTest::_d('log size', $log_size);
|
||||
@@ -71,6 +95,7 @@ PTDEVDEBUG && PerconaTest::_d('log size', $log_size);
|
||||
system("$cmd 0 --daemonize --log $log_file");
|
||||
PerconaTest::wait_until(sub { -s $log_file > $log_size });
|
||||
$output = slurp_file($log_file);
|
||||
|
||||
like(
|
||||
$output,
|
||||
qr/STDOUT\nSTDERR\nSTDOUT\nSTDERR\n/,
|
||||
@@ -82,6 +107,7 @@ like(
|
||||
# ##########################################################################
|
||||
rm_tmp_files();
|
||||
diag(`touch $pid_file`);
|
||||
|
||||
ok(
|
||||
-f $pid_file,
|
||||
'PID file already exists'
|
||||
@@ -90,7 +116,7 @@ ok(
|
||||
$output = `$cmd 2 --daemonize --pid $pid_file 2>&1`;
|
||||
like(
|
||||
$output,
|
||||
qr{The PID file $pid_file already exists},
|
||||
qr{PID file $pid_file exists},
|
||||
'Dies if PID file already exists'
|
||||
);
|
||||
|
||||
@@ -182,7 +208,7 @@ like(
|
||||
|
||||
like(
|
||||
slurp_file($tempfile),
|
||||
qr/$pid, is not running/,
|
||||
qr/Overwriting PID file $pid_file because PID $pid is not running/,
|
||||
'Says that old PID is not running (issue 419)'
|
||||
);
|
||||
|
||||
@@ -209,54 +235,55 @@ chomp($pid = slurp_file($pid_file));
|
||||
$output = `$cmd 0 --daemonize --pid $pid_file 2>&1`;
|
||||
like(
|
||||
$output,
|
||||
qr/$pid, is running/,
|
||||
qr/PID file $pid_file exists and PID $pid is running/,
|
||||
'Says that PID is running (issue 419)'
|
||||
);
|
||||
|
||||
kill SIGKILL => $pid
|
||||
if $pid;
|
||||
if ( $pid ) {
|
||||
kill 9, $pid;
|
||||
}
|
||||
|
||||
sleep 1;
|
||||
sleep 0.25;
|
||||
rm_tmp_files();
|
||||
|
||||
# #############################################################################
|
||||
# Test auto-PID file removal without having to daemonize (for issue 391).
|
||||
# #############################################################################
|
||||
my $pid_file2 = "/tmp/pt-daemon-test.pid2.$PID";
|
||||
{
|
||||
@ARGV = qw(--pid /tmp/d2.pid);
|
||||
$o->get_specs("$trunk/t/lib/samples/daemonizes.pl");
|
||||
$o->get_opts();
|
||||
my $d2 = new Daemon(o=>$o);
|
||||
$d2->make_PID_file();
|
||||
my $d2 = Daemon->new(
|
||||
pid_file => $pid_file2,
|
||||
);
|
||||
$d2->run();
|
||||
ok(
|
||||
-f '/tmp/d2.pid',
|
||||
-f $pid_file2,
|
||||
'PID file for non-daemon exists'
|
||||
);
|
||||
}
|
||||
# Since $d2 was locally scoped, it should have been destoryed by now.
|
||||
# This should have caused the PID file to be automatically removed.
|
||||
ok(
|
||||
!-f '/tmpo/d2.pid',
|
||||
!-f $pid_file2,
|
||||
'PID file auto-removed for non-daemon'
|
||||
);
|
||||
|
||||
# We should still die if the PID file already exists,
|
||||
# even if we're not a daemon.
|
||||
{
|
||||
`touch /tmp/d2.pid`;
|
||||
@ARGV = qw(--pid /tmp/d2.pid);
|
||||
$o->get_opts();
|
||||
diag(`touch $pid_file2`);
|
||||
eval {
|
||||
my $d2 = new Daemon(o=>$o); # should die here actually
|
||||
$d2->make_PID_file();
|
||||
my $d2 = Daemon->new(
|
||||
pid_file => $pid_file2,
|
||||
);
|
||||
$d2->run();
|
||||
};
|
||||
like(
|
||||
$EVAL_ERROR,
|
||||
qr{PID file /tmp/d2.pid already exists},
|
||||
qr/PID file $pid_file2 exists/,
|
||||
'Dies if PID file already exists for non-daemon'
|
||||
);
|
||||
|
||||
diag(`rm -rf /tmp/d2.pid >/dev/null`);
|
||||
unlink $pid_file2 if -f $pid_file2;
|
||||
}
|
||||
|
||||
# #############################################################################
|
||||
|
@@ -12,7 +12,9 @@ BEGIN {
|
||||
use strict;
|
||||
use warnings FATAL => 'all';
|
||||
use English qw(-no_match_vars);
|
||||
use constant PTDEVDEBUG => $ENV{PTDEVDEBUG};
|
||||
|
||||
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
|
||||
use constant PTDEVDEBUG => $ENV{PTDEVDEBUG} || 0;
|
||||
|
||||
use Time::HiRes qw(sleep);
|
||||
|
||||
@@ -31,14 +33,13 @@ if ( !defined $sleep_time ) {
|
||||
|
||||
$o->usage_or_errors();
|
||||
|
||||
my $daemon;
|
||||
if ( $o->get('daemonize') ) {
|
||||
PTDEVDEBUG && PerconaTest::_d('daemonizing');
|
||||
my $daemon = Daemon->new(
|
||||
daemonize => $o->get('daemonize'),
|
||||
pid_file => $o->get('pid'),
|
||||
log_file => $o->get('log'),
|
||||
);
|
||||
|
||||
$OUTPUT_AUTOFLUSH = 1;
|
||||
|
||||
$daemon = new Daemon(o=>$o);
|
||||
$daemon->daemonize();
|
||||
$daemon->run();
|
||||
PTDEVDEBUG && PerconaTest::_d('daemonized');
|
||||
|
||||
print "STDOUT\n";
|
||||
@@ -46,7 +47,6 @@ if ( $o->get('daemonize') ) {
|
||||
|
||||
PTDEVDEBUG && PerconaTest::_d('daemon sleep', $sleep_time);
|
||||
sleep $sleep_time;
|
||||
}
|
||||
|
||||
PTDEVDEBUG && PerconaTest::_d('daemon done');
|
||||
exit;
|
||||
|
Reference in New Issue
Block a user