/* 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); }