Don't call DEAD_WEAK finalizer again on shutdown (#7170)
authorSimon Marlow <marlowsd@gmail.com>
Mon, 1 Jun 2015 20:34:02 +0000 (21:34 +0100)
committerSimon Marlow <marlowsd@gmail.com>
Mon, 1 Jun 2015 20:34:02 +0000 (21:34 +0100)
Summary:
There's a race condition like this:

  # A foreign pointer gets promoted to the last generation
  # It has its finalizer called manually
  # We start shutting down the runtime in `hs_exit_` from the main
    thread
  # A minor GC starts running (`scheduleDoGC`) on one of the threads
  # The minor GC notices that we're in `SCHED_INTERRUPTING` state and
    advances to `SCHED_SHUTTING_DOWN`
  # The main thread tries to do major GC (with `scheduleDoGC`), but it
    exits early because we're in `SCHED_SHUTTING_DOWN` state
  # We end up with a `DEAD_WEAK` left on the list of weak pointers of
    the last generation, because it relied on major GC removing it from
    that list

This change:
  * Ignores DEAD_WEAK finalizers when shutting down
  * Makes the major GC on shutdown more likely
  * Fixes a bogus assert

Test Plan:
before this diff https://ghc.haskell.org/trac/ghc/ticket/7170#comment:5
reproduced and after it doesn't

Reviewers: ezyang, austin, simonmar

Reviewed By: simonmar

Subscribers: bgamari, thomie

Differential Revision: https://phabricator.haskell.org/D921

GHC Trac Issues: #7170

rts/Schedule.c
rts/Weak.c
rts/sm/MarkWeak.c

index 957aa4b..f81fc0e 100644 (file)
@@ -251,7 +251,7 @@ schedule (Capability *initialCapability, Task *task)
     case SCHED_INTERRUPTING:
         debugTrace(DEBUG_sched, "SCHED_INTERRUPTING");
         /* scheduleDoGC() deletes all the threads */
-        scheduleDoGC(&cap,task,rtsFalse);
+        scheduleDoGC(&cap,task,rtsTrue);
 
         // after scheduleDoGC(), we must be shutting down.  Either some
         // other Capability did the final GC, or we did it above,
@@ -1458,6 +1458,7 @@ scheduleDoGC (Capability **pcap, Task *task USED_IF_THREADS,
     Capability *cap = *pcap;
     rtsBool heap_census;
     nat collect_gen;
+    rtsBool major_gc;
 #ifdef THREADED_RTS
     nat gc_type;
     nat i, sync;
@@ -1476,6 +1477,7 @@ scheduleDoGC (Capability **pcap, Task *task USED_IF_THREADS,
     // Figure out which generation we are collecting, so that we can
     // decide whether this is a parallel GC or not.
     collect_gen = calcNeeded(force_major || heap_census, NULL);
+    major_gc = (collect_gen == RtsFlags.GcFlags.generations-1);
 
 #ifdef THREADED_RTS
     if (sched_state < SCHED_INTERRUPTING
@@ -1618,8 +1620,9 @@ delete_threads_and_gc:
      * We now have all the capabilities; if we're in an interrupting
      * state, then we should take the opportunity to delete all the
      * threads in the system.
+     * Checking for major_gc ensures that the last GC is major.
      */
-    if (sched_state == SCHED_INTERRUPTING) {
+    if (sched_state == SCHED_INTERRUPTING && major_gc) {
         deleteAllThreads(cap);
 #if defined(THREADED_RTS)
         // Discard all the sparks from every Capability.  Why?
index f8faa4e..92f1bdb 100644 (file)
@@ -43,7 +43,16 @@ runAllCFinalizers(StgWeak *list)
     }
 
     for (w = list; w; w = w->link) {
-        runCFinalizers((StgCFinalizerList *)w->cfinalizers);
+        // We need to filter out DEAD_WEAK objects, because it's not guaranteed
+        // that the list will not have them when shutting down.
+        // They only get filtered out during GC for the generation they
+        // belong to.
+        // If there's no major GC between the time that the finalizer for the
+        // object from the oldest generation is manually called and shutdown
+        // we end up running the same finalizer twice. See #7170.
+        if (w->header.info != &stg_DEAD_WEAK_info) {
+            runCFinalizers((StgCFinalizerList *)w->cfinalizers);
+        }
     }
 
     if (task != NULL) {
index c5a107c..60ac53f 100644 (file)
@@ -348,7 +348,8 @@ static void checkWeakPtrSanity(StgWeak *hd, StgWeak *tl)
 {
     StgWeak *w, *prev;
     for (w = hd; w != NULL; prev = w, w = w->link) {
-        ASSERT(INFO_PTR_TO_STRUCT(UNTAG_CLOSURE((StgClosure*)w)->header.info)->type == WEAK);
+        ASSERT(INFO_PTR_TO_STRUCT(UNTAG_CLOSURE((StgClosure*)w)->header.info)->type == WEAK
+            || UNTAG_CLOSURE((StgClosure*)w)->header.info == &stg_DEAD_WEAK_info);
         checkClosure((StgClosure*)w);
     }
     if (tl != NULL) {