summaryrefslogtreecommitdiff
path: root/server-tools
diff options
context:
space:
mode:
Diffstat (limited to 'server-tools')
-rw-r--r--server-tools/Makefile.am1
-rw-r--r--server-tools/instance-manager/Makefile.am31
-rw-r--r--server-tools/instance-manager/README11
-rw-r--r--server-tools/instance-manager/listener.cc72
-rw-r--r--server-tools/instance-manager/listener.h45
-rw-r--r--server-tools/instance-manager/log.cc167
-rw-r--r--server-tools/instance-manager/log.h84
-rw-r--r--server-tools/instance-manager/manager.cc62
-rw-r--r--server-tools/instance-manager/manager.h21
-rw-r--r--server-tools/instance-manager/mysqlmanager.cc245
-rw-r--r--server-tools/instance-manager/options.cc123
-rw-r--r--server-tools/instance-manager/options.h38
-rw-r--r--server-tools/instance-manager/thread_repository.cc185
-rw-r--r--server-tools/instance-manager/thread_repository.h113
14 files changed, 1198 insertions, 0 deletions
diff --git a/server-tools/Makefile.am b/server-tools/Makefile.am
new file mode 100644
index 00000000000..ed316b9ac38
--- /dev/null
+++ b/server-tools/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS= instance-manager
diff --git a/server-tools/instance-manager/Makefile.am b/server-tools/instance-manager/Makefile.am
new file mode 100644
index 00000000000..731c8503831
--- /dev/null
+++ b/server-tools/instance-manager/Makefile.am
@@ -0,0 +1,31 @@
+INCLUDES= -I$(top_srcdir)/include
+
+# As all autoconf variables depend from ${prefix} and being resolved only when
+# make is run, we can't put these defines to a header file (e.g. to
+# default_options.h, generated from default_options.h.in)
+# See automake/autoconf docs for details
+
+noinst_LIBRARIES= liboptions.a
+liboptions_a_CPPFLAGS= $(CPPFLAGS) \
+ -DDEFAULT_PID_FILE_NAME="$(localstatedir)/mysqlmanager.pid" \
+ -DDEFAULT_LOG_FILE_NAME="$(localstatedir)/mysqlmanager.log" \
+ -DDEFAULT_SOCKET_FILE_NAME="$(localstatedir)/mysqlmanager.sock"
+
+liboptions_a_SOURCES= options.h options.cc
+
+bin_PROGRAMS = mysqlmanager
+
+mysqlmanager_SOURCES= mysqlmanager.cc manager.h manager.cc log.h log.cc \
+ listener.h listener.cc \
+ thread_repository.h thread_repository.cc
+
+mysqlmanager_LDADD= liboptions.a \
+ $(top_builddir)/mysys/libmysys.a \
+ $(top_builddir)/strings/libmystrings.a \
+ $(top_builddir)/dbug/libdbug.a
+
+tags:
+ ctags -R *.h *.cc
+
+# Don't update the files from bitkeeper
+%::SCCS/s.%
diff --git a/server-tools/instance-manager/README b/server-tools/instance-manager/README
new file mode 100644
index 00000000000..ac799775003
--- /dev/null
+++ b/server-tools/instance-manager/README
@@ -0,0 +1,11 @@
+Instance Manager - manage MySQL instances locally and remotely.
+
+File description:
+ mysqlmanager.cc - entry point to the manager, main,
+ options.{h,cc} - handle startup options
+ manager.{h,cc} - manager process
+ mysql_connection.{h,cc} - handle one connection with mysql client.
+
+See also instance manager architecture description in mysqlmanager.cc.
+
+
diff --git a/server-tools/instance-manager/listener.cc b/server-tools/instance-manager/listener.cc
new file mode 100644
index 00000000000..4bbfaf0b81a
--- /dev/null
+++ b/server-tools/instance-manager/listener.cc
@@ -0,0 +1,72 @@
+#include "listener.h"
+#include "thread_repository.h"
+#include "log.h"
+
+C_MODE_START
+
+pthread_handler_decl(listener, arg)
+{
+ Thread_info info(pthread_self());
+ Thread_repository &thread_repository=
+ ((Listener_thread_args *) arg)->thread_repository;
+ thread_repository.register_thread(&info);
+
+ while (true)
+ {
+ log_info("listener is alive");
+ sleep(2);
+ if (thread_repository.is_shutdown())
+ break;
+ }
+ log_info("listener(): shutdown requested, exiting...");
+
+ thread_repository.unregister_thread(&info);
+ return 0;
+}
+
+C_MODE_END
+
+#if 0
+ while (true)
+ {
+ }
+ /*
+ Dummy manager implementation: listens on a UNIX socket and
+ starts echo server in a dedicated thread for each accepted connection.
+ Enough to test startup/shutdown/options/logging of the instance manager.
+ */
+
+ int fd= socket(AF_UNIX, SOCK_STREAM, 0);
+
+ if (!fd)
+ die("socket(): failed");
+
+ struct sockaddr_un address;
+ bzero(&address, sizeof(address));
+ address.sun_family= AF_UNIX;
+ strcpy(address.sun_path, socket_path);
+ int opt= 1;
+
+ if (unlink(socket_path) ||
+ bind(fd, (struct sockaddr *) &address, sizeof(address)) ||
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)))
+ die("unlink | bind | setsockopt failed");
+
+ if (listen(fd, 5))
+ die("listen() failed");
+
+ int client_fd;
+ while ((client_fd= accept(fd, 0, 0)) != -1);
+ {
+ printf("accepted\n");
+ const char *message= "\n10hel";
+ send(client_fd, message, strlen(message), 0);
+
+ int sleep_seconds= argc > 1 && atoi(argv[1]) ? atoi(argv[1]) : 1;
+ printf("sleeping %d seconds\n", sleep_seconds);
+ sleep(sleep_seconds);
+ close(client_fd);
+ }
+ printf("accept(): failed\n");
+ close(fd);
+#endif
diff --git a/server-tools/instance-manager/listener.h b/server-tools/instance-manager/listener.h
new file mode 100644
index 00000000000..9165e5a0ee7
--- /dev/null
+++ b/server-tools/instance-manager/listener.h
@@ -0,0 +1,45 @@
+#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_LISTENER_H
+#define INCLUDES_MYSQL_INSTANCE_MANAGER_LISTENER_H
+/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult 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; either version 2 of the License, or
+ (at your option) any later version.
+
+ 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 */
+
+#ifdef __GNUC__
+#pragma interface
+#endif
+
+#include <my_global.h>
+#include <my_pthread.h>
+
+C_MODE_START
+
+pthread_handler_decl(listener, arg);
+
+C_MODE_END
+
+class Thread_repository;
+
+struct Listener_thread_args
+{
+ Thread_repository &thread_repository;
+ const char *socket_file_name;
+
+ Listener_thread_args(Thread_repository &thread_repository_arg,
+ const char *socket_file_name_arg) :
+ thread_repository(thread_repository_arg),
+ socket_file_name(socket_file_name_arg) {}
+};
+
+#endif
diff --git a/server-tools/instance-manager/log.cc b/server-tools/instance-manager/log.cc
new file mode 100644
index 00000000000..7eb0f15b2a4
--- /dev/null
+++ b/server-tools/instance-manager/log.cc
@@ -0,0 +1,167 @@
+/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult 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; either version 2 of the License, or
+ (at your option) any later version.
+
+ 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 */
+
+#include "log.h"
+#include <my_global.h>
+#include <stdarg.h>
+#include <m_string.h>
+#include <my_sys.h>
+
+/*
+ TODO:
+ - add flexible header support
+ - rewrite all fprintf with fwrite
+ - think about using 'write' instead of fwrite/fprintf on POSIX systems
+*/
+
+/*
+ Format log entry and write it to the given stream.
+ SYNOPSYS
+ log()
+*/
+
+static inline void log(FILE *file, const char *format, va_list args)
+{
+ /*
+ log() should be thread-safe; it implies that we either call fprintf()
+ once per log(), or use flockfile()/funlockfile(). But flockfile() is
+ POSIX, not ANSI C, so we try to vsnprintf the whole message to the
+ stack, and if stack buffer is not enough, to malloced string. When
+ message is formatted, it is fprintf()'ed to the file.
+ */
+
+ /* Format time like MYSQL_LOG does. */
+ time_t now= time(0);
+ struct tm bd_time; // broken-down time
+ localtime_r(&now, &bd_time);
+
+ char buff_date[32];
+ sprintf(buff_date, "%02d%02d%02d %2d:%02d:%02d\t",
+ bd_time.tm_year % 100,
+ bd_time.tm_mon + 1,
+ bd_time.tm_mday,
+ bd_time.tm_hour,
+ bd_time.tm_min,
+ bd_time.tm_sec);
+ /* Format the message */
+ char buff_stack[256];
+
+ int n= vsnprintf(buff_stack, sizeof(buff_stack), format, args);
+ /*
+ return value of vsnprintf can vary, according to various standards;
+ try to check all cases.
+ */
+ char *buff_msg= buff_stack;
+ if (n < 0 || n == sizeof(buff_stack))
+ {
+ int size= sizeof(buff_stack) * 2;
+ buff_msg= (char *) my_malloc(size, 0);
+ while (true)
+ {
+ if (buff_msg == 0)
+ {
+ strmake(buff_stack, "log(): message is too big, my_malloc() failed",
+ sizeof(buff_stack));
+ buff_msg= buff_stack;
+ break;
+ }
+ n = vsnprintf(buff_msg, size, format, args);
+ if (n >= 0 && n < size)
+ break;
+ size*= 2;
+ /* realloc() does unnecessary memcpy */
+ my_free(buff_msg, 0);
+ buff_msg= (char *) my_malloc(size, 0);
+ }
+ }
+ else if ((size_t) n > sizeof(buff_stack))
+ {
+ buff_msg= (char *) my_malloc(n + 1, 0);
+#ifdef DBUG
+ DBUG_ASSERT(n == vsnprintf(buff_msg, n + 1, format, args));
+#else
+ vsnprintf(buff_msg, n + 1, format, args);
+#endif
+ }
+ fprintf(file, "%s%s\n", buff_date, buff_msg);
+ if (buff_msg != buff_stack)
+ my_free(buff_msg, 0);
+
+ /* don't fflush() the file: buffering strategy is set in log_init() */
+}
+
+
+void log_error(const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ log(stderr, format, args);
+ va_end(args);
+}
+
+
+void log_info(const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ log(stdout, format, args);
+ va_end(args);
+}
+
+/* TODO: rewrite with buffering print */
+void print_info(const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ vfprintf(stdout, format, args);
+ va_end(args);
+}
+
+void print_error(const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+}
+
+/*
+ init_logs()
+ RETURN VALUE
+ 0 ok
+ !0 error
+*/
+
+void log_init()
+{
+ /*
+ stderr is unbuffered by default; there is no good of line buffering,
+ as all logging is performed linewise - so remove buffering from stdout
+ also
+ */
+ setbuf(stdout, 0);
+}
+
+void die(const char *format, ...)
+{
+ va_list args;
+ fprintf(stderr,"%s: ", my_progname);
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+ fprintf(stderr, "\n");
+ exit(1);
+}
diff --git a/server-tools/instance-manager/log.h b/server-tools/instance-manager/log.h
new file mode 100644
index 00000000000..7b69e516edb
--- /dev/null
+++ b/server-tools/instance-manager/log.h
@@ -0,0 +1,84 @@
+#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_LOG_H
+#define INCLUDES_MYSQL_INSTANCE_MANAGER_LOG_H
+/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult 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; either version 2 of the License, or
+ (at your option) any later version.
+
+ 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 */
+
+/*
+ Logging facilities.
+
+ Two logging streams are supported: error log and info log. Additionally
+ libdbug may be used for debug information output.
+ ANSI C buffered I/O is used to perform logging.
+ Logging may be performed in two modes:
+ - console application mode (default), stdout/stderr is used for logging
+ init_logs() must be called to initialize logging environment
+ - daemon mode, without controlling terminal, call
+ init_logs_in_daemon_mode() to initialize
+
+ Rationale:
+ - no MYSQL_LOG as it has BIN mode, and not easy to fetch from sql_class.h
+ - no constructors/desctructors to make logging available all the time
+ Function names are subject to change.
+*/
+
+
+/* Precede error message with date and time and print it to the stdout */
+void log_info(const char *format, ...)
+#ifdef __GNUC__
+ __attribute__ ((format(printf, 1, 2)))
+#endif
+ ;
+
+
+/* Precede error message with date and time and print it to the stderr */
+void log_error(const char *format, ...)
+#ifdef __GNUC__
+ __attribute__ ((format (printf, 1, 2)))
+#endif
+ ;
+
+
+/*
+ Now this is simple catchouts for printf (no date/time is logged), to be
+ able to replace underlying streams in future.
+*/
+
+void print_info(const char *format, ...)
+#ifdef __GNUC__
+ __attribute__ ((format (printf, 1, 2)))
+#endif
+ ;
+
+
+void print_error(const char *format, ...)
+#ifdef __GNUC__
+ __attribute__ ((format (printf, 1, 2)))
+#endif
+ ;
+
+/* initialize logs */
+void log_init();
+
+
+/* print information to the error log and eixt(1) */
+
+void die(const char *format, ...)
+#ifdef __GNUC__
+ __attribute__ ((format (printf, 1, 2)))
+#endif
+ ;
+
+#endif // INCLUDES_MYSQL_INSTANCE_MANAGER_LOG_H
diff --git a/server-tools/instance-manager/manager.cc b/server-tools/instance-manager/manager.cc
new file mode 100644
index 00000000000..06e181d52d5
--- /dev/null
+++ b/server-tools/instance-manager/manager.cc
@@ -0,0 +1,62 @@
+/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult 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; either version 2 of the License, or
+ (at your option) any later version.
+
+ 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 */
+
+#include "manager.h"
+
+#include <my_global.h>
+#include <signal.h>
+
+#include "thread_repository.h"
+#include "listener.h"
+#include "log.h"
+
+
+void manager(const char *socket_file_name)
+{
+ Thread_repository thread_repository;
+ Listener_thread_args listener_args(thread_repository, socket_file_name);
+
+ /* write pid file */
+
+ /* block signals */
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGINT);
+ sigaddset(&mask, SIGTERM);
+ sigaddset(&mask, SIGHUP);
+
+ /* all new threads will inherite this signal mask */
+ pthread_sigmask(SIG_BLOCK, &mask, NULL);
+ {
+ /* create the listener */
+ pthread_t listener_thd_id;
+ pthread_attr_t listener_thd_attr;
+
+ pthread_attr_init(&listener_thd_attr);
+ pthread_attr_setdetachstate(&listener_thd_attr, PTHREAD_CREATE_DETACHED);
+ if (pthread_create(&listener_thd_id, &listener_thd_attr, listener,
+ &listener_args))
+ die("manager(): pthread_create(listener) failed");
+ }
+ /*
+ To work nicely with LinuxThreads, the signal thread is the first thread
+ in the process.
+ */
+ int signo;
+ sigwait(&mask, &signo);
+ thread_repository.deliver_shutdown();
+ /* don't pthread_exit to kill all threads who did not shut down in time */
+}
diff --git a/server-tools/instance-manager/manager.h b/server-tools/instance-manager/manager.h
new file mode 100644
index 00000000000..2f30813180a
--- /dev/null
+++ b/server-tools/instance-manager/manager.h
@@ -0,0 +1,21 @@
+#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_MANAGER_H
+#define INCLUDES_MYSQL_INSTANCE_MANAGER_MANAGER_H
+/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult 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; either version 2 of the License, or
+ (at your option) any later version.
+
+ 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 */
+
+void manager(const char *socket_file_name);
+
+#endif // INCLUDES_MYSQL_INSTANCE_MANAGER_MANAGER_H
diff --git a/server-tools/instance-manager/mysqlmanager.cc b/server-tools/instance-manager/mysqlmanager.cc
new file mode 100644
index 00000000000..cb2d2e1c5d5
--- /dev/null
+++ b/server-tools/instance-manager/mysqlmanager.cc
@@ -0,0 +1,245 @@
+/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult 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; either version 2 of the License, or
+ (at your option) any later version.
+
+ 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 */
+
+#include <my_global.h>
+#include <my_sys.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "manager.h"
+#include "options.h"
+#include "log.h"
+
+/*
+ Few notes about the Instance Manager architecture:
+ Instance Manager consisits of two processes: the angel process, and the
+ instance manager process. Responsibilities of the angel process is to
+ monitor the instance manager process, and restart it in case of
+ failure/shutdown. The angel process is started only if startup option
+ '--run-as-service' is provided.
+ The Instance Manager process consists of several
+ subsystems (thread sets):
+ - the signal handling thread: it's responsibilities are to handle
+ user signals and propogate them to the other threads. All other threads
+ are accounted in the signal handler thread Thread Repository.
+ - the listener: listens all sockets. There is a listening
+ socket for each (mysql, http, snmp, rendezvous (?)) subsystem.
+ - mysql subsystem: Instance Manager acts like an ordinary MySQL Server,
+ but with very restricted command set. Each MySQL client connection is
+ handled in a separate thread. All MySQL client connections threads
+ constitute mysql subsystem.
+ - http subsystem: it is also possible to talk with Instance Manager via
+ http. One thread per http connection is used. Threads are pooled.
+ - 'snmp' connections (FIXME: I know nothing about it yet)
+ - rendezvous threads
+*/
+
+static void init_environment(char *progname);
+static void daemonize(const char *log_file_name);
+static void angel(const Options &options);
+
+
+/*
+ main, entry point
+ - init environment
+ - handle options
+ - daemonize and run angel process (if necessary)
+ - run manager process
+*/
+
+int main(int argc, char *argv[])
+{
+ init_environment(argv[0]);
+ Options options;
+ options.load(argc, argv);
+ if (options.run_as_service)
+ {
+ daemonize(options.log_file_name);
+ angel(options);
+ }
+ else
+ manager(options.log_file_name);
+ return 0;
+}
+
+/******************* Auxilary functions implementation **********************/
+
+
+/*
+ Init environment, common for daemon and non-daemon
+*/
+
+static void init_environment(char *progname)
+{
+ MY_INIT(progname);
+ log_init();
+ umask(0117);
+}
+
+
+/*
+ Become a UNIX service
+ SYNOPSYS
+ daemonize()
+*/
+
+static void daemonize(const char *log_file_name)
+{
+ pid_t pid= fork();
+ switch (pid) {
+ case -1: // parent, fork error
+ die("daemonize(): fork failed, %s", strerror(errno));
+ case 0: // child, fork ok
+ int fd;
+ /*
+ Become a session leader: setsid must succeed because child is
+ guaranteed not to be a process group leader (it belongs to the
+ process group of the parent.)
+ The goal is not to have a controlling terminal.
+ */
+ setsid();
+ /*
+ As we now don't have a controlling terminal we will not receive
+ tty-related signals - no need to ignore them.
+ */
+
+ close(STDIN_FILENO);
+
+ fd= open(log_file_name, O_WRONLY | O_CREAT | O_APPEND | O_NOCTTY,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+ if (fd < 0)
+ die("daemonize(): failed to open log file %s, %s", log_file_name,
+ strerror(errno));
+ dup2(fd, STDOUT_FILENO);
+ dup2(fd, STDERR_FILENO);
+ if (fd != STDOUT_FILENO && fd != STDERR_FILENO)
+ close(fd);
+
+ /* TODO: chroot() and/or chdir() here */
+ break;
+ default:
+ /* successfully exit from parent */
+ exit(0);
+ }
+}
+
+
+enum { CHILD_OK= 0, CHILD_NEED_RESPAWN, CHILD_EXIT_ANGEL };
+
+static volatile sig_atomic_t child_status= CHILD_OK;
+
+/*
+ Signal handler for SIGCHLD: reap child, analyze child exit status, and set
+ child_status appropriately.
+*/
+
+void reap_child(int __attribute__((unused)) signo)
+{
+ int child_exit_status;
+ /* As we have only one child, no need to cycle waitpid */
+ if (waitpid(0, &child_exit_status, WNOHANG) > 0)
+ {
+ if (WIFSIGNALED(child_exit_status))
+ child_status= CHILD_NEED_RESPAWN;
+ else
+ /*
+ As we reap_child is not called for SIGSTOP, we should be here only
+ if the child exited normally.
+ */
+ child_status= CHILD_EXIT_ANGEL;
+ }
+}
+
+/* Not static to reuse it in childs */
+
+volatile sig_atomic_t is_terminated= 0;
+
+/*
+ Signal handler for terminate signals - SIGTERM, SIGHUP, SIGINT.
+ Set termination status and return.
+ (q) do we need to handle SIGQUIT?
+*/
+
+void terminate(int signo)
+{
+ is_terminated= signo;
+}
+
+
+/*
+ Fork a child and monitor it.
+ User can explicitly kill the angel process with SIGTERM/SIGHUP/SIGINT.
+ Angel process will exit silently if mysqlmanager exits normally.
+*/
+
+static void angel(const Options &options)
+{
+ /* install signal handlers */
+ sigset_t zeromask; // to sigsuspend in parent
+ struct sigaction sa_chld, sa_term;
+ struct sigaction sa_chld_out, sa_term_out, sa_int_out, sa_hup_out;
+
+ sigemptyset(&zeromask);
+ sigemptyset(&sa_chld.sa_mask);
+ sigemptyset(&sa_term.sa_mask);
+
+ sa_chld.sa_handler= reap_child;
+ sa_chld.sa_flags= SA_NOCLDSTOP;
+ sa_term.sa_handler= terminate;
+ sa_term.sa_flags= 0;
+
+ /* sigaction can fail only on wrong arguments */
+ sigaction(SIGCHLD, &sa_chld, &sa_chld_out);
+ sigaction(SIGTERM, &sa_term, &sa_term_out);
+ sigaction(SIGINT, &sa_term, &sa_int_out);
+ sigaction(SIGHUP, &sa_term, &sa_hup_out);
+
+ /* spawn a child */
+spawn:
+ pid_t pid= fork();
+ switch (pid) {
+ case -1:
+ die("angel(): fork failed, %s", strerror(errno));
+ case 0: // child, success
+ /*
+ restore default actions for signals to let the manager work with
+ signals as he wishes
+ */
+ sigaction(SIGCHLD, &sa_chld_out, 0);
+ sigaction(SIGTERM, &sa_term_out, 0);
+ sigaction(SIGINT, &sa_int_out, 0);
+ sigaction(SIGHUP, &sa_hup_out, 0);
+
+ manager(options.socket_file_name);
+ default: // parent, success
+ while (child_status == CHILD_OK && is_terminated == 0)
+ sigsuspend(&zeromask);
+
+ if (is_terminated)
+ log_info("angel got signal %d, exiting", is_terminated);
+ else if (child_status == CHILD_NEED_RESPAWN)
+ {
+ child_status= CHILD_OK;
+ log_error("angel(): mysqlmanager exited abnormally: respawning...");
+ sleep(1); /* don't respawn too fast */
+ goto spawn;
+ }
+ /* mysqlmanager successfully exited, let's silently evaporate */
+ }
+}
diff --git a/server-tools/instance-manager/options.cc b/server-tools/instance-manager/options.cc
new file mode 100644
index 00000000000..5bb4b180030
--- /dev/null
+++ b/server-tools/instance-manager/options.cc
@@ -0,0 +1,123 @@
+/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult 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; either version 2 of the License, or
+ (at your option) any later version.
+
+ 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 */
+
+#ifdef __GNUC__
+#pragma implementation
+#endif
+
+#include "options.h"
+
+#include <my_global.h>
+#include <my_sys.h>
+#include <my_getopt.h>
+
+
+#define QUOTE2(x) #x
+#define QUOTE(x) QUOTE2(x)
+
+char Options::run_as_service;
+const char *Options::log_file_name= QUOTE(DEFAULT_LOG_FILE_NAME);
+const char *Options::pid_file_name= QUOTE(DEFAULT_PID_FILE_NAME);
+const char *Options::socket_file_name= QUOTE(DEFAULT_SOCKET_FILE_NAME);
+
+/*
+ List of options, accepted by the instance manager.
+ List must be closed with empty option.
+*/
+
+enum options {
+ OPT_LOG= 256,
+ OPT_PID_FILE,
+ OPT_SOCKET,
+ OPT_RUN_AS_SERVICE
+};
+
+static struct my_option my_long_options[] =
+{
+ { "help", '?', "Display this help and exit.",
+ 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 },
+
+ { "log", OPT_LOG, "Path to log file. Used only with --run-as-service.",
+ (gptr *) &Options::log_file_name, (gptr *) &Options::log_file_name,
+ 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 },
+
+ { "pid-file", OPT_PID_FILE, "Pid file to use.",
+ (gptr *) &Options::pid_file_name, (gptr *) &Options::pid_file_name,
+ 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 },
+
+ { "socket", OPT_SOCKET, "Socket file to use for connection.",
+ (gptr *) &Options::socket_file_name, (gptr *) &Options::socket_file_name,
+ 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 },
+
+ { "run-as-service", OPT_RUN_AS_SERVICE,
+ "Daemonize and start angel process.", (gptr *) &Options::run_as_service,
+ 0, 0, GET_BOOL, NO_ARG, 0, 0, 1, 0, 0, 0 },
+
+ { "version", 'V', "Output version information and exit.", 0, 0, 0,
+ GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 },
+
+ { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }
+};
+
+static void version()
+{
+ static const char mysqlmanager_version[] = "0.1-alpha";
+ printf("%s Ver %s for %s on %s\n", my_progname, mysqlmanager_version,
+ SYSTEM_TYPE, MACHINE_TYPE);
+}
+
+static void usage()
+{
+ version();
+ my_print_help(my_long_options);
+}
+
+C_MODE_START
+
+static my_bool
+get_one_option(int optid,
+ const struct my_option *opt __attribute__((unused)),
+ char *argument __attribute__((unused)))
+{
+ switch(optid) {
+ case 'V':
+ version();
+ exit(0);
+ case 'I':
+ case '?':
+ usage();
+ exit(0);
+ }
+ return 0;
+}
+
+C_MODE_END
+
+
+/*
+ - call load_defaults to load configuration file section
+ - call handle_options to assign defaults and command-line arguments
+ to the class members
+ if either of these function fail, exit the program
+ May not return.
+*/
+
+void Options::load(int argc, char **argv)
+{
+ if (int rc= handle_options(&argc, &argv, my_long_options, get_one_option))
+ exit(rc);
+}
+
diff --git a/server-tools/instance-manager/options.h b/server-tools/instance-manager/options.h
new file mode 100644
index 00000000000..b2eacf220d7
--- /dev/null
+++ b/server-tools/instance-manager/options.h
@@ -0,0 +1,38 @@
+#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_OPTIONS_H
+#define INCLUDES_MYSQL_INSTANCE_MANAGER_OPTIONS_H
+/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult 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; either version 2 of the License, or
+ (at your option) any later version.
+
+ 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 */
+
+#ifdef __GNUC__
+#pragma interface
+#endif
+
+/*
+ Options - all possible options for the instance manager grouped in one
+ struct.
+*/
+
+struct Options
+{
+ static char run_as_service; /* handle_options doesn't support bool */
+ static const char *log_file_name;
+ static const char *pid_file_name;
+ static const char *socket_file_name;
+
+ static void load(int argc, char **argv);
+};
+
+#endif // INCLUDES_MYSQL_INSTANCE_MANAGER_OPTIONS_H
diff --git a/server-tools/instance-manager/thread_repository.cc b/server-tools/instance-manager/thread_repository.cc
new file mode 100644
index 00000000000..d0b302d29fb
--- /dev/null
+++ b/server-tools/instance-manager/thread_repository.cc
@@ -0,0 +1,185 @@
+/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult 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; either version 2 of the License, or
+ (at your option) any later version.
+
+ 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 */
+
+#ifdef __GNUC__
+#pragma implementation
+#endif
+
+#include "thread_repository.h"
+#include <assert.h>
+#include <signal.h>
+#include "log.h"
+
+
+/* Kick-off signal handler */
+
+enum { THREAD_KICK_OFF_SIGNAL= SIGUSR2 };
+
+static void handle_signal(int __attribute__((unused)) sig_no)
+{
+}
+
+
+/*
+ TODO: think about moving signal information (now it's shutdown_in_progress)
+ to Thread_info. It will reduce contention and allow signal deliverence to
+ a particular thread, not to the whole worker crew
+*/
+
+Thread_repository::Thread_repository() :
+ shutdown_in_progress(false)
+{
+ pthread_mutex_init(&LOCK_thread_repository, 0);
+ pthread_cond_init(&COND_thread_repository_is_empty, 0);
+
+ /* head is used by-value to simplify nodes inserting */
+ head.next= head.prev= &head;
+}
+
+
+Thread_repository::~Thread_repository()
+{
+ /* Check that no one uses the repository. */
+ pthread_mutex_lock(&LOCK_thread_repository);
+
+ /* All threads must unregister */
+ DBUG_ASSERT(head.next == &head);
+
+ pthread_mutex_unlock(&LOCK_thread_repository);
+ pthread_cond_destroy(&COND_thread_repository_is_empty);
+ pthread_mutex_destroy(&LOCK_thread_repository);
+}
+
+
+/*
+
+ Set signal handler for kick-off thread, and insert a thread info to the
+ repository. New node is appended to the end of the list; head.prev always
+ points to the last node.
+*/
+
+void Thread_repository::register_thread(Thread_info *info)
+{
+ struct sigaction sa;
+ sa.sa_handler= handle_signal;
+ sa.sa_flags= 0;
+ sigemptyset(&sa.sa_mask);
+ sigaction(THREAD_KICK_OFF_SIGNAL, &sa, 0);
+
+ info->current_cond= 0;
+
+ pthread_mutex_lock(&LOCK_thread_repository);
+ info->next= &head;
+ info->prev= head.prev;
+ head.prev->next= info;
+ head.prev= info;
+ pthread_mutex_unlock(&LOCK_thread_repository);
+}
+
+
+/*
+ Unregister a thread from the repository and free Thread_info structure.
+ Every registered thread must unregister. Unregistering should be the last
+ thing a thread is doing, otherwise it could have no time to finalize.
+*/
+
+void Thread_repository::unregister_thread(Thread_info *info)
+{
+ pthread_mutex_lock(&LOCK_thread_repository);
+ info->prev->next= info->next;
+ info->next->prev= info->prev;
+ if (head.next == &head)
+ pthread_cond_signal(&COND_thread_repository_is_empty);
+ pthread_mutex_unlock(&LOCK_thread_repository);
+}
+
+
+/*
+ Check whether shutdown is in progress, and if yes, return immidiately.
+ Else set info->current_cond and call pthread_cond_wait. When
+ pthread_cond_wait returns, unregister current cond and check the shutdown
+ status again.
+ RETURN VALUE
+ return value from pthread_cond_wait
+*/
+
+int Thread_repository::cond_wait(Thread_info *info, pthread_cond_t *cond,
+ pthread_mutex_t *mutex, bool *is_shutdown)
+{
+ pthread_mutex_lock(&LOCK_thread_repository);
+ *is_shutdown= shutdown_in_progress;
+ if (*is_shutdown)
+ {
+ pthread_mutex_unlock(&LOCK_thread_repository);
+ return 0;
+ }
+ info->current_cond= cond;
+ pthread_mutex_unlock(&LOCK_thread_repository);
+ /* sic: race condition here, cond can be signaled in deliver_shutdown */
+ int rc= pthread_cond_wait(cond, mutex);
+ pthread_mutex_lock(&LOCK_thread_repository);
+ info->current_cond= 0;
+ *is_shutdown= shutdown_in_progress;
+ pthread_mutex_unlock(&LOCK_thread_repository);
+ return rc;
+}
+
+
+/*
+ Deliver shutdown message to the workers crew.
+ As it's impossible to avoid all race conditions, we signal latecomers
+ again.
+*/
+
+void Thread_repository::deliver_shutdown()
+{
+ struct timespec shutdown_time;
+ set_timespec(shutdown_time, 1);
+ Thread_info *info;
+
+ pthread_mutex_lock(&LOCK_thread_repository);
+ shutdown_in_progress= true;
+ for (info= head.next; info != &head; info= info->next)
+ {
+ pthread_kill(info->thread_id, THREAD_KICK_OFF_SIGNAL);
+ /*
+ sic: race condition here, the thread may not yet fall into
+ pthread_cond_wait.
+ */
+ if (info->current_cond)
+ pthread_cond_signal(info->current_cond);
+ }
+ while (pthread_cond_timedwait(&COND_thread_repository_is_empty,
+ &LOCK_thread_repository,
+ &shutdown_time) != ETIMEDOUT &&
+ head.next != &head)
+ ;
+ /*
+ If previous signals did not reach some threads, they must be sleeping
+ in pthread_cond_wait or a blocking syscall. Wake them up:
+ every thread shall check signal variables after each syscall/cond_wait,
+ so this time everybody should be informed (presumably each worker can
+ get CPU during shutdown_time.)
+ */
+ for (info= head.next; info != &head; info= info->next)
+ {
+ pthread_kill(info->thread_id, THREAD_KICK_OFF_SIGNAL);
+ if (info->current_cond)
+ pthread_cond_signal(info->current_cond);
+ }
+ pthread_mutex_unlock(&LOCK_thread_repository);
+}
+
diff --git a/server-tools/instance-manager/thread_repository.h b/server-tools/instance-manager/thread_repository.h
new file mode 100644
index 00000000000..7bd21d66e3d
--- /dev/null
+++ b/server-tools/instance-manager/thread_repository.h
@@ -0,0 +1,113 @@
+#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_THREAD_REPOSITORY_HH
+#define INCLUDES_MYSQL_INSTANCE_MANAGER_THREAD_REPOSITORY_HH
+/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult 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; either version 2 of the License, or
+ (at your option) any later version.
+
+ 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 */
+
+/*
+ A multi-threaded application shall nicely work with signals.
+
+ This means it shall, first of all, shut down nicely on ``quit'' signals:
+ stop all running threads, cleanup and exit.
+
+ Note, that a thread can't be shut down nicely if it doesn't want to be.
+ That's why to perform clean shutdown, all threads consituting a process
+ must observe certain rules. Here we use the rules, described in Butenhof
+ book 'Programming with POSIX threads', namely:
+ - all user signals are handled in 'signal thread' in synchronous manner
+ (by means of sigwait). To guarantee that the signal thread is the only who
+ can receive user signals, all threads block them, and signal thread is
+ the only who calls sigwait() with an apporpriate sigmask.
+ To propogate a signal to the workers the signal thread sets
+ a variable, corresponding to the signal. Additionally the signal thread
+ sends each worker an internal signal (by means of pthread_kill) to kick it
+ out from possible blocking syscall, and possibly pthread_cond_signal if
+ some thread is blocked in pthread_cond_[timed]wait.
+ - a worker handles only internal 'kick' signal (the handler does nothing).
+ In case when a syscall returns 'EINTR' the worker checks all
+ signal-related variables and behaves accordingly.
+ Also these variables shall be checked from time to time in long
+ CPU-bounded operations, and before/after pthread_cond_wait. (It's supposed
+ that a worker thread either waits in a syscall/conditional variable, or
+ computes something.)
+ - to guarantee signal deliverence, there should be some kind of feedback,
+ e. g. all workers shall account in the signal thread Thread Repository and
+ unregister from it on exit.
+
+ Configuration reload (on SIGHUP) and thread timeouts/alarms can be handled
+ in manner, similar to ``quit'' signals.
+*/
+
+#ifdef __GNUC__
+#pragma interface
+#endif
+
+#include <my_global.h>
+#include <my_pthread.h>
+
+
+/*
+ Thread_info - repository entry for each worker thread
+ All entries comprise double-linked list like:
+ 0 -- entry -- entry -- entry - 0
+ Double-linked list is used to unregister threads easy.
+*/
+
+class Thread_info
+{
+ pthread_cond_t *current_cond;
+ Thread_info *prev, *next;
+ pthread_t thread_id;
+ Thread_info() {}
+ friend class Thread_repository;
+public:
+ Thread_info(pthread_t thread_id_arg) : thread_id(thread_id_arg) {}
+};
+
+
+/*
+ Thread_repository - contains handles for each worker thread to deliver
+ signal information to workers.
+*/
+
+class Thread_repository
+{
+public:
+ Thread_repository();
+ ~Thread_repository();
+
+ void register_thread(Thread_info *info);
+ void unregister_thread(Thread_info *info);
+ void deliver_shutdown();
+ inline bool is_shutdown();
+ int cond_wait(Thread_info *info, pthread_cond_t *cond,
+ pthread_mutex_t *mutex, bool *is_shutdown);
+private:
+ Thread_info head;
+ bool shutdown_in_progress;
+ pthread_mutex_t LOCK_thread_repository;
+ pthread_cond_t COND_thread_repository_is_empty;
+};
+
+
+inline bool Thread_repository::is_shutdown()
+{
+ pthread_mutex_lock(&LOCK_thread_repository);
+ bool res= shutdown_in_progress;
+ pthread_mutex_unlock(&LOCK_thread_repository);
+ return res;
+}
+
+#endif // INCLUDES_MYSQL_INSTANCE_MANAGER_THREAD_REPOSITORY_HH