Commit f762be1b authored by simonmar's avatar simonmar
Browse files

[project @ 2002-03-12 11:50:02 by simonmar]

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.
parent 44aa6bcb
/* -----------------------------------------------------------------------------
* $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;
}
/* -----------------------------------------------------------------------------
......
/* -----------------------------------------------------------------------------
* $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);
......
/* ---------------------------------------------------------------------------
* $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
*
......@@ -119,35 +119,10 @@
//@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);
}
......
/* -----------------------------------------------------------------------------
* $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
/* -----------------------------------------------------------------------------
* $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;