diff options
-rw-r--r-- | .bzrignore | 1 | ||||
-rw-r--r-- | configure.in | 2 | ||||
-rw-r--r-- | mysql-test/Makefile.am | 2 | ||||
-rw-r--r-- | mysql-test/lib/My/SafeProcess.pm | 35 | ||||
-rw-r--r-- | mysql-test/lib/My/SafeProcess/Makefile.am | 21 | ||||
-rw-r--r-- | mysql-test/lib/My/SafeProcess/safe_process.cc | 267 | ||||
-rwxr-xr-x | mysql-test/lib/t/SafeProcessStress.pl | 4 |
7 files changed, 318 insertions, 14 deletions
diff --git a/.bzrignore b/.bzrignore index 7a8066fdba1..7721217165b 100644 --- a/.bzrignore +++ b/.bzrignore @@ -3005,3 +3005,4 @@ win/vs71cache.txt win/vs8cache.txt zlib/*.ds? zlib/*.vcproj +mysql-test/lib/My/SafeProcess/my_safe_process diff --git a/configure.in b/configure.in index 34e22562e1f..dafcf1f067c 100644 --- a/configure.in +++ b/configure.in @@ -2680,7 +2680,7 @@ AC_CONFIG_FILES(Makefile extra/Makefile mysys/Makefile dnl server-tools/Makefile server-tools/instance-manager/Makefile dnl cmd-line-utils/Makefile cmd-line-utils/libedit/Makefile dnl libmysqld/Makefile libmysqld/examples/Makefile dnl - mysql-test/Makefile dnl + mysql-test/Makefile mysql-test/lib/My/SafeProcess/Makefile dnl netware/Makefile sql-bench/Makefile dnl include/mysql_version.h plugin/Makefile win/Makefile) diff --git a/mysql-test/Makefile.am b/mysql-test/Makefile.am index 13bcdbe73cf..b9ed71902f0 100644 --- a/mysql-test/Makefile.am +++ b/mysql-test/Makefile.am @@ -29,6 +29,8 @@ EXTRA_DIST = README \ test_SCRIPTS = mtr mysql-test-run CLEANFILES = $(test_SCRIPTS) +SUBDIRS = lib/My/SafeProcess + # Install all files and files in directories listed in EXTRA_DIST install-data-local: diff --git a/mysql-test/lib/My/SafeProcess.pm b/mysql-test/lib/My/SafeProcess.pm index 612096cceb7..ef3ba9146d1 100644 --- a/mysql-test/lib/My/SafeProcess.pm +++ b/mysql-test/lib/My/SafeProcess.pm @@ -90,22 +90,35 @@ my @safe_process_cmd; my $safe_kill; if (IS_WIN32PERL or IS_CYGWIN){ # Use my_safe_process.exe - my $exe= my_find_bin(".", "lib/My/SafeProcess", "my_safe_process.exe"); - die "Could not find my_safe_process.exe" unless $exe; + my $exe= my_find_bin(".", ["lib/My/SafeProcess", "My/SafeProcess"], + "my_safe_process"); + die "Could not find my_safe_process" unless $exe; push(@safe_process_cmd, $exe); # Use my_safe_kill.exe my $safe_kill= my_find_bin(".", "lib/My/SafeProcess", "my_safe_kill"); - die "Could not find my_safe_kill.exe" unless $safe_kill; + die "Could not find my_safe_kill" unless $safe_kill; } -else { - # Use safe_process.pl - my $script= "lib/My/SafeProcess/safe_process.pl"; - $script= "../$script" unless -f $script; - die "Could not find safe_process.pl" unless -f $script; - - # Call $script with Perl interpreter - push(@safe_process_cmd, $^X, $script); +else +{ + my $use_safe_process_binary= 1; + if ($use_safe_process_binary) { + # Use my_safe_process + my $exe= my_find_bin(".", ["lib/My/SafeProcess", "My/SafeProcess"], + "my_safe_process"); + die "Could not find my_safe_process" unless $exe; + push(@safe_process_cmd, $exe); + } + else + { + # Use safe_process.pl + my $script= "lib/My/SafeProcess/safe_process.pl"; + $script= "../$script" unless -f $script; + die "Could not find safe_process.pl" unless -f $script; + + # Call $script with Perl interpreter + push(@safe_process_cmd, $^X, $script); + } } diff --git a/mysql-test/lib/My/SafeProcess/Makefile.am b/mysql-test/lib/My/SafeProcess/Makefile.am new file mode 100644 index 00000000000..6ad43949b11 --- /dev/null +++ b/mysql-test/lib/My/SafeProcess/Makefile.am @@ -0,0 +1,21 @@ +# Copyright (C) 2000-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +bin_PROGRAMS = my_safe_process + +my_safe_process_SOURCES = safe_process.cc + +# Don't update the files from bitkeeper +%::SCCS/s.% diff --git a/mysql-test/lib/My/SafeProcess/safe_process.cc b/mysql-test/lib/My/SafeProcess/safe_process.cc new file mode 100644 index 00000000000..71ba5f2f12d --- /dev/null +++ b/mysql-test/lib/My/SafeProcess/safe_process.cc @@ -0,0 +1,267 @@ +/* Copyright (C) 2008 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +/* + Utility program that encapsulates process creation, monitoring + and bulletproof process cleanup + + Usage: + safe_process [options to safe_process] -- progname arg1 ... argn + + To safeguard mysqld you would invoke safe_process with a few options + for safe_process itself followed by a double dash to indicate start + of the command line for the program you really want to start + + $> safe_process --output=output.log -- mysqld --datadir=var/data1 ... + + This would redirect output to output.log and then start mysqld, + once it has done that it will continue to monitor the child as well + as the parent. + + The safe_process then checks the follwing things: + 1. Child exits, propagate the childs return code to the parent + by exiting with the same return code as the child. + + 2. Parent dies, immediately kill the child and exit, thus the + parent does not need to properly cleanup any child, it is handled + automatically. + + 3. Signal's recieced by the process will trigger same action as 2) + +*/ + +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <string.h> +#include <errno.h> + +int verbose= 0; +int terminated= 0; +pid_t child_pid= -1; +char safe_process_name[32]= {0}; + + +static void message(const char* fmt, ...) +{ + if (!verbose) + return; + va_list args; + fprintf(stderr, "%s: ", safe_process_name); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); + va_end(args); + fflush(stderr); +} + + +static void die(const char* fmt, ...) +{ + va_list args; + fprintf(stderr, "%s: FATAL ERROR, ", safe_process_name); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); + va_end(args); + if (int last_err= errno) + fprintf(stderr, "error: %d, %s\n", last_err, strerror(last_err)); + exit(1); +} + + +static void kill_child (void) +{ + int exit_code= 1; + int status= 0; + + message("Killing child: %d", child_pid); + // Terminate whole process group + kill(-child_pid, SIGKILL); + + pid_t ret_pid= waitpid(child_pid, &status, 0); + if (ret_pid == child_pid) + { + if (WIFEXITED(status)) + { + // Process has exited, collect return status + exit_code= WEXITSTATUS(status); + message("Child exit: %d", exit_code); + // Exit with exit status of the child + exit(exit_code); + } + + if (WIFSIGNALED(status)) + message("Child killed by signal: %d", WTERMSIG(status)); + + exit(exit_code); + } + exit(exit_code); +} + + +static void handle_signal (int sig) +{ + message("Got signal %d, child_pid: %d", sig, child_pid); + terminated= 1; + + // Ignore further signals + signal(SIGTERM, SIG_IGN); + signal(SIGINT, SIG_IGN); + signal(SIGCHLD, SIG_IGN); + + if (child_pid > 0) + kill_child(); + + // Continune execution, allow the child to be started and + // finally terminated by monitor loop +} + + +int main(int argc, char* const argv[] ) +{ + char* const* child_argv= 0; + pid_t own_pid= getpid(); + pid_t parent_pid= getppid(); + + /* Install signal handlers */ + signal(SIGTERM, handle_signal); + signal(SIGINT, handle_signal); + signal(SIGCHLD, handle_signal); + + sprintf(safe_process_name, "safe_process[%d]", own_pid); + + message("Started"); + + /* Parse arguments */ + for (int i= 1; i < argc; i++) { + const char* arg= argv[i]; + if (strcmp(arg, "--") == 0 && strlen(arg) == 2) { + /* Got the "--" delimiter */ + if (i >= argc) + die("No real args -> nothing to do"); + child_argv= &argv[i+1]; + break; + } else { + if ( strcmp(arg, "--verbose") == 0 ) + verbose++; + else if ( strncmp(arg, "--parent-pid", 10) == 0 ) + { + /* Override parent_pid with a value provided by user */ + const char* start; + if ((start= strstr(arg, "=")) == NULL) + die("Could not find start of option value in '%s'", arg); + start++; /* Step past = */ + if ((parent_pid= atoi(start)) == 0) + die("Invalid value '%s' passed to --parent-id", start); + } + else + die("Unknown option: %s", arg); + } + } + if (!child_argv || *child_argv == 0) + die("nothing to do"); + + message("parent_pid: %d", parent_pid); + if (parent_pid == own_pid) + die("parent_pid is equal to own pid!"); + + char buf; + int pfd[2]; + if (pipe(pfd) == -1) + die("Failed to create pipe"); + + /* Create the child process */ + while((child_pid= fork()) == -1) + { + message("fork failed"); + sleep(1); + } + + if (child_pid == 0) + { + close(pfd[0]); // Close unused read end + + // Use default signal handlers in child + signal(SIGTERM, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGCHLD, SIG_DFL); + + // Make this process it's own process group to be able to kill + // it and any childs(that hasn't changed group themself) + setpgid(0, 0); + + // Signal that child is ready + buf= 37; + write(pfd[1], &buf, 1); + // Close write end + close(pfd[1]); + + if (execvp(child_argv[0], child_argv) < 0) + die("Failed to exec child"); + } + + close(pfd[1]); // Close unused write end + + // Wait for child to signal it's ready + read(pfd[0], &buf, 1); + if(buf != 37) + die("Didn't get 37 from pipe"); + close(pfd[0]); // Close read end + + /* Monitor loop */ + message("Started child %d, terminated: %d", child_pid, terminated); + + while(!terminated) + { + // Check if parent is still alive + if (kill(parent_pid, 0) != 0){ + message("Parent is not alive anymore"); + break; + } + + // Check if child has exited, normally this wil be + // detected immediately with SIGCHLD handler + int status= 0; + pid_t ret_pid= waitpid(child_pid, &status, WNOHANG); + if (ret_pid == child_pid) + { + int ret_code= 2; + if (WIFEXITED(status)) + { + // Process has exited, collect return status + int ret_code= WEXITSTATUS(status); + message("Child exit: %d", ret_code); + // Exit with exit status of the child + exit(ret_code); + } + + if (WIFSIGNALED(status)) + message("Child killed by signal: %d", WTERMSIG(status)); + + exit(ret_code); + } + sleep(1); + } + kill_child(); + + exit(1); +} + diff --git a/mysql-test/lib/t/SafeProcessStress.pl b/mysql-test/lib/t/SafeProcessStress.pl index a31f3031262..0f7a59d67f0 100755 --- a/mysql-test/lib/t/SafeProcessStress.pl +++ b/mysql-test/lib/t/SafeProcessStress.pl @@ -11,7 +11,7 @@ use My::SafeProcess; my $perl_path= $^X; my $verbose= 0; -my $loops= 1000; +my $loops= 100; print "kill one and wait for one\n"; for (1...$loops){ @@ -33,7 +33,7 @@ for (1...$loops){ foreach my $proc (@procs) { $proc->kill(); - # dummyd will always be kiled and thus + # dummyd will always be killed and thus # exit_status should have been set to 1 die "oops, exit_status: ", $proc->exit_status() unless $proc->exit_status() == 1; |