Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC
Commits:
-
13d38047
by Luite Stegeman at 2026-10-31T09:19:11-04:00
5 changed files:
- + changelog.d/fix-nonmoving-weak-evacuation-race-23160
- rts/sm/Evac.c
- + testsuite/tests/rts/T23160.hs
- + testsuite/tests/rts/T23160.stdout
- testsuite/tests/rts/all.T
Changes:
| 1 | +section: rts
|
|
| 2 | +synopsis: Fix crash in the nonmoving GC caused by racing weak pointer evacuation
|
|
| 3 | +issues: #23160
|
|
| 4 | +mrs: !16522
|
|
| 5 | +description:
|
|
| 6 | + When two threads compete for evacuating the same ``Weak#`` object, the
|
|
| 7 | + losing thread would produces an object that is never put on a weak list.
|
|
| 8 | + This resulted in pointers in the object getting stale, and the nonmoving
|
|
| 9 | + GC crashing trying to follow them. This is fixed by immediately marking
|
|
| 10 | + the losing weak object as dead. |
| ... | ... | @@ -265,6 +265,17 @@ copy_tag(StgClosure **p, const StgInfoTable *info, |
| 265 | 265 | LDVW(to) = 0;
|
| 266 | 266 | }
|
| 267 | 267 | #endif
|
| 268 | + // The weak we just created is dead and doesn't appear on any
|
|
| 269 | + // weak pointer list. This means that its value, finalizer and link
|
|
| 270 | + // pointers will not be updated. However the nonmoving collector
|
|
| 271 | + // can still see it (update remembered set, mutable list), so we must
|
|
| 272 | + // tombstone it to avoid following stale pointers. See #23160.
|
|
| 273 | + |
|
| 274 | + // The copy is still thread-private here so a plain store
|
|
| 275 | + // suffices.
|
|
| 276 | + if (INFO_PTR_TO_STRUCT(info)->type == WEAK) {
|
|
| 277 | + ((StgClosure *)to)->header.info = &stg_DEAD_WEAK_info;
|
|
| 278 | + }
|
|
| 268 | 279 | return evacuate(p); // does the failed_to_evac stuff
|
| 269 | 280 | } else {
|
| 270 | 281 | // This doesn't need to have RELEASE ordering since we are guaranteed
|
| 1 | +{-# LANGUAGE UnboxedTuples #-}
|
|
| 2 | +{-# LANGUAGE MagicHash #-}
|
|
| 3 | + |
|
| 4 | +-- Regression test for multiple threads competing for evacuating a Weak#
|
|
| 5 | +-- Based on the reproducer by Ben Gamari in #23160
|
|
| 6 | + |
|
| 7 | + |
|
| 8 | +import GHC.Exts
|
|
| 9 | +import GHC.Weak
|
|
| 10 | +import GHC.IO (IO(..))
|
|
| 11 | +import Control.Concurrent
|
|
| 12 | +import Control.Exception
|
|
| 13 | +import Control.Monad
|
|
| 14 | +import System.Mem
|
|
| 15 | +import System.Mem.Weak
|
|
| 16 | + |
|
| 17 | +data MBA = MBA (MutableByteArray# RealWorld)
|
|
| 18 | + |
|
| 19 | +newPinnedMBA :: Int -> IO MBA
|
|
| 20 | +newPinnedMBA (I# n) = IO $ \s ->
|
|
| 21 | + case newPinnedByteArray# n s of
|
|
| 22 | + (# s', mba #) -> (# s', MBA mba #)
|
|
| 23 | + |
|
| 24 | +mkMBAWeak :: MBA -> a -> IO (Weak a)
|
|
| 25 | +mkMBAWeak (MBA mba) v = IO $
|
|
| 26 | + \s -> case mkWeakNoFinalizer# mba v s of
|
|
| 27 | + (# s', w #) -> (# s', Weak w #)
|
|
| 28 | + |
|
| 29 | +forced :: String -> IO Int
|
|
| 30 | +forced a = evaluate (length a)
|
|
| 31 | + |
|
| 32 | +worker :: IO ()
|
|
| 33 | +worker = forever $ do
|
|
| 34 | + xs <- mapM f [0..200000::Int]
|
|
| 35 | + mapM_ (\w -> deRefWeak w >>= mapM_ forced) (take 1000 xs)
|
|
| 36 | + where
|
|
| 37 | + f n = do
|
|
| 38 | + mba <- newPinnedMBA 10
|
|
| 39 | + v <- forced (show n)
|
|
| 40 | + mkMBAWeak mba (show (n + v))
|
|
| 41 | + |
|
| 42 | +main :: IO ()
|
|
| 43 | +main = do
|
|
| 44 | + forkIO $ forever $ do
|
|
| 45 | + threadDelay (100*1000)
|
|
| 46 | + performMajorGC
|
|
| 47 | + |
|
| 48 | + replicateM_ 10 (forkIO worker)
|
|
| 49 | + threadDelay (25*1000*1000)
|
|
| 50 | + putStrLn "OK" |
| 1 | +OK |
| ... | ... | @@ -767,3 +767,12 @@ test('T27526', |
| 767 | 767 | extra_hc_opts('-fno-prof-auto -fprof-late -rtsopts'),
|
| 768 | 768 | extra_run_opts('+RTS -kc2k -kb256 -A64k -AL8k -RTS')],
|
| 769 | 769 | compile_and_run, [''])
|
| 770 | + |
|
| 771 | +test('T23160',
|
|
| 772 | + [ req_target_smp
|
|
| 773 | + , extra_ways(['nonmoving_thr_sanity'])
|
|
| 774 | + , only_ways(['nonmoving_thr_sanity'])
|
|
| 775 | + , extra_run_opts('+RTS -A1M -N4 -RTS')
|
|
| 776 | + , unless(slow(), skip)
|
|
| 777 | + ],
|
|
| 778 | + compile_and_run, ['']) |