[project @ 2002-11-08 15:16:50 by simonpj]
[ghc-hetmet.git] / ghc / rts / Schedule.c
index 3cf6d21..17c7e74 100644 (file)
@@ -1,5 +1,5 @@
 /* ---------------------------------------------------------------------------
- * $Id: Schedule.c,v 1.144 2002/05/18 05:28:15 ken Exp $
+ * $Id: Schedule.c,v 1.157 2002/10/22 11:01:19 simonmar Exp $
  *
  * (c) The GHC Team, 1998-2000
  *
@@ -84,6 +84,7 @@
 #include "StgRun.h"
 #include "StgStartup.h"
 #include "Hooks.h"
+#define COMPILING_SCHEDULER
 #include "Schedule.h"
 #include "StgMiscClosures.h"
 #include "Storage.h"
@@ -96,6 +97,7 @@
 #include "Stats.h"
 #include "Itimer.h"
 #include "Prelude.h"
+#include "ThreadLabels.h"
 #ifdef PROFILING
 #include "Proftimer.h"
 #include "ProfHeap.h"
 #include <unistd.h>
 #endif
 
+#include <string.h>
+#include <stdlib.h>
 #include <stdarg.h>
 
 //@node Variables and Data structures, Prototypes, Includes, Main scheduling code
 /* Main thread queue.
  * Locks required: sched_mutex.
  */
-StgMainThread *main_threads;
+StgMainThread *main_threads = NULL;
 
 /* Thread queues.
  * Locks required: sched_mutex.
@@ -156,16 +160,18 @@ StgTSO *ccalling_threadss[MAX_PROC];
 
 #else /* !GRAN */
 
-StgTSO *run_queue_hd, *run_queue_tl;
-StgTSO *blocked_queue_hd, *blocked_queue_tl;
-StgTSO *sleeping_queue;                /* perhaps replace with a hash table? */
+StgTSO *run_queue_hd = NULL;
+StgTSO *run_queue_tl = NULL;
+StgTSO *blocked_queue_hd = NULL;
+StgTSO *blocked_queue_tl = NULL;
+StgTSO *sleeping_queue = NULL;    /* perhaps replace with a hash table? */
 
 #endif
 
 /* Linked list of all threads.
  * Used for detecting garbage collected threads.
  */
-StgTSO *all_threads;
+StgTSO *all_threads = NULL;
 
 /* When a thread performs a safe C call (_ccall_GC, using old
  * terminology), it gets put on the suspended_ccalling_threads
@@ -182,17 +188,17 @@ static StgTSO *threadStackOverflow(StgTSO *tso);
 
 /* flag set by signal handler to precipitate a context switch */
 //@cindex context_switch
-nat context_switch;
+nat context_switch = 0;
 
 /* if this flag is set as well, give up execution */
 //@cindex interrupted
-rtsBool interrupted;
+rtsBool interrupted = rtsFalse;
 
 /* Next thread ID to allocate.
  * Locks required: thread_id_mutex
  */
 //@cindex next_thread_id
-StgThreadID next_thread_id = 1;
+static StgThreadID next_thread_id = 1;
 
 /*
  * Pointers to the state of the current thread.
@@ -223,7 +229,7 @@ StgTSO *CurrentTSO;
  */
 StgTSO dummy_tso;
 
-rtsBool ready_to_gc;
+static rtsBool ready_to_gc;
 
 /*
  * Set to TRUE when entering a shutdown state (via shutdownHaskellAndExit()) --
@@ -271,21 +277,13 @@ rtsBool emitSchedule = rtsTrue;
 #endif
 
 #if DEBUG
-char *whatNext_strs[] = {
+static char *whatNext_strs[] = {
   "ThreadEnterGHC",
   "ThreadRunGHC",
   "ThreadEnterInterp",
   "ThreadKilled",
   "ThreadComplete"
 };
-
-char *threadReturnCode_strs[] = {
-  "HeapOverflow",                      /* might also be StackOverflow */
-  "StackOverflow",
-  "ThreadYielding",
-  "ThreadBlocked",
-  "ThreadFinished"
-};
 #endif
 
 #if defined(PAR)
@@ -451,8 +449,7 @@ schedule( void )
          m->stat = Success;
          broadcastCondition(&m->wakeup);
 #ifdef DEBUG
-         free(m->tso->label);
-         m->tso->label = NULL;
+         removeThreadLabel((StgWord)m->tso);
 #endif
          break;
        case ThreadKilled:
@@ -465,8 +462,7 @@ schedule( void )
          }
          broadcastCondition(&m->wakeup);
 #ifdef DEBUG
-         free(m->tso->label);
-         m->tso->label = NULL;
+         removeThreadLabel((StgWord)m->tso);
 #endif
          break;
        default:
@@ -488,8 +484,7 @@ schedule( void )
       if (m->tso->what_next == ThreadComplete
          || m->tso->what_next == ThreadKilled) {
 #ifdef DEBUG
-       free(m->tso->label);
-       m->tso->label = NULL;
+       removeThreadLabel((StgWord)m->tso);
 #endif
        main_threads = main_threads->link;
        if (m->tso->what_next == ThreadComplete) {
@@ -1132,11 +1127,22 @@ schedule( void )
              }           
              cap->r.rCurrentNursery->u.back = bd;
 
-             // initialise it as a nursery block
-             bd->step = g0s0;
-             bd->gen_no = 0;
-             bd->flags = 0;
-             bd->free = bd->start;
+             // initialise it as a nursery block.  We initialise the
+             // step, gen_no, and flags field of *every* sub-block in
+             // this large block, because this is easier than making
+             // sure that we always find the block head of a large
+             // block whenever we call Bdescr() (eg. evacuate() and
+             // isAlive() in the GC would both have to do this, at
+             // least).
+             { 
+                 bdescr *x;
+                 for (x = bd; x < bd + blocks; x++) {
+                     x->step = g0s0;
+                     x->gen_no = 0;
+                     x->flags = 0;
+                     x->free = x->start;
+                 }
+             }
 
              // don't forget to update the block count in g0s0.
              g0s0->n_blocks += blocks;
@@ -1432,6 +1438,8 @@ StgInt forkProcess(StgTSO* tso) {
 #ifndef mingw32_TARGET_OS
   pid_t pid;
   StgTSO* t,*next;
+  StgMainThread *m;
+  rtsBool doKill;
 
   IF_DEBUG(scheduler,sched_belch("forking!"));
 
@@ -1442,16 +1450,45 @@ StgInt forkProcess(StgTSO* tso) {
     
   } else { /* child */
   /* wipe all other threads */
-  run_queue_hd = tso;
+  run_queue_hd = run_queue_tl = tso;
   tso->link = END_TSO_QUEUE;
 
+  /* When clearing out the threads, we need to ensure
+     that a 'main thread' is left behind; if there isn't,
+     the Scheduler will shutdown next time it is entered.
+     
+     ==> we don't kill a thread that's on the main_threads
+         list (nor the current thread.)
+    
+     [ Attempts at implementing the more ambitious scheme of
+       killing the main_threads also, and then adding the
+       current thread onto the main_threads list if it wasn't
+       there already, failed -- waitThread() (for one) wasn't
+       up to it. If it proves to be desirable to also kill
+       the main threads, then this scheme will have to be
+       revisited (and fully debugged!)
+       
+       -- sof 7/2002
+     ]
+  */
   /* DO NOT TOUCH THE QUEUES directly because most of the code around
-     us is picky about finding the threat still in its queue when
+     us is picky about finding the thread still in its queue when
      handling the deleteThread() */
 
   for (t = all_threads; t != END_TSO_QUEUE; t = next) {
     next = t->link;
-    if (t->id != tso->id) {
+    
+    /* Don't kill the current thread.. */
+    if (t->id == tso->id) continue;
+    doKill=rtsTrue;
+    /* ..or a main thread */
+    for (m = main_threads; m != NULL; m = m->link) {
+       if (m->tso->id == t->id) {
+         doKill=rtsFalse;
+         break;
+       }
+    }
+    if (doKill) {
       deleteThread(t);
     }
   }
@@ -1620,10 +1657,11 @@ static void unblockThread(StgTSO *tso);
  * instances of Eq/Ord for ThreadIds.
  * ------------------------------------------------------------------------ */
 
-int cmp_thread(const StgTSO *tso1, const StgTSO *tso2) 
+int
+cmp_thread(StgPtr tso1, StgPtr tso2) 
 { 
-  StgThreadID id1 = tso1->id; 
-  StgThreadID id2 = tso2->id;
+  StgThreadID id1 = ((StgTSO *)tso1)->id; 
+  StgThreadID id2 = ((StgTSO *)tso2)->id;
  
   if (id1 < id2) return (-1);
   if (id1 > id2) return 1;
@@ -1635,27 +1673,28 @@ int cmp_thread(const StgTSO *tso1, const StgTSO *tso2)
  *
  * This is used in the implementation of Show for ThreadIds.
  * ------------------------------------------------------------------------ */
-int rts_getThreadId(const StgTSO *tso) 
+int
+rts_getThreadId(StgPtr tso) 
 {
-  return tso->id;
+  return ((StgTSO *)tso)->id;
 }
 
 #ifdef DEBUG
-void labelThread(StgTSO *tso, char *label)
+void
+labelThread(StgPtr tso, char *label)
 {
   int len;
   void *buf;
 
   /* Caveat: Once set, you can only set the thread name to "" */
   len = strlen(label)+1;
-  buf = realloc(tso->label,len);
+  buf = malloc(len);
   if (buf == NULL) {
     fprintf(stderr,"insufficient memory for labelThread!\n");
-    free(tso->label);
-    tso->label = NULL;
   } else
     strncpy(buf,label,len);
-  tso->label = buf;
+  /* Update will free the old memory for us */
+  updateThreadLabel((StgWord)tso,buf);
 }
 #endif /* DEBUG */
 
@@ -1720,10 +1759,6 @@ createThread(nat size)
 #endif
   tso->what_next     = ThreadEnterGHC;
 
-#ifdef DEBUG
-  tso->label = NULL;
-#endif
-
   /* tso->id needs to be unique.  For now we use a heavyweight mutex to
    * protect the increment operation on next_thread_id.
    * In future, we could use an atomic increment instead.
@@ -1908,6 +1943,13 @@ activateSpark (rtsSpark spark)
 }
 #endif
 
+static SchedulerStatus waitThread_(/*out*/StgMainThread* m
+#if defined(THREADED_RTS)
+                                  , rtsBool blockWaiting
+#endif
+                                  );
+
+
 /* ---------------------------------------------------------------------------
  * scheduleThread()
  *
@@ -1954,12 +1996,48 @@ scheduleThread_(StgTSO *tso
 
 void scheduleThread(StgTSO* tso)
 {
-  return scheduleThread_(tso, rtsFalse);
+  scheduleThread_(tso, rtsFalse);
 }
 
-void scheduleExtThread(StgTSO* tso)
+SchedulerStatus
+scheduleWaitThread(StgTSO* tso, /*[out]*/HaskellObj* ret)
 {
-  return scheduleThread_(tso, rtsTrue);
+  StgMainThread *m;
+
+  m = stgMallocBytes(sizeof(StgMainThread), "waitThread");
+  m->tso = tso;
+  m->ret = ret;
+  m->stat = NoStatus;
+#if defined(RTS_SUPPORTS_THREADS)
+  initCondition(&m->wakeup);
+#endif
+
+  /* Put the thread on the main-threads list prior to scheduling the TSO.
+     Failure to do so introduces a race condition in the MT case (as
+     identified by Wolfgang Thaller), whereby the new task/OS thread 
+     created by scheduleThread_() would complete prior to the thread
+     that spawned it managed to put 'itself' on the main-threads list.
+     The upshot of it all being that the worker thread wouldn't get to
+     signal the completion of the its work item for the main thread to
+     see (==> it got stuck waiting.)    -- sof 6/02.
+  */
+  ACQUIRE_LOCK(&sched_mutex);
+  IF_DEBUG(scheduler, sched_belch("== scheduler: waiting for thread (%d)\n", tso->id));
+  
+  m->link = main_threads;
+  main_threads = m;
+
+  /* Inefficient (scheduleThread_() acquires it again right away),
+   * but obviously correct.
+   */
+  RELEASE_LOCK(&sched_mutex);
+
+  scheduleThread_(tso, rtsTrue);
+#if defined(THREADED_RTS)
+  return waitThread_(m, rtsTrue);
+#else
+  return waitThread_(m);
+#endif
 }
 
 /* ---------------------------------------------------------------------------
@@ -2143,40 +2221,41 @@ finishAllThreads ( void )
 SchedulerStatus
 waitThread(StgTSO *tso, /*out*/StgClosure **ret)
 { 
+  StgMainThread *m;
+
+  m = stgMallocBytes(sizeof(StgMainThread), "waitThread");
+  m->tso = tso;
+  m->ret = ret;
+  m->stat = NoStatus;
+#if defined(RTS_SUPPORTS_THREADS)
+  initCondition(&m->wakeup);
+#endif
+
+  /* see scheduleWaitThread() comment */
+  ACQUIRE_LOCK(&sched_mutex);
+  IF_DEBUG(scheduler, sched_belch("== scheduler: waiting for thread (%d)\n", tso->id));
+  m->link = main_threads;
+  main_threads = m;
+  RELEASE_LOCK(&sched_mutex);
+
   IF_DEBUG(scheduler, sched_belch("== scheduler: waiting for thread (%d)\n", tso->id));
 #if defined(THREADED_RTS)
-  return waitThread_(tso,ret, rtsFalse);
+  return waitThread_(m, rtsFalse);
 #else
-  return waitThread_(tso,ret);
+  return waitThread_(m);
 #endif
 }
 
+static
 SchedulerStatus
-waitThread_(StgTSO *tso,
-           /*out*/StgClosure **ret
+waitThread_(StgMainThread* m
 #if defined(THREADED_RTS)
            , rtsBool blockWaiting
 #endif
           )
 {
-  StgMainThread *m;
   SchedulerStatus stat;
 
-  ACQUIRE_LOCK(&sched_mutex);
-  IF_DEBUG(scheduler, sched_belch("== scheduler: waiting for thread (%d)\n", tso->id));
-  
-  m = stgMallocBytes(sizeof(StgMainThread), "waitThread");
-
-  m->tso = tso;
-  m->ret = ret;
-  m->stat = NoStatus;
-#if defined(RTS_SUPPORTS_THREADS)
-  initCondition(&m->wakeup);
-#endif
-
-  m->link = main_threads;
-  main_threads = m;
-
   IF_DEBUG(scheduler, sched_belch("== scheduler: new main thread (%d)\n", m->tso->id));
 
 #if defined(RTS_SUPPORTS_THREADS)
@@ -2187,12 +2266,12 @@ waitThread_(StgTSO *tso,
      * gets to enter the RTS directly without going via another
      * task/thread.
      */
-    RELEASE_LOCK(&sched_mutex);
     schedule();
     ASSERT(m->stat != NoStatus);
   } else 
 # endif
   {
+    ACQUIRE_LOCK(&sched_mutex);
     do {
       waitCondition(&m->wakeup, &sched_mutex);
     } while (m->stat == NoStatus);
@@ -2396,6 +2475,30 @@ GetRoots(evac_fn evac)
 #if defined(PAR) || defined(GRAN)
   markSparkQueue(evac);
 #endif
+
+#ifndef mingw32_TARGET_OS
+  // mark the signal handlers (signals should be already blocked)
+  markSignalHandlers(evac);
+#endif
+
+  // main threads which have completed need to be retained until they
+  // are dealt with in the main scheduler loop.  They won't be
+  // retained any other way: the GC will drop them from the
+  // all_threads list, so we have to be careful to treat them as roots
+  // here.
+  { 
+      StgMainThread *m;
+      for (m = main_threads; m != NULL; m = m->link) {
+         switch (m->tso->what_next) {
+         case ThreadComplete:
+         case ThreadKilled:
+             evac((StgClosure **)&m->tso);
+             break;
+         default:
+             break;
+         }
+      }
+  }
 }
 
 /* -----------------------------------------------------------------------------
@@ -2411,7 +2514,7 @@ GetRoots(evac_fn evac)
    This needs to be protected by the GC condition variable above.  KH.
    -------------------------------------------------------------------------- */
 
-void (*extra_roots)(evac_fn);
+static void (*extra_roots)(evac_fn);
 
 void
 performGC(void)
@@ -3182,7 +3285,6 @@ raiseAsync(StgTSO *tso, StgClosure *exception)
   /* Remove it from any blocking queues */
   unblockThread(tso);
 
-  IF_DEBUG(scheduler, sched_belch("raising exception in thread %ld.", tso->id));
   /* The stack freezing code assumes there's a closure pointer on
    * the top of the stack.  This isn't always the case with compiled
    * code, so we have to push a dummy closure on the top which just
@@ -3197,6 +3299,8 @@ raiseAsync(StgTSO *tso, StgClosure *exception)
     nat i;
     StgAP_UPD * ap;
 
+    ASSERT((P_)su > (P_)sp);
+    
     /* If we find a CATCH_FRAME, and we've got an exception to raise,
      * then build the THUNK raise(exception), and leave it on
      * top of the CATCH_FRAME ready to enter.
@@ -3244,8 +3348,6 @@ raiseAsync(StgTSO *tso, StgClosure *exception)
      */
     ap = (StgAP_UPD *)allocate(AP_sizeW(words));
     
-    ASSERT(words >= 0);
-    
     ap->n_args = words;
     ap->fun    = (StgClosure *)sp[0];
     sp++;
@@ -3468,11 +3570,12 @@ detectBlackHoles( void )
 //@subsection Debugging Routines
 
 /* -----------------------------------------------------------------------------
-   Debugging: why is a thread blocked
+ * Debugging: why is a thread blocked
+ * [Also provides useful information when debugging threaded programs
+ *  at the Haskell source code level, so enable outside of DEBUG. --sof 7/02]
    -------------------------------------------------------------------------- */
 
-#ifdef DEBUG
-
+static
 void
 printThreadBlockage(StgTSO *tso)
 {
@@ -3520,6 +3623,7 @@ printThreadBlockage(StgTSO *tso)
   }
 }
 
+static
 void
 printThreadStatus(StgTSO *tso)
 {
@@ -3539,31 +3643,35 @@ void
 printAllThreads(void)
 {
   StgTSO *t;
+  void *label;
 
 # if defined(GRAN)
   char time_string[TIME_STR_LEN], node_str[NODE_STR_LEN];
   ullong_format_string(TIME_ON_PROC(CurrentProc), 
                       time_string, rtsFalse/*no commas!*/);
 
-  sched_belch("all threads at [%s]:", time_string);
+  fprintf(stderr, "all threads at [%s]:\n", time_string);
 # elif defined(PAR)
   char time_string[TIME_STR_LEN], node_str[NODE_STR_LEN];
   ullong_format_string(CURRENT_TIME,
                       time_string, rtsFalse/*no commas!*/);
 
-  sched_belch("all threads at [%s]:", time_string);
+  fprintf(stderr,"all threads at [%s]:\n", time_string);
 # else
-  sched_belch("all threads:");
+  fprintf(stderr,"all threads:\n");
 # endif
 
   for (t = all_threads; t != END_TSO_QUEUE; t = t->global_link) {
-    fprintf(stderr, "\tthread %d ", t->id);
-    if (t->label) fprintf(stderr,"[\"%s\"] ",t->label);
+    fprintf(stderr, "\tthread %d @ %p ", t->id, (void *)t);
+    label = lookupThreadLabel((StgWord)t);
+    if (label) fprintf(stderr,"[\"%s\"] ",(char *)label);
     printThreadStatus(t);
     fprintf(stderr,"\n");
   }
 }
     
+#ifdef DEBUG
+
 /* 
    Print a whole blocking queue attached to node (debugging only).
 */