don't return from setjmp, getcontext functions

This commit is contained in:
David Rose 2007-06-22 01:41:12 +00:00
parent 7d6680160c
commit 396c995001
4 changed files with 194 additions and 126 deletions

View File

@ -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 */

View File

@ -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
}

View File

@ -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

View File

@ -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();