Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC
Commits:
-
6e381626
by Duncan Coutts at 2026-07-01T22:29:55+01:00
-
89404ebc
by Duncan Coutts at 2026-07-01T22:29:55+01:00
-
4e60c5f6
by Duncan Coutts at 2026-07-01T22:29:56+01:00
-
2527026f
by Duncan Coutts at 2026-07-01T22:29:56+01:00
-
aa5a03a5
by Duncan Coutts at 2026-07-01T22:29:56+01:00
7 changed files:
- rts/Capability.c
- rts/Capability.h
- rts/Messages.c
- rts/Schedule.c
- rts/Ticker.h
- rts/posix/Ticker.c
- rts/sm/GC.c
Changes:
| ... | ... | @@ -525,13 +525,20 @@ giveCapabilityToTask (Capability *cap USED_IF_DEBUG, Task *task) |
| 525 | 525 | /* ----------------------------------------------------------------------------
|
| 526 | 526 | * releaseCapability
|
| 527 | 527 | *
|
| 528 | - * The current Task (cap->task) releases the Capability. The Capability is
|
|
| 529 | - * marked free, and if there is any work to do, an appropriate Task is woken up.
|
|
| 528 | + * This serves two purposes:
|
|
| 529 | + *
|
|
| 530 | + * 1. The current Task (cap->running_task) releases the Capability.
|
|
| 531 | + * The Capability is marked free, and if there is any work to do, an
|
|
| 532 | + * appropriate Task is woken up.
|
|
| 533 | + *
|
|
| 534 | + * 2. There is no current task (cap->task == NULL), and thus the Capability
|
|
| 535 | + * is idle, and we want to wake up an idle Task to animate the Capability.
|
|
| 536 | + * In this case set always_wakeup. See also prodCapability.
|
|
| 530 | 537 | *
|
| 531 | 538 | * The caller must hold cap->lock and will still hold it after
|
| 532 | 539 | * releaseCapability returns.
|
| 533 | 540 | *
|
| 534 | - * N.B. May need to take all_tasks_mutex.
|
|
| 541 | + * N.B. May need to take all_tasks_mutex, if it needs to start a new task.
|
|
| 535 | 542 | *
|
| 536 | 543 | * ------------------------------------------------------------------------- */
|
| 537 | 544 | |
| ... | ... | @@ -540,12 +547,18 @@ void |
| 540 | 547 | releaseCapability_ (Capability* cap,
|
| 541 | 548 | bool always_wakeup)
|
| 542 | 549 | {
|
| 543 | - Task *task;
|
|
| 544 | - |
|
| 545 | - task = cap->running_task;
|
|
| 546 | - |
|
| 547 | - ASSERT_PARTIAL_CAPABILITY_INVARIANTS(cap,task);
|
|
| 548 | - ASSERT_RETURNING_TASKS(cap,task);
|
|
| 550 | + {
|
|
| 551 | + Task *task = cap->running_task;
|
|
| 552 | + |
|
| 553 | + ASSERT(task || always_wakeup);
|
|
| 554 | + // To cover purpose 2 above, we allow the cap->running_task to be
|
|
| 555 | + // NULL, to handle cases where a thread (that is not itself a Task)
|
|
| 556 | + // needs to wake up an idle task for the capability.
|
|
| 557 | + if (task) {
|
|
| 558 | + ASSERT_PARTIAL_CAPABILITY_INVARIANTS(cap,task);
|
|
| 559 | + ASSERT_RETURNING_TASKS(cap,task);
|
|
| 560 | + }
|
|
| 561 | + }
|
|
| 549 | 562 | ASSERT_LOCK_HELD(&cap->lock);
|
| 550 | 563 | |
| 551 | 564 | RELAXED_STORE(&cap->running_task, NULL);
|
| ... | ... | @@ -581,8 +594,8 @@ releaseCapability_ (Capability* cap, |
| 581 | 594 | // assertion is false: in schedule() we force a yield after
|
| 582 | 595 | // ThreadBlocked, but the thread may be back on the run queue
|
| 583 | 596 | // by now.
|
| 584 | - task = peekRunQueue(cap)->bound->task;
|
|
| 585 | - giveCapabilityToTask(cap, task);
|
|
| 597 | + Task *btask = peekRunQueue(cap)->bound->task;
|
|
| 598 | + giveCapabilityToTask(cap, btask);
|
|
| 586 | 599 | return;
|
| 587 | 600 | }
|
| 588 | 601 | |
| ... | ... | @@ -1087,16 +1100,25 @@ yieldCapability |
| 1087 | 1100 | #if defined(THREADED_RTS)
|
| 1088 | 1101 | |
| 1089 | 1102 | void
|
| 1090 | -prodCapability (Capability *cap, Task *task)
|
|
| 1103 | +prodCapability (Capability *cap)
|
|
| 1091 | 1104 | {
|
| 1092 | 1105 | ACQUIRE_LOCK(&cap->lock);
|
| 1093 | 1106 | if (!cap->running_task) {
|
| 1094 | - cap->running_task = task;
|
|
| 1095 | 1107 | releaseCapability_(cap,true);
|
| 1096 | 1108 | }
|
| 1097 | 1109 | RELEASE_LOCK(&cap->lock);
|
| 1098 | 1110 | }
|
| 1099 | 1111 | |
| 1112 | +/* Ensure at least one capability is not idle. Used to wake up the RTS
|
|
| 1113 | + * in cases where we anticipate that all capabilities may be idle. In
|
|
| 1114 | + * particular it is used for the ctl-c handler, and after the idle GC
|
|
| 1115 | + * timeout to initiate idle GC. */
|
|
| 1116 | +void
|
|
| 1117 | +prodOneCapability (void)
|
|
| 1118 | +{
|
|
| 1119 | + prodCapability(getCapability(0));
|
|
| 1120 | +}
|
|
| 1121 | + |
|
| 1100 | 1122 | #endif /* THREADED_RTS */
|
| 1101 | 1123 | |
| 1102 | 1124 | /* ----------------------------------------------------------------------------
|
| ... | ... | @@ -348,11 +348,7 @@ bool yieldCapability (Capability** pCap, Task *task, bool gcAllowed); |
| 348 | 348 | // need to service some global event.
|
| 349 | 349 | //
|
| 350 | 350 | void prodOneCapability (void);
|
| 351 | -void prodCapability (Capability *cap, Task *task);
|
|
| 352 | - |
|
| 353 | -// Similar to prodOneCapability(), but prods all of them.
|
|
| 354 | -//
|
|
| 355 | -void prodAllCapabilities (void);
|
|
| 351 | +void prodCapability (Capability *cap);
|
|
| 356 | 352 | |
| 357 | 353 | // Attempt to gain control of a Capability if it is free.
|
| 358 | 354 | //
|
| ... | ... | @@ -49,9 +49,11 @@ void sendMessage(Capability *from_cap, Capability *to_cap, Message *msg) |
| 49 | 49 | recordClosureMutated(from_cap,(StgClosure*)msg);
|
| 50 | 50 | |
| 51 | 51 | if (to_cap->running_task == NULL) {
|
| 52 | - to_cap->running_task = myTask();
|
|
| 53 | - // precond for releaseCapability_()
|
|
| 54 | - releaseCapability_(to_cap,false);
|
|
| 52 | + /* Precond for releaseCapability_ is: running_task || always_wakeup.
|
|
| 53 | + * We have running_task == NULL, hence we must use always_wakeup. This
|
|
| 54 | + * is ok since the inbox is now non-empty, so we wake a task anyway.
|
|
| 55 | + */
|
|
| 56 | + releaseCapability_(to_cap, true /*always_wakeup*/);
|
|
| 55 | 57 | } else {
|
| 56 | 58 | interruptCapability(to_cap);
|
| 57 | 59 | }
|
| ... | ... | @@ -2885,8 +2885,8 @@ performBlockingMajorGC(void) |
| 2885 | 2885 | }
|
| 2886 | 2886 | |
| 2887 | 2887 | /* ---------------------------------------------------------------------------
|
| 2888 | - Interrupt execution.
|
|
| 2889 | - Might be called inside a signal handler so it mustn't do anything fancy.
|
|
| 2888 | + Interrupt execution in response to ctl-c.
|
|
| 2889 | + On posix, ctl-c is a signal, while on Win32 it is a console event.
|
|
| 2890 | 2890 | ------------------------------------------------------------------------ */
|
| 2891 | 2891 | |
| 2892 | 2892 | void
|
| ... | ... | @@ -2896,17 +2896,37 @@ interruptStgRts(void) |
| 2896 | 2896 | setSchedState(SCHED_INTERRUPTING);
|
| 2897 | 2897 | interruptAllCapabilities();
|
| 2898 | 2898 | #if defined(THREADED_RTS)
|
| 2899 | + /* It may be that all capabilities are idle. If so, we must wake one up. */
|
|
| 2900 | +#if defined(mingw32_HOST_OS)
|
|
| 2901 | + /* On win32, console handlers are invoked in a proper thread, so we can
|
|
| 2902 | + * directly call wakeUpRts. Although it is an OS thread, it is not one
|
|
| 2903 | + * we created or control necessarily, so it may have no associated Task.
|
|
| 2904 | + */
|
|
| 2899 | 2905 | wakeUpRts();
|
| 2906 | +#else
|
|
| 2907 | + /* On posix on the other hand, signal handlers are very limited in what
|
|
| 2908 | + * they can do. We cannot directly call wakeUpRts below because it is not
|
|
| 2909 | + * signal safe (it uses cond vars to wake up a task). So instead we proxy
|
|
| 2910 | + * it: we interrupt the ticker thread and ask the ticker thread to call
|
|
| 2911 | + * wakeUpRts below. The ticker thread is a proper thread and so can call
|
|
| 2912 | + * wakeUpRts. We can interrupt the ticker thread from signal handler
|
|
| 2913 | + * context safely because it only involves writing to a pipe/eventfd.
|
|
| 2914 | + */
|
|
| 2915 | + wakeUpRtsViaTicker();
|
|
| 2916 | +#endif
|
|
| 2900 | 2917 | #endif
|
| 2901 | 2918 | }
|
| 2902 | 2919 | |
| 2903 | 2920 | /* -----------------------------------------------------------------------------
|
| 2904 | 2921 | Wake up the RTS
|
| 2905 | 2922 | |
| 2906 | - This function causes at least one OS thread to wake up and run the
|
|
| 2907 | - scheduler loop. It is invoked when the RTS might be deadlocked, or
|
|
| 2908 | - an external event has arrived that may need servicing (eg. a
|
|
| 2909 | - keyboard interrupt).
|
|
| 2923 | + This function causes at least one task to wake up and run the scheduler
|
|
| 2924 | + loop on at least one capability.
|
|
| 2925 | + |
|
| 2926 | + It is invoked:
|
|
| 2927 | + 1. as part of the idle GC scheme: when the RTS has been idle for long enough
|
|
| 2928 | + and it is time to go back to the scheduler which will invoke idle GC; or
|
|
| 2929 | + 2. when a ctl-c occurs (posix sigint signal or win32 console event)
|
|
| 2910 | 2930 | |
| 2911 | 2931 | In the single-threaded RTS we don't do anything here; we only have
|
| 2912 | 2932 | one thread anyway, and the event that caused us to want to wake up
|
| ... | ... | @@ -2916,10 +2936,11 @@ interruptStgRts(void) |
| 2916 | 2936 | #if defined(THREADED_RTS)
|
| 2917 | 2937 | void wakeUpRts(void)
|
| 2918 | 2938 | {
|
| 2919 | - // This forces the IO Manager thread to wakeup, which will
|
|
| 2920 | - // in turn ensure that some OS thread wakes up and runs the
|
|
| 2921 | - // scheduler loop, which will cause a GC and deadlock check.
|
|
| 2922 | - wakeupIOManager();
|
|
| 2939 | + /* Our current thread may not have a Task, in particular it will not when
|
|
| 2940 | + * called from interruptStgRts or via wakeUpRtsViaTicker. This is ok,
|
|
| 2941 | + * prodOneCapability does not require one.
|
|
| 2942 | + */
|
|
| 2943 | + prodOneCapability();
|
|
| 2923 | 2944 | }
|
| 2924 | 2945 | #endif
|
| 2925 | 2946 |
| ... | ... | @@ -52,4 +52,8 @@ void exitTicker(void); |
| 52 | 52 | void pauseTicker(void);
|
| 53 | 53 | void unpauseTicker(void);
|
| 54 | 54 | |
| 55 | +#if defined(THREADED_RTS)
|
|
| 56 | +void wakeUpRtsViaTicker(void);
|
|
| 57 | +#endif
|
|
| 58 | + |
|
| 55 | 59 | #include "EndPrivate.h" |
| ... | ... | @@ -127,6 +127,13 @@ static Time ticker_interval = DEFAULT_TICK_INTERVAL; |
| 127 | 127 | // acknowledgement.
|
| 128 | 128 | static bool pause_request;
|
| 129 | 129 | |
| 130 | +#if defined(THREADED_RTS)
|
|
| 131 | +// Atomic variable used by the ctl-c handler (posix signal handler) to
|
|
| 132 | +// communicate that the ticker thread should wake up the rts. This
|
|
| 133 | +// communication is one-way, with no acknowledgement.
|
|
| 134 | +static bool interrupt_request;
|
|
| 135 | +#endif
|
|
| 136 | + |
|
| 130 | 137 | // Atomic variable used by other threads to communicate that they want the
|
| 131 | 138 | // ticker thread to exit.
|
| 132 | 139 | static bool exit_request;
|
| ... | ... | @@ -177,6 +184,13 @@ static void *ticker_thread_func(void *_handle_tick) |
| 177 | 184 | |
| 178 | 185 | paused = ACQUIRE_LOAD_ALWAYS(&pause_request);
|
| 179 | 186 | exit = RELAXED_LOAD_ALWAYS(&exit_request);
|
| 187 | + |
|
| 188 | +#if defined(THREADED_RTS)
|
|
| 189 | + if (RELAXED_LOAD_ALWAYS(&interrupt_request)) {
|
|
| 190 | + RELEASE_STORE_ALWAYS(&interrupt_request, false);
|
|
| 191 | + wakeUpRts();
|
|
| 192 | + }
|
|
| 193 | +#endif
|
|
| 180 | 194 | } else if (errno != EINTR) {
|
| 181 | 195 | // While the RTS attempts to mask signals, some foreign libraries
|
| 182 | 196 | // that rely on signal delivery may unmask them. Consequently we
|
| ... | ... | @@ -262,6 +276,14 @@ void pauseTicker(void) |
| 262 | 276 | sendFdWakeup(notifyfd_w);
|
| 263 | 277 | }
|
| 264 | 278 | |
| 279 | +#if defined(THREADED_RTS)
|
|
| 280 | +void wakeUpRtsViaTicker(void)
|
|
| 281 | +{
|
|
| 282 | + RELAXED_STORE_ALWAYS(&interrupt_request, true);
|
|
| 283 | + sendFdWakeup(notifyfd_w);
|
|
| 284 | +}
|
|
| 285 | +#endif
|
|
| 286 | + |
|
| 265 | 287 | /* Synchronous. Not idempotent.
|
| 266 | 288 | * The ticker is guaranteed stopped after this.
|
| 267 | 289 | */
|
| ... | ... | @@ -1522,7 +1522,7 @@ waitForGcThreads (Capability *cap, bool idle_cap[]) |
| 1522 | 1522 | for(i = 0; i < getNumCapabilities(); ++i) {
|
| 1523 | 1523 | if (i == me || idle_cap[i]) { continue; }
|
| 1524 | 1524 | if (SEQ_CST_LOAD(&gc_threads[i]->wakeup) != GC_THREAD_STANDING_BY) {
|
| 1525 | - prodCapability(getCapability(i), cap->running_task);
|
|
| 1525 | + prodCapability(getCapability(i));
|
|
| 1526 | 1526 | interruptCapability(getCapability(i));
|
| 1527 | 1527 | }
|
| 1528 | 1528 | }
|