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. */
 /* 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
 void
 init_thread_context(struct ThreadContext *context, 
 init_thread_context(struct ThreadContext *context, 
                     unsigned char *stack, size_t stack_size,
                     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);
   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 struct ThreadContext *st_context;
 static unsigned char *st_stack;
 static unsigned char *st_stack;
 static size_t st_stack_size;
 static size_t st_stack_size;
-static ContextFunction *st_main_func;
+static ContextFunction *st_thread_func;
 static void *st_data;
 static void *st_data;
 
 
 static jmp_buf orig_stack;
 static jmp_buf orig_stack;
@@ -88,7 +122,7 @@ static void
 setup_context_2(void) {
 setup_context_2(void) {
   /* Here we are running on the new stack.  Copy the key data onto our
   /* Here we are running on the new stack.  Copy the key data onto our
      new stack. */
      new stack. */
-  ContextFunction *volatile main_func = st_main_func;
+  ContextFunction *volatile thread_func = st_thread_func;
   void *volatile data = st_data;
   void *volatile data = st_data;
 
 
   if (setjmp(st_context->_jmp_context) == 0) {
   if (setjmp(st_context->_jmp_context) == 0) {
@@ -103,9 +137,9 @@ setup_context_2(void) {
   }
   }
 
 
   /* We come here the first time the thread starts. */
   /* 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. */
      return. */
   abort();
   abort();
 }
 }
@@ -150,17 +184,44 @@ setup_context_1(void) {
 void
 void
 init_thread_context(struct ThreadContext *context, 
 init_thread_context(struct ThreadContext *context, 
                     unsigned char *stack, size_t stack_size,
                     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
   /* Copy all of the input parameters to static variables, then begin
      the stack-switching process. */
      the stack-switching process. */
   st_context = context;
   st_context = context;
   st_stack = stack;
   st_stack = stack;
   st_stack_size = stack_size;
   st_stack_size = stack_size;
-  st_main_func = main_func;
+  st_thread_func = thread_func;
   st_data = data;
   st_data = data;
 
 
   setup_context_1();
   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  /* HAVE_UCONTEXT_H */
 #endif  /* THREAD_SIMPLE_IMPL */
 #endif  /* THREAD_SIMPLE_IMPL */

+ 17 - 28
panda/src/pipeline/contextSwitch.h

@@ -44,21 +44,6 @@ struct ThreadContext {
   ucontext_t _ucontext;
   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 */
 #else  /* HAVE_UCONTEXT_H */
 /* Unfortunately, setcontext() is not defined everywhere (even though
 /* Unfortunately, setcontext() is not defined everywhere (even though
    it claims to be adopted by Posix).  So we have to fall back to
    it claims to be adopted by Posix).  So we have to fall back to
@@ -69,14 +54,6 @@ struct ThreadContext {
   jmp_buf _jmp_context;
   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 */
 #endif  /* HAVE_UCONTEXT_H */
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
@@ -85,13 +62,25 @@ extern "C" {
 
 
 typedef void ContextFunction(void *);
 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, 
 void init_thread_context(struct ThreadContext *context, 
                          unsigned char *stack, size_t stack_size,
                          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
 #ifdef __cplusplus
 }
 }

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

@@ -192,24 +192,114 @@ next_context() {
   _current_thread->_python_state = PyThreadState_Swap(NULL);
   _current_thread->_python_state = PyThreadState_Swap(NULL);
 #endif  // HAVE_PYTHON
 #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
 #ifdef HAVE_PYTHON
-    PyThreadState_Swap(_current_thread->_python_state);
+  PyThreadState_Swap(_current_thread->_python_state);
 #endif  // HAVE_PYTHON
 #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) {
   while (!_finished.empty() && _finished.front() != _current_thread) {
     ThreadSimpleImpl *finished_thread = _finished.front();
     ThreadSimpleImpl *finished_thread = _finished.front();
     _finished.pop_front();
     _finished.pop_front();
     unref_delete(finished_thread->_parent_obj);
     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;
   _current_thread = NULL;
 
 
@@ -286,81 +376,6 @@ next_context() {
   abort();
   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
 //     Function: ThreadSimpleManager::wake_sleepers
 //       Access: Private
 //       Access: Private

+ 3 - 0
panda/src/pipeline/threadSimpleManager.h

@@ -72,6 +72,9 @@ public:
 
 
 private:
 private:
   static void init_pointers();
   static void init_pointers();
+
+  static void st_choose_next_context(void *data);
+  void choose_next_context();
   void wake_sleepers(double now);
   void wake_sleepers(double now);
   static void system_sleep(double seconds);
   static void system_sleep(double seconds);
   void report_deadlock();
   void report_deadlock();