2
# Copyright (c) 2007, 2011, Oracle and/or its affiliates.
3
# Copyright (c) 2009, 2011 Monty Program Ab
5
# This program is free software; you can redistribute it and/or
6
# modify it under the terms of the GNU Library General Public
7
# License as published by the Free Software Foundation; version 2
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
# Library General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19
package My::SafeProcess;
22
# Class that encapsulates process creation, monitoring and cleanup
24
# Spawns a monitor process which spawns a new process locally or
25
# remote using subclasses My::Process::Local or My::Process::Remote etc.
27
# The monitor process runs a simple event loop more or less just
28
# waiting for a reason to zap the process it monitors. Thus the user
29
# of this class does not need to care about process cleanup, it's
30
# handled automatically.
32
# The monitor process wait for:
33
# - the parent process to close the pipe, in that case it
34
# will zap the "monitored process" and exit
35
# - the "monitored process" to exit, in which case it will exit
36
# itself with same exit code as the "monitored process"
37
# - the parent process to send the "shutdown" signal in wich case
38
# monitor will kill the "monitored process" hard and exit
41
# When used it will look something like this:
44
# - [monitor for `mysqld`]
46
# - [monitor for `mysqld`]
48
# - [monitor for `mysqld`]
55
use POSIX qw(WNOHANG);
57
use My::SafeProcess::Base;
58
use base 'My::SafeProcess::Base';
68
# Kill any children still running
69
for my $proc (values %running){
70
if ( $proc->is_child($$) and ! $start_exit){
71
#print "Killing: $proc\n";
72
if ($proc->wait_one(0)){
81
my ($self, $parent_pid)= @_;
82
croak "usage: \$safe_proc->is_child()" unless (@_ == 2 and ref $self);
83
return ($self->{PARENT} == $parent_pid);
91
if(defined $ENV{MTR_BINDIR})
93
# This is an out-of-source build. Build directory
94
# is given in MTR_BINDIR env.variable
95
$bindir = $ENV{MTR_BINDIR}."/mysql-test";
103
# Find the safe process binary or script
105
if (IS_WIN32PERL or IS_CYGWIN)
107
# Use my_safe_process.exe
108
my $exe= my_find_bin($bindir, ["lib/My/SafeProcess", "My/SafeProcess"],
110
push(@safe_process_cmd, $exe);
112
# Use my_safe_kill.exe
113
$safe_kill= my_find_bin($bindir, "lib/My/SafeProcess", "my_safe_kill");
117
# Use my_safe_process
118
my $exe= my_find_bin($bindir, ["lib/My/SafeProcess", "My/SafeProcess"],
120
push(@safe_process_cmd, $exe);
134
my $path = delete($opts{'path'}) or croak "path required @_";
135
my $args = delete($opts{'args'}) or croak "args required @_";
136
my $input = delete($opts{'input'});
137
my $output = delete($opts{'output'});
138
my $error = delete($opts{'error'});
139
my $verbose = delete($opts{'verbose'}) || $::opt_verbose;
140
my $nocore = delete($opts{'nocore'});
141
my $host = delete($opts{'host'});
142
my $shutdown = delete($opts{'shutdown'});
143
my $user_data= delete($opts{'user_data'});
144
my $envs = delete($opts{'envs'});
146
# if (defined $host) {
147
# $safe_script= "lib/My/SafeProcess/safe_process_cpcd.pl";
151
$path= mixed_path($path);
152
$input= mixed_path($input);
153
$output= mixed_path($output);
154
$error= mixed_path($error);
158
my ($safe_path, $safe_script)= @safe_process_cmd;
159
push(@safe_args, $safe_script) if defined $safe_script;
161
push(@safe_args, "--verbose") if $verbose > 0;
162
push(@safe_args, "--nocore") if $nocore;
164
# Point the safe_process at the right parent if running on cygwin
165
push(@safe_args, "--parent-pid=".Cygwin::pid_to_winpid($$)) if IS_CYGWIN;
167
foreach my $env_var (@$envs) {
168
croak("Missing = in env string") unless $env_var =~ /=/;
169
croak("Env string $env_var seen, probably missing value for --mysqld-env")
170
if $env_var =~ /^--/;
171
push @safe_args, "--env $env_var";
174
push(@safe_args, "--");
175
push(@safe_args, $path); # The program safe_process should execute
177
if ($start_exit) { # Bypass safe_process instead, start program directly
181
push(@safe_args, @$$args);
183
print "### safe_path: ", $safe_path, " ", join(" ", @safe_args), "\n"
186
my $pid= create_process(
191
append => $opts{append},
195
my $name = delete($opts{'name'}) || "SafeProcess$pid";
199
SAFE_WINPID => $pid, # Inidicates this is always a real process
201
SAFE_SHUTDOWN => $shutdown,
203
SAFE_USER_DATA => $user_data,
206
# Put the new process in list of running
207
$running{$pid}= $proc;
216
return $proc->exit_status();
220
# Shutdown process nicely, and wait for shutdown_timeout seconds
221
# If processes hasn't shutdown, kill them hard and wait for return
224
my $shutdown_timeout= shift;
226
_verbose("shutdown, timeout: $shutdown_timeout, @processes");
228
return if (@processes == 0);
230
# Call shutdown function if process has one, else
232
foreach my $proc (@processes){
233
_verbose(" proc: $proc");
234
my $shutdown= $proc->{SAFE_SHUTDOWN};
235
if ($shutdown_timeout > 0 and defined $shutdown){
237
$proc->{WAS_SHUTDOWN}= 1;
244
my @kill_processes= ();
246
# Wait max shutdown_timeout seconds for those process
247
# that has been shutdown
248
foreach my $proc (@processes){
249
next unless $proc->{WAS_SHUTDOWN};
250
my $ret= $proc->wait_one($shutdown_timeout);
252
push(@kill_processes, $proc);
254
# Only wait for the first process with shutdown timeout
255
$shutdown_timeout= 0;
258
# Wait infinitely for those process
259
# that has been killed
260
foreach my $proc (@processes){
261
next if $proc->{WAS_SHUTDOWN};
262
my $ret= $proc->wait_one(undef);
264
warn "Wait for killed process failed!";
265
push(@kill_processes, $proc);
266
# Try one more time, best option...
270
# Return if all servers has exited
271
return if (@kill_processes == 0);
273
foreach my $proc (@kill_processes){
277
foreach my $proc (@kill_processes){
278
$proc->wait_one(undef);
288
# In win32 perl, the pid is already the winpid
289
return $pid unless IS_CYGWIN;
291
# In cygwin, the pid is the pseudo process ->
292
# get the real winpid of my_safe_process
293
return Cygwin::pid_to_winpid($pid);
298
# Tell the process to die as fast as possible
302
croak "usage: \$safe_proc->start_kill()" unless (@_ == 1 and ref $self);
303
_verbose("start_kill: $self");
306
my $pid= $self->{SAFE_PID};
307
die "INTERNAL ERROR: no pid" unless defined $pid;
309
if (IS_WINDOWS and defined $self->{SAFE_WINPID})
311
die "INTERNAL ERROR: no safe_kill" unless defined $safe_kill;
313
my $winpid= _winpid($pid);
314
$ret= system($safe_kill, $winpid) >> 8;
317
print "Couldn't open the winpid: $winpid ".
318
"for pid: $pid, try one more time\n";
320
$winpid= _winpid($pid);
321
$ret= system($safe_kill, $winpid) >> 8;
322
print "Couldn't open the winpid: $winpid ".
323
"for pid: $pid, continue and see what happens...\n";
328
$pid= $self->{SAFE_PID};
329
die "Can't kill not started process" unless defined $pid;
330
$ret= kill("TERM", $pid);
339
return if IS_WINDOWS;
340
my $pid= $self->{SAFE_PID};
341
die "Can't cet core from not started process" unless defined $pid;
342
_verbose("Sending ABRT to $self");
349
# Kill the process as fast as possible
350
# and wait for it to return
354
croak "usage: \$safe_proc->kill()" unless (@_ == 1 and ref $self);
363
my ($self, $exit_code)= @_;
365
$self->{EXIT_STATUS}= $exit_code;
366
_verbose("_collect: $self");
368
# Take the process out of running list
369
my $pid= $self->{SAFE_PID};
370
die unless delete($running{$pid});
374
# Wait for process to exit
375
# optionally with a timeout
378
# undef -> wait blocking infinitely
379
# 0 -> just poll with WNOHANG
380
# >0 -> wait blocking for max timeout seconds
387
my ($self, $timeout)= @_;
388
croak "usage: \$safe_proc->wait_one([timeout])" unless ref $self;
390
_verbose("wait_one $self, $timeout");
392
if ( ! defined($self->{SAFE_PID}) ) {
393
# No pid => not running
394
_verbose("No pid => not running");
398
if ( defined $self->{EXIT_STATUS} ) {
399
# Exit status already set => not running
400
_verbose("Exit status already set => not running");
404
my $pid= $self->{SAFE_PID};
408
if (defined $timeout)
412
# 0 -> just poll with WNOHANG
418
# >0 -> wait blocking for max timeout seconds
425
# undef -> wait blocking infinitely
429
#_verbose("blocking: $blocking, use_alarm: $use_alarm");
435
# alarm should break the wait
436
local $SIG{ALRM}= sub { die "waitpid timeout"; };
438
alarm($timeout) if $use_alarm;
440
$retpid= waitpid($pid, $blocking ? 0 : &WNOHANG);
443
alarm(0) if $use_alarm;
448
die "Got unexpected: $@" if ($@ !~ /waitpid timeout/);
449
if (!defined $retpid) {
451
_verbose("Got timeout");
454
# Got pid _and_ alarm, continue
455
_verbose("Got pid and alarm, continue");
458
if ( $retpid == 0 ) {
460
_verbose("0 => still running");
464
if ( not $blocking and $retpid == -1 ) {
466
_verbose("still running");
470
#warn "wait_one: expected pid $pid but got $retpid"
471
# unless( $retpid == $pid );
473
$self->_collect($exit_code);
479
# Wait for any process to exit
481
# Returns a reference to the SafeProcess that
482
# exited or undefined
489
# Can't wait for -1 => use a polling loop
491
Win32::Sleep(10); # 10 milli seconds
492
foreach my $pid (keys %running){
493
$ret_pid= waitpid($pid, &WNOHANG);
494
last if $pid == $ret_pid;
496
} while ($ret_pid == 0);
501
$ret_pid= waitpid(-1, 0);
503
# No more processes to wait for
504
print STDERR "wait_any, got invalid pid: $ret_pid\n";
510
# Look it up in "running" table
511
my $proc= $running{$ret_pid};
512
unless (defined $proc){
513
print STDERR "Could not find pid: $ret_pid in running list\n";
514
print STDERR "running: ". join(", ", keys(%running)). "\n";
517
$proc->_collect($exit_code);
523
# Wait for any process to exit, or a timeout
525
# Returns a reference to the SafeProcess that
526
# exited or a pseudo-process with $proc->{timeout} == 1
529
sub wait_any_timeout {
536
::mtr_milli_sleep($millis);
537
# Slowly increse interval up to max. 1 second
538
$millis++ if $millis < 1000;
539
# Return a "fake" process for timeout
540
if (::has_expired($timeout)) {
544
SAFE_NAME => "timer",
557
# Wait for all processes to exit
567
# Set global flag to tell all safe_process to exit after starting child
575
# Check if any process has exited, but don't wait.
577
# Returns a reference to the SafeProcess that
578
# exited or undefined
581
for my $proc (values %running){
582
if ( $proc->is_child($$) ) {
583
if (not $proc->wait_one(0)) {
584
_verbose ("Found exited $proc");
593
# Overload string operator
594
# and fallback to default functions if no
595
# overloaded function is found
603
# Return the process as a nicely formatted string
607
my $pid= $self->{SAFE_PID};
608
my $winpid= $self->{SAFE_WINPID};
609
my $name= $self->{SAFE_NAME};
610
my $exit_status= $self->{EXIT_STATUS};
612
my $str= "[$name - pid: $pid";
613
$str.= ", winpid: $winpid" if defined $winpid;
614
$str.= ", exit: $exit_status" if defined $exit_status;
619
return unless $_verbose;
620
print STDERR " ## ". @_. "\n";
626
return $self->{SAFE_PID};
631
return $self->{SAFE_USER_DATA};