Test harness process control enhancements

This commit is contained in:
Yang Tse 2009-12-16 15:16:06 +00:00
parent 3111701c38
commit 3802d027cd
4 changed files with 166 additions and 93 deletions

View File

@ -48,7 +48,7 @@ sub pidfromfile {
# the process is not alive, will return the negative value of the pid. # the process is not alive, will return the negative value of the pid.
# #
sub processexists { sub processexists {
# use POSIX ":sys_wait_h"; use POSIX ":sys_wait_h";
my $pidfile = $_[0]; my $pidfile = $_[0];
# fetch pid from pidfile # fetch pid from pidfile
@ -61,7 +61,7 @@ sub processexists {
} }
else { else {
# reap it if this has not already been done # reap it if this has not already been done
# waitpid($pid, &WNOHANG); waitpid($pid, &WNOHANG);
# get rid of the certainly invalid pidfile # get rid of the certainly invalid pidfile
unlink($pidfile) if($pid == pidfromfile($pidfile)); unlink($pidfile) if($pid == pidfromfile($pidfile));
return -$pid; # negative means dead process return -$pid; # negative means dead process
@ -70,36 +70,125 @@ sub processexists {
return 0; return 0;
} }
#######################################################################
# killpid attempts to gracefully stop processes in the given pid list
# with a SIGTERM signal and SIGKILLs those which haven't died on time.
#
sub killpid {
use POSIX ":sys_wait_h";
my ($verbose, $pidlist) = @_;
# The 'pidlist' argument is a string of whitespace separated pids.
return if(not defined $pidlist);
# For each pid which is alive send it a SIGTERM to gracefully
# stop it, otherwise reap it if this has not been done yet.
my @signalled;
my $prev = 0;
my @pids = sort({$a <=> $b} split(/\s+/, $pidlist));
foreach my $tmp (@pids) {
chomp $tmp;
if($tmp =~ /^(\d+)$/) {
my $pid = $1;
if(($pid > 0) && ($prev != $pid)) {
$prev = $pid;
if(kill(0, $pid)) {
print("RUN: Process with pid $pid signalled to die\n")
if($verbose);
kill("TERM", $pid);
push @signalled, $pid;
}
else {
print("RUN: Process with pid $pid already dead\n")
if($verbose);
waitpid($pid, &WNOHANG);
}
}
}
}
return if(not scalar(@signalled));
# Allow all signalled processes five seconds to gracefully die.
my $quarters = 20;
while($quarters--) {
for(my $i = scalar(@signalled) - 1; $i >= 0; $i--) {
my $pid = $signalled[$i];
if(!kill(0, $pid)) {
print("RUN: Process with pid $pid gracefully died\n")
if($verbose);
waitpid($pid, &WNOHANG);
splice @signalled, $i, 1;
}
}
return if(not scalar(@signalled));
select(undef, undef, undef, 0.25);
}
# Mercilessly SIGKILL processes still alive.
foreach my $pid (@signalled) {
print("RUN: Process with pid $pid forced to die with SIGKILL\n")
if($verbose);
kill("KILL", $pid);
waitpid($pid, 0);
}
}
############################################################################# #############################################################################
# Kill a specific slave # Kill a specific slave
# #
sub ftpkillslave { sub ftpkillslave {
my ($id, $ext, $verbose)=@_; my ($id, $ext, $verbose)=@_;
my $base; my $base;
my $pidlist;
my @pidfiles;
for $base (('filt', 'data')) { for $base (('filt', 'data')) {
my $f = ".sock$base$id$ext.pid"; my $f = ".sock$base$id$ext.pid";
my $pid = processexists($f); my $pid = processexists($f);
if($pid > 0) { if($pid > 0) {
printf ("* kill pid for %s => %d\n", "ftp-$base$id$ext", $pid) if($verbose); printf ("* kill pid for %s => %d\n", "ftp-$base$id$ext", $pid)
kill (9, $pid); # die! if($verbose);
waitpid($pid, 0); $pidlist .= "$pid ";
} }
unlink($f); push @pidfiles, $f;
}
killpid($verbose, $pidlist);
foreach my $pidfile (@pidfiles) {
unlink($pidfile);
} }
} }
############################################################################# #############################################################################
# Make sure no FTP leftovers are still running. Kill all slave processes. # Make sure no FTP leftovers are still running. Kill all slave processes.
# This uses pidfiles since it might be used by other processes. # This uses pidfiles since it might be used by other processes.
# #
sub ftpkillslaves { sub ftpkillslaves {
my ($versbose) = @_; my ($verbose) = @_;
for $ext (("", "ipv6")) { my $pidlist;
for $id (("", "2")) { my @pidfiles;
ftpkillslave ($id, $ext, $verbose);
for $ext (('', 'ipv6')) {
for $id (('', '2')) {
for $base (('filt', 'data')) {
my $f = ".sock$base$id$ext.pid";
my $pid = processexists($f);
if($pid > 0) {
printf ("* kill pid for %s => %d\n", "ftp-$base$id$ext",
$pid) if($verbose);
$pidlist .= "$pid ";
}
push @pidfiles, $f;
}
} }
} }
killpid($verbose, $pidlist);
foreach my $pidfile (@pidfiles) {
unlink($pidfile);
}
} }

View File

@ -39,25 +39,55 @@
use strict; use strict;
use IPC::Open2; use IPC::Open2;
#use Time::HiRes qw( gettimeofday ); # not available in perl 5.6
require "getpart.pm"; require "getpart.pm";
require "ftp.pm"; require "ftp.pm";
BEGIN {
if($] >= 5.006) {
use Time::HiRes qw( gettimeofday );
}
}
my $ftpdnum=""; my $ftpdnum="";
# open and close each time to allow removal at any time my $logfilename = 'log/logfile.log'; # Override this for each test server
#######################################################################
# getlogfilename returns a log file name depending on given arguments.
#
sub getlogfilename {
my ($proto, $ipversion, $ssl, $instance, $sockfilter) = @_;
my $filename;
# For now, simply mimic old behavior.
$filename = "log/ftpd$ftpdnum.log";
return $filename;
}
#######################################################################
# logmsg is general message logging subroutine for our test servers.
#
sub logmsg { sub logmsg {
# if later than perl 5.6 is used my $now;
# my ($seconds, $microseconds) = gettimeofday; if($] >= 5.006) {
my $seconds = time(); my ($seconds, $usec) = gettimeofday();
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
localtime($seconds); localtime($seconds);
open(FTPLOG, ">>log/ftpd$ftpdnum.log"); $now = sprintf("%02d:%02d:%02d.%06d ", $hour, $min, $sec, $usec);
printf FTPLOG ("%02d:%02d:%02d ", $hour, $min, $sec); }
print FTPLOG @_; else {
close(FTPLOG); my $seconds = time();
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
localtime($seconds);
$now = sprintf("%02d:%02d:%02d ", $hour, $min, $sec);
}
if(open(LOGFILEFH, ">>$logfilename")) {
print LOGFILEFH $now;
print LOGFILEFH @_;
close(LOGFILEFH);
}
} }
sub ftpmsg { sub ftpmsg {
@ -138,7 +168,7 @@ if($proto !~ /^(ftp|imap|pop3|smtp)\z/) {
sub catch_zap { sub catch_zap {
my $signame = shift; my $signame = shift;
ftpkillslaves(1); ftpkillslaves($verbose);
unlink($pidfile); unlink($pidfile);
if($serverlogslocked) { if($serverlogslocked) {
$serverlogslocked = 0; $serverlogslocked = 0;
@ -167,8 +197,7 @@ sub sysread_or_die {
($fcaller, $lcaller) = (caller)[1,2]; ($fcaller, $lcaller) = (caller)[1,2];
logmsg "Failed to read input\n"; logmsg "Failed to read input\n";
logmsg "Error: ftp$ftpdnum$ext sysread error: $!\n"; logmsg "Error: ftp$ftpdnum$ext sysread error: $!\n";
kill(9, $sfpid); killpid($verbose, $sfpid);
waitpid($sfpid, 0);
logmsg "Exited from sysread_or_die() at $fcaller " . logmsg "Exited from sysread_or_die() at $fcaller " .
"line $lcaller. ftp$ftpdnum$ext sysread error: $!\n"; "line $lcaller. ftp$ftpdnum$ext sysread error: $!\n";
unlink($pidfile); unlink($pidfile);
@ -182,8 +211,7 @@ sub sysread_or_die {
($fcaller, $lcaller) = (caller)[1,2]; ($fcaller, $lcaller) = (caller)[1,2];
logmsg "Failed to read input\n"; logmsg "Failed to read input\n";
logmsg "Error: ftp$ftpdnum$ext read zero\n"; logmsg "Error: ftp$ftpdnum$ext read zero\n";
kill(9, $sfpid); killpid($verbose, $sfpid);
waitpid($sfpid, 0);
logmsg "Exited from sysread_or_die() at $fcaller " . logmsg "Exited from sysread_or_die() at $fcaller " .
"line $lcaller. ftp$ftpdnum$ext read zero\n"; "line $lcaller. ftp$ftpdnum$ext read zero\n";
unlink($pidfile); unlink($pidfile);
@ -209,8 +237,7 @@ sub startsf {
if($pong !~ /^PONG/) { if($pong !~ /^PONG/) {
logmsg "Failed sockfilt command: $cmd\n"; logmsg "Failed sockfilt command: $cmd\n";
kill(9, $sfpid); killpid($verbose, $sfpid);
waitpid($sfpid, 0);
unlink($pidfile); unlink($pidfile);
if($serverlogslocked) { if($serverlogslocked) {
$serverlogslocked = 0; $serverlogslocked = 0;
@ -220,6 +247,8 @@ sub startsf {
} }
} }
$logfilename = getlogfilename();
startsf(); startsf();
logmsg sprintf("%s server listens on port IPv%d/$port\n", uc($proto), logmsg sprintf("%s server listens on port IPv%d/$port\n", uc($proto),
@ -716,8 +745,7 @@ sub PASV_command {
my $prev = processexists($pidf); my $prev = processexists($pidf);
if($prev > 0) { if($prev > 0) {
print "kill existing server: $prev\n" if($verbose); print "kill existing server: $prev\n" if($verbose);
kill(9, $prev); killpid($verbose, $prev);
waitpid($prev, 0);
} }
# We fire up a new sockfilt to do the data transfer for us. # We fire up a new sockfilt to do the data transfer for us.
@ -730,8 +758,7 @@ sub PASV_command {
sysread_or_die(\*DREAD, \$pong, 5); sysread_or_die(\*DREAD, \$pong, 5);
if($pong !~ /^PONG/) { if($pong !~ /^PONG/) {
kill(9, $slavepid); killpid($verbose, $slavepid);
waitpid($slavepid, 0);
sendcontrol "500 no free ports!\r\n"; sendcontrol "500 no free ports!\r\n";
logmsg "failed to run sockfilt for data connection\n"; logmsg "failed to run sockfilt for data connection\n";
return 0; return 0;
@ -865,8 +892,7 @@ sub PORT_command {
if($pong !~ /^PONG/) { if($pong !~ /^PONG/) {
logmsg "Failed sockfilt for data connection\n"; logmsg "Failed sockfilt for data connection\n";
kill(9, $slavepid); killpid($verbose, $slavepid);
waitpid($slavepid, 0);
} }
logmsg "====> Client DATA connect to port $port\n"; logmsg "====> Client DATA connect to port $port\n";
@ -986,8 +1012,7 @@ while(1) {
# flush data: # flush data:
$| = 1; $| = 1;
kill(9, $slavepid) if($slavepid); killpid($verbose, $slavepid);
waitpid($slavepid, 0) if($slavepid);
$slavepid=0; $slavepid=0;
&customize(); # read test control instructions &customize(); # read test control instructions

View File

@ -61,7 +61,6 @@ BEGIN {
} }
use strict; use strict;
#use Time::HiRes qw( gettimeofday );
#use warnings; #use warnings;
use Cwd; use Cwd;
@ -240,19 +239,12 @@ my $torture;
my $tortnum; my $tortnum;
my $tortalloc; my $tortalloc;
# open and close each time to allow removal at any time #######################################################################
# logmsg is our general message logging subroutine.
#
sub logmsg { sub logmsg {
# uncomment the Time::HiRes usage for this
# my ($seconds, $microseconds) = gettimeofday;
# my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
# localtime($seconds);
my $t;
if(1) {
# $t = sprintf ("%02d:%02d:%02d.%06d ", $hour, $min, $sec,
# $microseconds);
}
for(@_) { for(@_) {
print "${t}$_"; print "$_";
} }
} }
@ -272,7 +264,7 @@ $ENV{'HOME'}=$pwd;
sub catch_zap { sub catch_zap {
my $signame = shift; my $signame = shift;
logmsg "runtests.pl received SIG$signame, exiting\n"; logmsg "runtests.pl received SIG$signame, exiting\n";
stopservers(1); stopservers($verbose);
die "Somebody sent me a SIG$signame"; die "Somebody sent me a SIG$signame";
} }
$SIG{INT} = \&catch_zap; $SIG{INT} = \&catch_zap;
@ -550,45 +542,9 @@ sub torture {
# stop the given test server (pid) # stop the given test server (pid)
# #
sub stopserver { sub stopserver {
my ($pid) = @_; my ($pidlist) = @_;
if(not defined $pid || $pid <= 0) { killpid($verbose, $pidlist);
return; # whad'da'ya wanna'da with no pid ?
}
# It might be more than one pid
# Send each one a SIGTERM to gracefully kill it
my @killed;
my @pids = split(/\s+/, $pid);
for (@pids) {
chomp($_);
if($_ =~ /^(\d+)$/) {
if(($1 > 0) && kill(0, $1)) {
if($verbose) {
logmsg "RUN: Test server pid $1 signalled to die\n";
}
kill(15, $1); # die!
push @killed, $1;
}
}
}
# Give each process killed up to a few seconds to die, then send
# a SIGKILL to finish it off for good.
for (@killed) {
my $count = 5; # wait for this many seconds for server to die
while($count--) {
if (!kill(0, $_) || checkdied($_)) {
last;
}
sleep(1);
}
if ($count < 0) {
logmsg "RUN: forcing pid $_ to die with SIGKILL\n";
kill(9, $_); # die!
}
}
} }
####################################################################### #######################################################################
@ -2554,23 +2510,26 @@ sub singletest {
# Stop all running test servers # Stop all running test servers
sub stopservers { sub stopservers {
my ($verbose)=@_; my ($verbose)=@_;
my $pidlist;
for(keys %run) { for(keys %run) {
my $server = $_; my $server = $_;
my $pids=$run{$server}; my $pids=$run{$server};
my $pid; my $pid;
my $prev; my $prev;
foreach $pid (split(" ", $pids)) { foreach $pid (split(/\s+/, $pids)) {
if($pid != $prev) { if($pid != $prev) {
# no need to kill same pid twice! # no need to kill same pid twice!
logmsg sprintf("* kill pid for %s => %d\n", logmsg sprintf("* kill pid for %s => %d\n",
$server, $pid) if($verbose); $server, $pid) if($verbose);
stopserver($pid); $pidlist .= "$pid ";
$prev = $pid;
} }
$prev = $pid;
} }
delete $run{$server}; delete $run{$server};
} }
killpid($verbose, $pidlist);
ftpkillslaves($verbose); ftpkillslaves($verbose);
} }

View File

@ -86,7 +86,7 @@ void logmsg(const char *msg, ...)
vsnprintf(buffer, sizeof(buffer), msg, ap); vsnprintf(buffer, sizeof(buffer), msg, ap);
va_end(ap); va_end(ap);
logfp = fopen(serverlogfile, "a"); logfp = fopen(serverlogfile, "ab");
if(logfp) { if(logfp) {
fprintf(logfp, "%s %s\n", timebuf, buffer); fprintf(logfp, "%s %s\n", timebuf, buffer);
fclose(logfp); fclose(logfp);
@ -221,7 +221,7 @@ int write_pidfile(const char *filename)
long pid; long pid;
pid = (long)getpid(); pid = (long)getpid();
pidfile = fopen(filename, "w"); pidfile = fopen(filename, "wb");
if(!pidfile) { if(!pidfile) {
logmsg("Couldn't write pid file: %s %s", filename, strerror(ERRNO)); logmsg("Couldn't write pid file: %s %s", filename, strerror(ERRNO));
return 0; /* fail */ return 0; /* fail */