summaryrefslogtreecommitdiff
path: root/mysql-test/lib/My/SafeProcess.pm
diff options
context:
space:
mode:
Diffstat (limited to 'mysql-test/lib/My/SafeProcess.pm')
-rw-r--r--mysql-test/lib/My/SafeProcess.pm502
1 files changed, 502 insertions, 0 deletions
diff --git a/mysql-test/lib/My/SafeProcess.pm b/mysql-test/lib/My/SafeProcess.pm
new file mode 100644
index 00000000000..7e1f699ee25
--- /dev/null
+++ b/mysql-test/lib/My/SafeProcess.pm
@@ -0,0 +1,502 @@
+# -*- cperl -*-
+# Copyright (C) 2004-2006 MySQL AB
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+package My::SafeProcess;
+
+#
+# Class that encapsulates process creation, monitoring and cleanup
+#
+# Spawns a monitor process which spawns a new process locally or
+# remote using subclasses My::Process::Local or My::Process::Remote etc.
+#
+# The monitor process runs a simple event loop more or less just
+# waiting for a reason to zap the process it monitors. Thus the user
+# of this class does not need to care about process cleanup, it's
+# handled automatically.
+#
+# The monitor process wait for:
+# - the parent process to close the pipe, in that case it
+# will zap the "monitored process" and exit
+# - the "monitored process" to exit, in which case it will exit
+# itself with same exit code as the "monitored process"
+# - the parent process to send the "shutdown" signal in wich case
+# monitor will kill the "monitored process" hard and exit
+#
+#
+# When used it will look something like this:
+# $> ps
+# [script.pl]
+# - [monitor for `mysqld`]
+# - [mysqld]
+# - [monitor for `mysqld`]
+# - [mysqld]
+# - [monitor for `mysqld`]
+# - [mysqld]
+#
+#
+
+use strict;
+
+use POSIX qw(WNOHANG);
+
+use My::SafeProcess::Base;
+use base 'My::SafeProcess::Base';
+
+use My::Find;
+
+my %running;
+
+BEGIN {
+ if ($^O eq "MSWin32") {
+ eval 'sub IS_WIN32PERL () { 1 }';
+ }
+ else {
+ eval 'sub IS_WIN32PERL () { 0 }';
+ }
+ if ($^O eq "cygwin") {
+ eval 'sub IS_CYGWIN () { 1 }';
+ # Make sure cygpath works
+ if ((system("cygpath > /dev/null 2>&1") >> 8) != 1){
+ die "Could not execute 'cygpath': $!";
+ }
+ eval 'sub fixpath {
+ my ($path)= @_;
+ return unless defined $path;
+ $path= `cygpath -w $path`;
+ chomp $path;
+ return $path;
+ }';
+ }
+ else {
+ eval 'sub IS_CYGWIN () { 0 }';
+ }
+}
+
+# Find the safe process binary or script
+my $safe_path= $^X; # Path to perl binary
+my $safe_script;
+my $safe_kill;
+if (IS_WIN32PERL or IS_CYGWIN){
+ # Use my_safe_process.exe
+ $safe_path= my_find_bin(("extra","bin"), "my_safe_process.exe");
+ die "Could not find my_safe_process.exe" unless $safe_path;
+
+ # Use my_safe_kill.exe
+ $safe_path= my_find_bin(("extra","bin"), "my_safe_kill");
+ die "Could not find my_safe_kill.exe" unless $safe_kill;
+}
+else {
+ # Use safe_process.pl
+ $safe_script= "lib/My/SafeProcess/safe_process.pl";
+ $safe_script= "../$safe_script" unless -f $safe_script;
+ die "Could not find safe_process.pl" unless -f $safe_script;
+}
+
+
+sub new {
+ my $class= shift;
+
+ my %opts=
+ (
+ verbose => 0,
+ @_
+ );
+
+ my $path = delete($opts{'path'}) or die "path required";
+ my $args = delete($opts{'args'}) or die "args required";
+ my $input = delete($opts{'input'});
+ my $output = delete($opts{'output'});
+ my $error = delete($opts{'error'});
+ my $verbose = delete($opts{'verbose'});
+ my $host = delete($opts{'host'});
+ my $shutdown = delete($opts{'shutdown'});
+
+ if (defined $host) {
+ $safe_script= "lib/My/SafeProcess/safe_process_cpcd.pl";
+ }
+
+ if (IS_CYGWIN){
+ # safe_procss is a windows program and need
+ # windows paths
+ $path= fixpath($path);
+ $input= fixpath($input);
+ $output= fixpath($output);
+ $error= fixpath($error);
+ }
+
+ my @safe_args;
+ push(@safe_args, $safe_script) if defined $safe_script;
+
+ push(@safe_args, "--verbose") if $verbose > 0;
+
+ # Point the safe_process at the right parent if running on cygwin
+ push(@safe_args, "--parent-pid=".Cygwin::pid_to_winpid($$)) if IS_CYGWIN;
+
+ push(@safe_args, "--");
+ push(@safe_args, $path); # The program safe_process should execute
+ push(@safe_args, @$$args);
+
+ print "### safe_path: ", $safe_path, " ", join(" ", @safe_args), "\n"
+ if $verbose > 1;
+
+ my ($pid, $winpid)= create_process(
+ path => $safe_path,
+ input => $input,
+ output => $output,
+ error => $error,
+ append => $opts{append},
+ args => \@safe_args,
+ );
+
+ my $name = delete($opts{'name'}) || "SafeProcess$pid";
+ my $proc= bless
+ ({
+ SAFE_PID => $pid,
+ SAFE_WINPID => $winpid,
+ SAFE_NAME => $name,
+ SAFE_SHUTDOWN => $shutdown,
+ }, $class);
+
+ # Put the new process in list of running
+ $running{$pid}= $proc;
+ return $proc;
+
+}
+
+
+sub run {
+ my $proc= new(@_);
+ $proc->wait_one();
+ return $proc->exit_status();
+}
+
+#
+# Start a process that returns after "duration" seconds
+# or when it's parent process does not exist anymore
+#
+sub timer {
+ my $class= shift;
+ my $duration= shift or die "duration required";
+ my $parent_pid= $$;
+
+ my $pid= My::SafeProcess::Base::_safe_fork();
+ if ($pid){
+ # Parent
+ my $proc= bless
+ ({
+ SAFE_PID => $pid,
+ SAFE_NAME => "timer",
+ }, $class);
+
+ # Put the new process in list of running
+ $running{$pid}= $proc;
+ return $proc;
+ }
+
+ # Child, install signal handlers and sleep for "duration"
+ $SIG{INT}= 'DEFAULT';
+
+ $SIG{TERM}= sub {
+ #print STDERR "timer $$: woken up, exiting!\n";
+ exit(0);
+ };
+
+ $0= "safe_timer($duration)";
+ my $count_down= $duration;
+ while($count_down--){
+
+ # Check that parent is still alive
+ if (kill(0, $parent_pid) == 0){
+ #print STDERR "timer $$: parent gone, exiting!\n";
+ exit(0);
+ }
+
+ sleep(1);
+ }
+ print STDERR "timer $$: expired after $duration seconds\n";
+ exit(0);
+}
+
+
+#
+# Shutdown process nicely, and wait for shutdown_timeout seconds
+# If processes hasn't shutdown, kill them hard and wait for return
+#
+sub shutdown {
+ my $shutdown_timeout= shift;
+ my @processes= @_;
+
+ return if (@processes == 0);
+
+ #print "shutdown: @processes\n";
+
+ # Call shutdown function if process has one, else
+ # use kill
+ foreach my $proc (@processes){
+ my $shutdown= $proc->{SAFE_SHUTDOWN};
+ if ($shutdown_timeout > 0 and defined $shutdown){
+ $shutdown->();
+ }
+ else {
+ $proc->start_kill();
+ }
+ }
+
+ my @kill_processes= ();
+
+ # Wait for shutdown_timeout for processes to exit
+ foreach my $proc (@processes){
+ my $ret= $proc->wait_one($shutdown_timeout);
+ if ($ret != 0) {
+ push(@kill_processes, $proc);
+ }
+ # Only wait for the first process with shutdown timeout
+ $shutdown_timeout= 0;
+ }
+
+ # Return if all servers has exited
+ return if (@kill_processes == 0);
+
+ foreach my $proc (@kill_processes){
+ $proc->start_kill();
+ }
+
+ foreach my $proc (@kill_processes){
+ $proc->wait_one();
+ }
+ return;
+}
+
+
+#
+# Tell the process to die as fast as possible
+#
+sub start_kill {
+ my ($self)= @_;
+ die "usage: \$safe_proc->start_kill()" unless (@_ == 1 and ref $self);
+ #print "start_kill $self\n";
+
+ if (defined $safe_kill and $self->{SAFE_WINPID}){
+ # Use my_safe_kill to tell my_safe_process
+ # it's time to kill it's child and return
+ my $pid= $self->{SAFE_WINPID};
+ my $ret= system($safe_kill, $pid);
+ #print STDERR "start_kill, safe_killed $pid, ret: $ret\n";
+ } else {
+ my $pid= $self->{SAFE_PID};
+ die "Can't kill not started process" unless defined $pid;
+ my $ret= kill(15, $pid);
+ #print STDERR "start_kill, sent signal 15 to $pid, ret: $ret\n";
+ }
+ return 1;
+}
+
+
+#
+# Kill the process as fast as possible
+# and wait for it to return
+#
+sub kill {
+ my ($self)= @_;
+ die "usage: \$safe_proc->kill()" unless (@_ == 1 and ref $self);
+
+ $self->start_kill();
+ $self->wait_one();
+ return 1;
+}
+
+
+sub _collect {
+ my ($self)= @_;
+
+ #print "_collect\n";
+ $self->{EXIT_STATUS}= $?;
+
+ # Take the process out of running list
+ my $pid= $self->{SAFE_PID};
+ die unless delete($running{$pid});
+}
+
+
+# Wait for process to exit
+# optionally with a timeout
+#
+# timeout
+# undef -> wait blocking infinitely
+# 0 -> just poll with WNOHANG
+# >0 -> wait blocking for max timeout seconds
+#
+# RETURN VALUES
+# 0 Not running
+# 1 Still running
+#
+sub wait_one {
+ my ($self, $timeout)= @_;
+ die "usage: \$safe_proc->wait_one([timeout])" unless ref $self;
+
+ #print "wait_one $self, $timeout\n";
+
+ if ( ! defined($self->{SAFE_PID}) ) {
+ # No pid => not running
+ return 0;
+ }
+
+ if ( defined $self->{EXIT_STATUS} ) {
+ # Exit status already set => not running
+ return 0;
+ }
+
+ my $pid= $self->{SAFE_PID};
+
+ my $use_alarm;
+ my $blocking;
+ if (defined $timeout)
+ {
+ if ($timeout == 0)
+ {
+ # 0 -> just poll with WNOHANG
+ $blocking= 0;
+ $use_alarm= 0;
+ }
+ else
+ {
+ # >0 -> wait blocking for max timeout seconds
+ $blocking= 1;
+ $use_alarm= 1;
+ }
+ }
+ else
+ {
+ # undef -> wait blocking infinitely
+ $blocking= 1;
+ $use_alarm= 0;
+ }
+
+ my $retpid;
+ eval
+ {
+ # alarm should break the wait
+ local $SIG{ALRM}= sub { die "waitpid timeout"; };
+
+ alarm($timeout) if $use_alarm;
+
+ $retpid= waitpid($pid, $blocking ? 0 : &WNOHANG);
+
+ alarm(0) if $use_alarm;
+ };
+
+ if ($@)
+ {
+ die "Got unexpected: $@" if ($@ !~ /waitpid timeout/);
+ if (!defined $retpid) {
+ # Got timeout
+ return 1;
+ }
+ # Got pid _and_ alarm, continue
+ }
+
+ if ( $retpid == 0 ) {
+ # 0 => still running
+ return 1;
+ }
+
+ if ( not $blocking and $retpid == -1 ) {
+ # still running
+ return 1;
+ }
+
+ warn "wait_one: expected pid $pid but got $retpid"
+ unless( $retpid == $pid );
+
+ $self->_collect();
+ return 0;
+}
+
+
+#
+# Wait for any process to exit
+#
+# Returns a reference to the SafeProcess that
+# exited or undefined
+#
+sub wait_any {
+ my $ret_pid;
+ if (IS_WIN32PERL) {
+ # Can't wait for -1 => use a polling loop
+ do {
+ Win32::Sleep(10); # 10 milli seconds
+ foreach my $pid (keys %running){
+ $ret_pid= waitpid($pid, &WNOHANG);
+ last if $pid == $ret_pid;
+ }
+ } while ($ret_pid == 0);
+
+ # Special processig of return code
+ # since negative pids are valid
+ if ($ret_pid == 0 or $ret_pid == -1) {
+ print STDERR "wait_any, got invalid pid: $ret_pid\n";
+ return undef;
+ }
+ }
+ else
+ {
+ $ret_pid= waitpid(-1, 0);
+ if ($ret_pid <= 0){
+ # No more processes to wait for
+ print STDERR "wait_any, got invalid pid: $ret_pid\n";
+ return undef;
+ }
+ }
+
+ # Look it up in "running" table
+ my $proc= $running{$ret_pid};
+ unless (defined $proc){
+ print STDERR "Could not find pid in running list\n";
+ print STDERR "running: ". join(", ", keys(%running)). "\n";
+ return undef;
+ }
+ $proc->_collect;
+ return $proc;
+}
+
+#
+# Overload string operator
+# and fallback to default functions if no
+# overloaded function is found
+#
+use overload
+ '""' => \&self2str,
+ fallback => 1;
+
+
+#
+# Return the process as a nicely formatted string
+#
+sub self2str {
+ my ($self)= @_;
+ my $pid= $self->{SAFE_PID};
+ my $winpid= $self->{SAFE_WINPID};
+ my $name= $self->{SAFE_NAME};
+ my $exit_status= $self->{EXIT_STATUS};
+
+ my $str= "[$name - pid: $pid";
+ $str.= ", winpid: $winpid" if defined $winpid;
+ $str.= ", exit: $exit_status" if defined $exit_status;
+ $str.= "]";
+}
+
+
+1;