Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC
Commits:
-
63ce5770
by Luite Stegeman at 2026-05-28T12:23:35-04:00
6 changed files:
- + changelog.d/fix-blackhole-handling
- 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 |
| ... | ... | @@ -188,10 +188,7 @@ uint32_t messageBlackHole(Capability *cap, MessageBlackHole *msg) |
| 188 | 188 | // BLACKHOLE has already been updated, and GC has shorted out the
|
| 189 | 189 | // indirection, so the pointer no longer points to a BLACKHOLE at
|
| 190 | 190 | // all.
|
| 191 | - if (bh_info != &stg_BLACKHOLE_info &&
|
|
| 192 | - bh_info != &stg_CAF_BLACKHOLE_info &&
|
|
| 193 | - bh_info != &__stg_EAGER_BLACKHOLE_info &&
|
|
| 194 | - bh_info != &stg_WHITEHOLE_info) {
|
|
| 191 | + if (!IS_BLACKHOLE_OR_WHITEHOLE_INFO(bh_info)) {
|
|
| 195 | 192 | return 0;
|
| 196 | 193 | }
|
| 197 | 194 | |
| ... | ... | @@ -350,10 +347,7 @@ StgTSO * blackHoleOwner (StgClosure *bh) |
| 350 | 347 | |
| 351 | 348 | info = RELAXED_LOAD(&bh->header.info);
|
| 352 | 349 | |
| 353 | - if (info != &stg_BLACKHOLE_info &&
|
|
| 354 | - info != &stg_CAF_BLACKHOLE_info &&
|
|
| 355 | - info != &__stg_EAGER_BLACKHOLE_info &&
|
|
| 356 | - info != &stg_WHITEHOLE_info) {
|
|
| 350 | + if (!IS_BLACKHOLE_OR_WHITEHOLE_INFO(info)) {
|
|
| 357 | 351 | return NULL;
|
| 358 | 352 | }
|
| 359 | 353 |
| ... | ... | @@ -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,13 @@ 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
|
|
| 344 | + if(frame_info == &stg_bh_upd_frame_info
|
|
| 345 | + || IS_BLACKHOLE_INFO(bh_info)) {
|
|
| 346 | + // already a black hole: we do nothing
|
|
| 323 | 347 | |
| 324 | 348 | // it should be a black hole (but we may not own it, as another
|
| 325 | 349 | // thread could have raced us to claim it)
|
| 326 | - ASSERT(bh_info == &stg_BLACKHOLE_info ||
|
|
| 327 | - bh_info == &__stg_EAGER_BLACKHOLE_info ||
|
|
| 328 | - bh_info == &stg_CAF_BLACKHOLE_info);
|
|
| 350 | + ASSERT(IS_BLACKHOLE_INFO(bh_info));
|
|
| 329 | 351 | |
| 330 | 352 | } else {
|
| 331 | 353 | // lazy black hole
|
| ... | ... | @@ -474,7 +474,7 @@ checkBlockingQueues (Capability *cap, StgTSO *tso) |
| 474 | 474 | // thing the result would be the same in almost all cases. See #20093.
|
| 475 | 475 | p = UNTAG_CLOSURE(bq->bh);
|
| 476 | 476 | const StgInfoTable *pinfo = ACQUIRE_LOAD(&p->header.info);
|
| 477 | - if (pinfo != &stg_BLACKHOLE_info ||
|
|
| 477 | + if (!IS_BLACKHOLE_INFO(pinfo) ||
|
|
| 478 | 478 | (RELAXED_LOAD(&((StgInd *)p)->indirectee) != (StgClosure*)bq))
|
| 479 | 479 | {
|
| 480 | 480 | wakeBlockingQueue(cap,bq);
|
| ... | ... | @@ -498,10 +498,7 @@ updateThunk (Capability *cap, StgTSO *tso, StgClosure *thunk, StgClosure *val) |
| 498 | 498 | const StgInfoTable *i;
|
| 499 | 499 | |
| 500 | 500 | i = ACQUIRE_LOAD(&thunk->header.info);
|
| 501 | - if (i != &stg_BLACKHOLE_info &&
|
|
| 502 | - i != &stg_CAF_BLACKHOLE_info &&
|
|
| 503 | - i != &__stg_EAGER_BLACKHOLE_info &&
|
|
| 504 | - i != &stg_WHITEHOLE_info) {
|
|
| 501 | + if (!IS_BLACKHOLE_OR_WHITEHOLE_INFO(i)) {
|
|
| 505 | 502 | updateWithIndirection(cap, thunk, val);
|
| 506 | 503 | return;
|
| 507 | 504 | }
|
| ... | ... | @@ -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`).
|
| ... | ... | @@ -382,6 +382,18 @@ EXTERN_INLINE StgOffset BLACKHOLE_sizeW ( void ); |
| 382 | 382 | EXTERN_INLINE StgOffset BLACKHOLE_sizeW ( void )
|
| 383 | 383 | { return sizeofW(StgInd); } // a BLACKHOLE is a kind of indirection
|
| 384 | 384 | |
| 385 | +/* -----------------------------------------------------------------------------
|
|
| 386 | + Blackhole predicates
|
|
| 387 | + -------------------------------------------------------------------------- */
|
|
| 388 | + |
|
| 389 | +#define IS_BLACKHOLE_INFO(info) \
|
|
| 390 | + ((info) == &stg_BLACKHOLE_info || \
|
|
| 391 | + (info) == &__stg_EAGER_BLACKHOLE_info || \
|
|
| 392 | + (info) == &stg_CAF_BLACKHOLE_info)
|
|
| 393 | + |
|
| 394 | +#define IS_BLACKHOLE_OR_WHITEHOLE_INFO(info) \
|
|
| 395 | + (IS_BLACKHOLE_INFO(info) || (info) == &stg_WHITEHOLE_info)
|
|
| 396 | + |
|
| 385 | 397 | /* --------------------------------------------------------------------------
|
| 386 | 398 | Sizes of closures
|
| 387 | 399 | ------------------------------------------------------------------------*/
|