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:
Daniel Nichter
2013-04-05 12:16:16 -06:00
parent afa6533f43
commit 3d5325ae03
4 changed files with 435 additions and 278 deletions

View File

@@ -4091,59 +4091,86 @@ package Daemon;
use strict; use strict;
use warnings FATAL => 'all'; use warnings FATAL => 'all';
use English qw(-no_match_vars); use English qw(-no_match_vars);
use constant PTDEBUG => $ENV{PTDEBUG} || 0; use constant PTDEBUG => $ENV{PTDEBUG} || 0;
use POSIX qw(setsid); use POSIX qw(setsid);
use Fcntl qw(:DEFAULT);
sub new { sub new {
my ( $class, %args ) = @_; my ($class, %args) = @_;
foreach my $arg ( qw(o) ) {
die "I need a $arg argument" unless $args{$arg};
}
my $o = $args{o};
my $self = { my $self = {
o => $o, log_file => $args{log_file},
log_file => $o->has('log') ? $o->get('log') : undef, pid_file => $args{pid_file},
PID_file => $o->has('pid') ? $o->get('pid') : undef, daemonize => $args{daemonize},
}; };
check_PID_file(undef, $self->{PID_file});
PTDEBUG && _d('Daemonized child will log to', $self->{log_file});
return bless $self, $class; return bless $self, $class;
} }
sub daemonize { sub run {
my ( $self ) = @_; 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'); if ( $self->{daemonize} ) {
defined (my $pid = fork()) or die "Cannot fork: $OS_ERROR"; $self->_daemonize(
if ( $pid ) { pid => $pid,
PTDEBUG && _d('Parent PID', $PID, 'exiting after forking child PID',$pid); pid_file => $pid_file,
exit; 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); return;
$self->{PID_owner} = $PID; }
$self->{child} = 1;
POSIX::setsid() or die "Cannot start a new session: $OS_ERROR"; sub _daemonize {
chdir '/' or die "Cannot chdir to /: $OS_ERROR"; 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'); PTDEBUG && _d('Redirecting STDIN to /dev/null');
close STDIN; close STDIN;
open STDIN, '/dev/null' open STDIN, '/dev/null'
or die "Cannot reopen STDIN to /dev/null: $OS_ERROR"; or die "Cannot reopen STDIN to /dev/null: $OS_ERROR";
if ( $log_file ) {
if ( $self->{log_file} ) { PTDEBUG && _d('Redirecting STDOUT and STDERR to', $log_file);
PTDEBUG && _d('Redirecting STDOUT and STDERR to', $self->{log_file});
close STDOUT; close STDOUT;
open STDOUT, '>>', $self->{log_file} open STDOUT, '>>', $log_file
or die "Cannot open log file $self->{log_file}: $OS_ERROR"; or die "Cannot open log file $log_file: $OS_ERROR";
close STDERR; close STDERR;
open STDERR, ">&STDOUT" 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; return;
} }
sub check_PID_file {
my ( $self, $file ) = @_; sub _make_pid_file {
my $PID_file = $self ? $self->{PID_file} : $file; my ($self, %args) = @_;
PTDEBUG && _d('Checking PID file', $PID_file); my @required_args = qw(pid pid_file);
if ( $PID_file && -f $PID_file ) { foreach my $arg ( @required_args ) {
my $pid; die "I need a $arg argument" unless $args{$arg};
eval { };
chomp($pid = (slurp_file($PID_file) || '')); my $pid = $args{pid};
}; my $pid_file = $args{pid_file};
if ( $EVAL_ERROR ) {
die "The PID file $PID_file already exists but it cannot be read: " eval {
. $EVAL_ERROR; sysopen(PID_FH, $pid_file, O_RDWR|O_CREAT|O_EXCL) or die $OS_ERROR;
} print PID_FH $PID, "\n";
PTDEBUG && _d('PID file exists; it contains PID', $pid); close PID_FH;
if ( $pid ) { };
my $pid_is_alive = kill 0, $pid; if ( my $e = $EVAL_ERROR ) {
if ( $pid_is_alive ) { if ( $e =~ m/file exists/i ) {
die "The PID file $PID_file already exists " my $old_pid = $self->_check_pid_file(
. " and the PID that it contains, $pid, is running"; pid_file => $pid_file,
} );
else { if ( $old_pid ) {
warn "Overwriting PID file $PID_file because the PID that it " warn "Overwriting PID file $pid_file because PID $old_pid "
. "contains, $pid, is not running"; . "is not running.\n";
} }
$self->_update_pid_file(
pid => $PID,
pid_file => $pid_file
);
} }
else { else {
die "The PID file $PID_file already exists but it does not " die "Error creating PID file $pid_file: $e\n";
. "contain a PID";
} }
} }
else {
PTDEBUG && _d('No PID file');
}
return; return;
} }
sub make_PID_file { sub _check_pid_file {
my ( $self ) = @_; my ($self, %args) = @_;
if ( exists $self->{child} ) { my @required_args = qw(pid_file);
die "Do not call Daemon::make_PID_file() for daemonized scripts"; foreach my $arg ( @required_args ) {
} die "I need a $arg argument" unless $args{$arg};
$self->_make_PID_file(); };
$self->{PID_owner} = $PID; my $pid_file = $args{pid_file};
return;
}
sub _make_PID_file { PTDEBUG && _d('Checking if PID in', $pid_file, 'is running');
my ( $self ) = @_;
my $PID_file = $self->{PID_file}; if ( ! -f $pid_file ) {
if ( !$PID_file ) { PTDEBUG && _d('PID file', $pid_file, 'does not exist');
PTDEBUG && _d('No PID file to create');
return; return;
} }
$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 if ( $pid ) {
or die "Cannot open PID file $PID_file: $OS_ERROR"; PTDEBUG && _d('Checking if PID', $pid, 'is running');
print $PID_FH $PID my $pid_is_alive = kill 0, $pid;
or die "Cannot print to PID file $PID_file: $OS_ERROR"; if ( $pid_is_alive ) {
close $PID_FH die "PID file $pid_file exists and PID $pid is running\n";
or die "Cannot close PID file $PID_file: $OS_ERROR"; }
}
else {
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; return;
} }
sub _remove_PID_file { sub remove_pid_file {
my ( $self ) = @_; my ($self, $pid_file) = @_;
if ( $self->{PID_file} && -f $self->{PID_file} ) { $pid_file ||= $self->{pid_file};
unlink $self->{PID_file} if ( $pid_file && -f $pid_file ) {
or warn "Cannot remove PID file $self->{PID_file}: $OS_ERROR"; unlink $self->{pid_file}
or warn "Cannot remove PID file $pid_file: $OS_ERROR";
PTDEBUG && _d('Removed PID file'); PTDEBUG && _d('Removed PID file');
} }
else { else {
@@ -4251,20 +4319,15 @@ sub _remove_PID_file {
} }
sub DESTROY { sub DESTROY {
my ( $self ) = @_; my ($self) = @_;
$self->_remove_PID_file() if ($self->{PID_owner} || 0) == $PID; if ( ($self->{pid_file_owner} || 0) == $PID ) {
$self->remove_pid_file();
}
return; return;
} }
sub slurp_file {
my ($file) = @_;
return unless $file;
open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
return do { local $/; <$fh> };
}
sub _d { sub _d {
my ($package, undef, $line) = caller 0; my ($package, undef, $line) = caller 0;
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
@@ -4795,17 +4858,15 @@ sub main {
# ######################################################################## # ########################################################################
# Daemonize first so all output goes to the --log. # 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('send-data') ) {
if ( $o->get('daemonize') ) { $daemon->run();
$daemon = new Daemon(o=>$o); PTDEBUG && _d('I am a daemon now');
$daemon->daemonize();
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. # 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 # 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 # were destroyed but the dbhs were not disconnected because the parent
@@ -4819,7 +4880,7 @@ sub main {
# ######################################################################## # ########################################################################
# Connect to the Percona web API. # Connect to the Percona web API.
# ######################################################################## # ########################################################################
# TODO: --send-data should not use this because it calls init_agent() # TODO: --send-data should not use this because it calls init_agent()
# Check --lib. Until the agent is configured, the default lib dir # Check --lib. Until the agent is configured, the default lib dir
# may not work. Save stuff in /tmp so if we stop and start again # may not work. Save stuff in /tmp so if we stop and start again

View File

@@ -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. # Feedback and improvements are welcome.
# #
# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED # THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
@@ -17,59 +17,91 @@
# ########################################################################### # ###########################################################################
# Daemon package # Daemon package
# ########################################################################### # ###########################################################################
{
# Package: Daemon
# Daemon daemonizes the caller and handles daemon-related tasks like PID files.
package Daemon; package Daemon;
use strict; use strict;
use warnings FATAL => 'all'; use warnings FATAL => 'all';
use English qw(-no_match_vars); use English qw(-no_match_vars);
use constant PTDEBUG => $ENV{PTDEBUG} || 0; use constant PTDEBUG => $ENV{PTDEBUG} || 0;
use POSIX qw(setsid); use POSIX qw(setsid);
use Fcntl qw(:DEFAULT);
# The required o arg is an OptionParser object.
sub new { sub new {
my ( $class, %args ) = @_; my ($class, %args) = @_;
foreach my $arg ( qw(o) ) {
die "I need a $arg argument" unless $args{$arg};
}
my $o = $args{o};
my $self = { my $self = {
o => $o, log_file => $args{log_file},
log_file => $o->has('log') ? $o->get('log') : undef, pid_file => $args{pid_file},
PID_file => $o->has('pid') ? $o->get('pid') : undef, 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; return bless $self, $class;
} }
sub daemonize { sub run {
my ( $self ) = @_; 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'); if ( $self->{daemonize} ) {
defined (my $pid = fork()) or die "Cannot fork: $OS_ERROR"; $self->_daemonize(
if ( $pid ) { pid => $pid,
PTDEBUG && _d('Parent PID', $PID, 'exiting after forking child PID',$pid); pid_file => $pid_file,
exit; 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. return;
PTDEBUG && _d('Daemonizing child PID', $PID); }
$self->{PID_owner} = $PID;
$self->{child} = 1;
POSIX::setsid() or die "Cannot start a new session: $OS_ERROR"; sub _daemonize {
chdir '/' or die "Cannot chdir to /: $OS_ERROR"; 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 # 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 # otherwise it may be a pipe, in which case we didn't want to break
@@ -82,12 +114,11 @@ sub daemonize {
close STDIN; close STDIN;
open STDIN, '/dev/null' open STDIN, '/dev/null'
or die "Cannot reopen STDIN to /dev/null: $OS_ERROR"; or die "Cannot reopen STDIN to /dev/null: $OS_ERROR";
if ( $log_file ) {
if ( $self->{log_file} ) { PTDEBUG && _d('Redirecting STDOUT and STDERR to', $log_file);
PTDEBUG && _d('Redirecting STDOUT and STDERR to', $self->{log_file});
close STDOUT; close STDOUT;
open STDOUT, '>>', $self->{log_file} open STDOUT, '>>', $log_file
or die "Cannot open log file $self->{log_file}: $OS_ERROR"; or die "Cannot open log file $log_file: $OS_ERROR";
# If we don't close STDERR explicitly, then prove Daemon.t fails # If we don't close STDERR explicitly, then prove Daemon.t fails
# because STDERR gets written before STDOUT even though we print # 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; 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. # Call this for non-daemonized scripts to make a PID file.
sub make_PID_file { sub _make_pid_file {
my ( $self ) = @_; my ($self, %args) = @_;
if ( exists $self->{child} ) { my @required_args = qw(pid pid_file);
die "Do not call Daemon::make_PID_file() for daemonized scripts"; 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->_update_pid_file(
pid => $PID,
pid_file => $pid_file
);
}
else {
die "Error creating PID file $pid_file: $e\n";
}
} }
$self->_make_PID_file();
# This causes the PID file to be auto-removed when this obj is destroyed.
$self->{PID_owner} = $PID;
return; return;
} }
# Do not call this sub directly. For daemonized scripts, it's called sub _check_pid_file {
# automatically from daemonize() if there's a --pid opt. For non-daemonized my ($self, %args) = @_;
# scripts, call make_PID_file(). my @required_args = qw(pid_file);
sub _make_PID_file { foreach my $arg ( @required_args ) {
my ( $self ) = @_; die "I need a $arg argument" unless $args{$arg};
};
my $pid_file = $args{pid_file};
my $PID_file = $self->{PID_file}; PTDEBUG && _d('Checking if PID in', $pid_file, 'is running');
if ( !$PID_file ) {
PTDEBUG && _d('No PID file to create'); if ( ! -f $pid_file ) {
PTDEBUG && _d('PID file', $pid_file, 'does not exist');
return; return;
} }
# We checked this in new() but we'll double check here. open my $fh, '<', $pid_file
$self->check_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 if ( $pid ) {
or die "Cannot open PID file $PID_file: $OS_ERROR"; PTDEBUG && _d('Checking if PID', $pid, 'is running');
print $PID_FH $PID my $pid_is_alive = kill 0, $pid;
or die "Cannot print to PID file $PID_file: $OS_ERROR"; if ( $pid_is_alive ) {
close $PID_FH die "PID file $pid_file exists and PID $pid is running\n";
or die "Cannot close PID file $PID_file: $OS_ERROR"; }
}
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; return;
} }
sub _remove_PID_file { sub remove_pid_file {
my ( $self ) = @_; my ($self, $pid_file) = @_;
if ( $self->{PID_file} && -f $self->{PID_file} ) { $pid_file ||= $self->{pid_file};
unlink $self->{PID_file} if ( $pid_file && -f $pid_file ) {
or warn "Cannot remove PID file $self->{PID_file}: $OS_ERROR"; unlink $self->{pid_file}
or warn "Cannot remove PID file $pid_file: $OS_ERROR";
PTDEBUG && _d('Removed PID file'); PTDEBUG && _d('Removed PID file');
} }
else { else {
@@ -211,7 +286,7 @@ sub _remove_PID_file {
} }
sub DESTROY { sub DESTROY {
my ( $self ) = @_; my ($self) = @_;
# Remove the PID file only if we created it. There's two cases where # Remove the PID file only if we created it. There's two cases where
# it might be removed wrongly. 1) When the obj first daemonizes itself, # it might be removed wrongly. 1) When the obj first daemonizes itself,
@@ -220,21 +295,16 @@ sub DESTROY {
# have it. 2) When daemonized code forks its children get copies of # 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 # 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. # 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" # 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. # 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; if ( ($self->{pid_file_owner} || 0) == $PID ) {
$self->remove_pid_file();
}
return; return;
} }
sub slurp_file {
my ($file) = @_;
return unless $file;
open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
return do { local $/; <$fh> };
}
sub _d { sub _d {
my ($package, undef, $line) = caller 0; my ($package, undef, $line) = caller 0;
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
@@ -244,7 +314,6 @@ sub _d {
} }
1; 1;
}
# ########################################################################### # ###########################################################################
# End Daemon package # End Daemon package
# ########################################################################### # ###########################################################################

View File

@@ -9,31 +9,28 @@ BEGIN {
use strict; use strict;
use warnings FATAL => 'all'; use warnings FATAL => 'all';
use English qw(-no_match_vars); use English qw(-no_match_vars);
use Test::More; use Test::More;
use Time::HiRes qw(sleep); use Time::HiRes qw(sleep);
use File::Temp qw( tempfile ); use File::Temp qw(tempfile);
use Daemon; use Daemon;
use OptionParser;
use PerconaTest; use PerconaTest;
#plan skip_all => "Hm";
use constant PTDEVDEBUG => $ENV{PTDEVDEBUG} || 0; use constant PTDEVDEBUG => $ENV{PTDEVDEBUG} || 0;
my $o = new OptionParser(file => "$trunk/t/lib/samples/daemonizes.pl"); my $cmd = "$trunk/t/lib/samples/daemonizes.pl";
my $d = new Daemon(o=>$o); my $pid_file = "/tmp/pt-daemon-test.pid.$PID";
my $log_file = "/tmp/pt-daemon-test.log.$PID";
my $pid_file = '/tmp/daemonizes.pl.pid';
my $log_file = '/tmp/daemonizes.output';
sub rm_tmp_files() { sub rm_tmp_files() {
-e $pid_file && (unlink $pid_file || die "Error removing $pid_file"); -f $pid_file && (unlink $pid_file || die "Error removing $pid_file");
-e $log_file && (unlink $log_file || die "Error removing $log_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. # 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"); 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' die 'Cannot test Daemon.pm because t/daemonizes.pl is not working'
unless $ret_val == 0; 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); PerconaTest::wait_for_files($pid_file);
my $output = `ps wx | grep '$cmd 5' | grep -v grep`; 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); $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 # Wait until the process goes away
PerconaTest::wait_until(sub { !kill(0, $pid) }); 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 # Check that STDOUT can be redirected
@@ -59,10 +74,19 @@ rm_tmp_files();
system("$cmd 0 --daemonize --log $log_file"); system("$cmd 0 --daemonize --log $log_file");
PerconaTest::wait_for_files($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); $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; my $log_size = -s $log_file;
PTDEVDEBUG && PerconaTest::_d('log size', $log_size); 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"); system("$cmd 0 --daemonize --log $log_file");
PerconaTest::wait_until(sub { -s $log_file > $log_size }); PerconaTest::wait_until(sub { -s $log_file > $log_size });
$output = slurp_file($log_file); $output = slurp_file($log_file);
like( like(
$output, $output,
qr/STDOUT\nSTDERR\nSTDOUT\nSTDERR\n/, qr/STDOUT\nSTDERR\nSTDOUT\nSTDERR\n/,
@@ -82,6 +107,7 @@ like(
# ########################################################################## # ##########################################################################
rm_tmp_files(); rm_tmp_files();
diag(`touch $pid_file`); diag(`touch $pid_file`);
ok( ok(
-f $pid_file, -f $pid_file,
'PID file already exists' 'PID file already exists'
@@ -90,7 +116,7 @@ ok(
$output = `$cmd 2 --daemonize --pid $pid_file 2>&1`; $output = `$cmd 2 --daemonize --pid $pid_file 2>&1`;
like( like(
$output, $output,
qr{The PID file $pid_file already exists}, qr{PID file $pid_file exists},
'Dies if PID file already exists' 'Dies if PID file already exists'
); );
@@ -182,7 +208,7 @@ like(
like( like(
slurp_file($tempfile), 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)' '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`; $output = `$cmd 0 --daemonize --pid $pid_file 2>&1`;
like( like(
$output, $output,
qr/$pid, is running/, qr/PID file $pid_file exists and PID $pid is running/,
'Says that PID is running (issue 419)' '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(); rm_tmp_files();
# ############################################################################# # #############################################################################
# Test auto-PID file removal without having to daemonize (for issue 391). # 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); my $d2 = Daemon->new(
$o->get_specs("$trunk/t/lib/samples/daemonizes.pl"); pid_file => $pid_file2,
$o->get_opts(); );
my $d2 = new Daemon(o=>$o); $d2->run();
$d2->make_PID_file();
ok( ok(
-f '/tmp/d2.pid', -f $pid_file2,
'PID file for non-daemon exists' 'PID file for non-daemon exists'
); );
} }
# Since $d2 was locally scoped, it should have been destoryed by now. # Since $d2 was locally scoped, it should have been destoryed by now.
# This should have caused the PID file to be automatically removed. # This should have caused the PID file to be automatically removed.
ok( ok(
!-f '/tmpo/d2.pid', !-f $pid_file2,
'PID file auto-removed for non-daemon' 'PID file auto-removed for non-daemon'
); );
# We should still die if the PID file already exists, # We should still die if the PID file already exists,
# even if we're not a daemon. # even if we're not a daemon.
{ {
`touch /tmp/d2.pid`; diag(`touch $pid_file2`);
@ARGV = qw(--pid /tmp/d2.pid);
$o->get_opts();
eval { eval {
my $d2 = new Daemon(o=>$o); # should die here actually my $d2 = Daemon->new(
$d2->make_PID_file(); pid_file => $pid_file2,
);
$d2->run();
}; };
like( like(
$EVAL_ERROR, $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' 'Dies if PID file already exists for non-daemon'
); );
diag(`rm -rf /tmp/d2.pid >/dev/null`); unlink $pid_file2 if -f $pid_file2;
} }
# ############################################################################# # #############################################################################

View File

@@ -12,7 +12,9 @@ BEGIN {
use strict; use strict;
use warnings FATAL => 'all'; use warnings FATAL => 'all';
use English qw(-no_match_vars); 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); use Time::HiRes qw(sleep);
@@ -31,22 +33,20 @@ if ( !defined $sleep_time ) {
$o->usage_or_errors(); $o->usage_or_errors();
my $daemon; my $daemon = Daemon->new(
if ( $o->get('daemonize') ) { daemonize => $o->get('daemonize'),
PTDEVDEBUG && PerconaTest::_d('daemonizing'); pid_file => $o->get('pid'),
log_file => $o->get('log'),
);
$OUTPUT_AUTOFLUSH = 1; $daemon->run();
PTDEVDEBUG && PerconaTest::_d('daemonized');
$daemon = new Daemon(o=>$o); print "STDOUT\n";
$daemon->daemonize(); print STDERR "STDERR\n";
PTDEVDEBUG && PerconaTest::_d('daemonized');
print "STDOUT\n"; PTDEVDEBUG && PerconaTest::_d('daemon sleep', $sleep_time);
print STDERR "STDERR\n"; sleep $sleep_time;
PTDEVDEBUG && PerconaTest::_d('daemon sleep', $sleep_time);
sleep $sleep_time;
}
PTDEVDEBUG && PerconaTest::_d('daemon done'); PTDEVDEBUG && PerconaTest::_d('daemon done');
exit; exit;