diff --git a/bin/pt-agent b/bin/pt-agent index 89ce5d4a..fddd9a93 100755 --- a/bin/pt-agent +++ b/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 ($class, %args) = @_; 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) || '')); - }; - if ( $EVAL_ERROR ) { - 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"; + +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}; + + 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"; } + $self->_update_pid_file( + pid => $PID, + pid_file => $pid_file + ); } else { - die "The PID file $PID_file already exists but it does not " - . "contain a PID"; + die "Error creating PID file $pid_file: $e\n"; } } - else { - PTDEBUG && _d('No PID file'); - } + 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 _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}; -sub _make_PID_file { - my ( $self ) = @_; + PTDEBUG && _d('Checking if PID in', $pid_file, 'is running'); - my $PID_file = $self->{PID_file}; - if ( !$PID_file ) { - PTDEBUG && _d('No PID file to create'); + if ( ! -f $pid_file ) { + PTDEBUG && _d('PID file', $pid_file, 'does not exist'); 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 - 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 { + 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 { @@ -4251,20 +4319,15 @@ sub _remove_PID_file { } 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; } -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 { my ($package, undef, $line) = caller 0; @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } @@ -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(); - PTDEBUG && _d('I am a daemon now'); - } - elsif ( $o->get('pid') ) { - $daemon = new Daemon(o=>$o); - $daemon->make_PID_file(); - } + $daemon->run(); + PTDEBUG && _d('I am a daemon now'); + # 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 @@ -4819,7 +4880,7 @@ sub main { # ######################################################################## # Connect to the Percona web API. # ######################################################################## - + # TODO: --send-data should not use this because it calls init_agent() # 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 diff --git a/lib/Daemon.pm b/lib/Daemon.pm index 2e53cccd..5954fcd9 100644 --- a/lib/Daemon.pm +++ b/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 ($class, %args) = @_; 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->_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; } -# 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 { @@ -211,7 +286,7 @@ sub _remove_PID_file { } sub DESTROY { - my ( $self ) = @_; + my ($self) = @_; # 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, @@ -220,21 +295,16 @@ 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; + if ( ($self->{pid_file_owner} || 0) == $PID ) { + $self->remove_pid_file(); + } 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 { my ($package, undef, $line) = caller 0; @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } @@ -244,7 +314,6 @@ sub _d { } 1; -} # ########################################################################### # End Daemon package # ########################################################################### diff --git a/t/lib/Daemon.t b/t/lib/Daemon.t index 1240a4cd..f860d335 100644 --- a/t/lib/Daemon.t +++ b/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 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; } # ############################################################################# diff --git a/t/lib/samples/daemonizes.pl b/t/lib/samples/daemonizes.pl index b9319487..2f6c705a 100755 --- a/t/lib/samples/daemonizes.pl +++ b/t/lib/samples/daemonizes.pl @@ -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,22 +33,20 @@ 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->run(); +PTDEVDEBUG && PerconaTest::_d('daemonized'); - $daemon = new Daemon(o=>$o); - $daemon->daemonize(); - PTDEVDEBUG && PerconaTest::_d('daemonized'); +print "STDOUT\n"; +print STDERR "STDERR\n"; - print "STDOUT\n"; - print STDERR "STDERR\n"; - - PTDEVDEBUG && PerconaTest::_d('daemon sleep', $sleep_time); - sleep $sleep_time; -} +PTDEVDEBUG && PerconaTest::_d('daemon sleep', $sleep_time); +sleep $sleep_time; PTDEVDEBUG && PerconaTest::_d('daemon done'); exit;