Magnus pushed to branch wip/mangoiv/ghc-9.12-bp at Glasgow Haskell Compiler / GHC
Commits:
-
dddf7bea
by Luite Stegeman at 2026-05-28T22:50:24+02:00
-
4433f73d
by Tom McLaughlin at 2026-05-28T22:51:40+02:00
9 changed files:
- + changelog.d/fix-blackhole-handling
- + changelog.d/windows-rethrow-overlapped-exception
- compiler/GHC/SysTools/Cpp.hs
- libraries/ghc-internal/src/GHC/Internal/Event/Windows.hsc
- rts/Messages.c
- rts/ThreadPaused.c
- rts/Threads.c
- rts/Updates.h
- rts/include/rts/storage/ClosureMacros.h
Changes:
| 1 | +section: rts
|
|
| 2 | +synopsis: Fix several black hole handling bugs that could lead to deadlocks
|
|
| 3 | + or crashes in multithreaded programs. These could show up as the program
|
|
| 4 | + hanging or "END_TSO_QUEUE object entered" errors.
|
|
| 5 | +issues: #26922 #26936
|
|
| 6 | +mrs: !15640 |
| 1 | +section: rts
|
|
| 2 | +synopsis: Rethrow exceptions in overlapped IO when using the WinIO IO manager.
|
|
| 3 | +issues: #27283
|
|
| 4 | +mrs: !15887
|
|
| 5 | + |
|
| 6 | +description: {
|
|
| 7 | + This change was made to support WinIO in the network library; see https://github.com/haskell/network/issues/602.
|
|
| 8 | +} |
| ... | ... | @@ -149,7 +149,7 @@ doCpp logger tmpfs dflags unit_env opts input_fn output_fn = do |
| 149 | 149 | -- and BUILD is the same as our HOST.
|
| 150 | 150 | |
| 151 | 151 | let io_manager_defs =
|
| 152 | - [ "-D__IO_MANAGER_WINIO__=1" | isWindows ] ++
|
|
| 152 | + [ "-D__IO_MANAGER_WINIO__=2" | isWindows ] ++
|
|
| 153 | 153 | [ "-D__IO_MANAGER_MIO__=1" ]
|
| 154 | 154 | |
| 155 | 155 | let sse_defs =
|
| ... | ... | @@ -687,7 +687,11 @@ withOverlappedEx mgr fname h async offset startCB completionCB = do |
| 687 | 687 | -- can go into an unbounded alertable wait.
|
| 688 | 688 | delay <- runExpiredTimeouts mgr
|
| 689 | 689 | registerAlertableWait delay
|
| 690 | - return $ IOFailed Nothing
|
|
| 690 | + -- Re-throw the original exception rather than
|
|
| 691 | + -- returning IOFailed. This ensures that async
|
|
| 692 | + -- exceptions (e.g. Timeout from System.Timeout)
|
|
| 693 | + -- propagate correctly to their handlers.
|
|
| 694 | + E.throw e
|
|
| 691 | 695 | let runner = do debugIO $ (dbgMsg ":: waiting ") ++ " | " ++ show lpol
|
| 692 | 696 | res <- readIOPort signal `catch` cancel
|
| 693 | 697 | debugIO $ dbgMsg ":: signaled "
|
| ... | ... | @@ -176,10 +176,7 @@ uint32_t messageBlackHole(Capability *cap, MessageBlackHole *msg) |
| 176 | 176 | // BLACKHOLE has already been updated, and GC has shorted out the
|
| 177 | 177 | // indirection, so the pointer no longer points to a BLACKHOLE at
|
| 178 | 178 | // all.
|
| 179 | - if (bh_info != &stg_BLACKHOLE_info &&
|
|
| 180 | - bh_info != &stg_CAF_BLACKHOLE_info &&
|
|
| 181 | - bh_info != &__stg_EAGER_BLACKHOLE_info &&
|
|
| 182 | - bh_info != &stg_WHITEHOLE_info) {
|
|
| 179 | + if (!IS_BLACKHOLE_OR_WHITEHOLE_INFO(bh_info)) {
|
|
| 183 | 180 | return 0;
|
| 184 | 181 | }
|
| 185 | 182 | |
| ... | ... | @@ -338,10 +335,7 @@ StgTSO * blackHoleOwner (StgClosure *bh) |
| 338 | 335 | |
| 339 | 336 | info = RELAXED_LOAD(&bh->header.info);
|
| 340 | 337 | |
| 341 | - if (info != &stg_BLACKHOLE_info &&
|
|
| 342 | - info != &stg_CAF_BLACKHOLE_info &&
|
|
| 343 | - info != &__stg_EAGER_BLACKHOLE_info &&
|
|
| 344 | - info != &stg_WHITEHOLE_info) {
|
|
| 338 | + if (!IS_BLACKHOLE_OR_WHITEHOLE_INFO(info)) {
|
|
| 345 | 339 | return NULL;
|
| 346 | 340 | }
|
| 347 | 341 |
| ... | ... | @@ -183,6 +183,30 @@ stackSqueeze(Capability *cap, StgTSO *tso, StgPtr bottom) |
| 183 | 183 | }
|
| 184 | 184 | }
|
| 185 | 185 | |
| 186 | +/*
|
|
| 187 | + * Check whether tso is the owner of the black hole bh.
|
|
| 188 | + *
|
|
| 189 | + * We must call this from the capability that runs tso,
|
|
| 190 | + * since that guarantees that the writes to bh->indirectee
|
|
| 191 | + * by tso claiming ownership have been visible. If another
|
|
| 192 | + * tso has claimed it again afterwards we can safely suspend
|
|
| 193 | + * our work.
|
|
| 194 | + */
|
|
| 195 | +static bool
|
|
| 196 | +threadPausedBlackHoleOwner(StgTSO *tso, StgClosure *bh)
|
|
| 197 | +{
|
|
| 198 | + StgClosure *ind = RELAXED_LOAD(&((StgInd*)bh)->indirectee);
|
|
| 199 | + if (ind == (StgClosure*)tso) {
|
|
| 200 | + return true;
|
|
| 201 | + }
|
|
| 202 | + const StgInfoTable *ind_info = GET_INFO(UNTAG_CLOSURE(ind));
|
|
| 203 | + if (ind_info == &stg_BLOCKING_QUEUE_CLEAN_info
|
|
| 204 | + || ind_info == &stg_BLOCKING_QUEUE_DIRTY_info) {
|
|
| 205 | + return ((StgBlockingQueue*)UNTAG_CLOSURE(ind))->owner == tso;
|
|
| 206 | + }
|
|
| 207 | + return false;
|
|
| 208 | +}
|
|
| 209 | + |
|
| 186 | 210 | /* -----------------------------------------------------------------------------
|
| 187 | 211 | * Pausing a thread
|
| 188 | 212 | *
|
| ... | ... | @@ -255,11 +279,10 @@ threadPaused(Capability *cap, StgTSO *tso) |
| 255 | 279 | // Note [suspend duplicate work]
|
| 256 | 280 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 257 | 281 | // If the info table is a WHITEHOLE or a BLACKHOLE, then
|
| 258 | - // another thread has claimed it (via the SET_INFO()
|
|
| 259 | - // below), or is in the process of doing so. In that case
|
|
| 260 | - // we want to suspend the work that the current thread has
|
|
| 261 | - // done on this thunk and wait until the other thread has
|
|
| 262 | - // finished.
|
|
| 282 | + // some thread has claimed it, or is in the process of doing
|
|
| 283 | + // so. In that case we want to suspend the work that the
|
|
| 284 | + // current thread has done on this thunk and wait until the
|
|
| 285 | + // other thread has finished.
|
|
| 263 | 286 | //
|
| 264 | 287 | // If eager blackholing is taking place, it could be the
|
| 265 | 288 | // case that the blackhole points to the current
|
| ... | ... | @@ -287,8 +310,8 @@ threadPaused(Capability *cap, StgTSO *tso) |
| 287 | 310 | // Note that great care is required when entering computations
|
| 288 | 311 | // suspended by this mechanism. See Note [AP_STACKs must be eagerly
|
| 289 | 312 | // blackholed] for details.
|
| 290 | - if (((bh_info == &stg_BLACKHOLE_info)
|
|
| 291 | - && (RELAXED_LOAD(&((StgInd*)bh)->indirectee) != (StgClosure*)tso))
|
|
| 313 | + if ((IS_BLACKHOLE_INFO(bh_info)
|
|
| 314 | + && !threadPausedBlackHoleOwner(tso, bh))
|
|
| 292 | 315 | || (bh_info == &stg_WHITEHOLE_info))
|
| 293 | 316 | {
|
| 294 | 317 | debugTrace(DEBUG_squeeze,
|
| ... | ... | @@ -318,14 +341,14 @@ threadPaused(Capability *cap, StgTSO *tso) |
| 318 | 341 | // If we have a frame that is already eagerly blackholed, we
|
| 319 | 342 | // shouldn't overwrite its payload: There may already be a blocking
|
| 320 | 343 | // queue (see #26324).
|
| 321 | - if(frame_info == &stg_bh_upd_frame_info) {
|
|
| 322 | - // eager black hole: we do nothing
|
|
| 323 | - |
|
| 324 | - // it should be a black hole that we own
|
|
| 325 | - ASSERT(bh_info == &stg_BLACKHOLE_info ||
|
|
| 326 | - bh_info == &__stg_EAGER_BLACKHOLE_info ||
|
|
| 327 | - bh_info == &stg_CAF_BLACKHOLE_info);
|
|
| 328 | - ASSERT(blackHoleOwner(bh) == tso || blackHoleOwner(bh) == NULL);
|
|
| 344 | + if(frame_info == &stg_bh_upd_frame_info
|
|
| 345 | + || IS_BLACKHOLE_INFO(bh_info)) {
|
|
| 346 | + // already a black hole: we do nothing
|
|
| 347 | + |
|
| 348 | + // it should be a black hole (but we may not own it, as another
|
|
| 349 | + // thread could have raced us to claim it)
|
|
| 350 | + ASSERT(IS_BLACKHOLE_INFO(bh_info));
|
|
| 351 | + |
|
| 329 | 352 | } else {
|
| 330 | 353 | // lazy black hole
|
| 331 | 354 |
| ... | ... | @@ -439,7 +439,7 @@ checkBlockingQueues (Capability *cap, StgTSO *tso) |
| 439 | 439 | // thing the result would be the same in almost all cases. See #20093.
|
| 440 | 440 | p = UNTAG_CLOSURE(bq->bh);
|
| 441 | 441 | const StgInfoTable *pinfo = ACQUIRE_LOAD(&p->header.info);
|
| 442 | - if (pinfo != &stg_BLACKHOLE_info ||
|
|
| 442 | + if (!IS_BLACKHOLE_INFO(pinfo) ||
|
|
| 443 | 443 | (RELAXED_LOAD(&((StgInd *)p)->indirectee) != (StgClosure*)bq))
|
| 444 | 444 | {
|
| 445 | 445 | wakeBlockingQueue(cap,bq);
|
| ... | ... | @@ -463,10 +463,7 @@ updateThunk (Capability *cap, StgTSO *tso, StgClosure *thunk, StgClosure *val) |
| 463 | 463 | const StgInfoTable *i;
|
| 464 | 464 | |
| 465 | 465 | i = ACQUIRE_LOAD(&thunk->header.info);
|
| 466 | - if (i != &stg_BLACKHOLE_info &&
|
|
| 467 | - i != &stg_CAF_BLACKHOLE_info &&
|
|
| 468 | - i != &__stg_EAGER_BLACKHOLE_info &&
|
|
| 469 | - i != &stg_WHITEHOLE_info) {
|
|
| 466 | + if (!IS_BLACKHOLE_OR_WHITEHOLE_INFO(i)) {
|
|
| 470 | 467 | updateWithIndirection(cap, thunk, val);
|
| 471 | 468 | return;
|
| 472 | 469 | }
|
| ... | ... | @@ -190,9 +190,9 @@ |
| 190 | 190 | * frame is encountered, it checks the info table of the updatee and:
|
| 191 | 191 | *
|
| 192 | 192 | * - if it is `BLACKHOLE`, then the thunk has already been claimed for evaluation
|
| 193 | - * by another thread, and the yielding thread is instead added to the
|
|
| 194 | - * `BLACKHOLE`'s blocking queue (see Note [suspend duplicate work] in
|
|
| 195 | - * `ThreadPaused.c`).
|
|
| 193 | + * by some thread. If that's not the yielding thread itself, the yielding thread
|
|
| 194 | + * is added to the `BLACKHOLE`'s blocking queue (see Note [suspend duplicate
|
|
| 195 | + * work] in `ThreadPaused.c`).
|
|
| 196 | 196 | *
|
| 197 | 197 | * - if not, then it blackholes the thunk as done in eager blackholing (but
|
| 198 | 198 | * using the `BLACKHOLE_info` info table instead of `EAGER_BLACKHOLE_info`).
|
| ... | ... | @@ -374,6 +374,18 @@ EXTERN_INLINE StgOffset BLACKHOLE_sizeW ( void ); |
| 374 | 374 | EXTERN_INLINE StgOffset BLACKHOLE_sizeW ( void )
|
| 375 | 375 | { return sizeofW(StgInd); } // a BLACKHOLE is a kind of indirection
|
| 376 | 376 | |
| 377 | +/* -----------------------------------------------------------------------------
|
|
| 378 | + Blackhole predicates
|
|
| 379 | + -------------------------------------------------------------------------- */
|
|
| 380 | + |
|
| 381 | +#define IS_BLACKHOLE_INFO(info) \
|
|
| 382 | + ((info) == &stg_BLACKHOLE_info || \
|
|
| 383 | + (info) == &__stg_EAGER_BLACKHOLE_info || \
|
|
| 384 | + (info) == &stg_CAF_BLACKHOLE_info)
|
|
| 385 | + |
|
| 386 | +#define IS_BLACKHOLE_OR_WHITEHOLE_INFO(info) \
|
|
| 387 | + (IS_BLACKHOLE_INFO(info) || (info) == &stg_WHITEHOLE_info)
|
|
| 388 | + |
|
| 377 | 389 | /* --------------------------------------------------------------------------
|
| 378 | 390 | Sizes of closures
|
| 379 | 391 | ------------------------------------------------------------------------*/
|