[project @ 2002-03-12 11:50:02 by simonmar]
authorsimonmar <unknown>
Tue, 12 Mar 2002 11:51:07 +0000 (11:51 +0000)
committersimonmar <unknown>
Tue, 12 Mar 2002 11:51:07 +0000 (11:51 +0000)
Main threads are now not kept alive artificially, so it is possible
for a main thread to be sent the BlockedOnDeadMVar exception.  Main
threads are no longer GC roots.

This involved cleaning up the weak pointer processing somewhat, and
separating the processing of real weak pointers from the processing of
the all_threads list (which can be thought of as "weaker pointers": a
finalizer can keep a blocked thread alive, but not vice-versa).  The
new story is described in a detailed comment in GC.c.

One interesting consequence is that it's much harder to get a Deadlock
exception now - many deadlock situations involving main threads will
turn into BlockedOnDeadMVar situations instead.  For example, if there
are a group of threads in a circular deadlock, then they will all be
sent BlockedOnDeadMVar simultaneously, whereas before if one of them
was the main thread it would be sent Deadlock.  It's really hard to
get Deadlock now - you have to somehow keep an MVar independently
reachable, eg. by using a StablePtr.

ghc/rts/GC.c
ghc/rts/GCCompact.c
ghc/rts/Schedule.c
ghc/rts/Schedule.h
ghc/rts/StoragePriv.h

index 11f7911..0b236bf 100644 (file)
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
- * $Id: GC.c,v 1.131 2002/03/07 17:53:05 keithw Exp $
+ * $Id: GC.c,v 1.132 2002/03/12 11:50:02 simonmar Exp $
  *
  * (c) The GHC Team 1998-1999
  *
@@ -99,12 +99,17 @@ static nat evac_gen;
 /* Weak pointers
  */
 StgWeak *old_weak_ptr_list; // also pending finaliser list
-static rtsBool weak_done;         // all done for this pass
+
+/* Which stage of processing various kinds of weak pointer are we at?
+ * (see traverse_weak_ptr_list() below for discussion).
+ */
+typedef enum { WeakPtrs, WeakThreads, WeakDone } WeakStage;
+static WeakStage weak_stage;
 
 /* List of all threads during GC
  */
 static StgTSO *old_all_threads;
-static StgTSO *resurrected_threads;
+StgTSO *resurrected_threads;
 
 /* Flag indicating failure to evacuate an object to the desired
  * generation.
@@ -497,7 +502,7 @@ GarbageCollect ( void (*get_roots)(evac_fn), rtsBool force_major_gc )
   mark_weak_ptr_list(&weak_ptr_list);
   old_weak_ptr_list = weak_ptr_list;
   weak_ptr_list = NULL;
-  weak_done = rtsFalse;
+  weak_stage = WeakPtrs;
 
   /* The all_threads list is like the weak_ptr_list.  
    * See traverse_weak_ptr_list() for the details.
@@ -581,12 +586,32 @@ GarbageCollect ( void (*get_roots)(evac_fn), rtsBool force_major_gc )
 
     if (flag) { goto loop; }
 
-    // must be last... 
+    // must be last...  invariant is that everything is fully
+    // scavenged at this point.
     if (traverse_weak_ptr_list()) { // returns rtsTrue if evaced something 
       goto loop;
     }
   }
 
+  /* Update the pointers from the "main thread" list - these are
+   * treated as weak pointers because we want to allow a main thread
+   * to get a BlockedOnDeadMVar exception in the same way as any other
+   * thread.  Note that the threads should all have been retained by
+   * GC by virtue of being on the all_threads list, we're just
+   * updating pointers here.
+   */
+  {
+      StgMainThread *m;
+      StgTSO *tso;
+      for (m = main_threads; m != NULL; m = m->link) {
+         tso = (StgTSO *) isAlive((StgClosure *)m->tso);
+         if (tso == NULL) {
+             barf("main thread has been GC'd");
+         }
+         m->tso = tso;
+      }
+  }
+
 #if defined(PAR)
   // Reconstruct the Global Address tables used in GUM 
   rebuildGAtables(major_gc);
@@ -1024,6 +1049,30 @@ GarbageCollect ( void (*get_roots)(evac_fn), rtsBool force_major_gc )
    older generations than the one we're collecting.  This could
    probably be optimised by keeping per-generation lists of weak
    pointers, but for a few weak pointers this scheme will work.
+
+   There are three distinct stages to processing weak pointers:
+
+   - weak_stage == WeakPtrs
+
+     We process all the weak pointers whos keys are alive (evacuate
+     their values and finalizers), and repeat until we can find no new
+     live keys.  If no live keys are found in this pass, then we
+     evacuate the finalizers of all the dead weak pointers in order to
+     run them.
+
+   - weak_stage == WeakThreads
+
+     Now, we discover which *threads* are still alive.  Pointers to
+     threads from the all_threads and main thread lists are the
+     weakest of all: a pointers from the finalizer of a dead weak
+     pointer can keep a thread alive.  Any threads found to be unreachable
+     are evacuated and placed on the resurrected_threads list so we 
+     can send them a signal later.
+
+   - weak_stage == WeakDone
+
+     No more evacuation is done.
+
    -------------------------------------------------------------------------- */
 
 static rtsBool 
@@ -1033,127 +1082,144 @@ traverse_weak_ptr_list(void)
   StgClosure *new;
   rtsBool flag = rtsFalse;
 
-  if (weak_done) { return rtsFalse; }
-
-  /* doesn't matter where we evacuate values/finalizers to, since
-   * these pointers are treated as roots (iff the keys are alive).
-   */
-  evac_gen = 0;
-
-  last_w = &old_weak_ptr_list;
-  for (w = old_weak_ptr_list; w != NULL; w = next_w) {
+  switch (weak_stage) {
 
-    /* There might be a DEAD_WEAK on the list if finalizeWeak# was
-     * called on a live weak pointer object.  Just remove it.
-     */
-    if (w->header.info == &stg_DEAD_WEAK_info) {
-      next_w = ((StgDeadWeak *)w)->link;
-      *last_w = next_w;
-      continue;
-    }
-
-    ASSERT(get_itbl(w)->type == WEAK);
-
-    /* Now, check whether the key is reachable.
-     */
-    new = isAlive(w->key);
-    if (new != NULL) {
-      w->key = new;
-      // evacuate the value and finalizer 
-      w->value = evacuate(w->value);
-      w->finalizer = evacuate(w->finalizer);
-      // remove this weak ptr from the old_weak_ptr list 
-      *last_w = w->link;
-      // and put it on the new weak ptr list 
-      next_w  = w->link;
-      w->link = weak_ptr_list;
-      weak_ptr_list = w;
-      flag = rtsTrue;
-      IF_DEBUG(weak, belch("Weak pointer still alive at %p -> %p", w, w->key));
-      continue;
-    }
-    else {
-      last_w = &(w->link);
-      next_w = w->link;
-      continue;
-    }
-  }
-
-  /* Now deal with the all_threads list, which behaves somewhat like
-   * the weak ptr list.  If we discover any threads that are about to
-   * become garbage, we wake them up and administer an exception.
-   */
-  {
-    StgTSO *t, *tmp, *next, **prev;
-
-    prev = &old_all_threads;
-    for (t = old_all_threads; t != END_TSO_QUEUE; t = next) {
+  case WeakDone:
+      return rtsFalse;
 
-      (StgClosure *)tmp = isAlive((StgClosure *)t);
+  case WeakPtrs:
+      /* doesn't matter where we evacuate values/finalizers to, since
+       * these pointers are treated as roots (iff the keys are alive).
+       */
+      evac_gen = 0;
       
-      if (tmp != NULL) {
-         t = tmp;
-      }
-
-      ASSERT(get_itbl(t)->type == TSO);
-      switch (t->what_next) {
-      case ThreadRelocated:
-         next = t->link;
-         *prev = next;
-         continue;
-      case ThreadKilled:
-      case ThreadComplete:
-         // finshed or died.  The thread might still be alive, but we
-         // don't keep it on the all_threads list.  Don't forget to
-         // stub out its global_link field.
-         next = t->global_link;
-         t->global_link = END_TSO_QUEUE;
-         *prev = next;
-         continue;
-      default:
-         ;
+      last_w = &old_weak_ptr_list;
+      for (w = old_weak_ptr_list; w != NULL; w = next_w) {
+         
+         /* There might be a DEAD_WEAK on the list if finalizeWeak# was
+          * called on a live weak pointer object.  Just remove it.
+          */
+         if (w->header.info == &stg_DEAD_WEAK_info) {
+             next_w = ((StgDeadWeak *)w)->link;
+             *last_w = next_w;
+             continue;
+         }
+         
+         ASSERT(get_itbl(w)->type == WEAK);
+         
+         /* Now, check whether the key is reachable.
+          */
+         new = isAlive(w->key);
+         if (new != NULL) {
+             w->key = new;
+             // evacuate the value and finalizer 
+             w->value = evacuate(w->value);
+             w->finalizer = evacuate(w->finalizer);
+             // remove this weak ptr from the old_weak_ptr list 
+             *last_w = w->link;
+             // and put it on the new weak ptr list 
+             next_w  = w->link;
+             w->link = weak_ptr_list;
+             weak_ptr_list = w;
+             flag = rtsTrue;
+             IF_DEBUG(weak, belch("Weak pointer still alive at %p -> %p", 
+                                  w, w->key));
+             continue;
+         }
+         else {
+             last_w = &(w->link);
+             next_w = w->link;
+             continue;
+         }
       }
+      
+      /* If we didn't make any changes, then we can go round and kill all
+       * the dead weak pointers.  The old_weak_ptr list is used as a list
+       * of pending finalizers later on.
+       */
+      if (flag == rtsFalse) {
+         for (w = old_weak_ptr_list; w; w = w->link) {
+             w->finalizer = evacuate(w->finalizer);
+         }
 
-      if (tmp == NULL) {
-         // not alive (yet): leave this thread on the old_all_threads list.
-         prev = &(t->global_link);
-         next = t->global_link;
-      } 
-      else {
-         // alive: move this thread onto the all_threads list.
-         next = t->global_link;
-         t->global_link = all_threads;
-         all_threads  = t;
-         *prev = next;
+         // Next, move to the WeakThreads stage after fully
+         // scavenging the finalizers we've just evacuated.
+         weak_stage = WeakThreads;
       }
-    }
-  }
 
-  /* If we didn't make any changes, then we can go round and kill all
-   * the dead weak pointers.  The old_weak_ptr list is used as a list
-   * of pending finalizers later on.
-   */
-  if (flag == rtsFalse) {
-    for (w = old_weak_ptr_list; w; w = w->link) {
-      w->finalizer = evacuate(w->finalizer);
-    }
+      return rtsTrue;
 
-    /* And resurrect any threads which were about to become garbage.
-     */
-    {
-      StgTSO *t, *tmp, *next;
-      for (t = old_all_threads; t != END_TSO_QUEUE; t = next) {
-       next = t->global_link;
-       (StgClosure *)tmp = evacuate((StgClosure *)t);
-       tmp->global_link = resurrected_threads;
-       resurrected_threads = tmp;
+  case WeakThreads:
+      /* Now deal with the all_threads list, which behaves somewhat like
+       * the weak ptr list.  If we discover any threads that are about to
+       * become garbage, we wake them up and administer an exception.
+       */
+      {
+         StgTSO *t, *tmp, *next, **prev;
+         
+         prev = &old_all_threads;
+         for (t = old_all_threads; t != END_TSO_QUEUE; t = next) {
+             
+             (StgClosure *)tmp = isAlive((StgClosure *)t);
+             
+             if (tmp != NULL) {
+                 t = tmp;
+             }
+             
+             ASSERT(get_itbl(t)->type == TSO);
+             switch (t->what_next) {
+             case ThreadRelocated:
+                 next = t->link;
+                 *prev = next;
+                 continue;
+             case ThreadKilled:
+             case ThreadComplete:
+                 // finshed or died.  The thread might still be alive, but we
+                 // don't keep it on the all_threads list.  Don't forget to
+                 // stub out its global_link field.
+                 next = t->global_link;
+                 t->global_link = END_TSO_QUEUE;
+                 *prev = next;
+                 continue;
+             default:
+                 ;
+             }
+             
+             if (tmp == NULL) {
+                 // not alive (yet): leave this thread on the
+                 // old_all_threads list.
+                 prev = &(t->global_link);
+                 next = t->global_link;
+             } 
+             else {
+                 // alive: move this thread onto the all_threads list.
+                 next = t->global_link;
+                 t->global_link = all_threads;
+                 all_threads  = t;
+                 *prev = next;
+             }
+         }
       }
-    }
+      
+      /* And resurrect any threads which were about to become garbage.
+       */
+      {
+         StgTSO *t, *tmp, *next;
+         for (t = old_all_threads; t != END_TSO_QUEUE; t = next) {
+             next = t->global_link;
+             (StgClosure *)tmp = evacuate((StgClosure *)t);
+             tmp->global_link = resurrected_threads;
+             resurrected_threads = tmp;
+         }
+      }
+      
+      weak_stage = WeakDone;  // *now* we're done,
+      return rtsTrue;         // but one more round of scavenging, please
 
-    weak_done = rtsTrue;
+  default:
+      barf("traverse_weak_ptr_list");
   }
 
-  return rtsTrue;
 }
 
 /* -----------------------------------------------------------------------------
index dfb381d..ea2e474 100644 (file)
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
- * $Id: GCCompact.c,v 1.11 2001/12/11 12:03:23 simonmar Exp $
+ * $Id: GCCompact.c,v 1.12 2002/03/12 11:51:06 simonmar Exp $
  *
  * (c) The GHC Team 2001
  *
@@ -849,7 +849,6 @@ compact( void (*get_roots)(evac_fn) )
 {
     nat g, s, blocks;
     step *stp;
-    extern StgWeak *old_weak_ptr_list; // tmp
 
     // 1. thread the roots
     get_roots((evac_fn)thread);
@@ -871,6 +870,17 @@ compact( void (*get_roots)(evac_fn) )
     // the global thread list
     thread((StgPtr)&all_threads);
 
+    // any threads resurrected during this GC
+    thread((StgPtr)&resurrected_threads);
+
+    // the main threads list
+    {
+       StgMainThread *m;
+       for (m = main_threads; m != NULL; m = m->link) {
+           thread((StgPtr)&m->tso);
+       }
+    }
+
     // the static objects
     thread_static(scavenged_static_objects);
 
index 4197f58..5cef10c 100644 (file)
@@ -1,5 +1,5 @@
 /* ---------------------------------------------------------------------------
- * $Id: Schedule.c,v 1.132 2002/02/18 17:27:24 sof Exp $
+ * $Id: Schedule.c,v 1.133 2002/03/12 11:51:06 simonmar Exp $
  *
  * (c) The GHC Team, 1998-2000
  *
 //@node Variables and Data structures, Prototypes, Includes, Main scheduling code
 //@subsection Variables and Data structures
 
-/* Main threads:
- *
- * These are the threads which clients have requested that we run.  
- *
- * In a 'threaded' build, we might have several concurrent clients all
- * waiting for results, and each one will wait on a condition variable
- * until the result is available.
- *
- * In non-SMP, clients are strictly nested: the first client calls
- * into the RTS, which might call out again to C with a _ccall_GC, and
- * eventually re-enter the RTS.
- *
- * Main threads information is kept in a linked list:
- */
-//@cindex StgMainThread
-typedef struct StgMainThread_ {
-  StgTSO *         tso;
-  SchedulerStatus  stat;
-  StgClosure **    ret;
-#if defined(RTS_SUPPORTS_THREADS)
-  Condition        wakeup;
-#endif
-  struct StgMainThread_ *link;
-} StgMainThread;
-
 /* Main thread queue.
  * Locks required: sched_mutex.
  */
-static StgMainThread *main_threads;
+StgMainThread *main_threads;
 
 /* Thread queues.
  * Locks required: sched_mutex.
@@ -2296,9 +2271,6 @@ GetRoots(evac_fn evac)
   }
 #endif 
 
-  for (m = main_threads; m != NULL; m = m->link) {
-      evac((StgClosure **)&m->tso);
-  }
   if (suspended_ccalling_threads != END_TSO_QUEUE) {
       evac((StgClosure **)&suspended_ccalling_threads);
   }
index cb12eb1..10f950c 100644 (file)
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
- * $Id: Schedule.h,v 1.29 2002/02/15 07:50:37 sof Exp $
+ * $Id: Schedule.h,v 1.30 2002/03/12 11:51:07 simonmar Exp $
  *
  * (c) The GHC Team 1998-1999
  *
@@ -7,22 +7,11 @@
  * (RTS internal scheduler interface)
  *
  * -------------------------------------------------------------------------*/
+
 #ifndef __SCHEDULE_H__
 #define __SCHEDULE_H__
 #include "OSThreads.h"
 
-//@menu
-//* Scheduler Functions::      
-//* Scheduler Vars and Data Types::  
-//* Some convenient macros::   
-//* Index::                    
-//@end menu
-
-//@node Scheduler Functions, Scheduler Vars and Data Types
-//@subsection Scheduler Functions
-
-//@cindex initScheduler
-//@cindex exitScheduler
 /* initScheduler(), exitScheduler(), startTasks()
  * 
  * Called from STG :  no
@@ -31,7 +20,6 @@
 extern void initScheduler  ( void );
 extern void exitScheduler  ( void );
 
-//@cindex awakenBlockedQueue
 /* awakenBlockedQueue()
  *
  * Takes a pointer to the beginning of a blocked TSO queue, and
@@ -48,7 +36,6 @@ void awakenBlockedQueue(StgBlockingQueueElement *q, StgClosure *node);
 void awakenBlockedQueue(StgTSO *tso);
 #endif
 
-//@cindex unblockOne
 /* unblockOne()
  *
  * Takes a pointer to the beginning of a blocked TSO queue, and
@@ -63,7 +50,6 @@ StgBlockingQueueElement *unblockOne(StgBlockingQueueElement *bqe, StgClosure *no
 StgTSO *unblockOne(StgTSO *tso);
 #endif
 
-//@cindex raiseAsync
 /* raiseAsync()
  *
  * Raises an exception asynchronously in the specified thread.
@@ -73,7 +59,6 @@ StgTSO *unblockOne(StgTSO *tso);
  */
 void raiseAsync(StgTSO *tso, StgClosure *exception);
 
-//@cindex awaitEvent
 /* awaitEvent()
  *
  * Raises an exception asynchronously in the specified thread.
@@ -102,7 +87,6 @@ rtsBool wakeUpSleepingThreads(nat);  /* In Select.c */
 void GetRoots(evac_fn);
 
 // ToDo: check whether all fcts below are used in the SMP version, too
-//@cindex awaken_blocked_queue
 #if defined(GRAN)
 void    awaken_blocked_queue(StgBlockingQueueElement *q, StgClosure *node);
 void    unlink_from_bq(StgTSO* tso, StgClosure* node);
@@ -118,10 +102,6 @@ void    awaken_blocked_queue(StgTSO *q);
 void    initThread(StgTSO *tso, nat stack_size);
 #endif
 
-//@node Scheduler Vars and Data Types, Some convenient macros, Scheduler Functions
-//@subsection Scheduler Vars and Data Types
-
-//@cindex context_switch
 /* Context switch flag.
  * Locks required  : sched_mutex
  */
@@ -175,8 +155,39 @@ nat  run_queue_len(void);
 
 void resurrectThreads( StgTSO * );
 
-//@node Some convenient macros, Index, Scheduler Vars and Data Types
-//@subsection Some convenient macros
+/* Main threads:
+ *
+ * These are the threads which clients have requested that we run.  
+ *
+ * In a 'threaded' build, we might have several concurrent clients all
+ * waiting for results, and each one will wait on a condition variable
+ * until the result is available.
+ *
+ * In non-SMP, clients are strictly nested: the first client calls
+ * into the RTS, which might call out again to C with a _ccall_GC, and
+ * eventually re-enter the RTS.
+ *
+ * This is non-abstract at the moment because the garbage collector
+ * treats pointers to TSOs from the main thread list as "weak" - these
+ * pointers won't prevent a thread from receiving a BlockedOnDeadMVar
+ * exception.
+ *
+ * Main threads information is kept in a linked list:
+ */
+typedef struct StgMainThread_ {
+  StgTSO *         tso;
+  SchedulerStatus  stat;
+  StgClosure **    ret;
+#if defined(RTS_SUPPORTS_THREADS)
+  Condition        wakeup;
+#endif
+  struct StgMainThread_ *link;
+} StgMainThread;
+
+/* Main thread queue.
+ * Locks required: sched_mutex.
+ */
+extern StgMainThread *main_threads;
 
 /* debugging only 
  */
@@ -196,7 +207,6 @@ void print_bqe (StgBlockingQueueElement *bqe);
 
 /* END_TSO_QUEUE and friends now defined in includes/StgMiscClosures.h */
 
-//@cindex APPEND_TO_RUN_QUEUE
 /* Add a thread to the end of the run queue.
  * NOTE: tso->link should be END_TSO_QUEUE before calling this macro.
  */
@@ -209,7 +219,6 @@ void print_bqe (StgBlockingQueueElement *bqe);
     }                                          \
     run_queue_tl = tso;
 
-//@cindex PUSH_ON_RUN_QUEUE
 /* Push a thread on the beginning of the run queue.  Used for
  * newly awakened threads, so they get run as soon as possible.
  */
@@ -220,7 +229,6 @@ void print_bqe (StgBlockingQueueElement *bqe);
       run_queue_tl = tso;                      \
     }
 
-//@cindex POP_RUN_QUEUE
 /* Pop the first thread off the runnable queue.
  */
 #define POP_RUN_QUEUE()                                \
@@ -235,7 +243,6 @@ void print_bqe (StgBlockingQueueElement *bqe);
     t;                                         \
   })
 
-//@cindex APPEND_TO_BLOCKED_QUEUE
 /* Add a thread to the end of the blocked queue.
  */
 #define APPEND_TO_BLOCKED_QUEUE(tso)           \
@@ -247,7 +254,6 @@ void print_bqe (StgBlockingQueueElement *bqe);
     }                                          \
     blocked_queue_tl = tso;
 
-//@cindex THREAD_RUNNABLE
 /* Signal that a runnable thread has become available, in
  * case there are any waiting tasks to execute it.
  */
@@ -261,30 +267,9 @@ void print_bqe (StgBlockingQueueElement *bqe);
 #define THREAD_RUNNABLE()  /* nothing */
 #endif
 
-//@cindex EMPTY_RUN_QUEUE
 /* Check whether the run queue is empty i.e. the PE is idle
  */
 #define EMPTY_RUN_QUEUE()     (run_queue_hd == END_TSO_QUEUE)
 #define EMPTY_QUEUE(q)        (q == END_TSO_QUEUE)
 
 #endif /* __SCHEDULE_H__ */
-
-//@node Index,  , Some convenient macros
-//@subsection Index
-
-//@index
-//* APPEND_TO_BLOCKED_QUEUE::  @cindex\s-+APPEND_TO_BLOCKED_QUEUE
-//* APPEND_TO_RUN_QUEUE::  @cindex\s-+APPEND_TO_RUN_QUEUE
-//* POP_RUN_QUEUE    ::  @cindex\s-+POP_RUN_QUEUE    
-//* PUSH_ON_RUN_QUEUE::  @cindex\s-+PUSH_ON_RUN_QUEUE
-//* awaitEvent::  @cindex\s-+awaitEvent
-//* awakenBlockedQueue::  @cindex\s-+awakenBlockedQueue
-//* awaken_blocked_queue::  @cindex\s-+awaken_blocked_queue
-//* context_switch::  @cindex\s-+context_switch
-//* exitScheduler::  @cindex\s-+exitScheduler
-//* gc_pending_cond::  @cindex\s-+gc_pending_cond
-//* initScheduler::  @cindex\s-+initScheduler
-//* raiseAsync::  @cindex\s-+raiseAsync
-//* startTasks::  @cindex\s-+startTasks
-//* unblockOne::  @cindex\s-+unblockOne
-//@end index
index 033b06c..a75c568 100644 (file)
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
- * $Id: StoragePriv.h,v 1.19 2001/11/08 12:46:31 simonmar Exp $
+ * $Id: StoragePriv.h,v 1.20 2002/03/12 11:51:07 simonmar Exp $
  *
  * (c) The GHC Team, 1998-1999
  *
@@ -28,9 +28,13 @@ extern StgTSO *relocate_stack(StgTSO *dest, ptrdiff_t diff);
 extern StgClosure *static_objects;
 extern StgClosure *scavenged_static_objects;
 
+extern StgWeak *old_weak_ptr_list;
+
 extern StgWeak    *weak_ptr_list;
 extern StgClosure *caf_list;
 
+extern StgTSO *resurrected_threads;
+
 extern bdescr *small_alloc_list;
 extern bdescr *large_alloc_list;
 extern bdescr *pinned_object_block;