OSDN Git Service

* mark_rts.c (GC_approx_sp): Use __builtin_frame_address when
[pf3gnuchains/gcc-fork.git] / boehm-gc / pthread_stop_world.c
index 5dfd26d..bc3c901 100644 (file)
@@ -1,13 +1,19 @@
 #include "private/pthread_support.h"
 
 #if defined(GC_PTHREADS) && !defined(GC_SOLARIS_THREADS) \
-     && !defined(GC_IRIX_THREADS) && !defined(GC_WIN32_THREADS) \
-     && !defined(GC_DARWIN_THREADS) && !defined(GC_AIX_THREADS)
+     && !defined(GC_WIN32_THREADS) && !defined(GC_DARWIN_THREADS)
 
 #include <signal.h>
 #include <semaphore.h>
 #include <errno.h>
 #include <unistd.h>
+#include <sys/time.h>
+#ifndef HPUX
+# include <sys/select.h>
+  /* Doesn't exist on HP/UX 11.11. */
+#endif
+
+void suspend_self();
 
 #if DEBUG_THREADS
 
@@ -39,7 +45,50 @@ void GC_print_sig_mask()
 
 #endif
 
-word GC_stop_count;    /* Incremented at the beginning of GC_stop_world. */
+/* Remove the signals that we want to allow in thread stopping         */
+/* handler from a set.                                         */
+void GC_remove_allowed_signals(sigset_t *set)
+{
+#   ifdef NO_SIGNALS
+      if (sigdelset(set, SIGINT) != 0
+         || sigdelset(set, SIGQUIT) != 0
+         || sigdelset(set, SIGABRT) != 0
+         || sigdelset(set, SIGTERM) != 0) {
+        ABORT("sigdelset() failed");
+      }
+#   endif
+
+#   ifdef MPROTECT_VDB
+      /* Handlers write to the thread structure, which is in the heap, */
+      /* and hence can trigger a protection fault.                     */
+      if (sigdelset(set, SIGSEGV) != 0
+#        ifdef SIGBUS
+           || sigdelset(set, SIGBUS) != 0
+#        endif
+         ) {
+        ABORT("sigdelset() failed");
+      }
+#   endif
+}
+
+static sigset_t suspend_handler_mask;
+
+volatile sig_atomic_t GC_stop_count;
+                       /* Incremented at the beginning of GC_stop_world. */
+
+volatile sig_atomic_t GC_world_is_stopped = FALSE;
+                       /* FALSE ==> it is safe for threads to restart, i.e. */
+                       /* they will see another suspend signal before they  */
+                       /* are expected to stop (unless they have voluntarily */
+                       /* stopped).                                         */
+
+void GC_brief_async_signal_safe_sleep()
+{
+    struct timeval tv;
+    tv.tv_sec = 0;
+    tv.tv_usec = 1000 * TIME_LIMIT / 2;
+    select(0, 0, 0, 0, &tv);
+}
 
 #ifdef GC_OSF1_THREADS
   GC_bool GC_retry_signals = TRUE;
@@ -73,12 +122,45 @@ word GC_stop_count;        /* Incremented at the beginning of GC_stop_world. */
 
 sem_t GC_suspend_ack_sem;
 
+void GC_suspend_handler_inner(ptr_t sig_arg);
+
+#if defined(IA64) || defined(HP_PA) || defined(M68K)
+extern void GC_with_callee_saves_pushed();
+
 void GC_suspend_handler(int sig)
 {
+  GC_thread me = GC_lookup_thread (pthread_self());
+  if (me -> flags & SUSPENDED)
+    suspend_self();
+  else {
+    int old_errno = errno;
+    GC_with_callee_saves_pushed(GC_suspend_handler_inner, (ptr_t)(word)sig);
+    errno = old_errno;
+  }
+}
+
+#else
+/* We believe that in all other cases the full context is already      */
+/* in the signal handler frame.                                                */
+void GC_suspend_handler(int sig)
+{
+  GC_thread me = GC_lookup_thread(pthread_self());
+  if (me -> flags & SUSPENDED)
+    suspend_self();
+  else {
+    int old_errno = errno;
+    GC_suspend_handler_inner((ptr_t)(word)sig);
+    errno = old_errno;
+  }
+}
+#endif
+
+void GC_suspend_handler_inner(ptr_t sig_arg)
+{
+    int sig = (int)(word)sig_arg;
     int dummy;
     pthread_t my_thread = pthread_self();
     GC_thread me;
-    sigset_t mask;
 #   ifdef PARALLEL_MARK
        word my_mark_no = GC_mark_no;
        /* Marker can't proceed until we acknowledge.  Thus this is     */
@@ -125,24 +207,26 @@ void GC_suspend_handler(int sig)
     /* this thread a SIG_THR_RESTART signal.                   */
     /* SIG_THR_RESTART should be masked at this point.  Thus there     */
     /* is no race.                                             */
-    if (sigfillset(&mask) != 0) ABORT("sigfillset() failed");
-    if (sigdelset(&mask, SIG_THR_RESTART) != 0) ABORT("sigdelset() failed");
-#   ifdef NO_SIGNALS
-      if (sigdelset(&mask, SIGINT) != 0) ABORT("sigdelset() failed");
-      if (sigdelset(&mask, SIGQUIT) != 0) ABORT("sigdelset() failed");
-      if (sigdelset(&mask, SIGTERM) != 0) ABORT("sigdelset() failed");
-      if (sigdelset(&mask, SIGABRT) != 0) ABORT("sigdelset() failed");
-#   endif
-    do {
-           me->stop_info.signal = 0;
-           sigsuspend(&mask);             /* Wait for signal */
-    } while (me->stop_info.signal != SIG_THR_RESTART);
+    /* We do not continue until we receive a SIG_THR_RESTART,  */
+    /* but we do not take that as authoritative.  (We may be   */
+    /* accidentally restarted by one of the user signals we    */
+    /* don't block.)  After we receive the signal, we use a    */
+    /* primitive and expensive mechanism to wait until it's    */
+    /* really safe to proceed.  Under normal circumstances,    */
+    /* this code should not be executed.                       */
+    sigsuspend(&suspend_handler_mask);        /* Wait for signal */
+    while (GC_world_is_stopped && GC_stop_count == my_stop_count) {
+        GC_brief_async_signal_safe_sleep();
+#       if DEBUG_THREADS
+         GC_err_printf0("Sleeping in signal handler");
+#       endif
+    }
     /* If the RESTART signal gets lost, we can still lose.  That should be  */
     /* less likely than losing the SUSPEND signal, since we don't do much   */
     /* between the sem_post and sigsuspend.                                */
-    /* We'd need more handshaking to work around that, since we don't want  */
-    /* to accidentally leave a RESTART signal pending, thus causing us to   */
-    /* continue prematurely in a future round.                             */ 
+    /* We'd need more handshaking to work around that.                     */
+    /* Simply dropping the sigsuspend call should be safe, but is unlikely  */
+    /* to be efficient.                                                            */
 
 #if DEBUG_THREADS
     GC_printf1("Continuing 0x%lx\n", my_thread);
@@ -152,20 +236,11 @@ void GC_suspend_handler(int sig)
 void GC_restart_handler(int sig)
 {
     pthread_t my_thread = pthread_self();
-    GC_thread me;
 
     if (sig != SIG_THR_RESTART) ABORT("Bad signal in suspend_handler");
 
-    /* Let the GC_suspend_handler() know that we got a SIG_THR_RESTART. */
-    /* The lookup here is safe, since I'm doing this on behalf  */
-    /* of a thread which holds the allocation lock in order    */
-    /* to stop the world.  Thus concurrent modification of the */
-    /* data structure is impossible.                           */
-    me = GC_lookup_thread(my_thread);
-    me->stop_info.signal = SIG_THR_RESTART;
-
     /*
-    ** Note: even if we didn't do anything useful here,
+    ** Note: even if we don't do anything useful here,
     ** it would still be necessary to have a signal handler,
     ** rather than ignoring the signals, otherwise
     ** the signals will not be delivered at all, and
@@ -186,6 +261,7 @@ void GC_restart_handler(int sig)
 /* world is stopped.  Should not fail if it isn't.                     */
 void GC_push_all_stacks()
 {
+    GC_bool found_me = FALSE;
     int i;
     GC_thread p;
     ptr_t lo, hi;
@@ -206,6 +282,7 @@ void GC_push_all_stacks()
 #          else
                lo = GC_approx_sp();
 #           endif
+           found_me = TRUE;
            IF_IA64(bs_hi = (ptr_t)GC_save_regs_in_stack();)
        } else {
            lo = p -> stop_info.stack_ptr;
@@ -232,6 +309,11 @@ void GC_push_all_stacks()
           GC_push_all_stack(lo, hi);
 #      endif
 #      ifdef IA64
+#         if DEBUG_THREADS
+            GC_printf3("Reg stack for thread 0x%lx = [%lx,%lx)\n",
+               (unsigned long) p -> id,
+               (unsigned long) bs_lo, (unsigned long) bs_hi);
+#        endif
           if (pthread_equal(p -> id, me)) {
            GC_push_all_eager(bs_lo, bs_hi);
          } else {
@@ -240,6 +322,8 @@ void GC_push_all_stacks()
 #      endif
       }
     }
+    if (!found_me && !GC_in_thread_creation)
+      ABORT("Collecting from unknown thread.");
 }
 
 /* There seems to be a very rare thread stopping problem.  To help us  */
@@ -309,6 +393,7 @@ void GC_stop_world()
       /* We should have previously waited for it to become zero. */
 #   endif /* PARALLEL_MARK */
     ++GC_stop_count;
+    GC_world_is_stopped = TRUE;
     n_live_threads = GC_suspend_all();
 
       if (GC_retry_signals) {
@@ -341,9 +426,11 @@ void GC_stop_world()
          }
       }
     for (i = 0; i < n_live_threads; i++) {
-         if (0 != (code = sem_wait(&GC_suspend_ack_sem))) {
-             GC_err_printf1("Sem_wait returned %ld\n", (unsigned long)code);
-             ABORT("sem_wait for handler failed");
+         while (0 != (code = sem_wait(&GC_suspend_ack_sem))) {
+             if (errno != EINTR) {
+                GC_err_printf1("Sem_wait returned %ld\n", (unsigned long)code);
+                ABORT("sem_wait for handler failed");
+             }
          }
     }
 #   ifdef PARALLEL_MARK
@@ -355,6 +442,55 @@ void GC_stop_world()
     GC_stopping_thread = 0;  /* debugging only */
 }
 
+void suspend_self() {
+  GC_thread me = GC_lookup_thread(pthread_self());
+  if (me == NULL)
+    ABORT("attempting to suspend unknown thread");
+
+  me -> flags |= SUSPENDED;
+  GC_start_blocking();
+  while (me -> flags & SUSPENDED)
+    GC_brief_async_signal_safe_sleep();
+  GC_end_blocking();
+}
+
+void GC_suspend_thread(pthread_t thread) {
+  if (thread == pthread_self())
+    suspend_self();
+  else {
+    int result;
+    GC_thread t = GC_lookup_thread(thread);
+    if (t == NULL)
+      ABORT("attempting to suspend unknown thread");
+
+    t -> flags |= SUSPENDED;
+    result = pthread_kill (t -> id, SIG_SUSPEND);
+    switch (result) {
+    case ESRCH:
+    case 0:
+      break;
+    default:
+      ABORT("pthread_kill failed");
+    }
+  }
+}
+
+void GC_resume_thread(pthread_t thread) {
+  GC_thread t = GC_lookup_thread(thread);
+  if (t == NULL)
+    ABORT("attempting to resume unknown thread");
+
+  t -> flags &= ~SUSPENDED;
+}
+
+int GC_is_thread_suspended(pthread_t thread) {
+  GC_thread t = GC_lookup_thread(thread);
+  if (t == NULL)
+    ABORT("querying suspension state of unknown thread");
+
+  return (t -> flags & SUSPENDED);
+}
+
 /* Caller holds allocation lock, and has held it continuously since    */
 /* the world stopped.                                                  */
 void GC_start_world()
@@ -369,6 +505,7 @@ void GC_start_world()
       GC_printf0("World starting\n");
 #   endif
 
+    GC_world_is_stopped = FALSE;
     for (i = 0; i < THREAD_TABLE_SZ; i++) {
       for (p = GC_threads[i]; p != 0; p = p -> next) {
         if (p -> id != my_thread) {
@@ -378,8 +515,7 @@ void GC_start_world()
            #if DEBUG_THREADS
              GC_printf1("Sending restart signal to 0x%lx\n", p -> id);
            #endif
-        
-        result = pthread_kill(p -> id, SIG_THR_RESTART);
+            result = pthread_kill(p -> id, SIG_THR_RESTART);
            switch(result) {
                 case ESRCH:
                     /* Not really there anymore.  Possible? */
@@ -408,16 +544,9 @@ void GC_stop_init() {
     if (sigfillset(&act.sa_mask) != 0) {
        ABORT("sigfillset() failed");
     }
-#   ifdef NO_SIGNALS
-      if (sigdelset(&act.sa_mask, SIGINT) != 0
-         || sigdelset(&act.sa_mask, SIGQUIT != 0)
-         || sigdelset(&act.sa_mask, SIGABRT != 0)
-         || sigdelset(&act.sa_mask, SIGTERM != 0)) {
-        ABORT("sigdelset() failed");
-      }
-#   endif
-
-    /* SIG_THR_RESTART is unmasked by the handler when necessary.      */
+    GC_remove_allowed_signals(&act.sa_mask);
+    /* SIG_THR_RESTART is set in the resulting mask.           */
+    /* It is unmasked by the handler when necessary.           */
     act.sa_handler = GC_suspend_handler;
     if (sigaction(SIG_SUSPEND, &act, NULL) != 0) {
        ABORT("Cannot set SIG_SUSPEND handler");
@@ -428,6 +557,12 @@ void GC_stop_init() {
        ABORT("Cannot set SIG_THR_RESTART handler");
     }
 
+    /* Inititialize suspend_handler_mask. It excludes SIG_THR_RESTART. */
+      if (sigfillset(&suspend_handler_mask) != 0) ABORT("sigfillset() failed");
+      GC_remove_allowed_signals(&suspend_handler_mask);
+      if (sigdelset(&suspend_handler_mask, SIG_THR_RESTART) != 0)
+         ABORT("sigdelset() failed");
+
     /* Check for GC_RETRY_SIGNALS.     */
       if (0 != GETENV("GC_RETRY_SIGNALS")) {
          GC_retry_signals = TRUE;