diff options
Diffstat (limited to 'server-tools/instance-manager/instance.cc')
-rw-r--r-- | server-tools/instance-manager/instance.cc | 630 |
1 files changed, 417 insertions, 213 deletions
diff --git a/server-tools/instance-manager/instance.cc b/server-tools/instance-manager/instance.cc index 6b4289c5b29..b852a8323e5 100644 --- a/server-tools/instance-manager/instance.cc +++ b/server-tools/instance-manager/instance.cc @@ -36,7 +36,9 @@ #include "thread_registry.h" #include "instance_map.h" -/* {{{ Platform-specific functions. */ +/************************************************************************* + {{{ Platform-specific functions. +*************************************************************************/ #ifndef __WIN__ typedef pid_t My_process_info; @@ -45,34 +47,6 @@ typedef PROCESS_INFORMATION My_process_info; #endif /* - Proxy thread is a simple way to avoid all pitfalls of the threads - implementation in the OS (e.g. LinuxThreads). With such a thread we - don't have to process SIGCHLD, which is a tricky business if we want - to do it in a portable way. -*/ - -class Instance_monitor: public Thread -{ -public: - Instance_monitor(Instance *instance_arg) :instance(instance_arg) {} -protected: - virtual void run(); - void start_and_monitor_instance(Instance_options *old_instance_options, - Instance_map *instance_map, - Thread_registry *thread_registry); -private: - Instance *instance; -}; - -void Instance_monitor::run() -{ - start_and_monitor_instance(&instance->options, - Manager::get_instance_map(), - Manager::get_thread_registry()); - delete this; -} - -/* Wait for an instance SYNOPSIS @@ -285,113 +259,149 @@ int kill(pid_t pid, int signum) } #endif -/* }}} */ +/************************************************************************* + }}} +*************************************************************************/ + -/* {{{ Static constants. */ +/************************************************************************* + {{{ Static constants. +*************************************************************************/ const LEX_STRING Instance::DFLT_INSTANCE_NAME= { C_STRING_WITH_LEN("mysqld") }; -/* }}} */ +/************************************************************************* + }}} +*************************************************************************/ -/* - Fork child, exec an instance and monitor it. +/************************************************************************* + {{{ Instance Monitor thread. +*************************************************************************/ - SYNOPSIS - start_and_monitor_instance() - old_instance_options Pointer to the options of the instance to be - launched. This info is likely to become obsolete - when function returns from wait_process() - instance_map Pointer to the instance_map. We use it to protect - the instance from deletion, while we are working - with it. +/** + Proxy thread is a simple way to avoid all pitfalls of the threads + implementation in the OS (e.g. LinuxThreads). With such a thread we + don't have to process SIGCHLD, which is a tricky business if we want + to do it in a portable way. - DESCRIPTION - Fork a child, then exec and monitor it. When the child is dead, - find appropriate instance (for this purpose we save its name), - set appropriate flags and wake all threads waiting for instance - to stop. - - NOTE - A separate thread for starting/monitoring instance is a simple way - to avoid all pitfalls of the threads implementation in the OS (e.g. - LinuxThreads). For one, with such a thread we don't have to process - SIGCHLD, which is a tricky business if we want to do it in a - portable way. + Instance Monitor Thread forks a child process, execs mysqld and waits for + the child to die. - RETURN - Function returns no value + Instance Monitor assumes that the monitoring instance will not be dropped. + This is guaranteed by having flag monitoring_thread_active and + Instance::is_active() operation. */ -void -Instance_monitor:: -start_and_monitor_instance(Instance_options *old_instance_options, - Instance_map *instance_map, - Thread_registry *thread_registry) +class Instance_monitor: public Thread { - Instance_name instance_name(&old_instance_options->instance_name); - Instance *current_instance; - My_process_info process_info; - Thread_info thread_info; +public: + Instance_monitor(Instance *instance_arg) :instance(instance_arg) {} +protected: + virtual void run(); + void start_and_monitor_instance(); +private: + Instance *instance; +}; + + +void Instance_monitor::run() +{ + start_and_monitor_instance(); + delete this; +} + + +void Instance_monitor::start_and_monitor_instance() +{ + Thread_registry *thread_registry= Manager::get_thread_registry(); + Guardian *guardian= Manager::get_guardian(); + + My_process_info mysqld_process_info; + Thread_info monitor_thread_info; log_info("Instance '%s': Monitor: started.", (const char *) instance->get_name()->str); - if (!old_instance_options->nonguarded) - { - /* - Register thread in Thread_registry to wait for it to stop on shutdown - only if instance is guarded. If instance is guarded, the thread will not - finish, because nonguarded instances are not stopped on shutdown. - */ - thread_registry->register_thread(&thread_info, FALSE); - } - /* - Lock instance map to guarantee that no instances are deleted during - strmake() and execv() calls. + For guarded instance register the thread in Thread_registry to wait for + the thread to stop on shutdown (nonguarded instances are not stopped on + shutdown, so the thread will no finish). */ - instance_map->lock(); - /* - Save the instance name in the case if Instance object we - are using is destroyed. (E.g. by "FLUSH INSTANCES") - */ + if (instance->is_guarded()) + { + thread_registry->register_thread(&monitor_thread_info, FALSE); + } + + /* Starting mysqld. */ log_info("Instance '%s': Monitor: starting mysqld...", (const char *) instance->get_name()->str); - if (start_process(old_instance_options, &process_info)) + if (start_process(&instance->options, &mysqld_process_info)) { - instance_map->unlock(); - return; /* error is logged */ + instance->lock(); + instance->monitoring_thread_active= FALSE; + instance->unlock(); + + return; } - /* allow users to delete instances */ - instance_map->unlock(); + /* Waiting for mysqld to die. */ log_info("Instance '%s': Monitor: waiting for mysqld to stop...", (const char *) instance->get_name()->str); - wait_process(&process_info); /* Don't check for return value. */ + wait_process(&mysqld_process_info); /* Don't check for return value. */ - instance_map->lock(); + log_info("Instance '%s': Monitor: mysqld stopped.", + (const char *) instance->get_name()->str); - current_instance= instance_map->find(instance_name.get_str()); + /* Update instance status. */ - if (current_instance) - current_instance->set_crash_flag_n_wake_all(); + instance->lock(); - instance_map->unlock(); + if (instance->is_guarded()) + thread_registry->unregister_thread(&monitor_thread_info); - if (!old_instance_options->nonguarded) - thread_registry->unregister_thread(&thread_info); + instance->crashed= TRUE; + instance->monitoring_thread_active= FALSE; log_info("Instance '%s': Monitor: finished.", (const char *) instance->get_name()->str); + + instance->unlock(); + + /* Wake up guardian. */ + + guardian->ping(); } +/************************************************************************** + }}} +**************************************************************************/ + + +/************************************************************************** + {{{ Static operations. +**************************************************************************/ + +/** + The operation is intended to check whether string is a well-formed + instance name or not. + + SYNOPSIS + is_name_valid() + name string to check + + RETURN + TRUE string is a valid instance name + FALSE string is not a valid instance name + + TODO: Move to Instance_name class: Instance_name::is_valid(). +*/ bool Instance::is_name_valid(const LEX_STRING *name) { @@ -405,21 +415,83 @@ bool Instance::is_name_valid(const LEX_STRING *name) } +/** + The operation is intended to check if the given instance name is + mysqld-compatible or not. + + SYNOPSIS + is_mysqld_compatible_name() + name name to check + + RETURN + TRUE name is mysqld-compatible + FALSE otherwise + + TODO: Move to Instance_name class: Instance_name::is_mysqld_compatible(). +*/ + bool Instance::is_mysqld_compatible_name(const LEX_STRING *name) { return strcmp(name->str, DFLT_INSTANCE_NAME.str) == 0; } +/** + Return client state name. Must not be used outside the class. + Use Instance::get_state_name() instead. +*/ + +const char * Instance::get_instance_state_name(enum_instance_state state) +{ + switch (state) { + case STOPPED: + return "offline"; + + case NOT_STARTED: + return "not started"; + + case STARTING: + return "starting"; + + case STARTED: + return "online"; + + case JUST_CRASHED: + return "failed"; + + case CRASHED: + return "crashed"; -/* {{{ Constructor & destructor */ + case CRASHED_AND_ABANDONED: + return "abandoned"; + + case STOPPING: + return "stopping"; + } + + return NULL; /* just to ignore compiler warning. */ +} + +/************************************************************************** + }}} +**************************************************************************/ + + +/************************************************************************** + {{{ Initialization & deinitialization. +**************************************************************************/ Instance::Instance() - :crashed(FALSE), - configured(FALSE) + :monitoring_thread_active(FALSE), + crashed(FALSE), + configured(FALSE), + /* mysqld_compatible is initialized in init() */ + state(NOT_STARTED), + restart_counter(0), + crash_moment(0), + last_checked(0) { pthread_mutex_init(&LOCK_instance, 0); - pthread_cond_init(&COND_instance_stopped, 0); } @@ -427,13 +499,11 @@ Instance::~Instance() { log_info("Instance '%s': destroying...", (const char *) get_name()->str); - pthread_cond_destroy(&COND_instance_stopped); pthread_mutex_destroy(&LOCK_instance); } -/* }}} */ -/* +/** Initialize instance options. SYNOPSIS @@ -453,7 +523,7 @@ bool Instance::init(const LEX_STRING *name_arg) } -/* +/** Complete instance options initialization. SYNOPSIS @@ -474,7 +544,47 @@ bool Instance::complete_initialization() */ } -/* +/************************************************************************** + }}} +**************************************************************************/ + + +/************************************************************************** + {{{ Instance: public interface implementation. +**************************************************************************/ + +/** + Determine if there is some activity with the instance. + + SYNOPSIS + is_active() + + DESCRIPTION + An instance is active if one of the following conditions is true: + - Instance-monitoring thread is running; + - Instance is guarded and its state is other than STOPPED; + - Corresponding mysqld-server accepts connections. + + MT-NOTE: instance must be locked before calling the operation. + + RETURN + TRUE - instance is active + FALSE - otherwise. +*/ + +bool Instance::is_active() +{ + if (monitoring_thread_active) + return TRUE; + + if (is_guarded() && get_state() != STOPPED) + return TRUE; + + return is_mysqld_running(); +} + + +/** Determine if mysqld is accepting connections. SYNOPSIS @@ -484,7 +594,7 @@ bool Instance::complete_initialization() Try to connect to mysqld with fake login/password to check whether it is accepting connections or not. - MT-NOTE: this operation must be called under acquired LOCK_instance. + MT-NOTE: instance must be locked before calling the operation. RETURN TRUE - mysqld is alive and accept connections @@ -508,8 +618,6 @@ bool Instance::is_mysqld_running() if (!port && !options.mysqld_socket) port= SERVER_DEFAULT_PORT; - pthread_mutex_lock(&LOCK_instance); - mysql_init(&mysql); /* try to connect to a server with a fake username/password pair */ if (mysql_real_connect(&mysql, LOCAL_HOST, username, @@ -523,7 +631,6 @@ bool Instance::is_mysqld_running() */ log_error("Instance '%s': was able to log into mysqld.", (const char *) get_name()->str); - pthread_mutex_unlock(&LOCK_instance); return_val= TRUE; /* server is alive */ } else @@ -531,145 +638,145 @@ bool Instance::is_mysqld_running() sizeof(access_denied_message) - 1)); mysql_close(&mysql); - pthread_mutex_unlock(&LOCK_instance); return return_val; } -/* - The method starts an instance. + +/** + Start mysqld. SYNOPSIS - start() + start_mysqld() + + DESCRIPTION + Reset flags and start Instance Monitor thread, which will start mysqld. + + MT-NOTE: instance must be locked before calling the operation. RETURN - 0 ok - ER_CANNOT_START_INSTANCE Cannot start instance - ER_INSTANCE_ALREADY_STARTED The instance on the specified port/socket - is already started + FALSE - ok + TRUE - could not start instance */ -int Instance::start() +bool Instance::start_mysqld() { - /* clear crash flag */ - pthread_mutex_lock(&LOCK_instance); - crashed= FALSE; - pthread_mutex_unlock(&LOCK_instance); + Instance_monitor *instance_monitor; + /* + Prepare instance to start Instance Monitor thread. - if (configured && !is_mysqld_running()) - { - Instance_monitor *instance_monitor; - remove_pid(); + NOTE: It's important to set these actions here in order to avoid + race conditions -- these actions must be done under acquired lock on + Instance. + */ - instance_monitor= new Instance_monitor(this); + crashed= FALSE; + monitoring_thread_active= TRUE; - if (instance_monitor == NULL || instance_monitor->start(Thread::DETACHED)) - { - delete instance_monitor; - log_error("Instance::start(): failed to create the monitoring thread" - " to start an instance"); - return ER_CANNOT_START_INSTANCE; - } - /* The monitoring thread will delete itself when it's finished. */ + remove_pid(); - return 0; - } + /* Create and start the Instance Monitor thread. */ - /* The instance is started already or misconfigured. */ - return configured ? ER_INSTANCE_ALREADY_STARTED : ER_INSTANCE_MISCONFIGURED; -} + instance_monitor= new Instance_monitor(this); -/* - The method sets the crash flag and wakes all waiters on - COND_instance_stopped and COND_guardian + if (instance_monitor == NULL || instance_monitor->start(Thread::DETACHED)) + { + delete instance_monitor; + monitoring_thread_active= FALSE; - SYNOPSIS - set_crash_flag_n_wake_all() + log_error("Instance '%s': can not create instance monitor thread.", + (const char *) get_name()->str); - DESCRIPTION - The method is called when an instance is crashed or terminated. - In the former case it might indicate that guardian probably should - restart it. + return TRUE; + } - RETURN - Function returns no value -*/ + ++restart_counter; -void Instance::set_crash_flag_n_wake_all() -{ - /* set instance state to crashed */ - pthread_mutex_lock(&LOCK_instance); - crashed= TRUE; - pthread_mutex_unlock(&LOCK_instance); + /* The Instance Monitor thread will delete itself when it's finished. */ - /* - Wake connection threads waiting for an instance to stop. This - is needed if a user issued command to stop an instance via - mysql connection. This is not the case if Guardian stop the thread. - */ - pthread_cond_signal(&COND_instance_stopped); - /* wake guardian */ - pthread_cond_signal(&Manager::get_guardian()->COND_guardian); + return FALSE; } -/* - Stop an instance. +/** + Stop mysqld. SYNOPSIS - stop() + stop_mysqld() - RETURN: - 0 ok - ER_INSTANCE_IS_NOT_STARTED Looks like the instance it is not started - ER_STOP_INSTANCE mysql_shutdown reported an error -*/ + DESCRIPTION + Try to stop mysqld gracefully. Otherwise kill it with SIGKILL. -int Instance::stop() -{ - struct timespec timeout; - uint waitchild= (uint) DEFAULT_SHUTDOWN_DELAY; + MT-NOTE: instance must be locked before calling the operation. - if (is_mysqld_running()) - { - waitchild= options.get_shutdown_delay(); + RETURN + FALSE - ok + TRUE - could not stop the instance +*/ - kill_mysqld(SIGTERM); - /* sleep on condition to wait for SIGCHLD */ +bool Instance::stop_mysqld() +{ + log_info("Instance '%s': stopping mysqld...", + (const char *) get_name()->str); - timeout.tv_sec= time(NULL) + waitchild; - timeout.tv_nsec= 0; - if (pthread_mutex_lock(&LOCK_instance)) - return ER_STOP_INSTANCE; + kill_mysqld(SIGTERM); - while (options.load_pid() != 0) /* while server isn't stopped */ - { - int status; + if (!wait_for_stop()) + { + log_info("Instance '%s': mysqld stopped gracefully.", + (const char *) get_name()->str); + return FALSE; + } - status= pthread_cond_timedwait(&COND_instance_stopped, - &LOCK_instance, - &timeout); - if (status == ETIMEDOUT || status == ETIME) - break; - } + log_info("Instance '%s': mysqld failed to stop gracefully within %d seconds.", + (const char *) get_name()->str, + (int) options.get_shutdown_delay()); - pthread_mutex_unlock(&LOCK_instance); + log_info("Instance'%s': killing mysqld...", + (const char *) get_name()->str); - kill_mysqld(SIGKILL); + kill_mysqld(SIGKILL); - return 0; + if (!wait_for_stop()) + { + log_info("Instance '%s': mysqld has been killed.", + (const char *) get_name()->str); + return FALSE; } - return ER_INSTANCE_IS_NOT_STARTED; + log_info("Instance '%s': can not kill mysqld within %d seconds.", + (const char *) get_name()->str, + (int) options.get_shutdown_delay()); + + return TRUE; } -/* +/** Send signal to mysqld. SYNOPSIS kill_mysqld() + + DESCRIPTION + Load pid from the pid file and send the given signal to that process. + If the signal is SIGKILL, remove the pid file after sending the signal. + + MT-NOTE: instance must be locked before calling the operation. + + TODO + This too low-level and OS-specific operation for public interface. + Also, it has some implicit behaviour for SIGKILL signal. Probably, we + should have the following public operations instead: + - start_mysqld() -- as is; + - stop_mysqld -- request mysqld to shutdown gracefully (send SIGTERM); + don't wait for complete shutdown; + - wait_for_stop() (or join_mysqld()) -- wait for mysqld to stop within + time interval; + - kill_mysqld() -- request to terminate mysqld; don't wait for + completion. + These operations should also be used in Guardian to manage instances. */ void Instance::kill_mysqld(int signum) @@ -707,27 +814,91 @@ void Instance::kill_mysqld(int signum) } } -/* - Return crashed flag. - SYNOPSIS - is_crashed() - - RETURN - TRUE - mysqld crashed - FALSE - mysqld hasn't crashed yet +/** + Lock instance. */ -bool Instance::is_crashed() +void Instance::lock() { - bool val; pthread_mutex_lock(&LOCK_instance); - val= crashed; +} + + +/** + Unlock instance. +*/ + +void Instance::unlock() +{ pthread_mutex_unlock(&LOCK_instance); - return val; } -/* + +/** + Return instance state name. + + SYNOPSIS + get_state_name() + + DESCRIPTION + The operation returns user-friendly state name. The operation can be + used both for guarded and non-guarded instances. + + MT-NOTE: instance must be locked before calling the operation. + + TODO: Replace with the static get_state_name(state_code) function. +*/ + +const char *Instance::get_state_name() +{ + if (!is_configured()) + return "misconfigured"; + + if (is_guarded()) + { + /* The instance is managed by Guardian: we can report precise state. */ + + return get_instance_state_name(get_state()); + } + + /* The instance is not managed by Guardian: we can report status only. */ + + return is_active() ? "online" : "offline"; +} + + +/** + Reset statistics. + + SYNOPSIS + reset_stat() + + DESCRIPTION + The operation resets statistics used for guarding the instance. + + MT-NOTE: instance must be locked before calling the operation. + + TODO: Make private. +*/ + +void Instance::reset_stat() +{ + restart_counter= 0; + crash_moment= 0; + last_checked= 0; +} + +/************************************************************************** + }}} +**************************************************************************/ + + +/************************************************************************** + {{{ Instance: implementation of private operations. +**************************************************************************/ + +/** Remove pid file. */ @@ -744,3 +915,36 @@ void Instance::remove_pid() (const char *) options.instance_name.str); } } + + +/** + Wait for mysqld to stop within shutdown interval. +*/ + +bool Instance::wait_for_stop() +{ + int start_time= time(NULL); + int finish_time= start_time + options.get_shutdown_delay(); + + log_info("Instance '%s': waiting for mysqld to stop " + "(timeout: %d seconds)...", + (const char *) get_name()->str, + (int) options.get_shutdown_delay()); + + while (true) + { + if (options.load_pid() == 0 && !is_mysqld_running()) + return FALSE; + + if (time(NULL) >= finish_time) + return TRUE; + + /* Sleep for 0.3 sec and check again. */ + + my_sleep(300000); + } +} + +/************************************************************************** + }}} +**************************************************************************/ |