don't return from setjmp, getcontext functions
This commit is contained in:
parent
7d6680160c
commit
396c995001
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in New Issue