From 396c995001996b797f5ae3e2b318ce87a0e11be3 Mon Sep 17 00:00:00 2001 From: David Rose Date: Fri, 22 Jun 2007 01:41:12 +0000 Subject: [PATCH] don't return from setjmp, getcontext functions --- panda/src/pipeline/contextSwitch.c | 93 +++++++++-- panda/src/pipeline/contextSwitch.h | 43 ++--- panda/src/pipeline/threadSimpleManager.cxx | 181 +++++++++++---------- panda/src/pipeline/threadSimpleManager.h | 3 + 4 files changed, 194 insertions(+), 126 deletions(-) diff --git a/panda/src/pipeline/contextSwitch.c b/panda/src/pipeline/contextSwitch.c index 64300415b6..42f23b9e6b 100644 --- a/panda/src/pipeline/contextSwitch.c +++ b/panda/src/pipeline/contextSwitch.c @@ -27,24 +27,58 @@ /* The getcontext() / setcontext() implementation. Easy-peasy. */ -static void * -begin_context(ContextFunction *main_func, void *data) { - (*main_func)(data); - return data; +static void +begin_context(ContextFunction *thread_func, void *data) { + (*thread_func)(data); } void init_thread_context(struct ThreadContext *context, unsigned char *stack, size_t stack_size, - ContextFunction *main_func, void *data) { + ContextFunction *thread_func, void *data) { getcontext(&context->_ucontext); - _ucontext.uc_stack.ss_sp = stack; - _ucontext.uc_stack.ss_size = stack_size; - _ucontext.uc_stack.ss_flags = 0; - _ucontext.uc_link = NULL; + context->_ucontext.uc_stack.ss_sp = stack; + context->_ucontext.uc_stack.ss_size = stack_size; + context->_ucontext.uc_stack.ss_flags = 0; + context->_ucontext.uc_link = NULL; - makecontext(&context->_ucontext, begin_context, 2, main_func, this); + makecontext(&context->_ucontext, (void (*)())&begin_context, 2, thread_func, data); +} + +void save_thread_context(struct ThreadContext *context, + ContextFunction *next_context, void *data) { + /* getcontext requires us to use a volatile auto variable to + differentiate between pass 1 (immediate return) and pass 2 + (return from setcontext). */ + volatile int context_return = 0; + + getcontext(&context->_ucontext); + if (context_return) { + /* We have just returned from setcontext. In this case, return + from the function. The stack is still good. */ + return; + } + + context_return = 1; + + /* We are still in the calling thread. In this case, we cannot + return from the function without damaging the stack. Insted, + call next_context() and trust the caller to call + switch_to_thread_context() in there somewhere. */ + + (*next_context)(data); + + /* We shouldn't get here. */ + abort(); +} + +void +switch_to_thread_context(struct ThreadContext *context) { + setcontext(&context->_ucontext); + + /* Shouldn't get here. */ + abort(); } @@ -79,7 +113,7 @@ init_thread_context(struct ThreadContext *context, static struct ThreadContext *st_context; static unsigned char *st_stack; static size_t st_stack_size; -static ContextFunction *st_main_func; +static ContextFunction *st_thread_func; static void *st_data; static jmp_buf orig_stack; @@ -88,7 +122,7 @@ static void setup_context_2(void) { /* Here we are running on the new stack. Copy the key data onto our new stack. */ - ContextFunction *volatile main_func = st_main_func; + ContextFunction *volatile thread_func = st_thread_func; void *volatile data = st_data; if (setjmp(st_context->_jmp_context) == 0) { @@ -103,9 +137,9 @@ setup_context_2(void) { } /* We come here the first time the thread starts. */ - (*main_func)(data); + (*thread_func)(data); - /* We shouldn't get here, since we don't expect the main_func to + /* We shouldn't get here, since we don't expect the thread_func to return. */ abort(); } @@ -150,17 +184,44 @@ setup_context_1(void) { void init_thread_context(struct ThreadContext *context, unsigned char *stack, size_t stack_size, - ContextFunction *main_func, void *data) { + ContextFunction *thread_func, void *data) { /* Copy all of the input parameters to static variables, then begin the stack-switching process. */ st_context = context; st_stack = stack; st_stack_size = stack_size; - st_main_func = main_func; + st_thread_func = thread_func; st_data = data; setup_context_1(); } +void save_thread_context(struct ThreadContext *context, + ContextFunction *next_context, void *data) { + if (setjmp(context->_jmp_context) != 0) { + /* We have just returned from longjmp. In this case, return from + the function. The stack is still good. */ + return; + } + + /* We are still in the calling thread. In this case, we cannot + return from the function without damaging the stack. Insted, + call next_context() and trust the caller to call + switch_to_thread_context() in there somewhere. */ + + (*next_context)(data); + + /* We shouldn't get here. */ + abort(); +} + +void +switch_to_thread_context(struct ThreadContext *context) { + longjmp(context->_jmp_context, 1) + + /* Shouldn't get here. */ + abort(); +} + #endif /* HAVE_UCONTEXT_H */ #endif /* THREAD_SIMPLE_IMPL */ diff --git a/panda/src/pipeline/contextSwitch.h b/panda/src/pipeline/contextSwitch.h index a179d95187..d0e503e1d4 100644 --- a/panda/src/pipeline/contextSwitch.h +++ b/panda/src/pipeline/contextSwitch.h @@ -44,21 +44,6 @@ struct ThreadContext { ucontext_t _ucontext; }; -#define save_thread_context(switched, context) \ -{ \ - volatile int context_return = 0; \ - getcontext(&context->_ucontext); \ - if (context_return) { \ - (*(switched)) = 1; \ - } \ - - context_return = 1; \ - (*(switched)) = 0; \ -} - -#define switch_to_thread_context(context) \ - setcontext(&(context)->_ucontext) - #else /* HAVE_UCONTEXT_H */ /* Unfortunately, setcontext() is not defined everywhere (even though it claims to be adopted by Posix). So we have to fall back to @@ -69,14 +54,6 @@ struct ThreadContext { jmp_buf _jmp_context; }; -#define save_thread_context(switched, context) \ -{ \ - (*(switched)) = setjmp((context)->_jmp_context); \ -} - -#define switch_to_thread_context(context) \ - longjmp((context)->_jmp_context, 1) - #endif /* HAVE_UCONTEXT_H */ #ifdef __cplusplus @@ -85,13 +62,25 @@ extern "C" { typedef void ContextFunction(void *); +/* Call this to fill in the appropriate values in context. The stack + must already have been allocated. The context will be initialized + so that when switch_to_thread_context() is called, it will begin + executing thread_func(data), which should not return. This function + will return normally. */ void init_thread_context(struct ThreadContext *context, unsigned char *stack, size_t stack_size, - ContextFunction *main_func, void *data); + ContextFunction *thread_func, void *data); -/* These two functions are defined as macros, above. */ -/* void save_thread_context(int *switched, struct ThreadContext *context); */ -/* void switch_to_thread_context(struct ThreadContext *context); */ +/* Call this to save the current thread context. This function does + not return until switch_to_thread_context() is called. Instead it + immediately calls next_context(data), which should not return. */ +void save_thread_context(struct ThreadContext *context, + ContextFunction *next_context, void *data); + +/* Call this to resume executing a previously saved context. When + called, it will return from save_thread_context() in the saved + stack (or begin executing thread_func()). */ +void switch_to_thread_context(struct ThreadContext *context); #ifdef __cplusplus } diff --git a/panda/src/pipeline/threadSimpleManager.cxx b/panda/src/pipeline/threadSimpleManager.cxx index f43a424a69..43dc772a9c 100644 --- a/panda/src/pipeline/threadSimpleManager.cxx +++ b/panda/src/pipeline/threadSimpleManager.cxx @@ -192,19 +192,109 @@ next_context() { _current_thread->_python_state = PyThreadState_Swap(NULL); #endif // HAVE_PYTHON - int switched; - save_thread_context(&switched, &_current_thread->_context); - if (switched) { - // Pass 2: we have returned into the context, and are now resuming - // the current thread. + save_thread_context(&_current_thread->_context, st_choose_next_context, this); + // Pass 2: we have returned into the context, and are now resuming + // the current thread. #ifdef HAVE_PYTHON - PyThreadState_Swap(_current_thread->_python_state); + PyThreadState_Swap(_current_thread->_python_state); #endif // HAVE_PYTHON - return; +} + +//////////////////////////////////////////////////////////////////// +// Function: ThreadSimpleManager::prepare_for_exit +// Access: Public +// Description: Blocks until all running threads (other than the +// current thread) have finished. You should probably +// only call this from the main thread. +//////////////////////////////////////////////////////////////////// +void ThreadSimpleManager:: +prepare_for_exit() { + nassertv(_waiting_for_exit == NULL); + _waiting_for_exit = _current_thread; + + // At this point, any non-joinable threads on any of the queues are + // automatically killed. + kill_non_joinable(_ready); + + Blocked::iterator bi = _blocked.begin(); + while (bi != _blocked.end()) { + Blocked::iterator bnext = bi; + ++bnext; + BlockerSimple *blocker = (*bi).first; + FifoThreads &threads = (*bi).second; + kill_non_joinable(threads); + if (threads.empty()) { + blocker->_flags &= ~BlockerSimple::F_has_waiters; + _blocked.erase(bi); + } + bi = bnext; } - // Pass 1: we have saved the context successfully. + kill_non_joinable(_sleeping); + next_context(); + + while (!_finished.empty() && _finished.front() != _current_thread) { + ThreadSimpleImpl *finished_thread = _finished.front(); + _finished.pop_front(); + unref_delete(finished_thread->_parent_obj); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: ThreadSimpleManager::set_current_thread +// Access: Public +// Description: Sets the initial value of the current_thread pointer, +// i.e. the main thread. It is valid to call this +// method only exactly once. +//////////////////////////////////////////////////////////////////// +void ThreadSimpleManager:: +set_current_thread(ThreadSimpleImpl *current_thread) { + nassertv(_current_thread == (ThreadSimpleImpl *)NULL); + _current_thread = current_thread; +} + +//////////////////////////////////////////////////////////////////// +// Function: ThreadSimpleManager::init_pointers +// Access: Private, Static +// Description: Should be called at startup to initialize the +// simple threading system. +//////////////////////////////////////////////////////////////////// +void ThreadSimpleManager:: +init_pointers() { + if (!_pointers_initialized) { + _pointers_initialized = true; + _global_ptr = new ThreadSimpleManager; + Thread::get_main_thread(); + +#ifdef HAVE_PYTHON + // Ensure that the Python threading system is initialized and ready + // to go. + PyEval_InitThreads(); +#endif + } +} + +//////////////////////////////////////////////////////////////////// +// Function: ThreadSimpleManager::st_choose_next_context +// Access: Private, Static +// Description: Select the next context to run. Continuing the work +// of next_context(). +//////////////////////////////////////////////////////////////////// +void ThreadSimpleManager:: +st_choose_next_context(void *data) { + ThreadSimpleManager *self = (ThreadSimpleManager *)data; + self->choose_next_context(); +} + +//////////////////////////////////////////////////////////////////// +// Function: ThreadSimpleManager::choose_next_context +// Access: Private +// Description: Select the next context to run. Continuing the work +// of next_context(). +//////////////////////////////////////////////////////////////////// +void ThreadSimpleManager:: +choose_next_context() { while (!_finished.empty() && _finished.front() != _current_thread) { ThreadSimpleImpl *finished_thread = _finished.front(); _finished.pop_front(); @@ -286,81 +376,6 @@ next_context() { abort(); } -//////////////////////////////////////////////////////////////////// -// Function: ThreadSimpleManager::prepare_for_exit -// Access: Public -// Description: Blocks until all running threads (other than the -// current thread) have finished. You should probably -// only call this from the main thread. -//////////////////////////////////////////////////////////////////// -void ThreadSimpleManager:: -prepare_for_exit() { - nassertv(_waiting_for_exit == NULL); - _waiting_for_exit = _current_thread; - - // At this point, any non-joinable threads on any of the queues are - // automatically killed. - kill_non_joinable(_ready); - - Blocked::iterator bi = _blocked.begin(); - while (bi != _blocked.end()) { - Blocked::iterator bnext = bi; - ++bnext; - BlockerSimple *blocker = (*bi).first; - FifoThreads &threads = (*bi).second; - kill_non_joinable(threads); - if (threads.empty()) { - blocker->_flags &= ~BlockerSimple::F_has_waiters; - _blocked.erase(bi); - } - bi = bnext; - } - - kill_non_joinable(_sleeping); - - next_context(); - - while (!_finished.empty() && _finished.front() != _current_thread) { - ThreadSimpleImpl *finished_thread = _finished.front(); - _finished.pop_front(); - unref_delete(finished_thread->_parent_obj); - } -} - -//////////////////////////////////////////////////////////////////// -// Function: ThreadSimpleManager::set_current_thread -// Access: Public -// Description: Sets the initial value of the current_thread pointer, -// i.e. the main thread. It is valid to call this -// method only exactly once. -//////////////////////////////////////////////////////////////////// -void ThreadSimpleManager:: -set_current_thread(ThreadSimpleImpl *current_thread) { - nassertv(_current_thread == (ThreadSimpleImpl *)NULL); - _current_thread = current_thread; -} - -//////////////////////////////////////////////////////////////////// -// Function: ThreadSimpleManager::init_pointers -// Access: Private, Static -// Description: Should be called at startup to initialize the -// simple threading system. -//////////////////////////////////////////////////////////////////// -void ThreadSimpleManager:: -init_pointers() { - if (!_pointers_initialized) { - _pointers_initialized = true; - _global_ptr = new ThreadSimpleManager; - Thread::get_main_thread(); - -#ifdef HAVE_PYTHON - // Ensure that the Python threading system is initialized and ready - // to go. - PyEval_InitThreads(); -#endif - } -} - //////////////////////////////////////////////////////////////////// // Function: ThreadSimpleManager::wake_sleepers // Access: Private diff --git a/panda/src/pipeline/threadSimpleManager.h b/panda/src/pipeline/threadSimpleManager.h index 365c3cbc55..9a75b4e98d 100644 --- a/panda/src/pipeline/threadSimpleManager.h +++ b/panda/src/pipeline/threadSimpleManager.h @@ -72,6 +72,9 @@ public: private: static void init_pointers(); + + static void st_choose_next_context(void *data); + void choose_next_context(); void wake_sleepers(double now); static void system_sleep(double seconds); void report_deadlock();