Browse Source

don't return from setjmp, getcontext functions

David Rose 18 years ago
parent
commit
396c995001

+ 77 - 16
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);
+
+  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, (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();
+}
 
-  _ucontext.uc_stack.ss_sp = stack;
-  _ucontext.uc_stack.ss_size = stack_size;
-  _ucontext.uc_stack.ss_flags = 0;
-  _ucontext.uc_link = NULL;
+void
+switch_to_thread_context(struct ThreadContext *context) {
+  setcontext(&context->_ucontext);
 
-  makecontext(&context->_ucontext, begin_context, 2, main_func, this);
+  /* 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 */

+ 17 - 28
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);
-
-/* 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); */
+                         ContextFunction *thread_func, void *data);
+
+/* 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
 }

+ 98 - 83
panda/src/pipeline/threadSimpleManager.cxx

@@ -192,24 +192,114 @@ 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();
+    unref_delete(finished_thread->_parent_obj);
+  }
 
   _current_thread = NULL;
 
@@ -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

+ 3 - 0
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();