Rodrigo Mesquita pushed to branch wip/romes/27729 at Glasgow Haskell Compiler / GHC
Commits:
-
9dfc886c
by Rodrigo Mesquita at 2026-08-25T10:36:32+01:00
4 changed files:
Changes:
| ... | ... | @@ -88,6 +88,7 @@ void sendCloneStackMessage(StgTSO *tso, HsStablePtr mvar) { |
| 88 | 88 | void handleCloneStackMessage(Capability *cap, MessageCloneStack *msg){
|
| 89 | 89 | // We must check that the current owner of the thread we want to clone the stack for
|
| 90 | 90 | // is still this capability.
|
| 91 | + // See Note [TSO owner may change in between Msg being sent and received]
|
|
| 91 | 92 | Capability *owner = RELAXED_LOAD(&msg->tso->cap);
|
| 92 | 93 | if (owner != cap) {
|
| 93 | 94 | // The target TSO may have migrated after the message was queued on the old
|
| ... | ... | @@ -66,6 +66,62 @@ void sendMessage(Capability *from_cap, Capability *to_cap, Message *msg) |
| 66 | 66 | Handle a message
|
| 67 | 67 | ------------------------------------------------------------------------- */
|
| 68 | 68 | |
| 69 | +/*
|
|
| 70 | +Note [TSO owner may change in between Msg being sent and received]
|
|
| 71 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 72 | +When a message is sent from Capability (C1) to a target TSO (T2) (e.g.
|
|
| 73 | +MessageUpdTSOFlag, MessageCloneStack, ...), it is queued on the TSO's owner
|
|
| 74 | +Capability (C3) inbox (inboxes are owned by Capabilities, not TSOs).
|
|
| 75 | + |
|
| 76 | +At a later point, the Capability (C3) will process its inbox. Upon receiving
|
|
| 77 | +the message meant for a specific TSO (T2), it must first always check that the
|
|
| 78 | +TSO's owner is *still* itself (C3).
|
|
| 79 | + |
|
| 80 | +The target TSO (T2) may have migrated after the message was queued on its old
|
|
| 81 | +capability (C3). In that case we must forward the request to the new owner
|
|
| 82 | +(say, C4); otherwise the Capability C3 could be modifying a TSO it no longer
|
|
| 83 | +owns, racing with its actual owner mutating it, since it is no longer the owner.
|
|
| 84 | + |
|
| 85 | +The message meant for a TSO should only be executed when the receiving
|
|
| 86 | +Capability is still the owner of that TSO. Otherwise, it must be forwarded to
|
|
| 87 | +the new owner.
|
|
| 88 | + |
|
| 89 | +The general pattern is one where there's a top-level function which assumes it
|
|
| 90 | +can be called by capabilities other than the TSO's owner. The function checks
|
|
| 91 | +whether the current capability is the TSO owner. If yes, execute the action. If
|
|
| 92 | +not, then it sends a message to the current TSO's owner. On receiving the
|
|
| 93 | +message, the new capability will just call that top-level function, which will
|
|
| 94 | +ensure the message is forwarded again if the TSO owner changed.
|
|
| 95 | +It will look something like:
|
|
| 96 | + |
|
| 97 | + runMyMsg(Capability *from, StgTSO *target, ...) {
|
|
| 98 | + |
|
| 99 | +#if defined(THREADED_RTS)
|
|
| 100 | + Capability *owner = RELAXED_LOAD(&target->cap)
|
|
| 101 | + if (owner != from) {
|
|
| 102 | + MessageMyMsg* msg = ...
|
|
| 103 | + sendMessage(cap, owner, msg)
|
|
| 104 | + return
|
|
| 105 | + }
|
|
| 106 | +#endif
|
|
| 107 | + |
|
| 108 | + actuallyDoTheWork(...)
|
|
| 109 | + }
|
|
| 110 | + |
|
| 111 | + executeMessage(...) {
|
|
| 112 | + |
|
| 113 | + if (i == &stg_MY_MSG_info) {
|
|
| 114 | + |
|
| 115 | + MessageMyMsg* msg = (MessageMyMsg*) m
|
|
| 116 | + runMyMsg(cap, m->tso, ...)
|
|
| 117 | + |
|
| 118 | + }
|
|
| 119 | + }
|
|
| 120 | + |
|
| 121 | +See example `updThreadFlag` and `executeMessage`'s `stg_MSG_UPD_TSO_FLAG_info`,
|
|
| 122 | +or `tryWakeUpThread` and `stg_MSG_TRY_WAKEUP_info` for two live examples.
|
|
| 123 | +*/
|
|
| 124 | + |
|
| 69 | 125 | #if defined(THREADED_RTS)
|
| 70 | 126 | |
| 71 | 127 | void
|
| ... | ... | @@ -142,13 +198,9 @@ loop: |
| 142 | 198 | }
|
| 143 | 199 | else if(i == &stg_MSG_UPD_TSO_FLAG_info){
|
| 144 | 200 | MessageUpdTSOFlag *u = (MessageUpdTSOFlag*) m;
|
| 145 | - if (u->set) {
|
|
| 146 | - u->tso->flags |= u->flag;
|
|
| 147 | - }
|
|
| 148 | - else {
|
|
| 149 | - u->tso->flags &= ~u->flag;
|
|
| 150 | - }
|
|
| 151 | - return;
|
|
| 201 | + |
|
| 202 | + StgTSO *tso = RELAXED_LOAD(&u->tso);
|
|
| 203 | + updThreadFlag(cap, tso, u->flag, u->set);
|
|
| 152 | 204 | }
|
| 153 | 205 | else
|
| 154 | 206 | {
|
| ... | ... | @@ -379,23 +379,25 @@ migrateThread (Capability *from, StgTSO *tso, Capability *to) |
| 379 | 379 | sets or unsets a flag in a given TSO
|
| 380 | 380 | ------------------------------------------------------------------------- */
|
| 381 | 381 | |
| 382 | -static void
|
|
| 383 | -updThreadFlag(Capability *from, StgTSO *tso, StgWord32 flag, StgBool set);
|
|
| 384 | - |
|
| 385 | 382 | void setThreadFlag(Capability *from, StgTSO *tso, StgWord32 flag)
|
| 386 | 383 | {
|
| 387 | - updThreadFlag(from, tso, flag, 1);
|
|
| 384 | + updThreadFlag(from, tso, flag, true);
|
|
| 388 | 385 | }
|
| 389 | 386 | |
| 390 | 387 | void unsetThreadFlag(Capability *from, StgTSO *tso, StgWord32 flag)
|
| 391 | 388 | {
|
| 392 | - updThreadFlag(from, tso, flag, 0);
|
|
| 389 | + updThreadFlag(from, tso, flag, false);
|
|
| 393 | 390 | }
|
| 394 | 391 | |
| 395 | -static void
|
|
| 396 | -updThreadFlag(Capability *from, StgTSO *tso, StgWord32 flag, StgBool set /* true=set, false=unset */)
|
|
| 392 | +void
|
|
| 393 | +updThreadFlag(Capability *from USED_IF_THREADS, StgTSO *tso, StgWord32 flag, StgBool set /* true=set, false=unset */)
|
|
| 397 | 394 | {
|
| 398 | 395 | #if defined(THREADED_RTS)
|
| 396 | + // If we're the current owner of the thread we want to modify, do it.
|
|
| 397 | + // Otherwise, we must forward the message to the actual owner.
|
|
| 398 | + // When executing the upd message, we check again that we're still the TSO
|
|
| 399 | + // owner (which may have changed since the message was queued on this cap.)
|
|
| 400 | + // See Note [TSO owner may change in between Msg being sent and received]
|
|
| 399 | 401 | Capability *tso_owner = RELAXED_LOAD(&tso->cap);
|
| 400 | 402 | if (from != tso_owner) {
|
| 401 | 403 | MessageUpdTSOFlag *msg;
|
| ... | ... | @@ -407,8 +409,6 @@ updThreadFlag(Capability *from, StgTSO *tso, StgWord32 flag, StgBool set /* true |
| 407 | 409 | sendMessage(from, tso_owner, (Message*)msg);
|
| 408 | 410 | return;
|
| 409 | 411 | }
|
| 410 | -#else
|
|
| 411 | - (void)from; // unused in non-threaded case
|
|
| 412 | 412 | #endif
|
| 413 | 413 | |
| 414 | 414 | if (set) {
|
| ... | ... | @@ -21,6 +21,7 @@ void migrateThread (Capability *from, StgTSO *tso, Capability *to); |
| 21 | 21 | |
| 22 | 22 | void setThreadFlag (Capability *from, StgTSO *tso, StgWord32 flag);
|
| 23 | 23 | void unsetThreadFlag (Capability *from, StgTSO *tso, StgWord32 flag);
|
| 24 | +void updThreadFlag (Capability *from, StgTSO *tso, StgWord32 flag, StgBool set);
|
|
| 24 | 25 | |
| 25 | 26 | // Wakes up a thread on a Capability (probably a different Capability
|
| 26 | 27 | // from the one held by the current Task).
|