[pve-devel] [PATCH common v2] Add run_fork_with_timeout utility

Emmanuel Kasper e.kasper at proxmox.com
Wed Jun 28 12:04:28 CEST 2017


This runs subroutine in a forked process
and kills it after a timeout
---
changes since V1:
 * add a second pipe to catch die() causes in the passed sub
 * disable pending alarms until we reaped the child
 * disable signals until we reaped the child ( both to avoid Zombies)
 * rename the subroutine to run_fork_with_timeout as suggested

NB: I have my own test suite for this but it requires an external NFS
server to be remotely stopped. Not sure if we want to have this in git.

 src/PVE/Tools.pm | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 65 insertions(+)

diff --git a/src/PVE/Tools.pm b/src/PVE/Tools.pm
index da7da5d..a48ff0a 100644
--- a/src/PVE/Tools.pm
+++ b/src/PVE/Tools.pm
@@ -841,6 +841,71 @@ sub next_spice_port {
     return next_unused_port(61000, 61099, $family, $address);
 }
 
+# sigkill after $timeout  a $sub running in a fork if it can't write a pipe
+# the $sub has to return a single scalar
+sub run_fork_with_timeout {
+    my ($timeout, $sub) = @_;
+
+    my $res;
+    my $error;
+    my $pipe_out = IO::Pipe->new();
+    my $pipe_err = IO::Pipe->new();
+
+    # disable pending alarms, save their remaining time
+    my $prev_alarm = alarm 0;
+
+    # trap before forking to avoid leaving a zombie if the parent get killed
+    my $sig_received;
+    local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {
+	$sig_received++;
+    };
+
+    my $child = fork();
+    if (!defined($child)) {
+	die "fork failed: $!\n";
+	return $res;
+    }
+
+    if (!$child) {
+	$pipe_out->writer();
+	eval {
+	    $res = $sub->();
+	    print {$pipe_out} "$res";
+	    $pipe_out->close();
+	};
+	if (my $err = $@) {
+	    $pipe_err->writer();
+	    print {$pipe_err} "$err";
+	    $pipe_err->close();
+	    POSIX::_exit(1);
+	}
+	POSIX::_exit(0);
+    }
+
+    $pipe_out->reader();
+    $pipe_err->reader();
+
+    my $readvalues = sub {
+	local $/ = undef;
+	$res = <$pipe_out>;
+	$error = <$pipe_err>;
+    };
+    eval {
+	run_with_timeout($timeout, $readvalues);
+    };
+    warn $@ if $@;
+    $pipe_out->close();
+    $pipe_err->close();
+    kill('KILL', $child);
+    waitpid($child, 0);
+
+    alarm $prev_alarm;
+    die "interrupted by unexpected signal\n" if $sig_received;
+
+    die $error if $error;
+    return $res;
+}
+
 # NOTE: NFS syscall can't be interrupted, so alarm does
 # not work to provide timeouts.
 # from 'man nfs': "Only SIGKILL can interrupt a pending NFS operation"
-- 
2.11.0





More information about the pve-devel mailing list