Duncan Coutts pushed to branch wip/dcoutts/issue-26717 at Glasgow Haskell Compiler / GHC
Commits:
-
e4e28ed8
by Duncan Coutts at 2026-05-05T23:30:53+01:00
-
ba23ba28
by Duncan Coutts at 2026-05-05T23:33:22+01:00
-
18cc92f2
by Duncan Coutts at 2026-05-05T23:33:22+01:00
-
61518e26
by Duncan Coutts at 2026-05-05T23:33:22+01:00
-
0f6dc851
by Duncan Coutts at 2026-05-05T23:33:22+01:00
-
bb625ec1
by Duncan Coutts at 2026-05-05T23:33:22+01:00
-
620b8462
by Duncan Coutts at 2026-05-05T23:33:22+01:00
-
7494dedc
by Duncan Coutts at 2026-05-05T23:33:22+01:00
-
3d9eb500
by Duncan Coutts at 2026-05-05T23:33:22+01:00
-
e016e3c8
by Duncan Coutts at 2026-05-05T23:33:22+01:00
-
f8830056
by Duncan Coutts at 2026-05-05T23:33:22+01:00
-
ff1abaa4
by Duncan Coutts at 2026-05-05T23:33:22+01:00
-
917d2087
by Duncan Coutts at 2026-05-05T23:33:22+01:00
-
90b94dbd
by Duncan Coutts at 2026-05-05T23:33:22+01:00
-
8d928079
by Duncan Coutts at 2026-05-05T23:33:22+01:00
-
df8b49f7
by Duncan Coutts at 2026-05-05T23:33:22+01:00
-
7c6995b8
by Duncan Coutts at 2026-05-05T23:33:22+01:00
-
011fdc27
by Duncan Coutts at 2026-05-05T23:33:22+01:00
25 changed files:
- + changelog.d/T26716
- docs/users_guide/eventlog-formats.rst
- libraries/ghc-internal/src/GHC/Internal/Conc/Sync.hs
- rts/IOManager.c
- rts/IOManager.h
- rts/PrimOps.cmm
- rts/RaiseAsync.c
- rts/RaiseAsync.h
- rts/STM.c
- rts/Schedule.c
- rts/Threads.c
- rts/Trace.c
- rts/Trace.h
- rts/TraverseHeap.c
- rts/include/rts/Constants.h
- rts/include/rts/EventLogFormat.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 | +} |
| ... | ... | @@ -211,7 +211,7 @@ Thread and scheduling events |
| 211 | 211 | * 1: HeapOverflow
|
| 212 | 212 | * 2: StackOverflow
|
| 213 | 213 | * 3: ThreadYielding
|
| 214 | - * 4: ThreadBlocked
|
|
| 214 | + * 4: unused
|
|
| 215 | 215 | * 5: ThreadFinished
|
| 216 | 216 | * 6: ForeignCall
|
| 217 | 217 | * 7: BlockedOnMVar
|
| ... | ... | @@ -237,6 +237,10 @@ Thread and scheduling events |
| 237 | 237 | these eventlog stop thread codes are now independent. We are nevertheless
|
| 238 | 238 | left with some historical warts:
|
| 239 | 239 | |
| 240 | + * 4: this was previously documented as `ThreadBlocked`. This code was used
|
|
| 241 | + in GHC 6.12.x (the first GHC version with eventlog support) and in 7.0.x.
|
|
| 242 | + From GHC 7.2 onwards this code is no longer used. Whenever a thread
|
|
| 243 | + blocks, a more detailed `BlockedOn*` code is used instead.
|
|
| 240 | 244 | * 14,15: these correspond to GHC internal status codes `BlockedOnGA` and
|
| 241 | 245 | `BlockedOnGA_NoSend` that are no longer used (and may never have been
|
| 242 | 246 | used by any released version of GHC).
|
| ... | ... | @@ -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
|
| ... | ... | @@ -611,41 +611,6 @@ void markCapabilityIOManager(evac_fn evac, void *user, Capability *cap) |
| 611 | 611 | }
|
| 612 | 612 | |
| 613 | 613 | |
| 614 | -void scavengeTSOIOManager(StgTSO *tso)
|
|
| 615 | -{
|
|
| 616 | - switch (iomgr_type) {
|
|
| 617 | - |
|
| 618 | - /* case IO_MANAGER_SELECT:
|
|
| 619 | - * BlockedOn{Read,Write} uses block_info.fd
|
|
| 620 | - * BlockedOnDelay uses block_info.target
|
|
| 621 | - * both of these are not GC pointers, so there is nothing to do.
|
|
| 622 | - */
|
|
| 623 | - |
|
| 624 | -#if defined(IOMGR_ENABLED_POLL)
|
|
| 625 | - case IO_MANAGER_POLL:
|
|
| 626 | - /* BlockedOn{Read,Write} uses block_info.aiop
|
|
| 627 | - * BlockedOnDelay uses block_info.timeout
|
|
| 628 | - * both of these are heap allocated, so we can do the same in all
|
|
| 629 | - * cases, which is why we can use the generic block_info.closure.
|
|
| 630 | - */
|
|
| 631 | - evacuate(&tso->block_info.closure);
|
|
| 632 | - break;
|
|
| 633 | -#endif
|
|
| 634 | - |
|
| 635 | - /* case IO_MANAGER_WIN32_LEGACY:
|
|
| 636 | - * BlockedOn{Read,Write,DoProc} uses block_info.async_reqID
|
|
| 637 | - * which is a plain integer, so nothing to scavenge.
|
|
| 638 | - */
|
|
| 639 | - |
|
| 640 | - default:
|
|
| 641 | - /* All the other I/O managers do not use I/O-related why_blocked
|
|
| 642 | - * reasons, so there are no cases to handle.
|
|
| 643 | - */
|
|
| 644 | - break;
|
|
| 645 | - }
|
|
| 646 | -}
|
|
| 647 | - |
|
| 648 | - |
|
| 649 | 614 | /* Declared in rts/IOInterface.h. Used only by the MIO threaded I/O manager on
|
| 650 | 615 | * Unix platforms.
|
| 651 | 616 | */
|
| ... | ... | @@ -807,16 +772,17 @@ bool syncIOWaitReady(Capability *cap, |
| 807 | 772 | #if defined(IOMGR_ENABLED_SELECT)
|
| 808 | 773 | case IO_MANAGER_SELECT:
|
| 809 | 774 | {
|
| 810 | - StgWord why_blocked = rw == IORead ? BlockedOnRead : BlockedOnWrite;
|
|
| 775 | + StgThreadWhyBlocked why_blocked = (rw == IORead ? BlockedOnRead
|
|
| 776 | + : BlockedOnWrite)
|
|
| 777 | + | BlockInfoForceNonClosure;
|
|
| 811 | 778 | tso->block_info.fd = fd;
|
| 812 | - RELEASE_STORE(&tso->why_blocked, why_blocked);
|
|
| 813 | 779 | appendToIOBlockedQueue(cap, tso);
|
| 780 | + RELEASE_STORE(&tso->why_blocked, why_blocked);
|
|
| 814 | 781 | return true;
|
| 815 | 782 | }
|
| 816 | 783 | #endif
|
| 817 | 784 | #if defined(IOMGR_ENABLED_POLL)
|
| 818 | 785 | case IO_MANAGER_POLL:
|
| 819 | - ASSERT(tso->why_blocked == NotBlocked);
|
|
| 820 | 786 | return syncIOWaitReadyPoll(cap, tso, rw, fd);
|
| 821 | 787 | #endif
|
| 822 | 788 | default:
|
| ... | ... | @@ -868,8 +834,8 @@ bool syncDelay(Capability *cap, StgTSO *tso, HsInt us_delay) |
| 868 | 834 | {
|
| 869 | 835 | LowResTime target = getDelayTarget(us_delay);
|
| 870 | 836 | tso->block_info.target = target;
|
| 871 | - RELEASE_STORE(&tso->why_blocked, BlockedOnDelay);
|
|
| 872 | 837 | insertIntoSleepingQueue(cap, tso, target);
|
| 838 | + RELEASE_STORE(&tso->why_blocked, BlockedOnDelay | BlockInfoForceNonClosure);
|
|
| 873 | 839 | return true;
|
| 874 | 840 | }
|
| 875 | 841 | #endif
|
| ... | ... | @@ -889,8 +855,8 @@ bool syncDelay(Capability *cap, StgTSO *tso, HsInt us_delay) |
| 889 | 855 | * simplifies matters, so set the status to OnDoProc and put the
|
| 890 | 856 | * delayed thread on the blocked_queue.
|
| 891 | 857 | */
|
| 892 | - RELEASE_STORE(&tso->why_blocked, BlockedOnDoProc);
|
|
| 893 | 858 | appendToIOBlockedQueue(cap, tso);
|
| 859 | + RELEASE_STORE(&tso->why_blocked, BlockedOnDoProc);
|
|
| 894 | 860 | return true;
|
| 895 | 861 | }
|
| 896 | 862 | #endif
|
| ... | ... | @@ -906,6 +872,7 @@ void syncDelayCancel(Capability *cap, StgTSO *tso) |
| 906 | 872 | switch (iomgr_type) {
|
| 907 | 873 | #if defined(IOMGR_ENABLED_SELECT)
|
| 908 | 874 | case IO_MANAGER_SELECT:
|
| 875 | + ASSERT(tso->why_blocked == (BlockedOnDelay | BlockInfoForceNonClosure));
|
|
| 909 | 876 | removeThreadFromQueue(cap, &cap->iomgr->sleeping_queue, tso);
|
| 910 | 877 | break;
|
| 911 | 878 | #endif
|
| ... | ... | @@ -291,11 +291,6 @@ void wakeupIOManager(void); |
| 291 | 291 | void markCapabilityIOManager(evac_fn evac, void *user, Capability *cap);
|
| 292 | 292 | |
| 293 | 293 | |
| 294 | -/* GC hook: scavenge I/O related tso->block_info. Used by scavengeTSO.
|
|
| 295 | - */
|
|
| 296 | -void scavengeTSOIOManager(StgTSO *tso);
|
|
| 297 | - |
|
| 298 | - |
|
| 299 | 294 | /* Several code paths are almost identical between read and write paths. In
|
| 300 | 295 | * such cases we use a shared code path with an enum to say which we're doing.
|
| 301 | 296 | */
|
| ... | ... | @@ -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 | |
| ... | ... | @@ -2313,7 +2313,8 @@ stg_asyncReadzh ( W_ fd, W_ is_sock, W_ len, W_ buf ) |
| 2313 | 2313 | StgTSO_block_info(CurrentTSO) = reqID;
|
| 2314 | 2314 | |
| 2315 | 2315 | ASSERT(StgTSO_why_blocked(CurrentTSO) == NotBlocked::I32);
|
| 2316 | - %release StgTSO_why_blocked(CurrentTSO) = BlockedOnRead::I32;
|
|
| 2316 | + %release StgTSO_why_blocked(CurrentTSO) = BlockedOnRead::I32
|
|
| 2317 | + | BlockInfoForceNonClosure::I32;
|
|
| 2317 | 2318 | |
| 2318 | 2319 | ccall appendToIOBlockedQueue(MyCapability() "ptr", CurrentTSO "ptr");
|
| 2319 | 2320 | jump stg_block_async();
|
| ... | ... | @@ -2332,7 +2333,8 @@ stg_asyncWritezh ( W_ fd, W_ is_sock, W_ len, W_ buf ) |
| 2332 | 2333 | StgTSO_block_info(CurrentTSO) = reqID;
|
| 2333 | 2334 | |
| 2334 | 2335 | ASSERT(StgTSO_why_blocked(CurrentTSO) == NotBlocked::I32);
|
| 2335 | - %release StgTSO_why_blocked(CurrentTSO) = BlockedOnWrite::I32;
|
|
| 2336 | + %release StgTSO_why_blocked(CurrentTSO) = BlockedOnWrite::I32
|
|
| 2337 | + | BlockInfoForceNonClosure::I32;
|
|
| 2336 | 2338 | |
| 2337 | 2339 | ccall appendToIOBlockedQueue(MyCapability() "ptr", CurrentTSO "ptr");
|
| 2338 | 2340 | jump stg_block_async();
|
| ... | ... | @@ -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 | }
|
| ... | ... | @@ -174,6 +174,9 @@ static void deleteAllThreads (void); |
| 174 | 174 | static void deleteThread_(StgTSO *tso);
|
| 175 | 175 | #endif
|
| 176 | 176 | |
| 177 | +static inline EventThreadStatus eventlogThreadStatus(StgThreadReturnCode ret_code);
|
|
| 178 | +static inline EventThreadStatus eventlogThreadStatusBlocked(StgThreadWhyBlocked why_blocked);
|
|
| 179 | + |
|
| 177 | 180 | /* ---------------------------------------------------------------------------
|
| 178 | 181 | Main scheduling loop.
|
| 179 | 182 | |
| ... | ... | @@ -522,20 +525,21 @@ run_thread: |
| 522 | 525 | #endif
|
| 523 | 526 | |
| 524 | 527 | if (ret == ThreadBlocked) {
|
| 525 | - uint16_t why_blocked = ACQUIRE_LOAD(&t->why_blocked);
|
|
| 528 | + StgThreadWhyBlocked why_blocked = ACQUIRE_LOAD(&t->why_blocked);
|
|
| 529 | + EventThreadStatus status = eventlogThreadStatusBlocked(why_blocked);
|
|
| 530 | + StgWord32 status_detail = 0;
|
|
| 526 | 531 | if (why_blocked == BlockedOnBlackHole) {
|
| 527 | 532 | StgTSO *owner = blackHoleOwner(t->block_info.bh->bh);
|
| 528 | - traceEventStopThread(cap, t, t->why_blocked + 6,
|
|
| 529 | - owner != NULL ? owner->id : 0);
|
|
| 530 | - } else {
|
|
| 531 | - traceEventStopThread(cap, t, t->why_blocked + 6, 0);
|
|
| 533 | + status_detail = owner != NULL ? owner->id : 0;
|
|
| 532 | 534 | }
|
| 535 | + traceEventStopThread(cap, t, status, status_detail);
|
|
| 533 | 536 | } else {
|
| 537 | + EventThreadStatus status = eventlogThreadStatus(ret);
|
|
| 538 | + StgWord32 status_detail = 0;
|
|
| 534 | 539 | if (ret == StackOverflow) {
|
| 535 | - traceEventStopThread(cap, t, ret, t->tot_stack_size);
|
|
| 536 | - } else {
|
|
| 537 | - traceEventStopThread(cap, t, ret, 0);
|
|
| 540 | + status_detail = t->tot_stack_size;
|
|
| 538 | 541 | }
|
| 542 | + traceEventStopThread(cap, t, status, status_detail);
|
|
| 539 | 543 | }
|
| 540 | 544 | |
| 541 | 545 | ASSERT_FULL_CAPABILITY_INVARIANTS(cap,task);
|
| ... | ... | @@ -1096,7 +1100,7 @@ schedulePostRunThread (Capability *cap, StgTSO *t) |
| 1096 | 1100 | //
|
| 1097 | 1101 | // and a is never equal to b given a consistent view of memory.
|
| 1098 | 1102 | //
|
| 1099 | - if (t -> trec != NO_TREC && t -> why_blocked == NotBlocked) {
|
|
| 1103 | + if (t -> trec != NO_TREC && RELAXED_LOAD(&t->why_blocked) == NotBlocked) {
|
|
| 1100 | 1104 | if (!stmValidateNestOfTransactions(cap, t -> trec, true)) {
|
| 1101 | 1105 | debugTrace(DEBUG_sched | DEBUG_stm,
|
| 1102 | 1106 | "trec %p found wasting its time", t);
|
| ... | ... | @@ -2512,17 +2516,18 @@ suspendThread (StgRegTable *reg, bool interruptible) |
| 2512 | 2516 | task = cap->running_task;
|
| 2513 | 2517 | tso = cap->r.rCurrentTSO;
|
| 2514 | 2518 | |
| 2515 | - traceEventStopThread(cap, tso, THREAD_SUSPENDED_FOREIGN_CALL, 0);
|
|
| 2519 | + traceEventStopThread(cap, tso, STOP_THREAD_ForeignCall, 0);
|
|
| 2516 | 2520 | |
| 2517 | 2521 | // XXX this might not be necessary --SDM
|
| 2518 | 2522 | RELAXED_STORE(&tso->what_next, ThreadRunGHC);
|
| 2519 | 2523 | |
| 2520 | 2524 | threadPaused(cap,tso);
|
| 2521 | 2525 | |
| 2526 | + tso->block_info.unused = END_TSO_QUEUE;
|
|
| 2522 | 2527 | if (interruptible) {
|
| 2523 | - tso->why_blocked = BlockedOnCCall_Interruptible;
|
|
| 2528 | + RELEASE_STORE(&tso->why_blocked, BlockedOnCCall_Interruptible);
|
|
| 2524 | 2529 | } else {
|
| 2525 | - tso->why_blocked = BlockedOnCCall;
|
|
| 2530 | + RELEASE_STORE(&tso->why_blocked, BlockedOnCCall);
|
|
| 2526 | 2531 | }
|
| 2527 | 2532 | |
| 2528 | 2533 | // Hand back capability
|
| ... | ... | @@ -2580,16 +2585,25 @@ resumeThread (void *task_) |
| 2580 | 2585 | tso = incall->suspended_tso;
|
| 2581 | 2586 | incall->suspended_tso = NULL;
|
| 2582 | 2587 | incall->suspended_cap = NULL;
|
| 2588 | + |
|
| 2589 | + // we set why_blocked previously in suspendThread
|
|
| 2590 | + ASSERT(tso->why_blocked == BlockedOnCCall ||
|
|
| 2591 | + tso->why_blocked == BlockedOnCCall_Interruptible);
|
|
| 2592 | + |
|
| 2583 | 2593 | // we will modify tso->_link
|
| 2584 | 2594 | IF_NONMOVING_WRITE_BARRIER_ENABLED {
|
| 2585 | 2595 | updateRemembSetPushClosure(cap, (StgClosure *)tso->_link);
|
| 2586 | 2596 | }
|
| 2587 | 2597 | tso->_link = END_TSO_QUEUE;
|
| 2598 | + // but no need to modify tso->block_info.prev as coincidentally
|
|
| 2599 | + // it has the value we want already (since in suspendThread we set
|
|
| 2600 | + // tso->block_info.unused to END_TSO_QUEUE for BlockedOnCCall).
|
|
| 2601 | + ASSERT(tso->block_info.prev == END_TSO_QUEUE);
|
|
| 2588 | 2602 | |
| 2589 | 2603 | traceEventRunThread(cap, tso);
|
| 2590 | 2604 | |
| 2591 | 2605 | /* Reset blocking status */
|
| 2592 | - tso->why_blocked = NotBlocked;
|
|
| 2606 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 2593 | 2607 | |
| 2594 | 2608 | if ((tso->flags & TSO_BLOCKEX) == 0) {
|
| 2595 | 2609 | // avoid locking the TSO if we don't have to
|
| ... | ... | @@ -2941,8 +2955,9 @@ deleteThread (StgTSO *tso) |
| 2941 | 2955 | // The TSO must be on the run queue of the Capability we own, or
|
| 2942 | 2956 | // we must own all Capabilities.
|
| 2943 | 2957 | |
| 2944 | - if (tso->why_blocked != BlockedOnCCall &&
|
|
| 2945 | - tso->why_blocked != BlockedOnCCall_Interruptible) {
|
|
| 2958 | + StgThreadWhyBlocked why_blocked = RELAXED_LOAD(&tso->why_blocked);
|
|
| 2959 | + if (why_blocked != BlockedOnCCall &&
|
|
| 2960 | + why_blocked != BlockedOnCCall_Interruptible) {
|
|
| 2946 | 2961 | throwToSingleThreaded(tso->cap,tso,NULL);
|
| 2947 | 2962 | }
|
| 2948 | 2963 | }
|
| ... | ... | @@ -2953,10 +2968,12 @@ deleteThread_(StgTSO *tso) |
| 2953 | 2968 | { // for forkProcess only:
|
| 2954 | 2969 | // like deleteThread(), but we delete threads in foreign calls, too.
|
| 2955 | 2970 | |
| 2956 | - if (tso->why_blocked == BlockedOnCCall ||
|
|
| 2957 | - tso->why_blocked == BlockedOnCCall_Interruptible) {
|
|
| 2971 | + StgThreadWhyBlocked why_blocked = RELAXED_LOAD(&tso->why_blocked);
|
|
| 2972 | + if (why_blocked == BlockedOnCCall ||
|
|
| 2973 | + why_blocked == BlockedOnCCall_Interruptible) {
|
|
| 2958 | 2974 | tso->what_next = ThreadKilled;
|
| 2959 | 2975 | appendToRunQueue(tso->cap, tso);
|
| 2976 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 2960 | 2977 | } else {
|
| 2961 | 2978 | deleteThread(tso);
|
| 2962 | 2979 | }
|
| ... | ... | @@ -3307,7 +3324,7 @@ resurrectThreads (StgTSO *threads) |
| 3307 | 3324 | // Wake up the thread on the Capability it was last on
|
| 3308 | 3325 | cap = tso->cap;
|
| 3309 | 3326 | |
| 3310 | - switch (tso->why_blocked) {
|
|
| 3327 | + switch (UntagWhyBlocked(RELAXED_LOAD(&tso->why_blocked))) {
|
|
| 3311 | 3328 | case BlockedOnMVar:
|
| 3312 | 3329 | case BlockedOnMVarRead:
|
| 3313 | 3330 | /* Called by GC - sched_mutex lock is currently held. */
|
| ... | ... | @@ -3346,3 +3363,40 @@ void setAllocLimitKill(bool shouldKill, bool shouldHook) |
| 3346 | 3363 | allocLimitKill = shouldKill;
|
| 3347 | 3364 | allocLimitRunHook = shouldHook;
|
| 3348 | 3365 | }
|
| 3366 | + |
|
| 3367 | +/* Map from the internal thread return codes and the tso->why_blocked values to
|
|
| 3368 | + * the external eventlog STOP_THREAD status codes. See issue #9003 for what
|
|
| 3369 | + * goes wrong if we do not handle this mapping in an intentional fashion.
|
|
| 3370 | + *
|
|
| 3371 | + * For the internal values see Constants.h
|
|
| 3372 | + * For the external values see rts/include/rts/EventLogFormat.h and
|
|
| 3373 | + * docs/users_guide/eventlog-formats.rst
|
|
| 3374 | + */
|
|
| 3375 | +static const unsigned char thread_stop_code[] = {
|
|
| 3376 | + [HeapOverflow] = STOP_THREAD_HeapOverflow,
|
|
| 3377 | + [StackOverflow] = STOP_THREAD_StackOverflow,
|
|
| 3378 | + [ThreadYielding] = STOP_THREAD_ThreadYielding,
|
|
| 3379 | + [ThreadFinished] = STOP_THREAD_ThreadFinished
|
|
| 3380 | +};
|
|
| 3381 | + |
|
| 3382 | +static const unsigned char thread_blocked_code[] = {
|
|
| 3383 | + [BlockedOnMVar] = STOP_THREAD_BlockedOnMVar,
|
|
| 3384 | + [BlockedOnMVarRead] = STOP_THREAD_BlockedOnMVarRead,
|
|
| 3385 | + [BlockedOnBlackHole] = STOP_THREAD_BlockedOnBlackHole,
|
|
| 3386 | + [BlockedOnRead] = STOP_THREAD_BlockedOnRead,
|
|
| 3387 | + [BlockedOnWrite] = STOP_THREAD_BlockedOnWrite,
|
|
| 3388 | + [BlockedOnDelay] = STOP_THREAD_BlockedOnDelay,
|
|
| 3389 | + [BlockedOnSTM] = STOP_THREAD_BlockedOnSTM,
|
|
| 3390 | + [BlockedOnDoProc] = STOP_THREAD_BlockedOnDoProc,
|
|
| 3391 | + [BlockedOnMsgThrowTo] = STOP_THREAD_BlockedOnMsgThrowTo,
|
|
| 3392 | +};
|
|
| 3393 | + |
|
| 3394 | +static inline EventThreadStatus eventlogThreadStatus(StgThreadReturnCode ret_code)
|
|
| 3395 | +{
|
|
| 3396 | + return thread_stop_code[ret_code];
|
|
| 3397 | +}
|
|
| 3398 | + |
|
| 3399 | +static inline EventThreadStatus eventlogThreadStatusBlocked(StgThreadWhyBlocked why_blocked)
|
|
| 3400 | +{
|
|
| 3401 | + return thread_blocked_code[UntagWhyBlocked(why_blocked)];
|
|
| 3402 | +} |
| ... | ... | @@ -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 | }
|
| ... | ... | @@ -879,9 +879,9 @@ loop: |
| 879 | 879 | |
| 880 | 880 | // save why_blocked here, because waking up the thread destroys
|
| 881 | 881 | // this information
|
| 882 | - StgWord why_blocked = ACQUIRE_LOAD(&tso->why_blocked);
|
|
| 882 | + StgThreadWhyBlocked why_blocked = ACQUIRE_LOAD(&tso->why_blocked);
|
|
| 883 | 883 | ASSERT(why_blocked == BlockedOnMVarRead || why_blocked == BlockedOnMVar);
|
| 884 | - ASSERT(tso->block_info.closure == (StgClosure*)mvar);
|
|
| 884 | + ASSERT(tso->block_info.mvar == mvar);
|
|
| 885 | 885 | |
| 886 | 886 | // actually perform the takeMVar
|
| 887 | 887 | StgStack* stack = tso->stackobj;
|
| ... | ... | @@ -952,7 +952,7 @@ end: |
| 952 | 952 | void
|
| 953 | 953 | printThreadBlockage(StgTSO *tso)
|
| 954 | 954 | {
|
| 955 | - switch (ACQUIRE_LOAD(&tso->why_blocked)) {
|
|
| 955 | + switch (UntagWhyBlocked(ACQUIRE_LOAD(&tso->why_blocked))) {
|
|
| 956 | 956 | #if defined(mingw32_HOST_OS)
|
| 957 | 957 | case BlockedOnDoProc:
|
| 958 | 958 | debugBelch("is blocked on proc (request: %" FMT_Word ")", tso->block_info.async_reqID);
|
| ... | ... | @@ -971,10 +971,10 @@ printThreadBlockage(StgTSO *tso) |
| 971 | 971 | #endif
|
| 972 | 972 | break;
|
| 973 | 973 | case BlockedOnMVar:
|
| 974 | - debugBelch("is blocked on an MVar @ %p", tso->block_info.closure);
|
|
| 974 | + debugBelch("is blocked on an MVar @ %p", tso->block_info.mvar);
|
|
| 975 | 975 | break;
|
| 976 | 976 | case BlockedOnMVarRead:
|
| 977 | - debugBelch("is blocked on atomic MVar read @ %p", tso->block_info.closure);
|
|
| 977 | + debugBelch("is blocked on atomic MVar read @ %p", tso->block_info.mvar);
|
|
| 978 | 978 | break;
|
| 979 | 979 | break;
|
| 980 | 980 | case BlockedOnBlackHole:
|
| ... | ... | @@ -1049,7 +1049,7 @@ printAllThreads(void) |
| 1049 | 1049 | debugBelch("other threads:\n");
|
| 1050 | 1050 | for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
|
| 1051 | 1051 | for (t = generations[g].threads; t != END_TSO_QUEUE; t = next) {
|
| 1052 | - if (t->why_blocked != NotBlocked) {
|
|
| 1052 | + if (RELAXED_LOAD(&t->why_blocked) != NotBlocked) {
|
|
| 1053 | 1053 | printThreadStatus(t);
|
| 1054 | 1054 | }
|
| 1055 | 1055 | next = t->global_link;
|
| ... | ... | @@ -169,24 +169,20 @@ static void tracePreface (void) |
| 169 | 169 | |
| 170 | 170 | #if defined(DEBUG)
|
| 171 | 171 | static char *thread_stop_reasons[] = {
|
| 172 | - [HeapOverflow] = "heap overflow",
|
|
| 173 | - [StackOverflow] = "stack overflow",
|
|
| 174 | - [ThreadYielding] = "yielding",
|
|
| 175 | - [ThreadBlocked] = "blocked",
|
|
| 176 | - [ThreadFinished] = "finished",
|
|
| 177 | - [THREAD_SUSPENDED_FOREIGN_CALL] = "suspended while making a foreign call",
|
|
| 178 | - [6 + BlockedOnMVar] = "blocked on an MVar",
|
|
| 179 | - [6 + BlockedOnMVarRead] = "blocked on an atomic MVar read",
|
|
| 180 | - [6 + BlockedOnBlackHole] = "blocked on a black hole",
|
|
| 181 | - [6 + BlockedOnRead] = "blocked on a read operation",
|
|
| 182 | - [6 + BlockedOnWrite] = "blocked on a write operation",
|
|
| 183 | - [6 + BlockedOnDelay] = "blocked on a delay operation",
|
|
| 184 | - [6 + BlockedOnSTM] = "blocked on STM",
|
|
| 185 | - [6 + BlockedOnDoProc] = "blocked on asyncDoProc",
|
|
| 186 | - [6 + BlockedOnCCall] = "blocked on a foreign call",
|
|
| 187 | - [6 + BlockedOnCCall_Interruptible] = "blocked on a foreign call (interruptible)",
|
|
| 188 | - [6 + BlockedOnMsgThrowTo] = "blocked on throwTo",
|
|
| 189 | - [6 + ThreadMigrating] = "migrating"
|
|
| 172 | + [STOP_THREAD_HeapOverflow] = "heap overflow",
|
|
| 173 | + [STOP_THREAD_StackOverflow] = "stack overflow",
|
|
| 174 | + [STOP_THREAD_ThreadYielding] = "yielding",
|
|
| 175 | + [STOP_THREAD_ThreadFinished] = "finished",
|
|
| 176 | + [STOP_THREAD_ForeignCall] = "suspended while making a foreign call",
|
|
| 177 | + [STOP_THREAD_BlockedOnMVar] = "blocked on an MVar",
|
|
| 178 | + [STOP_THREAD_BlockedOnMVarRead] = "blocked on an atomic MVar read",
|
|
| 179 | + [STOP_THREAD_BlockedOnBlackHole] = "blocked on a black hole",
|
|
| 180 | + [STOP_THREAD_BlockedOnRead] = "blocked on a read operation",
|
|
| 181 | + [STOP_THREAD_BlockedOnWrite] = "blocked on a write operation",
|
|
| 182 | + [STOP_THREAD_BlockedOnDelay] = "blocked on a delay operation",
|
|
| 183 | + [STOP_THREAD_BlockedOnSTM] = "blocked on STM",
|
|
| 184 | + [STOP_THREAD_BlockedOnDoProc] = "blocked on asyncDoProc",
|
|
| 185 | + [STOP_THREAD_BlockedOnMsgThrowTo] = "blocked on throwTo"
|
|
| 190 | 186 | };
|
| 191 | 187 | #endif
|
| 192 | 188 | |
| ... | ... | @@ -230,10 +226,10 @@ static void traceSchedEvent_stderr (Capability *cap, EventTypeNum tag, |
| 230 | 226 | break;
|
| 231 | 227 | |
| 232 | 228 | case EVENT_STOP_THREAD: // (cap, thread, status)
|
| 233 | - if (info1 == 6 + BlockedOnBlackHole) {
|
|
| 229 | + if (info1 == STOP_THREAD_BlockedOnBlackHole) {
|
|
| 234 | 230 | debugBelch("cap %d: thread %" FMT_Word "[\"%.*s\"]" " stopped (blocked on black hole owned by thread %lu)\n",
|
| 235 | 231 | cap->no, (W_)tso->id, threadLabelLen, threadLabel, (long)info2);
|
| 236 | - } else if (info1 == StackOverflow) {
|
|
| 232 | + } else if (info1 == STOP_THREAD_StackOverflow) {
|
|
| 237 | 233 | debugBelch("cap %d: thread %" FMT_Word "[\"%.*s\"]"
|
| 238 | 234 | " stopped (stack overflow, size %lu)\n",
|
| 239 | 235 | cap->no, (W_)tso->id, threadLabelLen, threadLabel, (long)info2);
|
| ... | ... | @@ -600,7 +600,7 @@ INLINE_HEADER void traceEventRunThread(Capability *cap STG_UNUSED, |
| 600 | 600 | |
| 601 | 601 | INLINE_HEADER void traceEventStopThread(Capability *cap STG_UNUSED,
|
| 602 | 602 | StgTSO *tso STG_UNUSED,
|
| 603 | - StgThreadReturnCode status STG_UNUSED,
|
|
| 603 | + EventThreadStatus status STG_UNUSED,
|
|
| 604 | 604 | StgWord32 info STG_UNUSED)
|
| 605 | 605 | {
|
| 606 | 606 | traceSchedEvent2(cap, EVENT_STOP_THREAD, tso, status, info);
|
| ... | ... | @@ -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,83 @@ |
| 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,DoProc} which are used by in-RTS I/O managers, and the only
|
|
| 275 | + * ones that need to use block_info members that are not a closure are
|
|
| 276 | + * the legacy I/O managers select and win32-legacy. So when these I/O
|
|
| 277 | + * managers are 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: eventlogStopStatus which
|
|
| 292 | + * converts the constants here to the ones used in the eventlog.
|
|
| 293 | + */
|
|
| 294 | +#define NotBlocked 0 /* Uses block_info.prev */
|
|
| 295 | +#define BlockedOnMVar 1 /* Uses block_info.mvar */
|
|
| 296 | +#define BlockedOnMVarRead 2 /* Uses block_info.mvar */
|
|
| 297 | +#define BlockedOnBlackHole 3 /* Uses block_info.bh */
|
|
| 298 | +#define BlockedOnMsgThrowTo 4 /* Uses block_info.throwto */
|
|
| 299 | +#define BlockedOnRead 5 /* Uses block_info.aiop or uses .fd or
|
|
| 300 | + .async_result with BlockInfoForceNonClosure */
|
|
| 301 | +#define BlockedOnWrite 6 /* Uses block_info.aiop or uses .fd or
|
|
| 302 | + .async_result with BlockInfoForceNonClosure */
|
|
| 303 | +#define BlockedOnDelay 7 /* Uses block_info.timeout or
|
|
| 304 | + uses .target with BlockInfoForceNonClosure */
|
|
| 305 | + |
|
| 306 | +#define BlockedOnSTM 8 /* Uses block_info.unused */
|
|
| 307 | +#define BlockedOnCCall 9 /* Uses block_info.unused */
|
|
| 308 | +#define BlockedOnCCall_Interruptible 10 /* Uses block_info.unused
|
|
| 309 | + * Same as BlockedOnCCall but permits
|
|
| 310 | + * killing the worker thread */
|
|
| 311 | +#define ThreadMigrating 11 /* Uses block_info.unused */
|
|
| 312 | +#define BlockedOnDoProc 12 /* Uses block_info.async_result
|
|
| 313 | + * used by win32-legacy I/O manager */
|
|
| 314 | + |
|
| 315 | +/* Reserved values, not values that why_blocked currently use. They
|
|
| 316 | + * are used in primop stg_threadStatuszh and must not overlap with
|
|
| 317 | + * other why_blocked status values. They could be changed, if the
|
|
| 318 | + * threadStatus in ghc-internal is updated too.
|
|
| 319 | + */
|
|
| 320 | +#define BlockedThreadComplete 16
|
|
| 321 | +#define BlockedThreadKilled 17
|
|
| 272 | 322 | |
| 273 | -/* The thread is not on any run queues, but can be woken up
|
|
| 274 | - by tryWakeupThread() */
|
|
| 275 | -#define ThreadMigrating 13
|
|
| 323 | +/* Next available non-closure why_blocked tag numbers are: 13,14,15
|
|
| 324 | + * For more closure tag numbers, shift up all the non-closure ones
|
|
| 325 | + * and adjust the BlockInfoForceNonClosure tag and related macros.
|
|
| 326 | + * If we reach BlockInfoForceNonClosure then shift that up. */
|
|
| 276 | 327 | |
| 277 | -/* Next number is 15. */
|
|
| 278 | 328 | |
| 279 | 329 | /*
|
| 280 | 330 | * These constants are returned to the scheduler by a thread that has
|
| ... | ... | @@ -286,6 +336,7 @@ |
| 286 | 336 | #define ThreadYielding 3
|
| 287 | 337 | #define ThreadBlocked 4
|
| 288 | 338 | #define ThreadFinished 5
|
| 339 | +/* If this is ever extended, also adjust the eventlogStopStatus mapping */
|
|
| 289 | 340 | |
| 290 | 341 | /*
|
| 291 | 342 | * Flags for the tso->flags field.
|
| ... | ... | @@ -26,7 +26,7 @@ |
| 26 | 26 | * - generate the event itself by calling postEvent() somewhere
|
| 27 | 27 | *
|
| 28 | 28 | * - Describe the meaning and encoding of the event in the users guide
|
| 29 | - * (docs/user_guide/eventlog-formats.rst)
|
|
| 29 | + * (docs/users_guide/eventlog-formats.rst)
|
|
| 30 | 30 | *
|
| 31 | 31 | * - In the Haskell code to parse the event log file:
|
| 32 | 32 | * - add types and code to read the new event
|
| ... | ... | @@ -85,27 +85,26 @@ |
| 85 | 85 | |
| 86 | 86 | /*
|
| 87 | 87 | * Status values for EVENT_STOP_THREAD
|
| 88 | - *
|
|
| 89 | - * 1-5 are the StgRun return values (from rts/include/Constants.h):
|
|
| 90 | - *
|
|
| 91 | - * #define HeapOverflow 1
|
|
| 92 | - * #define StackOverflow 2
|
|
| 93 | - * #define ThreadYielding 3
|
|
| 94 | - * #define ThreadBlocked 4
|
|
| 95 | - * #define ThreadFinished 5
|
|
| 96 | - * #define ForeignCall 6
|
|
| 97 | - * #define BlockedOnMVar 7
|
|
| 98 | - * #define BlockedOnBlackHole 8
|
|
| 99 | - * #define BlockedOnRead 9
|
|
| 100 | - * #define BlockedOnWrite 10
|
|
| 101 | - * #define BlockedOnDelay 11
|
|
| 102 | - * #define BlockedOnSTM 12
|
|
| 103 | - * #define BlockedOnDoProc 13
|
|
| 104 | - * #define BlockedOnCCall -- not used (see ForeignCall)
|
|
| 105 | - * #define BlockedOnCCall_NoUnblockExc -- not used (see ForeignCall)
|
|
| 106 | - * #define BlockedOnMsgThrowTo 16
|
|
| 88 | + * type EventThreadStatus
|
|
| 89 | + * Keep values in sync with docs/users_guide/eventlog-formats.rst
|
|
| 107 | 90 | */
|
| 108 | -#define THREAD_SUSPENDED_FOREIGN_CALL 6
|
|
| 91 | +#define STOP_THREAD_HeapOverflow 1
|
|
| 92 | +#define STOP_THREAD_StackOverflow 2
|
|
| 93 | +#define STOP_THREAD_ThreadYielding 3
|
|
| 94 | +/* 4 unused */
|
|
| 95 | +#define STOP_THREAD_ThreadFinished 5
|
|
| 96 | +#define STOP_THREAD_ForeignCall 6
|
|
| 97 | +#define STOP_THREAD_BlockedOnMVar 7
|
|
| 98 | +#define STOP_THREAD_BlockedOnBlackHole 8
|
|
| 99 | +#define STOP_THREAD_BlockedOnRead 9
|
|
| 100 | +#define STOP_THREAD_BlockedOnWrite 10
|
|
| 101 | +#define STOP_THREAD_BlockedOnDelay 11
|
|
| 102 | +#define STOP_THREAD_BlockedOnSTM 12
|
|
| 103 | +#define STOP_THREAD_BlockedOnDoProc 13
|
|
| 104 | +/* 14-17 unused */
|
|
| 105 | +#define STOP_THREAD_BlockedOnMsgThrowTo 18
|
|
| 106 | +/* 19 unused */
|
|
| 107 | +#define STOP_THREAD_BlockedOnMVarRead 20
|
|
| 109 | 108 | |
| 110 | 109 | /*
|
| 111 | 110 | * Capset type values for EVENT_CAPSET_CREATE
|
| ... | ... | @@ -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,20 +46,46 @@ 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 I/O managers */
|
|
| 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)
|
| 82 | + /* case BlockedOnRead, BlockedOnWrite, BlockedOnDoProc:
|
|
| 83 | + * used by the win32-legacy I/O manager */
|
|
| 51 | 84 | StgWord async_reqID;
|
| 52 | 85 | #endif
|
| 86 | + |
|
| 53 | 87 | #if !defined(THREADED_RTS)
|
| 88 | + /* case BlockedOnDelay: used by the select I/O manager */
|
|
| 54 | 89 | StgWord target;
|
| 55 | 90 | // Only for the non-threaded RTS: the target time for a thread
|
| 56 | 91 | // blocked in threadDelay, in units of 1ms. This is a
|
| ... | ... | @@ -71,7 +106,21 @@ typedef union { |
| 71 | 106 | * have the reason in the why_blocked field of the TSO, and some
|
| 72 | 107 | * further info (such as the closure the thread is blocked on, or the
|
| 73 | 108 | * file descriptor if the thread is waiting on I/O) in the block_info
|
| 74 | - * field.
|
|
| 109 | + * field. See Constants.h for the why_blocked values.
|
|
| 110 | + *
|
|
| 111 | + * The why_blocked field must be updated atomically. The protocol for
|
|
| 112 | + * updating block_info and why_blocked fields together is as follows:
|
|
| 113 | + *
|
|
| 114 | + * Writes:
|
|
| 115 | + * - first write block_info (normal non-atomic write)
|
|
| 116 | + * - then write why_blocked with an atomic *store release*
|
|
| 117 | + *
|
|
| 118 | + * Reads:
|
|
| 119 | + * - first read why_blocked with an atomic *load acquire*
|
|
| 120 | + * - then read block_info (normal non-atomic read)
|
|
| 121 | + *
|
|
| 122 | + * Read of only why_blocked without block_info:
|
|
| 123 | + * - read why_blocked with an atomic *relaxed load*
|
|
| 75 | 124 | */
|
| 76 | 125 | |
| 77 | 126 | typedef struct StgTSO_ {
|
| ... | ... | @@ -121,11 +170,7 @@ typedef struct StgTSO_ { |
| 121 | 170 | StgWord16 what_next; // Values defined in Constants.h
|
| 122 | 171 | StgWord32 flags; // Values defined in Constants.h
|
| 123 | 172 | |
| 124 | - /*
|
|
| 125 | - * N.B. why_blocked only has a handful of values but must be atomically
|
|
| 126 | - * updated; the smallest width which AArch64 supports for is 32-bits.
|
|
| 127 | - */
|
|
| 128 | - StgWord32 why_blocked; // Values defined in Constants.h
|
|
| 173 | + StgThreadWhyBlocked why_blocked; // Values defined in Constants.h
|
|
| 129 | 174 | StgTSOBlockInfo block_info; // Barrier provided by why_blocked
|
| 130 | 175 | StgThreadID id;
|
| 131 | 176 | StgWord32 saved_errno;
|
| ... | ... | @@ -146,8 +146,9 @@ bool syncIOWaitReadyPoll(Capability *cap, StgTSO *tso, |
| 146 | 146 | aiop->notify.tso = tso;
|
| 147 | 147 | aiop->notify_type = NotifyTSO;
|
| 148 | 148 | aiop->live = &stg_ASYNCIO_LIVE0_closure;
|
| 149 | - tso->why_blocked = rw == IORead ? BlockedOnRead : BlockedOnWrite;
|
|
| 150 | 149 | tso->block_info.aiop = aiop;
|
| 150 | + RELEASE_STORE(&tso->why_blocked, rw == IORead ? BlockedOnRead
|
|
| 151 | + : BlockedOnWrite);
|
|
| 151 | 152 | return asyncIOWaitReadyPoll(cap, aiop, rw, fd);
|
| 152 | 153 | }
|
| 153 | 154 | |
| ... | ... | @@ -194,7 +195,6 @@ void syncIOCancelPoll(Capability *cap, StgTSO *tso) |
| 194 | 195 | * We don't put the TSO back on the run queue or change the why_blocked
|
| 195 | 196 | * status, as that is done by removeFromQueues (in the throwTo* functions).
|
| 196 | 197 | */
|
| 197 | - tso->block_info.closure = (StgClosure *)END_TSO_QUEUE;
|
|
| 198 | 198 | }
|
| 199 | 199 | |
| 200 | 200 | |
| ... | ... | @@ -259,10 +259,9 @@ static void notifyIOCompletion(Capability *cap, StgAsyncIOOp *aiop) |
| 259 | 259 | * cap because the tso was not on the run queue of any cap and
|
| 260 | 260 | * so is not subject to thread migration.
|
| 261 | 261 | */
|
| 262 | - StgTSO *tso = aiop->notify.tso;
|
|
| 263 | - tso->why_blocked = NotBlocked;
|
|
| 264 | - tso->_link = END_TSO_QUEUE;
|
|
| 262 | + StgTSO *tso = aiop->notify.tso;
|
|
| 265 | 263 | pushOnRunQueue(cap, tso);
|
| 264 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 266 | 265 | }
|
| 267 | 266 | break;
|
| 268 | 267 | }
|
| ... | ... | @@ -105,11 +105,10 @@ static bool wakeUpSleepingThreads (Capability *cap, LowResTime now) |
| 105 | 105 | break;
|
| 106 | 106 | }
|
| 107 | 107 | iomgr->sleeping_queue = tso->_link;
|
| 108 | - RELAXED_STORE(&tso->why_blocked, NotBlocked);
|
|
| 109 | - tso->_link = END_TSO_QUEUE;
|
|
| 110 | 108 | IF_DEBUG(scheduler, debugBelch("Waking up sleeping thread %"
|
| 111 | 109 | FMT_StgThreadID "\n", tso->id));
|
| 112 | 110 | pushOnRunQueue(cap,tso);
|
| 111 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 113 | 112 | flag = true;
|
| 114 | 113 | }
|
| 115 | 114 | return flag;
|
| ... | ... | @@ -268,7 +267,7 @@ awaitCompletedTimeoutsOrIOSelect(Capability *cap, bool wait) |
| 268 | 267 | * So the (int) cast should be removed across the code base once
|
| 269 | 268 | * GHC requires a version of FreeBSD that has that change in it.
|
| 270 | 269 | */
|
| 271 | - switch (ACQUIRE_LOAD(&tso->why_blocked)) {
|
|
| 270 | + switch (UntagWhyBlocked(ACQUIRE_LOAD(&tso->why_blocked))) {
|
|
| 272 | 271 | case BlockedOnRead:
|
| 273 | 272 | {
|
| 274 | 273 | int fd = tso->block_info.fd;
|
| ... | ... | @@ -397,7 +396,7 @@ awaitCompletedTimeoutsOrIOSelect(Capability *cap, bool wait) |
| 397 | 396 | int fd;
|
| 398 | 397 | enum FdState fd_state = RTS_FD_IS_BLOCKING;
|
| 399 | 398 | |
| 400 | - switch (tso->why_blocked) {
|
|
| 399 | + switch (UntagWhyBlocked(ACQUIRE_LOAD(&tso->why_blocked))) {
|
|
| 401 | 400 | case BlockedOnRead:
|
| 402 | 401 | fd = tso->block_info.fd;
|
| 403 | 402 | |
| ... | ... | @@ -436,9 +435,8 @@ awaitCompletedTimeoutsOrIOSelect(Capability *cap, bool wait) |
| 436 | 435 | IF_DEBUG(scheduler,
|
| 437 | 436 | debugBelch("Waking up blocked thread %" FMT_StgThreadID "\n",
|
| 438 | 437 | tso->id));
|
| 439 | - tso->why_blocked = NotBlocked;
|
|
| 440 | - tso->_link = END_TSO_QUEUE;
|
|
| 441 | 438 | pushOnRunQueue(cap,tso);
|
| 439 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 442 | 440 | break;
|
| 443 | 441 | case RTS_FD_IS_BLOCKING:
|
| 444 | 442 | if (prev == NULL)
|
| ... | ... | @@ -48,8 +48,8 @@ bool syncDelayTimeout(Capability *cap, StgTSO *tso, HsInt us_delay) |
| 48 | 48 | initElemTimeoutQueue(timeout, notify, NotifyTSO, 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(&cap->iomgr->timeout_queue, timeout, target);
|
| 55 | 55 | |
| ... | ... | @@ -67,8 +67,6 @@ void syncDelayCancelTimeout(Capability *cap, StgTSO *tso) |
| 67 | 67 | |
| 68 | 68 | deleteTimeoutQueue(&cap->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(cap, (StgClosure *)timeout);
|
| ... | ... | @@ -120,10 +118,10 @@ static void notifyTimeoutCompletion(Capability *cap, StgTimeout *timeout) |
| 120 | 118 | switch (timeout->notify_type) {
|
| 121 | 119 | case NotifyTSO:
|
| 122 | 120 | {
|
| 123 | - StgTSO *tso = timeout->notify.tso;
|
|
| 124 | - tso->why_blocked = NotBlocked;
|
|
| 125 | - tso->_link = END_TSO_QUEUE;
|
|
| 121 | + StgTSO *tso = timeout->notify.tso;
|
|
| 122 | + tso->_link = END_TSO_QUEUE;
|
|
| 126 | 123 | pushOnRunQueue(cap, tso);
|
| 124 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 127 | 125 | break;
|
| 128 | 126 | }
|
| 129 | 127 | 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 | +#if defined(mingw32_HOST_OS) && !defined(THREADED_RTS)
|
|
| 803 | + case BlockedOnDoProc:
|
|
| 804 | +#endif
|
|
| 805 | + ASSERT(!IsBlockInfoClosure(why_blocked));
|
|
| 806 | + ASSERT(tso->block_info.unused == END_TSO_QUEUE);
|
|
| 807 | + break;
|
|
| 808 | + |
|
| 809 | +#if !defined(THREADED_RTS)
|
|
| 810 | + // Only these three can use BlockInfoForceNonClosure
|
|
| 811 | + case BlockedOnRead | BlockInfoForceNonClosure:
|
|
| 812 | + case BlockedOnWrite | BlockInfoForceNonClosure:
|
|
| 813 | + case BlockedOnDelay | BlockInfoForceNonClosure:
|
|
| 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;
|
| ... | ... | @@ -298,7 +298,7 @@ start: |
| 298 | 298 | for(tso = iomgr->blocked_queue_hd; tso != END_TSO_QUEUE;
|
| 299 | 299 | tso = tso->_link) {
|
| 300 | 300 | |
| 301 | - switch(ACQUIRE_LOAD(&tso->why_blocked)) {
|
|
| 301 | + switch (UntagWhyBlocked(ACQUIRE_LOAD(&tso->why_blocked))) {
|
|
| 302 | 302 | case BlockedOnRead:
|
| 303 | 303 | case BlockedOnWrite:
|
| 304 | 304 | case BlockedOnDoProc:
|
| ... | ... | @@ -318,8 +318,6 @@ start: |
| 318 | 318 | }
|
| 319 | 319 | |
| 320 | 320 | // Terminates the run queue + this inner for-loop.
|
| 321 | - tso->_link = END_TSO_QUEUE;
|
|
| 322 | - tso->why_blocked = NotBlocked;
|
|
| 323 | 321 | // For stg_block_async frames (read/write/doProc),
|
| 324 | 322 | // write len and errCode directly to the stack.
|
| 325 | 323 | // For stg_block_noregs frames (delay), nothing
|
| ... | ... | @@ -329,14 +327,14 @@ start: |
| 329 | 327 | tso->stackobj->sp[2] = (W_)errCode;
|
| 330 | 328 | }
|
| 331 | 329 | pushOnRunQueue(&MainCapability, tso);
|
| 330 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 332 | 331 | break;
|
| 333 | 332 | }
|
| 334 | 333 | break;
|
| 335 | - default:
|
|
| 336 | - if (tso->why_blocked != NotBlocked) {
|
|
| 337 | - barf("awaitRequests: odd thread state");
|
|
| 338 | - }
|
|
| 334 | + case NotBlocked:
|
|
| 339 | 335 | break;
|
| 336 | + default:
|
|
| 337 | + barf("awaitRequests: odd thread state");
|
|
| 340 | 338 | }
|
| 341 | 339 | |
| 342 | 340 | prev = tso;
|