Duncan Coutts pushed to branch wip/dcoutts/issue-26717 at Glasgow Haskell Compiler / GHC
Commits:
-
3ea1f237
by Duncan Coutts at 2026-07-21T13:16:35+01:00
-
c43b9929
by Duncan Coutts at 2026-07-21T13:17:30+01:00
-
4aca39e4
by Duncan Coutts at 2026-07-21T13:20:52+01:00
-
9c55e3c2
by Duncan Coutts at 2026-07-21T13:20:55+01:00
-
e4e4a32c
by Duncan Coutts at 2026-07-21T13:20:55+01:00
-
eeb795a2
by Duncan Coutts at 2026-07-21T13:20:55+01:00
-
37cfcd73
by Duncan Coutts at 2026-07-21T13:20:55+01:00
-
0afedcf5
by Duncan Coutts at 2026-07-21T13:20:55+01:00
-
51b04410
by Duncan Coutts at 2026-07-21T13:20:55+01:00
-
a553a482
by Duncan Coutts at 2026-07-21T13:20:55+01:00
-
6fa2d9be
by Duncan Coutts at 2026-07-21T13:20:55+01:00
-
573b54eb
by Duncan Coutts at 2026-07-21T13:20:55+01:00
-
40dc6744
by Duncan Coutts at 2026-07-21T13:20:55+01:00
20 changed files:
- + changelog.d/T26716
- rts/IOManager.c
- rts/IOManager.h
- rts/Messages.c
- rts/PrimOps.cmm
- rts/RaiseAsync.c
- rts/RaiseAsync.h
- 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 a use-after-free bug in the poll I/O manager
|
|
| 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 pointer or not.
|
|
| 15 | +} |
| ... | ... | @@ -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) {
|
| ... | ... | @@ -370,8 +369,9 @@ 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)
|
|
| 372 | + StgThreadWhyBlocked why_blocked_still = ACQUIRE_LOAD(&target->why_blocked);
|
|
| 373 | + if (( why_blocked_still != BlockedOnMVar
|
|
| 374 | + && why_blocked_still != BlockedOnMVarRead)
|
|
| 375 | 375 | || target->block_info.mvar != mvar) {
|
| 376 | 376 | unlockClosure((StgClosure *)mvar, info);
|
| 377 | 377 | goto retry;
|
| ... | ... | @@ -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 | }
|
| ... | ... | @@ -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:
|
| ... | ... | @@ -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);
|
| ... | ... | @@ -2508,9 +2508,9 @@ suspendThread (StgRegTable *reg, bool interruptible) |
| 2508 | 2508 | |
| 2509 | 2509 | tso->block_info.unused = END_TSO_QUEUE;
|
| 2510 | 2510 | if (interruptible) {
|
| 2511 | - tso->why_blocked = BlockedOnCCall_Interruptible;
|
|
| 2511 | + RELEASE_STORE(&tso->why_blocked, BlockedOnCCall_Interruptible);
|
|
| 2512 | 2512 | } else {
|
| 2513 | - tso->why_blocked = BlockedOnCCall;
|
|
| 2513 | + RELEASE_STORE(&tso->why_blocked, BlockedOnCCall);
|
|
| 2514 | 2514 | }
|
| 2515 | 2515 | |
| 2516 | 2516 | // Hand back capability
|
| ... | ... | @@ -2568,16 +2568,25 @@ resumeThread (void *task_) |
| 2568 | 2568 | tso = incall->suspended_tso;
|
| 2569 | 2569 | incall->suspended_tso = NULL;
|
| 2570 | 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 | + |
|
| 2571 | 2576 | // we will modify tso->_link
|
| 2572 | 2577 | IF_NONMOVING_WRITE_BARRIER_ENABLED {
|
| 2573 | 2578 | updateRemembSetPushClosure(cap, (StgClosure *)tso->_link);
|
| 2574 | 2579 | }
|
| 2575 | 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);
|
|
| 2576 | 2585 | |
| 2577 | 2586 | traceEventRunThread(cap, tso);
|
| 2578 | 2587 | |
| 2579 | 2588 | /* Reset blocking status */
|
| 2580 | - tso->why_blocked = NotBlocked;
|
|
| 2589 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 2581 | 2590 | |
| 2582 | 2591 | if ((tso->flags & TSO_BLOCKEX) == 0) {
|
| 2583 | 2592 | // avoid locking the TSO if we don't have to
|
| ... | ... | @@ -2950,8 +2959,9 @@ deleteThread (StgTSO *tso) |
| 2950 | 2959 | // The TSO must be on the run queue of the Capability we own, or
|
| 2951 | 2960 | // we must own all Capabilities.
|
| 2952 | 2961 | |
| 2953 | - if (tso->why_blocked != BlockedOnCCall &&
|
|
| 2954 | - tso->why_blocked != BlockedOnCCall_Interruptible) {
|
|
| 2962 | + StgThreadWhyBlocked why_blocked = RELAXED_LOAD(&tso->why_blocked);
|
|
| 2963 | + if (why_blocked != BlockedOnCCall &&
|
|
| 2964 | + why_blocked != BlockedOnCCall_Interruptible) {
|
|
| 2955 | 2965 | throwToSingleThreaded(tso->cap,tso,NULL);
|
| 2956 | 2966 | }
|
| 2957 | 2967 | }
|
| ... | ... | @@ -2962,10 +2972,12 @@ deleteThread_(StgTSO *tso) |
| 2962 | 2972 | { // for forkProcess only:
|
| 2963 | 2973 | // like deleteThread(), but we delete threads in foreign calls, too.
|
| 2964 | 2974 | |
| 2965 | - if (tso->why_blocked == BlockedOnCCall ||
|
|
| 2966 | - tso->why_blocked == BlockedOnCCall_Interruptible) {
|
|
| 2975 | + StgThreadWhyBlocked why_blocked = RELAXED_LOAD(&tso->why_blocked);
|
|
| 2976 | + if (why_blocked == BlockedOnCCall ||
|
|
| 2977 | + why_blocked == BlockedOnCCall_Interruptible) {
|
|
| 2967 | 2978 | tso->what_next = ThreadKilled;
|
| 2968 | 2979 | appendToRunQueue(tso->cap, tso);
|
| 2980 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 2969 | 2981 | } else {
|
| 2970 | 2982 | deleteThread(tso);
|
| 2971 | 2983 | }
|
| ... | ... | @@ -3355,7 +3367,7 @@ resurrectThreads (StgTSO *threads) |
| 3355 | 3367 | // Wake up the thread on the Capability it was last on
|
| 3356 | 3368 | cap = tso->cap;
|
| 3357 | 3369 | |
| 3358 | - switch (tso->why_blocked) {
|
|
| 3370 | + switch (UntagWhyBlocked(RELAXED_LOAD(&tso->why_blocked))) {
|
|
| 3359 | 3371 | case BlockedOnMVar:
|
| 3360 | 3372 | case BlockedOnMVarRead:
|
| 3361 | 3373 | /* Called by GC - sched_mutex lock is currently held. */
|
| ... | ... | @@ -3427,7 +3439,7 @@ static inline EventThreadStatus eventlogThreadStatus(StgThreadReturnCode ret_cod |
| 3427 | 3439 | return thread_stop_code[ret_code];
|
| 3428 | 3440 | }
|
| 3429 | 3441 | |
| 3430 | -static inline EventThreadStatus eventlogThreadStatusBlocked(StgWord why_blocked)
|
|
| 3442 | +static inline EventThreadStatus eventlogThreadStatusBlocked(StgThreadWhyBlocked why_blocked)
|
|
| 3431 | 3443 | {
|
| 3432 | - return thread_blocked_code[why_blocked];
|
|
| 3444 | + return thread_blocked_code[UntagWhyBlocked(why_blocked)];
|
|
| 3433 | 3445 | } |
| ... | ... | @@ -291,7 +291,7 @@ 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:
|
| ... | ... | @@ -335,8 +335,8 @@ tryWakeupThread (Capability *cap, StgTSO *tso) |
| 335 | 335 | unblock:
|
| 336 | 336 | // just run the thread now, if the BH is not really available,
|
| 337 | 337 | // we'll block again.
|
| 338 | - tso->why_blocked = NotBlocked;
|
|
| 339 | 338 | appendToRunQueue(cap,tso);
|
| 339 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 340 | 340 | |
| 341 | 341 | // We used to set the context switch flag here, which would
|
| 342 | 342 | // trigger a context switch a short time in the future (at the end
|
| ... | ... | @@ -368,7 +368,7 @@ migrateThread (Capability *from, StgTSO *tso, Capability *to) |
| 368 | 368 | // ThreadMigrating tells the target cap that it needs to be added to
|
| 369 | 369 | // the run queue when it receives the MSG_TRY_WAKEUP.
|
| 370 | 370 | tso->block_info.unused = END_TSO_QUEUE;
|
| 371 | - tso->why_blocked = ThreadMigrating;
|
|
| 371 | + RELEASE_STORE(&tso->why_blocked, ThreadMigrating);
|
|
| 372 | 372 | tso->cap = to;
|
| 373 | 373 | tryWakeupThread(from, tso);
|
| 374 | 374 | }
|
| ... | ... | @@ -876,7 +876,7 @@ 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 | 881 | ASSERT(tso->block_info.mvar == mvar);
|
| 882 | 882 | |
| ... | ... | @@ -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);
|
| ... | ... | @@ -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 | }
|
| ... | ... | @@ -253,6 +253,33 @@ |
| 253 | 253 | * block_info union. The comment for each tag below says which member
|
| 254 | 254 | * of the block_info union is used.
|
| 255 | 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.
|
|
| 278 | + */
|
|
| 279 | +#define BlockInfoForceNonClosure 16
|
|
| 280 | +#define UntagWhyBlocked(why) ((why) & 15)
|
|
| 281 | +#define IsBlockInfoClosure(why) (((why) & 24) == 0)
|
|
| 282 | +/*
|
|
| 256 | 283 | * In the threaded RTS there is an invariant that the block_info union
|
| 257 | 284 | * is always a valid GC closure. To ensure this, the tags that use
|
| 258 | 285 | * block_info.unused, always set it to END_TSO_QUEUE. The non-closure
|
| ... | ... | @@ -269,21 +296,31 @@ |
| 269 | 296 | #define BlockedOnMVarRead 2 /* Uses block_info.mvar */
|
| 270 | 297 | #define BlockedOnBlackHole 3 /* Uses block_info.bh */
|
| 271 | 298 | #define BlockedOnMsgThrowTo 4 /* Uses block_info.throwto */
|
| 272 | -#define BlockedOnRead 5 /* Uses block_info.aiop or uses .fd or
|
|
| 273 | - .async_result */
|
|
| 274 | -#define BlockedOnWrite 6 /* Uses block_info.aiop or uses .fd or
|
|
| 275 | - .async_result */
|
|
| 276 | -#define BlockedOnDelay 7 /* Uses block_info.timeout or
|
|
| 299 | +#define BlockedOnRead 5 /* Uses block_info.aiop
|
|
| 300 | + or with BlockInfoForceNonClosure
|
|
| 301 | + uses .fd or .async_reqID */
|
|
| 302 | +#define BlockedOnWrite 6 /* Uses block_info.aiop
|
|
| 303 | + or with BlockInfoForceNonClosure
|
|
| 304 | + uses .fd or .async_reqID */
|
|
| 305 | +#define BlockedOnDelay 7 /* Uses block_info.timeout
|
|
| 306 | + or with BlockInfoForceNonClosure
|
|
| 277 | 307 | uses .target */
|
| 278 | 308 | |
| 279 | 309 | #define BlockedOnSTM 8 /* Uses block_info.unused */
|
| 280 | 310 | #define BlockedOnCCall 9 /* Uses block_info.unused */
|
| 281 | 311 | #define BlockedOnCCall_Interruptible 10 /* Uses block_info.unused
|
| 282 | - * Same as BlockedOnCCall but permits
|
|
| 283 | - * killing the worker thread */
|
|
| 312 | + * Same as BlockedOnCCall but permits
|
|
| 313 | + * killing the worker thread */
|
|
| 284 | 314 | #define ThreadMigrating 11 /* Uses block_info.unused */
|
| 285 | -#define BlockedOnDoProc 12 /* Uses block_info.async_result
|
|
| 286 | - * used by win32-legacy I/O manager */
|
|
| 315 | +#define BlockedOnDoProc 12 /* Uses block_info.async_reqID */
|
|
| 316 | + |
|
| 317 | +/* Reserved values, not values that why_blocked currently use. They
|
|
| 318 | + * are used in primop stg_threadStatuszh and must not overlap with
|
|
| 319 | + * other why_blocked status values. They could be changed, if the
|
|
| 320 | + * threadStatus in ghc-internal is updated too.
|
|
| 321 | + */
|
|
| 322 | +#define BlockedThreadComplete 16
|
|
| 323 | +#define BlockedThreadKilled 17
|
|
| 287 | 324 | |
| 288 | 325 | /* Next available non-closure why_blocked tag numbers are: 13,14,15
|
| 289 | 326 | * For more closure tag numbers, shift up all the non-closure ones
|
| ... | ... | @@ -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
|
| ... | ... | @@ -98,7 +107,21 @@ typedef union { |
| 98 | 107 | * have the reason in the why_blocked field of the TSO, and some
|
| 99 | 108 | * further info (such as the closure the thread is blocked on, or the
|
| 100 | 109 | * file descriptor if the thread is waiting on I/O) in the block_info
|
| 101 | - * 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*
|
|
| 102 | 125 | */
|
| 103 | 126 | |
| 104 | 127 | typedef struct StgTSO_ {
|
| ... | ... | @@ -148,11 +171,7 @@ typedef struct StgTSO_ { |
| 148 | 171 | StgWord16 what_next; // Values defined in Constants.h
|
| 149 | 172 | StgWord32 flags; // Values defined in Constants.h
|
| 150 | 173 | |
| 151 | - /*
|
|
| 152 | - * N.B. why_blocked only has a handful of values but must be atomically
|
|
| 153 | - * updated; the smallest width which AArch64 supports for is 32-bits.
|
|
| 154 | - */
|
|
| 155 | - StgWord32 why_blocked; // Values defined in Constants.h
|
|
| 174 | + StgThreadWhyBlocked why_blocked; // Values defined in Constants.h
|
|
| 156 | 175 | StgTSOBlockInfo block_info; // Barrier provided by why_blocked
|
| 157 | 176 | StgThreadID id;
|
| 158 | 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 | |
| ... | ... | @@ -299,10 +300,9 @@ static void notifyIOCompletion(CapIOManager *iomgr, StgAsyncIOOp *aiop) |
| 299 | 300 | * cap because the tso was not on the run queue of any cap and
|
| 300 | 301 | * so is not subject to thread migration.
|
| 301 | 302 | */
|
| 302 | - StgTSO *tso = aiop->notify.tso;
|
|
| 303 | - tso->why_blocked = NotBlocked;
|
|
| 304 | - tso->_link = END_TSO_QUEUE;
|
|
| 303 | + StgTSO *tso = aiop->notify.tso;
|
|
| 305 | 304 | pushOnRunQueue(iomgr->cap, tso);
|
| 305 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 306 | 306 | }
|
| 307 | 307 | /* For the TSO case, the aiop was only reachable from the TSO
|
| 308 | 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 | |
| ... | ... | @@ -116,10 +116,9 @@ static void notifyTimeoutCompletion(CapIOManager *iomgr, StgTimeout *timeout) |
| 116 | 116 | switch (timeout->notify_type) {
|
| 117 | 117 | case NotifyTSO:
|
| 118 | 118 | {
|
| 119 | - StgTSO *tso = timeout->notify.tso;
|
|
| 120 | - tso->why_blocked = NotBlocked;
|
|
| 121 | - tso->_link = END_TSO_QUEUE;
|
|
| 119 | + StgTSO *tso = timeout->notify.tso;
|
|
| 122 | 120 | pushOnRunQueue(iomgr->cap, tso);
|
| 121 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 123 | 122 | break;
|
| 124 | 123 | }
|
| 125 | 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,21 +138,9 @@ 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
|
| ... | ... | @@ -160,7 +148,6 @@ scavengeTSO (StgTSO *tso) |
| 160 | 148 | // BlockedOnRead/BlockedOnWrite) or a time value (BlockedOnDelay)
|
| 161 | 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;
|