summaryrefslogtreecommitdiff
path: root/Porting/bisect-runner.pl
diff options
context:
space:
mode:
authorNicholas Clark <nick@ccl4.org>2012-07-31 18:54:24 +0200
committerNicholas Clark <nick@ccl4.org>2013-05-28 09:19:29 +0200
commit1fdd0dcc48757998b9a4aea3f011e35a5bfb262a (patch)
tree91fb08de0e8d884ee65b9b0b6cea49672d14e9d4 /Porting/bisect-runner.pl
parentb9dcd8de22bd198ab6c928d76949419e8c24c131 (diff)
downloadperl-1fdd0dcc48757998b9a4aea3f011e35a5bfb262a.tar.gz
bisect.pl can now optionally timeout the user's test case.
This permits bisection to locate the cause (or cure) of bugs which cause programs to hang. When using a timeout, bisect-runner.pl defaults to running the test case in its own process group, and tries hard to ensure that all processes in that process group are killed if the timeout fires.
Diffstat (limited to 'Porting/bisect-runner.pl')
-rwxr-xr-xPorting/bisect-runner.pl63
1 files changed, 57 insertions, 6 deletions
diff --git a/Porting/bisect-runner.pl b/Porting/bisect-runner.pl
index 110d67c72f..83d0a87c24 100755
--- a/Porting/bisect-runner.pl
+++ b/Porting/bisect-runner.pl
@@ -60,7 +60,7 @@ unless(GetOptions(\%options,
$options{match} = $_[1];
$options{'expect-pass'} = 0;
},
- 'force-manifest', 'force-regen', 'setpgrp!',
+ 'force-manifest', 'force-regen', 'setpgrp!', 'timeout=i',
'test-build', 'validate',
'all-fixups', 'early-fixup=s@', 'late-fixup=s@', 'valgrind',
'check-args', 'check-shebang!', 'usage|help|?', 'gold=s',
@@ -488,10 +488,18 @@ C<--expect-pass=0> is equivalent to C<--expect-fail>. I<1> is the default.
=item *
+--timeout I<seconds>
+
+Run the testcase with the given timeout. If this is exceeded, kill it (and
+by default all its children), and treat it as a failure.
+
+=item *
+
--setpgrp
Run the testcase in its own process group. Specifically, call C<setpgrp 0, 0>
-just before C<exec>-ing the user testcase.
+just before C<exec>-ing the user testcase. The default is not to set the
+process group, unless a timeout is used.
=item *
@@ -724,22 +732,63 @@ sub run_with_options {
my $name = $options->{name};
$name = "@_" unless defined $name;
+ my $setgrp = $options->{setpgrp};
+ if ($options->{timeout}) {
+ # Unless you explicitly disabled it on the commandline, set it:
+ $setgrp = 1 unless defined $setgrp;
+ }
my $pid = fork;
die_255("Can't fork: $!") unless defined $pid;
if (!$pid) {
if (exists $options->{stdin}) {
open STDIN, '<', $options->{stdin}
- or die "Can't open STDIN from $options->{stdin}: $!";
+ or die "Can't open STDIN from $options->{stdin}: $!";
}
- if ($options->{setpgrp}) {
+ if ($setgrp) {
setpgrp 0, 0
or die "Can't setpgrp 0, 0: $!";
}
{ exec @_ };
die_255("Failed to start $name: $!");
}
+ my $start;
+ if ($options->{timeout}) {
+ require Errno;
+ require POSIX;
+ die_255("No POSIX::WNOHANG")
+ unless &POSIX::WNOHANG;
+ $start = time;
+ $SIG{ALRM} = sub {
+ my $victim = $setgrp ? -$pid : $pid;
+ my $delay = 1;
+ kill 'TERM', $victim;
+ waitpid(-1, &POSIX::WNOHANG);
+ while (kill 0, $victim) {
+ sleep $delay;
+ waitpid(-1, &POSIX::WNOHANG);
+ $delay *= 2;
+ if ($delay > 8) {
+ if (kill 'KILL', $victim) {
+ print STDERR "$0: Had to kill 'KILL', $victim\n"
+ } elsif (! $!{ESRCH}) {
+ print STDERR "$0: kill 'KILL', $victim failed: $!\n";
+ }
+ last;
+ }
+ }
+ report_and_exit(0, 'No timeout', 'Timeout', "when running $name");
+ };
+ alarm $options->{timeout};
+ }
waitpid $pid, 0
- or die_255("wait for $name, pid $pid failed: $!");
+ or die_255("wait for $name, pid $pid failed: $!");
+ alarm 0;
+ if ($options->{timeout}) {
+ my $elapsed = time - $start;
+ if ($elapsed / $options->{timeout} > 0.8) {
+ print STDERR "$0: Beware, took $elapsed seconds of $options->{timeout} permitted to run $name\n";
+ }
+ }
return $?;
}
@@ -1015,7 +1064,9 @@ sub report_and_exit {
}
sub run_report_and_exit {
- my $ret = run_with_options({setprgp => $options{setpgrp}}, @_);
+ my $ret = run_with_options({setprgp => $options{setpgrp},
+ timeout => $options{timeout},
+ }, @_);
report_and_exit(!$ret, 'zero exit from', 'non-zero exit from', "@_");
}