Duncan Coutts pushed to branch wip/dcoutts/issue-26717 at Glasgow Haskell Compiler / GHC
Commits:
-
28d9b4db
by Duncan Coutts at 2026-07-21T13:52:03+01:00
-
41689cd2
by Duncan Coutts at 2026-07-21T13:52:11+01:00
-
b54269a6
by Duncan Coutts at 2026-07-21T13:52:11+01:00
-
2b6e63f9
by Duncan Coutts at 2026-07-21T13:52:11+01:00
-
c500e1a6
by Duncan Coutts at 2026-07-21T13:52:11+01:00
-
0989884c
by Duncan Coutts at 2026-07-21T13:52:11+01:00
-
d2ff8432
by Duncan Coutts at 2026-07-21T13:52:11+01:00
-
40bc048c
by Duncan Coutts at 2026-07-21T13:52:11+01:00
-
11bb96ec
by Duncan Coutts at 2026-07-21T13:52:11+01:00
-
9b6e3db0
by Duncan Coutts at 2026-07-21T13:52:12+01:00
-
f309f939
by Duncan Coutts at 2026-07-21T13:52:12+01:00
-
f33be455
by Duncan Coutts at 2026-07-21T13:52:12+01:00
-
894b8f61
by Duncan Coutts at 2026-07-21T13:52:12+01:00
-
c51c04a1
by Duncan Coutts at 2026-07-21T13:52:12+01:00
-
11ca05c0
by Duncan Coutts at 2026-07-21T13:52:12+01:00
-
10dff2ef
by Duncan Coutts at 2026-07-21T13:55:12+01:00
24 changed files:
- + changelog.d/T26716
- libraries/ghc-heap/GHC/Exts/Heap/FFIClosures_ProfilingDisabled.hsc
- libraries/ghc-heap/GHC/Exts/Heap/FFIClosures_ProfilingEnabled.hsc
- libraries/ghc-internal/src/GHC/Internal/Conc/Sync.hs
- rts/IOManager.c
- rts/IOManager.h
- rts/Messages.c
- rts/PrimOps.cmm
- rts/RaiseAsync.c
- rts/RaiseAsync.h
- rts/STM.c
- rts/Schedule.c
- rts/Threads.c
- rts/TraverseHeap.c
- rts/include/rts/Constants.h
- rts/include/rts/storage/TSO.h
- rts/posix/Poll.c
- rts/posix/Select.c
- rts/posix/Timeout.c
- rts/sm/Compact.c
- rts/sm/NonMovingMark.c
- rts/sm/Sanity.c
- rts/sm/Scav.c
- rts/win32/AsyncMIO.c
Changes:
| 1 | +section: rts
|
|
| 2 | +synopsis: Fix design of TSO blocking info, fixing a use-after-free bug
|
|
| 3 | +issues: #26716 #26717
|
|
| 4 | +mrs: !15519
|
|
| 5 | +description: {
|
|
| 6 | + Experimental work on ASAN support for GHC (MR !15168) revealed a
|
|
| 7 | + use-after-free bug when using the combination of the new poll I/O
|
|
| 8 | + manager with the compacting GC. The ultimate cause is that a TSO's
|
|
| 9 | + `block_info` (used by I/O managers and many other parts of the RTS)
|
|
| 10 | + is sometimes a GC pointer and sometimes not, but without a consistent
|
|
| 11 | + and easy-to-follow rule for when this is the case. The solution has
|
|
| 12 | + been to clean up and enforce that the TSO's `why_blocked` enumeration
|
|
| 13 | + is a proper tag for the `block_info`, and to use an encoding that
|
|
| 14 | + determines precisely when the `block_info` is a GC pointer or not.
|
|
| 15 | +} |
| ... | ... | @@ -63,7 +63,7 @@ parseWhatNext w = case w of |
| 63 | 63 | _ -> WhatNextUnknownValue w
|
| 64 | 64 | |
| 65 | 65 | parseWhyBlocked :: Word16 -> WhyBlocked
|
| 66 | -parseWhyBlocked w = case w of
|
|
| 66 | +parseWhyBlocked w = case untagWhyBlocked w of
|
|
| 67 | 67 | (#const NotBlocked) -> NotBlocked
|
| 68 | 68 | (#const BlockedOnMVar) -> BlockedOnMVar
|
| 69 | 69 | (#const BlockedOnMVarRead) -> BlockedOnMVarRead
|
| ... | ... | @@ -78,6 +78,9 @@ parseWhyBlocked w = case w of |
| 78 | 78 | (#const BlockedOnMsgThrowTo) -> BlockedOnMsgThrowTo
|
| 79 | 79 | (#const ThreadMigrating) -> ThreadMigrating
|
| 80 | 80 | _ -> WhyBlockedUnknownValue w
|
| 81 | + where
|
|
| 82 | + -- See Constants.h encoding for why_blocked
|
|
| 83 | + untagWhyBlocked why = why .&. 0x0f
|
|
| 81 | 84 | |
| 82 | 85 | parseTsoFlags :: Word32 -> [TsoFlags]
|
| 83 | 86 | parseTsoFlags w | isSet (#const TSO_LOCKED) w = TsoLocked : parseTsoFlags (unset (#const TSO_LOCKED) w)
|
| ... | ... | @@ -63,7 +63,7 @@ parseWhatNext w = case w of |
| 63 | 63 | _ -> WhatNextUnknownValue w
|
| 64 | 64 | |
| 65 | 65 | parseWhyBlocked :: Word16 -> WhyBlocked
|
| 66 | -parseWhyBlocked w = case w of
|
|
| 66 | +parseWhyBlocked w = case untagWhyBlocked w of
|
|
| 67 | 67 | (#const NotBlocked) -> NotBlocked
|
| 68 | 68 | (#const BlockedOnMVar) -> BlockedOnMVar
|
| 69 | 69 | (#const BlockedOnMVarRead) -> BlockedOnMVarRead
|
| ... | ... | @@ -78,6 +78,9 @@ parseWhyBlocked w = case w of |
| 78 | 78 | (#const BlockedOnMsgThrowTo) -> BlockedOnMsgThrowTo
|
| 79 | 79 | (#const ThreadMigrating) -> ThreadMigrating
|
| 80 | 80 | _ -> WhyBlockedUnknownValue w
|
| 81 | + where
|
|
| 82 | + -- See Constants.h encoding for why_blocked
|
|
| 83 | + untagWhyBlocked why = why .&. 0x0f
|
|
| 81 | 84 | |
| 82 | 85 | parseTsoFlags :: Word32 -> [TsoFlags]
|
| 83 | 86 | parseTsoFlags w | isSet (#const TSO_LOCKED) w = TsoLocked : parseTsoFlags (unset (#const TSO_LOCKED) w)
|
| ... | ... | @@ -607,13 +607,16 @@ threadStatus (ThreadId t) = IO $ \s -> |
| 607 | 607 | -- NB. keep these in sync with rts/include/rts/Constants.h
|
| 608 | 608 | mk_stat 0 = ThreadRunning
|
| 609 | 609 | mk_stat 1 = ThreadBlocked BlockedOnMVar
|
| 610 | - mk_stat 2 = ThreadBlocked BlockedOnBlackHole
|
|
| 611 | - mk_stat 6 = ThreadBlocked BlockedOnSTM
|
|
| 610 | + mk_stat 2 = ThreadBlocked BlockedOnMVar -- BlockedOnMVarRead
|
|
| 611 | + mk_stat 3 = ThreadBlocked BlockedOnBlackHole
|
|
| 612 | + mk_stat 4 = ThreadBlocked BlockedOnException
|
|
| 613 | + -- 5,6,7: BlockedOn{Read,Write,Delay}
|
|
| 614 | + mk_stat 8 = ThreadBlocked BlockedOnSTM
|
|
| 615 | + mk_stat 9 = ThreadBlocked BlockedOnForeignCall
|
|
| 612 | 616 | mk_stat 10 = ThreadBlocked BlockedOnForeignCall
|
| 613 | - mk_stat 11 = ThreadBlocked BlockedOnForeignCall
|
|
| 614 | - mk_stat 12 = ThreadBlocked BlockedOnException
|
|
| 615 | - mk_stat 14 = ThreadBlocked BlockedOnMVar -- possibly: BlockedOnMVarRead
|
|
| 616 | - -- NB. these are hardcoded in rts/PrimOps.cmm
|
|
| 617 | + -- 11: ThreadMigrating
|
|
| 618 | + -- 12: BlockedOnDoProc
|
|
| 619 | + -- 13,14,15: unused
|
|
| 617 | 620 | mk_stat 16 = ThreadFinished
|
| 618 | 621 | mk_stat 17 = ThreadDied
|
| 619 | 622 | mk_stat _ = ThreadBlocked BlockedOnOther
|
| ... | ... | @@ -589,41 +589,6 @@ void markCapabilityIOManager(evac_fn evac, void *user, CapIOManager *iomgr) |
| 589 | 589 | }
|
| 590 | 590 | |
| 591 | 591 | |
| 592 | -void scavengeTSOIOManager(StgTSO *tso)
|
|
| 593 | -{
|
|
| 594 | - switch (iomgr_type) {
|
|
| 595 | - |
|
| 596 | - /* case IO_MANAGER_SELECT:
|
|
| 597 | - * BlockedOn{Read,Write} uses block_info.fd
|
|
| 598 | - * BlockedOnDelay uses block_info.target
|
|
| 599 | - * both of these are not GC pointers, so there is nothing to do.
|
|
| 600 | - */
|
|
| 601 | - |
|
| 602 | -#if defined(IOMGR_ENABLED_POLL)
|
|
| 603 | - case IO_MANAGER_POLL:
|
|
| 604 | - /* BlockedOn{Read,Write} uses block_info.aiop
|
|
| 605 | - * BlockedOnDelay uses block_info.timeout
|
|
| 606 | - * both of these are heap allocated, so we can do the same in all
|
|
| 607 | - * cases, which is why we can use the generic block_info.closure.
|
|
| 608 | - */
|
|
| 609 | - evacuate(&tso->block_info.closure);
|
|
| 610 | - break;
|
|
| 611 | -#endif
|
|
| 612 | - |
|
| 613 | - /* case IO_MANAGER_WIN32_LEGACY:
|
|
| 614 | - * BlockedOn{Read,Write,DoProc} uses block_info.async_reqID
|
|
| 615 | - * which is a plain integer, so nothing to scavenge.
|
|
| 616 | - */
|
|
| 617 | - |
|
| 618 | - default:
|
|
| 619 | - /* All the other I/O managers do not use I/O-related why_blocked
|
|
| 620 | - * reasons, so there are no cases to handle.
|
|
| 621 | - */
|
|
| 622 | - break;
|
|
| 623 | - }
|
|
| 624 | -}
|
|
| 625 | - |
|
| 626 | - |
|
| 627 | 592 | /* Declared in rts/IOInterface.h. Used only by the MIO threaded I/O manager on
|
| 628 | 593 | * Unix platforms.
|
| 629 | 594 | */
|
| ... | ... | @@ -824,16 +789,17 @@ bool syncIOWaitReady(CapIOManager *iomgr, |
| 824 | 789 | #if defined(IOMGR_ENABLED_SELECT)
|
| 825 | 790 | case IO_MANAGER_SELECT:
|
| 826 | 791 | {
|
| 827 | - StgWord why_blocked = rw == IORead ? BlockedOnRead : BlockedOnWrite;
|
|
| 792 | + StgThreadWhyBlocked why_blocked = (rw == IORead ? BlockedOnRead
|
|
| 793 | + : BlockedOnWrite)
|
|
| 794 | + | BlockInfoForceNonClosure;
|
|
| 828 | 795 | tso->block_info.fd = fd;
|
| 829 | - RELEASE_STORE(&tso->why_blocked, why_blocked);
|
|
| 830 | 796 | appendToIOBlockedQueue(iomgr, tso);
|
| 797 | + RELEASE_STORE(&tso->why_blocked, why_blocked);
|
|
| 831 | 798 | return true;
|
| 832 | 799 | }
|
| 833 | 800 | #endif
|
| 834 | 801 | #if defined(IOMGR_ENABLED_POLL)
|
| 835 | 802 | case IO_MANAGER_POLL:
|
| 836 | - ASSERT(tso->why_blocked == NotBlocked);
|
|
| 837 | 803 | return syncIOWaitReadyPoll(iomgr, tso, rw, fd);
|
| 838 | 804 | #endif
|
| 839 | 805 | default:
|
| ... | ... | @@ -890,8 +856,8 @@ bool syncDelay(CapIOManager *iomgr, StgTSO *tso, HsInt us_delay) |
| 890 | 856 | {
|
| 891 | 857 | LowResTime target = getDelayTarget(us_delay);
|
| 892 | 858 | tso->block_info.target = target;
|
| 893 | - RELEASE_STORE(&tso->why_blocked, BlockedOnDelay);
|
|
| 894 | 859 | insertIntoSleepingQueue(iomgr, tso, target);
|
| 860 | + RELEASE_STORE(&tso->why_blocked, BlockedOnDelay | BlockInfoForceNonClosure);
|
|
| 895 | 861 | return true;
|
| 896 | 862 | }
|
| 897 | 863 | #endif
|
| ... | ... | @@ -911,8 +877,8 @@ bool syncDelay(CapIOManager *iomgr, StgTSO *tso, HsInt us_delay) |
| 911 | 877 | * simplifies matters, so set the status to OnDoProc and put the
|
| 912 | 878 | * delayed thread on the blocked_queue.
|
| 913 | 879 | */
|
| 914 | - RELEASE_STORE(&tso->why_blocked, BlockedOnDoProc);
|
|
| 915 | 880 | appendToIOBlockedQueue(iomgr, tso);
|
| 881 | + RELEASE_STORE(&tso->why_blocked, BlockedOnDoProc);
|
|
| 916 | 882 | return true;
|
| 917 | 883 | }
|
| 918 | 884 | #endif
|
| ... | ... | @@ -928,6 +894,7 @@ void syncDelayCancel(CapIOManager *iomgr, StgTSO *tso) |
| 928 | 894 | switch (iomgr_type) {
|
| 929 | 895 | #if defined(IOMGR_ENABLED_SELECT)
|
| 930 | 896 | case IO_MANAGER_SELECT:
|
| 897 | + ASSERT(tso->why_blocked == (BlockedOnDelay | BlockInfoForceNonClosure));
|
|
| 931 | 898 | removeThreadFromQueue(iomgr->cap, &iomgr->sleeping_queue, tso);
|
| 932 | 899 | break;
|
| 933 | 900 | #endif
|
| ... | ... | @@ -311,11 +311,6 @@ void exitIOManager(bool wait_threads); |
| 311 | 311 | void markCapabilityIOManager(evac_fn evac, void *user, CapIOManager *iomgr);
|
| 312 | 312 | |
| 313 | 313 | |
| 314 | -/* GC hook: scavenge I/O related tso->block_info. Used by scavengeTSO.
|
|
| 315 | - */
|
|
| 316 | -void scavengeTSOIOManager(StgTSO *tso);
|
|
| 317 | - |
|
| 318 | - |
|
| 319 | 314 | /* Several code paths are almost identical between read and write paths. In
|
| 320 | 315 | * such cases we use a shared code path with an enum to say which we're doing.
|
| 321 | 316 | */
|
| ... | ... | @@ -267,7 +267,8 @@ uint32_t messageBlackHole(Capability *cap, MessageBlackHole *msg) |
| 267 | 267 | // NB. we check to make sure that the owner is not the same as
|
| 268 | 268 | // the current thread, since in that case it will not be on
|
| 269 | 269 | // the run queue.
|
| 270 | - if (owner->why_blocked == NotBlocked && owner->id != msg->tso->id) {
|
|
| 270 | + if (RELAXED_LOAD(&owner->why_blocked) == NotBlocked &&
|
|
| 271 | + owner->id != msg->tso->id) {
|
|
| 271 | 272 | promoteInRunQueue(cap, owner);
|
| 272 | 273 | }
|
| 273 | 274 | |
| ... | ... | @@ -328,7 +329,8 @@ uint32_t messageBlackHole(Capability *cap, MessageBlackHole *msg) |
| 328 | 329 | msg->tso->id, owner->id);
|
| 329 | 330 | |
| 330 | 331 | // See above, #3838
|
| 331 | - if (owner->why_blocked == NotBlocked && owner->id != msg->tso->id) {
|
|
| 332 | + if (RELAXED_LOAD(&owner->why_blocked) == NotBlocked &&
|
|
| 333 | + owner->id != msg->tso->id) {
|
|
| 332 | 334 | promoteInRunQueue(cap, owner);
|
| 333 | 335 | }
|
| 334 | 336 |
| ... | ... | @@ -1145,12 +1145,12 @@ stg_threadStatuszh ( gcptr tso ) |
| 1145 | 1145 | // contents of block_info too, then we'd have to do some synchronisation.
|
| 1146 | 1146 | |
| 1147 | 1147 | if (what_next == ThreadComplete) {
|
| 1148 | - ret = 16; // NB. magic, matches up with GHC.Conc.threadStatus
|
|
| 1148 | + ret = BlockedThreadComplete; // NB. magic, matches up with GHC.Conc.threadStatus
|
|
| 1149 | 1149 | } else {
|
| 1150 | 1150 | if (what_next == ThreadKilled) {
|
| 1151 | - ret = 17;
|
|
| 1151 | + ret = BlockedThreadKilled;
|
|
| 1152 | 1152 | } else {
|
| 1153 | - ret = why_blocked;
|
|
| 1153 | + ret = UntagWhyBlocked(why_blocked);
|
|
| 1154 | 1154 | }
|
| 1155 | 1155 | }
|
| 1156 | 1156 | |
| ... | ... | @@ -2319,7 +2319,8 @@ stg_asyncReadzh ( W_ fd, W_ is_sock, W_ len, W_ buf ) |
| 2319 | 2319 | StgTSO_block_info(CurrentTSO) = reqID;
|
| 2320 | 2320 | |
| 2321 | 2321 | ASSERT(StgTSO_why_blocked(CurrentTSO) == NotBlocked::I32);
|
| 2322 | - %release StgTSO_why_blocked(CurrentTSO) = BlockedOnRead::I32;
|
|
| 2322 | + %release StgTSO_why_blocked(CurrentTSO) = BlockedOnRead::I32
|
|
| 2323 | + | BlockInfoForceNonClosure::I32;
|
|
| 2323 | 2324 | |
| 2324 | 2325 | ccall appendToIOBlockedQueue(Capability_iomgr(MyCapability()) "ptr",
|
| 2325 | 2326 | CurrentTSO "ptr");
|
| ... | ... | @@ -2339,7 +2340,8 @@ stg_asyncWritezh ( W_ fd, W_ is_sock, W_ len, W_ buf ) |
| 2339 | 2340 | StgTSO_block_info(CurrentTSO) = reqID;
|
| 2340 | 2341 | |
| 2341 | 2342 | ASSERT(StgTSO_why_blocked(CurrentTSO) == NotBlocked::I32);
|
| 2342 | - %release StgTSO_why_blocked(CurrentTSO) = BlockedOnWrite::I32;
|
|
| 2343 | + %release StgTSO_why_blocked(CurrentTSO) = BlockedOnWrite::I32
|
|
| 2344 | + | BlockInfoForceNonClosure::I32;
|
|
| 2343 | 2345 | |
| 2344 | 2346 | ccall appendToIOBlockedQueue(Capability_iomgr(MyCapability()) "ptr",
|
| 2345 | 2347 | CurrentTSO "ptr");
|
| ... | ... | @@ -233,7 +233,6 @@ throwTo (Capability *cap, // the Capability we hold |
| 233 | 233 | uint32_t
|
| 234 | 234 | throwToMsg (Capability *cap, MessageThrowTo *msg)
|
| 235 | 235 | {
|
| 236 | - StgWord status;
|
|
| 237 | 236 | StgTSO *target = ACQUIRE_LOAD(&msg->target);
|
| 238 | 237 | Capability *target_cap;
|
| 239 | 238 | |
| ... | ... | @@ -268,9 +267,9 @@ check_target: |
| 268 | 267 | return THROWTO_BLOCKED;
|
| 269 | 268 | }
|
| 270 | 269 | |
| 271 | - status = ACQUIRE_LOAD(&target->why_blocked);
|
|
| 270 | + StgThreadWhyBlocked why_blocked = ACQUIRE_LOAD(&target->why_blocked);
|
|
| 272 | 271 | |
| 273 | - switch (status) {
|
|
| 272 | + switch (UntagWhyBlocked(why_blocked)) {
|
|
| 274 | 273 | case NotBlocked:
|
| 275 | 274 | {
|
| 276 | 275 | if ((target->flags & TSO_BLOCKEX) == 0) {
|
| ... | ... | @@ -354,7 +353,7 @@ check_target: |
| 354 | 353 | StgMVar *mvar;
|
| 355 | 354 | StgInfoTable *info USED_IF_THREADS;
|
| 356 | 355 | |
| 357 | - mvar = (StgMVar *)target->block_info.closure;
|
|
| 356 | + mvar = target->block_info.mvar;
|
|
| 358 | 357 | |
| 359 | 358 | // ASSUMPTION: tso->block_info must always point to a
|
| 360 | 359 | // closure. In the threaded RTS it does.
|
| ... | ... | @@ -370,9 +369,10 @@ check_target: |
| 370 | 369 | |
| 371 | 370 | // we have the MVar, let's check whether the thread
|
| 372 | 371 | // is still blocked on the same MVar.
|
| 373 | - if ((target->why_blocked != BlockedOnMVar
|
|
| 374 | - && target->why_blocked != BlockedOnMVarRead)
|
|
| 375 | - || (StgMVar *)target->block_info.closure != mvar) {
|
|
| 372 | + StgThreadWhyBlocked why_blocked_still = ACQUIRE_LOAD(&target->why_blocked);
|
|
| 373 | + if (( why_blocked_still != BlockedOnMVar
|
|
| 374 | + && why_blocked_still != BlockedOnMVarRead)
|
|
| 375 | + || target->block_info.mvar != mvar) {
|
|
| 376 | 376 | unlockClosure((StgClosure *)mvar, info);
|
| 377 | 377 | goto retry;
|
| 378 | 378 | }
|
| ... | ... | @@ -490,7 +490,7 @@ check_target: |
| 490 | 490 | goto retry;
|
| 491 | 491 | |
| 492 | 492 | default:
|
| 493 | - barf("throwTo: unrecognised why_blocked (%d)", target->why_blocked);
|
|
| 493 | + barf("throwTo: unrecognised why_blocked (%d)", why_blocked);
|
|
| 494 | 494 | }
|
| 495 | 495 | barf("throwTo");
|
| 496 | 496 | }
|
| ... | ... | @@ -625,7 +625,7 @@ awakenBlockedExceptionQueue (Capability *cap, StgTSO *tso) |
| 625 | 625 | static void
|
| 626 | 626 | removeFromMVarBlockedQueue (StgTSO *tso)
|
| 627 | 627 | {
|
| 628 | - StgMVar *mvar = (StgMVar*)tso->block_info.closure;
|
|
| 628 | + StgMVar *mvar = tso->block_info.mvar;
|
|
| 629 | 629 | StgMVarTSOQueue *q = (StgMVarTSOQueue*)tso->_link;
|
| 630 | 630 | |
| 631 | 631 | if (q == (StgMVarTSOQueue*)END_TSO_QUEUE) {
|
| ... | ... | @@ -667,7 +667,7 @@ removeFromMVarBlockedQueue (StgTSO *tso) |
| 667 | 667 | static void
|
| 668 | 668 | removeFromQueues(Capability *cap, StgTSO *tso)
|
| 669 | 669 | {
|
| 670 | - switch (tso->why_blocked) {
|
|
| 670 | + switch (UntagWhyBlocked(ACQUIRE_LOAD(&tso->why_blocked))) {
|
|
| 671 | 671 | |
| 672 | 672 | case NotBlocked:
|
| 673 | 673 | case ThreadMigrating:
|
| ... | ... | @@ -721,8 +721,8 @@ removeFromQueues(Capability *cap, StgTSO *tso) |
| 721 | 721 | }
|
| 722 | 722 | |
| 723 | 723 | done:
|
| 724 | - RELAXED_STORE(&tso->why_blocked, NotBlocked);
|
|
| 725 | 724 | appendToRunQueue(cap, tso);
|
| 725 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 726 | 726 | }
|
| 727 | 727 | |
| 728 | 728 | /* -----------------------------------------------------------------------------
|
| ... | ... | @@ -1105,9 +1105,9 @@ done: |
| 1105 | 1105 | IF_DEBUG(sanity, checkTSO(tso));
|
| 1106 | 1106 | |
| 1107 | 1107 | // wake it up
|
| 1108 | - if (tso->why_blocked != NotBlocked) {
|
|
| 1109 | - tso->why_blocked = NotBlocked;
|
|
| 1108 | + if (RELAXED_LOAD(&tso->why_blocked) != NotBlocked) {
|
|
| 1110 | 1109 | appendToRunQueue(cap,tso);
|
| 1110 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 1111 | 1111 | }
|
| 1112 | 1112 | |
| 1113 | 1113 | return tso;
|
| ... | ... | @@ -56,7 +56,7 @@ void awakenBlockedExceptionQueue (Capability *cap, StgTSO *tso); |
| 56 | 56 | INLINE_HEADER int
|
| 57 | 57 | interruptible(StgTSO *t)
|
| 58 | 58 | {
|
| 59 | - switch (t->why_blocked) {
|
|
| 59 | + switch (UntagWhyBlocked(t->why_blocked)) {
|
|
| 60 | 60 | case BlockedOnMVar:
|
| 61 | 61 | case BlockedOnSTM:
|
| 62 | 62 | case BlockedOnMVarRead:
|
| ... | ... | @@ -264,7 +264,7 @@ static StgBool cond_lock_tvar(Capability *cap, |
| 264 | 264 | |
| 265 | 265 | static void park_tso(StgTSO *tso) {
|
| 266 | 266 | ASSERT(tso -> why_blocked == NotBlocked);
|
| 267 | - tso -> block_info.closure = (StgClosure *) END_TSO_QUEUE;
|
|
| 267 | + tso->block_info.unused = END_TSO_QUEUE;
|
|
| 268 | 268 | RELEASE_STORE(&tso -> why_blocked, BlockedOnSTM);
|
| 269 | 269 | TRACE("park_tso on tso=%p", tso);
|
| 270 | 270 | }
|
| ... | ... | @@ -181,7 +181,7 @@ static void truncateRunQueue(Capability *cap); |
| 181 | 181 | static StgTSO *popRunQueue (Capability *cap);
|
| 182 | 182 | |
| 183 | 183 | static inline EventThreadStatus eventlogThreadStatus(StgThreadReturnCode ret_code);
|
| 184 | -static inline EventThreadStatus eventlogThreadStatusBlocked(StgWord why_blocked);
|
|
| 184 | +static inline EventThreadStatus eventlogThreadStatusBlocked(StgThreadWhyBlocked why_blocked);
|
|
| 185 | 185 | |
| 186 | 186 | /* ---------------------------------------------------------------------------
|
| 187 | 187 | Main scheduling loop.
|
| ... | ... | @@ -531,7 +531,7 @@ run_thread: |
| 531 | 531 | #endif
|
| 532 | 532 | |
| 533 | 533 | if (ret == ThreadBlocked) {
|
| 534 | - uint16_t why_blocked = ACQUIRE_LOAD(&t->why_blocked);
|
|
| 534 | + StgThreadWhyBlocked why_blocked = ACQUIRE_LOAD(&t->why_blocked);
|
|
| 535 | 535 | EventThreadStatus status = eventlogThreadStatusBlocked(why_blocked);
|
| 536 | 536 | StgWord32 status_detail = 0;
|
| 537 | 537 | if (why_blocked == BlockedOnBlackHole) {
|
| ... | ... | @@ -1074,7 +1074,7 @@ schedulePostRunThread (Capability *cap, StgTSO *t) |
| 1074 | 1074 | //
|
| 1075 | 1075 | // and a is never equal to b given a consistent view of memory.
|
| 1076 | 1076 | //
|
| 1077 | - if (t -> trec != NO_TREC && t -> why_blocked == NotBlocked) {
|
|
| 1077 | + if (t -> trec != NO_TREC && RELAXED_LOAD(&t->why_blocked) == NotBlocked) {
|
|
| 1078 | 1078 | if (!stmValidateNestOfTransactions(cap, t -> trec, true)) {
|
| 1079 | 1079 | debugTrace(DEBUG_sched | DEBUG_stm,
|
| 1080 | 1080 | "trec %p found wasting its time", t);
|
| ... | ... | @@ -2506,10 +2506,11 @@ suspendThread (StgRegTable *reg, bool interruptible) |
| 2506 | 2506 | |
| 2507 | 2507 | threadPaused(cap,tso);
|
| 2508 | 2508 | |
| 2509 | + tso->block_info.unused = END_TSO_QUEUE;
|
|
| 2509 | 2510 | if (interruptible) {
|
| 2510 | - tso->why_blocked = BlockedOnCCall_Interruptible;
|
|
| 2511 | + RELEASE_STORE(&tso->why_blocked, BlockedOnCCall_Interruptible);
|
|
| 2511 | 2512 | } else {
|
| 2512 | - tso->why_blocked = BlockedOnCCall;
|
|
| 2513 | + RELEASE_STORE(&tso->why_blocked, BlockedOnCCall);
|
|
| 2513 | 2514 | }
|
| 2514 | 2515 | |
| 2515 | 2516 | // Hand back capability
|
| ... | ... | @@ -2567,16 +2568,25 @@ resumeThread (void *task_) |
| 2567 | 2568 | tso = incall->suspended_tso;
|
| 2568 | 2569 | incall->suspended_tso = NULL;
|
| 2569 | 2570 | incall->suspended_cap = NULL;
|
| 2571 | + |
|
| 2572 | + // we set why_blocked previously in suspendThread
|
|
| 2573 | + ASSERT(tso->why_blocked == BlockedOnCCall ||
|
|
| 2574 | + tso->why_blocked == BlockedOnCCall_Interruptible);
|
|
| 2575 | + |
|
| 2570 | 2576 | // we will modify tso->_link
|
| 2571 | 2577 | IF_NONMOVING_WRITE_BARRIER_ENABLED {
|
| 2572 | 2578 | updateRemembSetPushClosure(cap, (StgClosure *)tso->_link);
|
| 2573 | 2579 | }
|
| 2574 | 2580 | tso->_link = END_TSO_QUEUE;
|
| 2581 | + // but no need to modify tso->block_info.prev as coincidentally
|
|
| 2582 | + // it has the value we want already (since in suspendThread we set
|
|
| 2583 | + // tso->block_info.unused to END_TSO_QUEUE for BlockedOnCCall).
|
|
| 2584 | + ASSERT(tso->block_info.prev == END_TSO_QUEUE);
|
|
| 2575 | 2585 | |
| 2576 | 2586 | traceEventRunThread(cap, tso);
|
| 2577 | 2587 | |
| 2578 | 2588 | /* Reset blocking status */
|
| 2579 | - tso->why_blocked = NotBlocked;
|
|
| 2589 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 2580 | 2590 | |
| 2581 | 2591 | if ((tso->flags & TSO_BLOCKEX) == 0) {
|
| 2582 | 2592 | // avoid locking the TSO if we don't have to
|
| ... | ... | @@ -2949,8 +2959,9 @@ deleteThread (StgTSO *tso) |
| 2949 | 2959 | // The TSO must be on the run queue of the Capability we own, or
|
| 2950 | 2960 | // we must own all Capabilities.
|
| 2951 | 2961 | |
| 2952 | - if (tso->why_blocked != BlockedOnCCall &&
|
|
| 2953 | - tso->why_blocked != BlockedOnCCall_Interruptible) {
|
|
| 2962 | + StgThreadWhyBlocked why_blocked = RELAXED_LOAD(&tso->why_blocked);
|
|
| 2963 | + if (why_blocked != BlockedOnCCall &&
|
|
| 2964 | + why_blocked != BlockedOnCCall_Interruptible) {
|
|
| 2954 | 2965 | throwToSingleThreaded(tso->cap,tso,NULL);
|
| 2955 | 2966 | }
|
| 2956 | 2967 | }
|
| ... | ... | @@ -2961,10 +2972,12 @@ deleteThread_(StgTSO *tso) |
| 2961 | 2972 | { // for forkProcess only:
|
| 2962 | 2973 | // like deleteThread(), but we delete threads in foreign calls, too.
|
| 2963 | 2974 | |
| 2964 | - if (tso->why_blocked == BlockedOnCCall ||
|
|
| 2965 | - tso->why_blocked == BlockedOnCCall_Interruptible) {
|
|
| 2975 | + StgThreadWhyBlocked why_blocked = RELAXED_LOAD(&tso->why_blocked);
|
|
| 2976 | + if (why_blocked == BlockedOnCCall ||
|
|
| 2977 | + why_blocked == BlockedOnCCall_Interruptible) {
|
|
| 2966 | 2978 | tso->what_next = ThreadKilled;
|
| 2967 | 2979 | appendToRunQueue(tso->cap, tso);
|
| 2980 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 2968 | 2981 | } else {
|
| 2969 | 2982 | deleteThread(tso);
|
| 2970 | 2983 | }
|
| ... | ... | @@ -3354,7 +3367,7 @@ resurrectThreads (StgTSO *threads) |
| 3354 | 3367 | // Wake up the thread on the Capability it was last on
|
| 3355 | 3368 | cap = tso->cap;
|
| 3356 | 3369 | |
| 3357 | - switch (tso->why_blocked) {
|
|
| 3370 | + switch (UntagWhyBlocked(RELAXED_LOAD(&tso->why_blocked))) {
|
|
| 3358 | 3371 | case BlockedOnMVar:
|
| 3359 | 3372 | case BlockedOnMVarRead:
|
| 3360 | 3373 | /* Called by GC - sched_mutex lock is currently held. */
|
| ... | ... | @@ -3426,7 +3439,7 @@ static inline EventThreadStatus eventlogThreadStatus(StgThreadReturnCode ret_cod |
| 3426 | 3439 | return thread_stop_code[ret_code];
|
| 3427 | 3440 | }
|
| 3428 | 3441 | |
| 3429 | -static inline EventThreadStatus eventlogThreadStatusBlocked(StgWord why_blocked)
|
|
| 3442 | +static inline EventThreadStatus eventlogThreadStatusBlocked(StgThreadWhyBlocked why_blocked)
|
|
| 3430 | 3443 | {
|
| 3431 | - return thread_blocked_code[why_blocked];
|
|
| 3444 | + return thread_blocked_code[UntagWhyBlocked(why_blocked)];
|
|
| 3432 | 3445 | } |
| ... | ... | @@ -97,8 +97,8 @@ createThread(Capability *cap, W_ size) |
| 97 | 97 | |
| 98 | 98 | // Always start with the compiled code evaluator
|
| 99 | 99 | tso->what_next = ThreadRunGHC;
|
| 100 | - tso->block_info.closure = (StgClosure *)END_TSO_QUEUE;
|
|
| 101 | - tso->why_blocked = NotBlocked;
|
|
| 100 | + tso->block_info.prev = END_TSO_QUEUE;
|
|
| 101 | + tso->why_blocked = NotBlocked;
|
|
| 102 | 102 | tso->blocked_exceptions = END_BLOCKED_EXCEPTIONS_QUEUE;
|
| 103 | 103 | tso->bq = (StgBlockingQueue *)END_TSO_QUEUE;
|
| 104 | 104 | tso->flags = 0;
|
| ... | ... | @@ -291,13 +291,12 @@ tryWakeupThread (Capability *cap, StgTSO *tso) |
| 291 | 291 | }
|
| 292 | 292 | #endif
|
| 293 | 293 | |
| 294 | - switch (ACQUIRE_LOAD(&tso->why_blocked))
|
|
| 294 | + switch (UntagWhyBlocked(ACQUIRE_LOAD(&tso->why_blocked)))
|
|
| 295 | 295 | {
|
| 296 | 296 | case BlockedOnMVar:
|
| 297 | 297 | case BlockedOnMVarRead:
|
| 298 | 298 | {
|
| 299 | 299 | if (tso->_link == END_TSO_QUEUE) {
|
| 300 | - tso->block_info.closure = (StgClosure*)END_TSO_QUEUE;
|
|
| 301 | 300 | goto unblock;
|
| 302 | 301 | } else {
|
| 303 | 302 | return;
|
| ... | ... | @@ -336,8 +335,8 @@ tryWakeupThread (Capability *cap, StgTSO *tso) |
| 336 | 335 | unblock:
|
| 337 | 336 | // just run the thread now, if the BH is not really available,
|
| 338 | 337 | // we'll block again.
|
| 339 | - tso->why_blocked = NotBlocked;
|
|
| 340 | 338 | appendToRunQueue(cap,tso);
|
| 339 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 341 | 340 | |
| 342 | 341 | // We used to set the context switch flag here, which would
|
| 343 | 342 | // trigger a context switch a short time in the future (at the end
|
| ... | ... | @@ -368,7 +367,8 @@ migrateThread (Capability *from, StgTSO *tso, Capability *to) |
| 368 | 367 | traceEventMigrateThread (from, tso, to->no);
|
| 369 | 368 | // ThreadMigrating tells the target cap that it needs to be added to
|
| 370 | 369 | // the run queue when it receives the MSG_TRY_WAKEUP.
|
| 371 | - tso->why_blocked = ThreadMigrating;
|
|
| 370 | + tso->block_info.unused = END_TSO_QUEUE;
|
|
| 371 | + RELEASE_STORE(&tso->why_blocked, ThreadMigrating);
|
|
| 372 | 372 | tso->cap = to;
|
| 373 | 373 | tryWakeupThread(from, tso);
|
| 374 | 374 | }
|
| ... | ... | @@ -876,9 +876,9 @@ loop: |
| 876 | 876 | |
| 877 | 877 | // save why_blocked here, because waking up the thread destroys
|
| 878 | 878 | // this information
|
| 879 | - StgWord why_blocked = ACQUIRE_LOAD(&tso->why_blocked);
|
|
| 879 | + StgThreadWhyBlocked why_blocked = ACQUIRE_LOAD(&tso->why_blocked);
|
|
| 880 | 880 | ASSERT(why_blocked == BlockedOnMVarRead || why_blocked == BlockedOnMVar);
|
| 881 | - ASSERT(tso->block_info.closure == (StgClosure*)mvar);
|
|
| 881 | + ASSERT(tso->block_info.mvar == mvar);
|
|
| 882 | 882 | |
| 883 | 883 | // actually perform the takeMVar
|
| 884 | 884 | StgStack* stack = tso->stackobj;
|
| ... | ... | @@ -949,7 +949,7 @@ end: |
| 949 | 949 | void
|
| 950 | 950 | printThreadBlockage(StgTSO *tso)
|
| 951 | 951 | {
|
| 952 | - switch (ACQUIRE_LOAD(&tso->why_blocked)) {
|
|
| 952 | + switch (UntagWhyBlocked(ACQUIRE_LOAD(&tso->why_blocked))) {
|
|
| 953 | 953 | #if defined(mingw32_HOST_OS)
|
| 954 | 954 | case BlockedOnDoProc:
|
| 955 | 955 | debugBelch("is blocked on proc (request: %" FMT_Word ")", tso->block_info.async_reqID);
|
| ... | ... | @@ -968,10 +968,10 @@ printThreadBlockage(StgTSO *tso) |
| 968 | 968 | #endif
|
| 969 | 969 | break;
|
| 970 | 970 | case BlockedOnMVar:
|
| 971 | - debugBelch("is blocked on an MVar @ %p", tso->block_info.closure);
|
|
| 971 | + debugBelch("is blocked on an MVar @ %p", tso->block_info.mvar);
|
|
| 972 | 972 | break;
|
| 973 | 973 | case BlockedOnMVarRead:
|
| 974 | - debugBelch("is blocked on atomic MVar read @ %p", tso->block_info.closure);
|
|
| 974 | + debugBelch("is blocked on atomic MVar read @ %p", tso->block_info.mvar);
|
|
| 975 | 975 | break;
|
| 976 | 976 | break;
|
| 977 | 977 | case BlockedOnBlackHole:
|
| ... | ... | @@ -1046,7 +1046,7 @@ printAllThreads(void) |
| 1046 | 1046 | debugBelch("other threads:\n");
|
| 1047 | 1047 | for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
|
| 1048 | 1048 | for (t = generations[g].threads; t != END_TSO_QUEUE; t = next) {
|
| 1049 | - if (t->why_blocked != NotBlocked) {
|
|
| 1049 | + if (RELAXED_LOAD(&t->why_blocked) != NotBlocked) {
|
|
| 1050 | 1050 | printThreadStatus(t);
|
| 1051 | 1051 | }
|
| 1052 | 1052 | next = t->global_link;
|
| ... | ... | @@ -1242,15 +1242,12 @@ inner_loop: |
| 1242 | 1242 | traversePushClosure(ts, (StgClosure *) tso->blocked_exceptions, c, sep, child_data);
|
| 1243 | 1243 | traversePushClosure(ts, (StgClosure *) tso->bq, c, sep, child_data);
|
| 1244 | 1244 | traversePushClosure(ts, (StgClosure *) tso->trec, c, sep, child_data);
|
| 1245 | - switch (ACQUIRE_LOAD(&tso->why_blocked)) {
|
|
| 1246 | - case BlockedOnMVar:
|
|
| 1247 | - case BlockedOnMVarRead:
|
|
| 1248 | - case BlockedOnBlackHole:
|
|
| 1249 | - case BlockedOnMsgThrowTo:
|
|
| 1245 | + |
|
| 1246 | + StgThreadWhyBlocked why_blocked = ACQUIRE_LOAD(&tso->why_blocked);
|
|
| 1247 | + if (IsBlockInfoClosure(why_blocked) && why_blocked != NotBlocked) {
|
|
| 1248 | + // The NotBlocked case uses block_info.prev as a TSO back link.
|
|
| 1249 | + // Do not follow in that case or we'll get into a loop.
|
|
| 1250 | 1250 | traversePushClosure(ts, tso->block_info.closure, c, sep, child_data);
|
| 1251 | - break;
|
|
| 1252 | - default:
|
|
| 1253 | - break;
|
|
| 1254 | 1251 | }
|
| 1255 | 1252 | goto loop;
|
| 1256 | 1253 | }
|
| ... | ... | @@ -248,33 +248,86 @@ |
| 248 | 248 | |
| 249 | 249 | /*
|
| 250 | 250 | * Constants for the why_blocked field of a TSO
|
| 251 | - * NB. keep these in sync with GHC/Conc/Sync.hs: threadStatus
|
|
| 251 | + *
|
|
| 252 | + * These say why the TSO is blocked, and also act as the tag for the
|
|
| 253 | + * block_info union. The comment for each tag below says which member
|
|
| 254 | + * of the block_info union is used.
|
|
| 255 | + *
|
|
| 256 | + * We also use the why_blocked to determine if the block_info contains
|
|
| 257 | + * a closure or not. There are three classes of tag:
|
|
| 258 | + * 1. why_blocked tags where block_info is always a closure;
|
|
| 259 | + * 2. why_blocked tags where block_info is never a closure;
|
|
| 260 | + * 3. why_blocked tags where block_info is sometimes a closure;
|
|
| 261 | + *
|
|
| 262 | + * We use the following encoding scheme for the three classes above:
|
|
| 263 | + * 1. the tag value has bits 3 and 4 unset (values 0..7);
|
|
| 264 | + * 2. the tag value has bit 3 set (values 8..15); and
|
|
| 265 | + * 3. the tag value has bit 4 set when it is not a closure and unset
|
|
| 266 | + * when it is a closure.
|
|
| 267 | + *
|
|
| 268 | + * This scheme makes it cheap and simple to check if the GC needs to
|
|
| 269 | + * look at the block_info.closure.
|
|
| 270 | + *
|
|
| 271 | + * The reason for the encoding using 2 marker bits rather than 1 is
|
|
| 272 | + * that it minimises the cases in the code that need to use or check
|
|
| 273 | + * the tag bits. The only tags in class 3 are BlockedOn{Read,Write
|
|
| 274 | + * Delay} which are used by in-RTS I/O managers, and the only ones that
|
|
| 275 | + * need to use block_info members that are not a closure are the legacy
|
|
| 276 | + * I/O managers select and win32-legacy. So when these I/O managers are
|
|
| 277 | + * removed then we can simplify the encoding.
|
|
| 252 | 278 | */
|
| 253 | -#define NotBlocked 0
|
|
| 254 | -#define BlockedOnMVar 1
|
|
| 255 | -#define BlockedOnMVarRead 14 /* TODO: renumber me, see #9003 */
|
|
| 256 | -#define BlockedOnBlackHole 2
|
|
| 257 | -#define BlockedOnRead 3
|
|
| 258 | -#define BlockedOnWrite 4
|
|
| 259 | -#define BlockedOnDelay 5
|
|
| 260 | -#define BlockedOnSTM 6
|
|
| 261 | - |
|
| 262 | -/* Win32 only: */
|
|
| 263 | -#define BlockedOnDoProc 7
|
|
| 264 | - |
|
| 265 | -/* Only relevant for THREADED_RTS: */
|
|
| 266 | -#define BlockedOnCCall 10
|
|
| 267 | -#define BlockedOnCCall_Interruptible 11
|
|
| 268 | - /* same as above but permit killing the worker thread */
|
|
| 269 | - |
|
| 270 | -/* Involved in a message sent to tso->msg_cap */
|
|
| 271 | -#define BlockedOnMsgThrowTo 12
|
|
| 279 | +#define BlockInfoForceNonClosure 16
|
|
| 280 | +#define UntagWhyBlocked(why) ((why) & 15)
|
|
| 281 | +#define IsBlockInfoClosure(why) (((why) & 24) == 0)
|
|
| 282 | +/*
|
|
| 283 | + * In the threaded RTS there is an invariant that the block_info union
|
|
| 284 | + * is always a valid GC closure. To ensure this, the tags that use
|
|
| 285 | + * block_info.unused, always set it to END_TSO_QUEUE. The non-closure
|
|
| 286 | + * why_blocked tags are only used by I/O managers on the non-threaded
|
|
| 287 | + * RTS. New in-RTS I/O managers use the AIOP and TimeoutQueue mechanism
|
|
| 288 | + * which are closures.
|
|
| 289 | + *
|
|
| 290 | + * Note: keep these in sync with GHC/Conc/Sync.hs: threadStatus
|
|
| 291 | + * Note: keep these in sync with Schedule.c: eventlogThreadStatusBlocked which
|
|
| 292 | + * converts the constants here to the ones used in the eventlog.
|
|
| 293 | + * Note: keep the encoding here in sync with parseWhyBlocked in ghc-heap
|
|
| 294 | + */
|
|
| 295 | +#define NotBlocked 0 /* Uses block_info.prev */
|
|
| 296 | +#define BlockedOnMVar 1 /* Uses block_info.mvar */
|
|
| 297 | +#define BlockedOnMVarRead 2 /* Uses block_info.mvar */
|
|
| 298 | +#define BlockedOnBlackHole 3 /* Uses block_info.bh */
|
|
| 299 | +#define BlockedOnMsgThrowTo 4 /* Uses block_info.throwto */
|
|
| 300 | +#define BlockedOnRead 5 /* Uses block_info.aiop
|
|
| 301 | + or with BlockInfoForceNonClosure
|
|
| 302 | + uses .fd or .async_reqID */
|
|
| 303 | +#define BlockedOnWrite 6 /* Uses block_info.aiop
|
|
| 304 | + or with BlockInfoForceNonClosure
|
|
| 305 | + uses .fd or .async_reqID */
|
|
| 306 | +#define BlockedOnDelay 7 /* Uses block_info.timeout
|
|
| 307 | + or with BlockInfoForceNonClosure
|
|
| 308 | + uses .target */
|
|
| 309 | + |
|
| 310 | +#define BlockedOnSTM 8 /* Uses block_info.unused */
|
|
| 311 | +#define BlockedOnCCall 9 /* Uses block_info.unused */
|
|
| 312 | +#define BlockedOnCCall_Interruptible 10 /* Uses block_info.unused
|
|
| 313 | + * Same as BlockedOnCCall but permits
|
|
| 314 | + * killing the worker thread */
|
|
| 315 | +#define ThreadMigrating 11 /* Uses block_info.unused */
|
|
| 316 | +#define BlockedOnDoProc 12 /* Uses block_info.async_reqID */
|
|
| 317 | + |
|
| 318 | +/* Reserved values, not values that why_blocked currently use. They
|
|
| 319 | + * are used in primop stg_threadStatuszh and must not overlap with
|
|
| 320 | + * other why_blocked status values. They could be changed, if the
|
|
| 321 | + * threadStatus in ghc-internal is updated too.
|
|
| 322 | + */
|
|
| 323 | +#define BlockedThreadComplete 16
|
|
| 324 | +#define BlockedThreadKilled 17
|
|
| 272 | 325 | |
| 273 | -/* The thread is not on any run queues, but can be woken up
|
|
| 274 | - by tryWakeupThread() */
|
|
| 275 | -#define ThreadMigrating 13
|
|
| 326 | +/* Next available non-closure why_blocked tag numbers are: 13,14,15
|
|
| 327 | + * For more closure tag numbers, shift up all the non-closure ones
|
|
| 328 | + * and adjust the BlockInfoForceNonClosure tag and related macros.
|
|
| 329 | + * If we reach BlockInfoForceNonClosure then shift that up. */
|
|
| 276 | 330 | |
| 277 | -/* Next number is 15. */
|
|
| 278 | 331 | |
| 279 | 332 | /*
|
| 280 | 333 | * These constants are returned to the scheduler by a thread that has
|
| ... | ... | @@ -286,6 +339,7 @@ |
| 286 | 339 | #define ThreadYielding 3
|
| 287 | 340 | #define ThreadBlocked 4
|
| 288 | 341 | #define ThreadFinished 5
|
| 342 | +/* If this is ever extended, also adjust the eventlogStopStatus mapping */
|
|
| 289 | 343 | |
| 290 | 344 | /*
|
| 291 | 345 | * Flags for the tso->flags field.
|
| ... | ... | @@ -30,6 +30,15 @@ typedef StgWord64 StgThreadID; |
| 30 | 30 | |
| 31 | 31 | #define tsoLocked(tso) ((tso)->flags & TSO_LOCKED)
|
| 32 | 32 | |
| 33 | +/* Type for the tso->why_blocked field. See values in Constants.h.
|
|
| 34 | + *
|
|
| 35 | + * The StgThreadWhyBlocked type could be 8-bits, but for reasons
|
|
| 36 | + * unclear it is currently 32-bits. Previous comments here claimed
|
|
| 37 | + * that the smallest atomic type on AArch64 is 32-bits, but this is
|
|
| 38 | + * false.
|
|
| 39 | + */
|
|
| 40 | +typedef StgWord32 StgThreadWhyBlocked;
|
|
| 41 | + |
|
| 33 | 42 | /*
|
| 34 | 43 | * Type returned after running a thread. Values of this type
|
| 35 | 44 | * include HeapOverflow, StackOverflow etc. See Constants.h for the
|
| ... | ... | @@ -37,22 +46,47 @@ typedef StgWord64 StgThreadID; |
| 37 | 46 | */
|
| 38 | 47 | typedef unsigned int StgThreadReturnCode;
|
| 39 | 48 | |
| 40 | -/* Reason for thread being blocked. See comment above struct StgTso_. */
|
|
| 49 | +/* Additional information about how the thread is blocked.
|
|
| 50 | + * The tso->why_blocked is the tag for this union. */
|
|
| 41 | 51 | typedef union {
|
| 52 | + /* Used for generic read, for cases where block_info is a closure.
|
|
| 53 | + * Never used for writes. Use .unused below instead. */
|
|
| 42 | 54 | StgClosure *closure;
|
| 43 | - StgTSO *prev; // a back-link when the TSO is on the run queue (NotBlocked)
|
|
| 55 | + |
|
| 56 | + /* For why_blocked cases where block_info is unused, this will be set to
|
|
| 57 | + * END_TSO_QUEUE, to maintain invariant that block_info.closure is valid */
|
|
| 58 | + StgTSO *unused;
|
|
| 59 | + |
|
| 60 | + /* case NotBlocked: A back-link when the TSO is on the run queue */
|
|
| 61 | + StgTSO *prev;
|
|
| 62 | + |
|
| 63 | + /* case BlockedOnMVar, BlockedOnMVarRead: the mvar the TSO is blocked on */
|
|
| 64 | + StgMVar *mvar;
|
|
| 65 | + |
|
| 66 | + /* case BlockedOnBlackHole */
|
|
| 44 | 67 | struct MessageBlackHole_ *bh;
|
| 68 | + |
|
| 69 | + /* case BlockedOnMsgThrowTo */
|
|
| 45 | 70 | struct MessageThrowTo_ *throwto;
|
| 46 | - struct MessageWakeup_ *wakeup;
|
|
| 71 | + |
|
| 72 | + /* case BlockedOnRead, BlockedOnWrite: legacy select I/O manager */
|
|
| 47 | 73 | StgInt fd; /* StgInt instead of int, so that it's the same size as the ptrs */
|
| 74 | + |
|
| 75 | + /* case BlockedOnRead, BlockedOnWrite: new I/O managers */
|
|
| 48 | 76 | StgAsyncIOOp *aiop;
|
| 77 | + |
|
| 78 | + /* case BlockedOnDelay: new I/O managers */
|
|
| 49 | 79 | StgTimeoutQueue *timeout;
|
| 80 | + |
|
| 50 | 81 | #if defined(mingw32_HOST_OS)
|
| 51 | - // Only used by the Legacy Win32 I/O manager: the async request id for the
|
|
| 52 | - // operation.
|
|
| 82 | + /* case BlockedOnRead, BlockedOnWrite, BlockedOnDoProc:
|
|
| 83 | + * only used by the win32-legacy I/O manager.
|
|
| 84 | + * This is the async request id for the operation. */
|
|
| 53 | 85 | StgWord async_reqID;
|
| 54 | 86 | #endif
|
| 87 | + |
|
| 55 | 88 | #if !defined(THREADED_RTS)
|
| 89 | + /* case BlockedOnDelay: used by the select I/O manager */
|
|
| 56 | 90 | StgWord target;
|
| 57 | 91 | // Only for the legacy select I/O manager: the target time for a thread
|
| 58 | 92 | // blocked in threadDelay, in units of 1ms. This is a compromise: we don't
|
| ... | ... | @@ -73,7 +107,21 @@ typedef union { |
| 73 | 107 | * have the reason in the why_blocked field of the TSO, and some
|
| 74 | 108 | * further info (such as the closure the thread is blocked on, or the
|
| 75 | 109 | * file descriptor if the thread is waiting on I/O) in the block_info
|
| 76 | - * field.
|
|
| 110 | + * field. See Constants.h for the why_blocked values.
|
|
| 111 | + *
|
|
| 112 | + * The why_blocked field must be updated atomically. The protocol for
|
|
| 113 | + * updating block_info and why_blocked fields together is as follows:
|
|
| 114 | + *
|
|
| 115 | + * Writes:
|
|
| 116 | + * - first write block_info (normal non-atomic write)
|
|
| 117 | + * - then write why_blocked with an atomic *store release*
|
|
| 118 | + *
|
|
| 119 | + * Reads:
|
|
| 120 | + * - first read why_blocked with an atomic *load acquire*
|
|
| 121 | + * - then read block_info (normal non-atomic read)
|
|
| 122 | + *
|
|
| 123 | + * Read of only why_blocked without block_info:
|
|
| 124 | + * - read why_blocked with an atomic *relaxed load*
|
|
| 77 | 125 | */
|
| 78 | 126 | |
| 79 | 127 | typedef struct StgTSO_ {
|
| ... | ... | @@ -123,11 +171,7 @@ typedef struct StgTSO_ { |
| 123 | 171 | StgWord16 what_next; // Values defined in Constants.h
|
| 124 | 172 | StgWord32 flags; // Values defined in Constants.h
|
| 125 | 173 | |
| 126 | - /*
|
|
| 127 | - * N.B. why_blocked only has a handful of values but must be atomically
|
|
| 128 | - * updated; the smallest width which AArch64 supports for is 32-bits.
|
|
| 129 | - */
|
|
| 130 | - StgWord32 why_blocked; // Values defined in Constants.h
|
|
| 174 | + StgThreadWhyBlocked why_blocked; // Values defined in Constants.h
|
|
| 131 | 175 | StgTSOBlockInfo block_info; // Barrier provided by why_blocked
|
| 132 | 176 | StgThreadID id;
|
| 133 | 177 | StgWord32 saved_errno;
|
| ... | ... | @@ -183,8 +183,9 @@ bool syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso, |
| 183 | 183 | aiop->notify.tso = tso;
|
| 184 | 184 | aiop->notify_type = NotifyTSO;
|
| 185 | 185 | aiop->live = &stg_ASYNCIO_LIVE0_closure;
|
| 186 | - tso->why_blocked = rw == IORead ? BlockedOnRead : BlockedOnWrite;
|
|
| 187 | 186 | tso->block_info.aiop = aiop;
|
| 187 | + RELEASE_STORE(&tso->why_blocked, rw == IORead ? BlockedOnRead
|
|
| 188 | + : BlockedOnWrite);
|
|
| 188 | 189 | return asyncIOWaitReadyPoll(iomgr, aiop, rw, fd);
|
| 189 | 190 | }
|
| 190 | 191 | |
| ... | ... | @@ -230,7 +231,6 @@ void syncIOCancelPoll(CapIOManager *iomgr, StgTSO *tso) |
| 230 | 231 | * We don't put the TSO back on the run queue or change the why_blocked
|
| 231 | 232 | * status, as that is done by removeFromQueues (in the throwTo* functions).
|
| 232 | 233 | */
|
| 233 | - tso->block_info.closure = (StgClosure *)END_TSO_QUEUE;
|
|
| 234 | 234 | |
| 235 | 235 | /* We are in the TSO case, where the aiop was only reachable from the TSO
|
| 236 | 236 | * itself, and thus it is now no longer be reachable at all.
|
| ... | ... | @@ -300,10 +300,9 @@ static void notifyIOCompletion(CapIOManager *iomgr, StgAsyncIOOp *aiop) |
| 300 | 300 | * cap because the tso was not on the run queue of any cap and
|
| 301 | 301 | * so is not subject to thread migration.
|
| 302 | 302 | */
|
| 303 | - StgTSO *tso = aiop->notify.tso;
|
|
| 304 | - tso->why_blocked = NotBlocked;
|
|
| 305 | - tso->_link = END_TSO_QUEUE;
|
|
| 303 | + StgTSO *tso = aiop->notify.tso;
|
|
| 306 | 304 | pushOnRunQueue(iomgr->cap, tso);
|
| 305 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 307 | 306 | }
|
| 308 | 307 | /* For the TSO case, the aiop was only reachable from the TSO
|
| 309 | 308 | * itself, and thus it is now no longer be reachable at all.
|
| ... | ... | @@ -138,11 +138,10 @@ static bool wakeUpSleepingThreads (CapIOManager *iomgr, LowResTime now) |
| 138 | 138 | break;
|
| 139 | 139 | }
|
| 140 | 140 | iomgr->sleeping_queue = tso->_link;
|
| 141 | - RELAXED_STORE(&tso->why_blocked, NotBlocked);
|
|
| 142 | - tso->_link = END_TSO_QUEUE;
|
|
| 143 | 141 | IF_DEBUG(scheduler, debugBelch("Waking up sleeping thread %"
|
| 144 | 142 | FMT_StgThreadID "\n", tso->id));
|
| 145 | 143 | pushOnRunQueue(iomgr->cap,tso);
|
| 144 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 146 | 145 | flag = true;
|
| 147 | 146 | }
|
| 148 | 147 | return flag;
|
| ... | ... | @@ -311,7 +310,7 @@ awaitCompletedTimeoutsOrIOSelect(CapIOManager *iomgr, bool wait) |
| 311 | 310 | * So the (int) cast should be removed across the code base once
|
| 312 | 311 | * GHC requires a version of FreeBSD that has that change in it.
|
| 313 | 312 | */
|
| 314 | - switch (ACQUIRE_LOAD(&tso->why_blocked)) {
|
|
| 313 | + switch (UntagWhyBlocked(ACQUIRE_LOAD(&tso->why_blocked))) {
|
|
| 315 | 314 | case BlockedOnRead:
|
| 316 | 315 | {
|
| 317 | 316 | int fd = tso->block_info.fd;
|
| ... | ... | @@ -449,7 +448,7 @@ awaitCompletedTimeoutsOrIOSelect(CapIOManager *iomgr, bool wait) |
| 449 | 448 | int fd;
|
| 450 | 449 | enum FdState fd_state = RTS_FD_IS_BLOCKING;
|
| 451 | 450 | |
| 452 | - switch (tso->why_blocked) {
|
|
| 451 | + switch (UntagWhyBlocked(ACQUIRE_LOAD(&tso->why_blocked))) {
|
|
| 453 | 452 | case BlockedOnRead:
|
| 454 | 453 | fd = tso->block_info.fd;
|
| 455 | 454 | |
| ... | ... | @@ -488,9 +487,8 @@ awaitCompletedTimeoutsOrIOSelect(CapIOManager *iomgr, bool wait) |
| 488 | 487 | IF_DEBUG(scheduler,
|
| 489 | 488 | debugBelch("Waking up blocked thread %" FMT_StgThreadID "\n",
|
| 490 | 489 | tso->id));
|
| 491 | - tso->why_blocked = NotBlocked;
|
|
| 492 | - tso->_link = END_TSO_QUEUE;
|
|
| 493 | 490 | pushOnRunQueue(iomgr->cap,tso);
|
| 491 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 494 | 492 | break;
|
| 495 | 493 | case RTS_FD_IS_BLOCKING:
|
| 496 | 494 | if (prev == NULL)
|
| ... | ... | @@ -48,8 +48,8 @@ bool syncDelayTimeout(CapIOManager *iomgr, StgTSO *tso, HsInt us_delay) |
| 48 | 48 | initElemTimeoutQueue(timeout, notify, NotifyTSO, iomgr->cap->r.rCCCS);
|
| 49 | 49 | |
| 50 | 50 | ASSERT(tso->why_blocked == NotBlocked);
|
| 51 | - tso->why_blocked = BlockedOnDelay;
|
|
| 52 | 51 | tso->block_info.timeout = timeout;
|
| 52 | + RELEASE_STORE(&tso->why_blocked, BlockedOnDelay);
|
|
| 53 | 53 | |
| 54 | 54 | insertTimeoutQueue(&iomgr->timeout_queue, timeout, target);
|
| 55 | 55 | |
| ... | ... | @@ -67,8 +67,6 @@ void syncDelayCancelTimeout(CapIOManager *iomgr, StgTSO *tso) |
| 67 | 67 | |
| 68 | 68 | deleteTimeoutQueue(&iomgr->timeout_queue, timeout);
|
| 69 | 69 | |
| 70 | - tso->block_info.closure = (StgClosure *)END_TSO_QUEUE;
|
|
| 71 | - |
|
| 72 | 70 | /* the timeout is no longer accessible from anywhere (except here) */
|
| 73 | 71 | IF_NONMOVING_WRITE_BARRIER_ENABLED {
|
| 74 | 72 | updateRemembSetPushClosure(iomgr->cap, (StgClosure *)timeout);
|
| ... | ... | @@ -118,10 +116,9 @@ static void notifyTimeoutCompletion(CapIOManager *iomgr, StgTimeout *timeout) |
| 118 | 116 | switch (timeout->notify_type) {
|
| 119 | 117 | case NotifyTSO:
|
| 120 | 118 | {
|
| 121 | - StgTSO *tso = timeout->notify.tso;
|
|
| 122 | - tso->why_blocked = NotBlocked;
|
|
| 123 | - tso->_link = END_TSO_QUEUE;
|
|
| 119 | + StgTSO *tso = timeout->notify.tso;
|
|
| 124 | 120 | pushOnRunQueue(iomgr->cap, tso);
|
| 121 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 125 | 122 | break;
|
| 126 | 123 | }
|
| 127 | 124 | case NotifyMVar:
|
| ... | ... | @@ -468,16 +468,10 @@ thread_TSO (StgTSO *tso) |
| 468 | 468 | thread_(&tso->_link);
|
| 469 | 469 | thread_(&tso->global_link);
|
| 470 | 470 | |
| 471 | - switch (ACQUIRE_LOAD(&tso->why_blocked)) {
|
|
| 472 | - case BlockedOnMVar:
|
|
| 473 | - case BlockedOnMVarRead:
|
|
| 474 | - case BlockedOnBlackHole:
|
|
| 475 | - case BlockedOnMsgThrowTo:
|
|
| 476 | - case NotBlocked:
|
|
| 471 | + if (IsBlockInfoClosure(ACQUIRE_LOAD(&tso->why_blocked))) {
|
|
| 472 | + /* This also follows the block_info.prev back-link in
|
|
| 473 | + * the NotBlocked case, which may not be necessary. */
|
|
| 477 | 474 | thread_(&tso->block_info.closure);
|
| 478 | - break;
|
|
| 479 | - default:
|
|
| 480 | - break;
|
|
| 481 | 475 | }
|
| 482 | 476 | thread_(&tso->blocked_exceptions);
|
| 483 | 477 | thread_(&tso->bq);
|
| ... | ... | @@ -1055,16 +1055,10 @@ trace_tso (MarkQueue *queue, StgTSO *tso) |
| 1055 | 1055 | if (tso->label != NULL) {
|
| 1056 | 1056 | markQueuePushClosure_(queue, (StgClosure *) tso->label);
|
| 1057 | 1057 | }
|
| 1058 | - switch (ACQUIRE_LOAD(&tso->why_blocked)) {
|
|
| 1059 | - case BlockedOnMVar:
|
|
| 1060 | - case BlockedOnMVarRead:
|
|
| 1061 | - case BlockedOnBlackHole:
|
|
| 1062 | - case BlockedOnMsgThrowTo:
|
|
| 1063 | - case NotBlocked:
|
|
| 1058 | + if (IsBlockInfoClosure(ACQUIRE_LOAD(&tso->why_blocked))) {
|
|
| 1059 | + /* This also follows the block_info.prev back-link in
|
|
| 1060 | + * the NotBlocked case, which may not be necessary. */
|
|
| 1064 | 1061 | markQueuePushClosure_(queue, tso->block_info.closure);
|
| 1065 | - break;
|
|
| 1066 | - default:
|
|
| 1067 | - break;
|
|
| 1068 | 1062 | }
|
| 1069 | 1063 | }
|
| 1070 | 1064 |
| ... | ... | @@ -779,13 +779,45 @@ checkTSO(StgTSO *tso) |
| 779 | 779 | info == &stg_WHITEHOLE_info); // used to happen due to STM doing
|
| 780 | 780 | // lockTSO(), might not happen now
|
| 781 | 781 | |
| 782 | - if ( tso->why_blocked == BlockedOnMVar
|
|
| 783 | - || tso->why_blocked == BlockedOnMVarRead
|
|
| 784 | - || tso->why_blocked == BlockedOnBlackHole
|
|
| 785 | - || tso->why_blocked == BlockedOnMsgThrowTo
|
|
| 786 | - || tso->why_blocked == NotBlocked
|
|
| 787 | - ) {
|
|
| 782 | + StgThreadWhyBlocked why_blocked = ACQUIRE_LOAD(&tso->why_blocked);
|
|
| 783 | + switch (why_blocked) {
|
|
| 784 | + case NotBlocked:
|
|
| 785 | + case BlockedOnMVar:
|
|
| 786 | + case BlockedOnMVarRead:
|
|
| 787 | + case BlockedOnBlackHole:
|
|
| 788 | + case BlockedOnMsgThrowTo:
|
|
| 789 | + case BlockedOnRead:
|
|
| 790 | + case BlockedOnWrite:
|
|
| 791 | + case BlockedOnDelay:
|
|
| 792 | + //TODO: we could be more specific and check BlockedOnMVar has an MVar,
|
|
| 793 | + // BlockedOnBlackHole has a message, BlockedOnRead has an AIOP etc.
|
|
| 794 | + ASSERT(IsBlockInfoClosure(why_blocked));
|
|
| 788 | 795 | ASSERT(LOOKS_LIKE_CLOSURE_PTR(tso->block_info.closure));
|
| 796 | + break;
|
|
| 797 | + |
|
| 798 | + case BlockedOnSTM:
|
|
| 799 | + case BlockedOnCCall:
|
|
| 800 | + case BlockedOnCCall_Interruptible:
|
|
| 801 | + case ThreadMigrating:
|
|
| 802 | + ASSERT(!IsBlockInfoClosure(why_blocked));
|
|
| 803 | + ASSERT(tso->block_info.unused == END_TSO_QUEUE);
|
|
| 804 | + break;
|
|
| 805 | + |
|
| 806 | +#if !defined(THREADED_RTS)
|
|
| 807 | + // Only these three can use BlockInfoForceNonClosure
|
|
| 808 | + case BlockedOnRead | BlockInfoForceNonClosure:
|
|
| 809 | + case BlockedOnWrite | BlockInfoForceNonClosure:
|
|
| 810 | + case BlockedOnDelay | BlockInfoForceNonClosure:
|
|
| 811 | +#if defined(mingw32_HOST_OS)
|
|
| 812 | + case BlockedOnDoProc:
|
|
| 813 | +#endif
|
|
| 814 | + ASSERT(!IsBlockInfoClosure(why_blocked));
|
|
| 815 | + break;
|
|
| 816 | +#endif
|
|
| 817 | + |
|
| 818 | + default:
|
|
| 819 | + barf("checkTSO: strange tso->why_blocked: %d for TSO %"
|
|
| 820 | + FMT_StgThreadID " (%p)", why_blocked, tso->id, tso);
|
|
| 789 | 821 | }
|
| 790 | 822 | |
| 791 | 823 | ASSERT(LOOKS_LIKE_CLOSURE_PTR(tso->bq));
|
| ... | ... | @@ -138,29 +138,16 @@ scavengeTSO (StgTSO *tso) |
| 138 | 138 | evacuate((StgClosure **)&tso->label);
|
| 139 | 139 | }
|
| 140 | 140 | |
| 141 | - switch (ACQUIRE_LOAD(&tso->why_blocked)) {
|
|
| 142 | - case BlockedOnMVar:
|
|
| 143 | - case BlockedOnMVarRead:
|
|
| 144 | - case BlockedOnBlackHole:
|
|
| 145 | - case BlockedOnMsgThrowTo:
|
|
| 146 | - case NotBlocked:
|
|
| 141 | + if (IsBlockInfoClosure(ACQUIRE_LOAD(&tso->why_blocked))) {
|
|
| 147 | 142 | evacuate(&tso->block_info.closure);
|
| 148 | - break;
|
|
| 149 | - case BlockedOnRead:
|
|
| 150 | - case BlockedOnWrite:
|
|
| 151 | - case BlockedOnDelay:
|
|
| 152 | - case BlockedOnDoProc:
|
|
| 153 | - scavengeTSOIOManager(tso);
|
|
| 154 | - break;
|
|
| 155 | - default:
|
|
| 143 | + } else {
|
|
| 156 | 144 | #if defined(THREADED_RTS)
|
| 157 | 145 | // in the THREADED_RTS, block_info.closure must always point to a
|
| 158 | 146 | // valid closure, because we assume this in throwTo(). In the
|
| 159 | 147 | // non-threaded RTS it might be a FD (for
|
| 160 | 148 | // BlockedOnRead/BlockedOnWrite) or a time value (BlockedOnDelay)
|
| 161 | - tso->block_info.closure = (StgClosure *)END_TSO_QUEUE;
|
|
| 149 | + ASSERT(tso->block_info.unused == END_TSO_QUEUE);
|
|
| 162 | 150 | #endif
|
| 163 | - break;
|
|
| 164 | 151 | }
|
| 165 | 152 | |
| 166 | 153 | tso->dirty = gct->failed_to_evac;
|
| ... | ... | @@ -304,7 +304,7 @@ start: |
| 304 | 304 | for(tso = iomgr->blocked_queue_hd; tso != END_TSO_QUEUE;
|
| 305 | 305 | tso = tso->_link) {
|
| 306 | 306 | |
| 307 | - switch(ACQUIRE_LOAD(&tso->why_blocked)) {
|
|
| 307 | + switch (UntagWhyBlocked(ACQUIRE_LOAD(&tso->why_blocked))) {
|
|
| 308 | 308 | case BlockedOnRead:
|
| 309 | 309 | case BlockedOnWrite:
|
| 310 | 310 | case BlockedOnDoProc:
|
| ... | ... | @@ -324,8 +324,6 @@ start: |
| 324 | 324 | }
|
| 325 | 325 | |
| 326 | 326 | // Terminates the run queue + this inner for-loop.
|
| 327 | - tso->_link = END_TSO_QUEUE;
|
|
| 328 | - tso->why_blocked = NotBlocked;
|
|
| 329 | 327 | // For stg_block_async frames (read/write/doProc),
|
| 330 | 328 | // write len and errCode directly to the stack.
|
| 331 | 329 | // For stg_block_noregs frames (delay), nothing
|
| ... | ... | @@ -335,14 +333,14 @@ start: |
| 335 | 333 | tso->stackobj->sp[2] = (W_)errCode;
|
| 336 | 334 | }
|
| 337 | 335 | pushOnRunQueue(&MainCapability, tso);
|
| 336 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 338 | 337 | break;
|
| 339 | 338 | }
|
| 340 | 339 | break;
|
| 341 | - default:
|
|
| 342 | - if (tso->why_blocked != NotBlocked) {
|
|
| 343 | - barf("awaitRequests: odd thread state");
|
|
| 344 | - }
|
|
| 340 | + case NotBlocked:
|
|
| 345 | 341 | break;
|
| 342 | + default:
|
|
| 343 | + barf("awaitRequests: odd thread state");
|
|
| 346 | 344 | }
|
| 347 | 345 | |
| 348 | 346 | prev = tso;
|