summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Klebinger <klebinger.andreas@gmx.at>2022-11-29 12:19:16 +0100
committerMarge Bot <ben+marge-bot@smart-cactus.org>2023-01-12 15:52:23 -0500
commita1491c8791c57a64d94bc08d639d585815c8d4e2 (patch)
tree2798061cf74e3ea784939f4da456fd8af3200857
parent905d0b6e1db714b306a940fb58a570c9294aa88d (diff)
downloadhaskell-a1491c8791c57a64d94bc08d639d585815c8d4e2.tar.gz
Only gc sparks locally when we can ensure marking is done.
When performing GC without work stealing there was no guarantee that spark pruning was happening after marking of the sparks. This could cause us to GC live sparks under certain circumstances. Fixes #22528.
-rw-r--r--docs/users_guide/9.6.1-notes.rst9
-rw-r--r--rts/Sparks.c30
-rw-r--r--rts/sm/GC.c21
3 files changed, 46 insertions, 14 deletions
diff --git a/docs/users_guide/9.6.1-notes.rst b/docs/users_guide/9.6.1-notes.rst
index 797ff72f3a..9fd0ca2d75 100644
--- a/docs/users_guide/9.6.1-notes.rst
+++ b/docs/users_guide/9.6.1-notes.rst
@@ -129,6 +129,9 @@ Runtime system
Previously only live blocks were taken into account.
This makes it more likely to trigger promptly when the heap is highly fragmented.
+- Fixed a bug that sometimes caused live sparks to be GC'ed too early either during
+ minor GC or major GC with workstealing disabled. See #22528.
+
``base`` library
~~~~~~~~~~~~~~~~
@@ -149,9 +152,9 @@ Runtime system
- Updated to `Unicode 15.0.0 <https://www.unicode.org/versions/Unicode15.0.0/>`_.
-- Add standard Unicode case predicates :base-ref:`Data.Char.isUpperCase` and
- :base-ref:`Data.Char.isLowerCase`. These predicates use the standard Unicode
- case properties and are more intuitive than :base-ref:`Data.Char.isUpper` and
+- Add standard Unicode case predicates :base-ref:`Data.Char.isUpperCase` and
+ :base-ref:`Data.Char.isLowerCase`. These predicates use the standard Unicode
+ case properties and are more intuitive than :base-ref:`Data.Char.isUpper` and
:base-ref:`Data.Char.isLower`.
``ghc-prim`` library
diff --git a/rts/Sparks.c b/rts/Sparks.c
index 6e2c0f9fb6..3f8980485a 100644
--- a/rts/Sparks.c
+++ b/rts/Sparks.c
@@ -79,6 +79,34 @@ newSpark (StgRegTable *reg, StgClosure *p)
return 1;
}
+/* Note [Pruning the spark pool]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+pruneSparkQueue checks if closures have been evacuated to know weither or
+not a spark can be GCed. If it was evacuated it's live and we keep the spark
+alive. If it hasn't been evacuated at the end of GC we can assume it's dead and
+remove the spark from the pool.
+
+To make this sound we must ensure GC has finished evacuating live objects before
+we prune the spark pool. Otherwise we might GC a spark before it has been evaluated.
+
+* If we run sequential GC then the GC Lead simply prunes after
+everything has been evacuated.
+
+* If we run parallel gc without work stealing then GC workers are not synchronized
+at any point before the worker returns. So we leave it to the GC Lead to prune
+sparks once evacuation has been finished and all workers returned.
+
+* If work stealing is enabled all GC threads will be running
+scavenge_until_all_done until regular heap marking is done. After which
+all workers will converge on a synchronization point. This means
+we can perform spark pruning inside the GC workers at this point.
+The only wart is that if we prune sparks locally we might
+miss sparks reachable via weak pointers as these are marked in the main
+thread concurrently to the calls to pruneSparkQueue. To fix this problem would
+require a GC barrier but that seems to high a price to pay.
+*/
+
+
/* --------------------------------------------------------------------------
* Remove all sparks from the spark queues which should not spark any
* more. Called after GC. We assume exclusive access to the structure
@@ -181,7 +209,7 @@ pruneSparkQueue (bool nonmovingMarkFinished, Capability *cap)
cap->spark_stats.fizzled++;
traceEventSparkFizzle(cap);
} else {
- info = spark->header.info;
+ info = RELAXED_LOAD(&spark->header.info);
load_load_barrier();
if (IS_FORWARDING_PTR(info)) {
tmp = (StgClosure*)UN_FORWARDING_PTR(info);
diff --git a/rts/sm/GC.c b/rts/sm/GC.c
index 5bfee440a3..2438ad2816 100644
--- a/rts/sm/GC.c
+++ b/rts/sm/GC.c
@@ -292,6 +292,7 @@ GarbageCollect (uint32_t collect_gen,
any_work, scav_find_work, max_n_todo_overflow;
#if defined(THREADED_RTS)
gc_thread *saved_gct;
+ bool gc_sparks_all_caps;
#endif
uint32_t g, n;
// The time we should report our heap census as occurring at, if necessary.
@@ -559,6 +560,9 @@ GarbageCollect (uint32_t collect_gen,
StgTSO *resurrected_threads = END_TSO_QUEUE;
// must be last... invariant is that everything is fully
// scavenged at this point.
+#if defined(THREADED_RTS)
+ gc_sparks_all_caps = !work_stealing || !is_par_gc();
+#endif
work_stealing = false;
while (traverseWeakPtrList(&dead_weak_ptr_list, &resurrected_threads))
{
@@ -571,8 +575,9 @@ GarbageCollect (uint32_t collect_gen,
gcStableNameTable();
#if defined(THREADED_RTS)
- if (!is_par_gc()) {
- for (n = 0; n < getNumCapabilities(); n++) {
+ // See Note [Pruning the spark pool]
+ if(gc_sparks_all_caps) {
+ for (n = 0; n < n_capabilities; n++) {
pruneSparkQueue(false, getCapability(n));
}
} else {
@@ -1379,7 +1384,6 @@ void
gcWorkerThread (Capability *cap)
{
gc_thread *saved_gct;
-
// necessary if we stole a callee-saves register for gct:
saved_gct = gct;
@@ -1410,13 +1414,10 @@ gcWorkerThread (Capability *cap)
scavenge_until_all_done();
#if defined(THREADED_RTS)
- // Now that the whole heap is marked, we discard any sparks that
- // were found to be unreachable. The main GC thread is currently
- // marking heap reachable via weak pointers, so it is
- // non-deterministic whether a spark will be retained if it is
- // only reachable via weak pointers. To fix this problem would
- // require another GC barrier, which is too high a price.
- pruneSparkQueue(false, cap);
+ // See Note [Pruning the spark pool]
+ if(work_stealing && is_par_gc()) {
+ pruneSparkQueue(false, cap);
+ }
#endif
// Wait until we're told to continue