Duncan Coutts pushed to branch wip/dcoutts/issue-27105-stopTicker at Glasgow Haskell Compiler / GHC
Commits:
-
9421fe08
by Duncan Coutts at 2026-06-06T23:51:30+02:00
-
542e0742
by Duncan Coutts at 2026-06-07T09:19:31+02:00
-
5d90f476
by Duncan Coutts at 2026-06-07T09:19:31+02:00
-
233e6c16
by Duncan Coutts at 2026-06-07T09:19:31+02:00
-
baba3e89
by Duncan Coutts at 2026-06-07T09:19:31+02:00
-
57d166ae
by Duncan Coutts at 2026-06-07T09:19:31+02:00
-
e2c8b108
by Duncan Coutts at 2026-06-07T09:19:31+02:00
-
5a26a760
by Duncan Coutts at 2026-06-07T11:17:48+02:00
-
bcab4fff
by Duncan Coutts at 2026-06-07T13:47:21+02:00
-
efa24f6a
by Duncan Coutts at 2026-06-07T14:06:12+02:00
-
55547c28
by Duncan Coutts at 2026-06-07T14:07:49+02:00
-
6a667450
by Duncan Coutts at 2026-06-07T14:12:09+02:00
15 changed files:
- + changelog.d/T27105
- rts/Capability.c
- rts/RtsStartup.c
- rts/Schedule.c
- rts/Ticker.h
- rts/Timer.c
- rts/Timer.h
- rts/eventlog/EventLog.c
- rts/eventlog/EventLog.h
- rts/include/rts/OSThreads.h
- rts/include/rts/Timer.h
- rts/posix/Ticker.c
- rts/win32/Ticker.c
- + testsuite/tests/concurrent/should_run/T27105.hs
- testsuite/tests/concurrent/should_run/all.T
Changes:
| 1 | +section: rts
|
|
| 2 | +issues: #27105 #25165 #27335
|
|
| 3 | +mrs: !16147 !16023
|
|
| 4 | +synopsis: The RTS public APIs `stopTimer` and `startTimer` are now no-ops
|
|
| 5 | +description: {
|
|
| 6 | + As a result of fixing some timer/ticker concurrency bugs, the exported RTS
|
|
| 7 | + APIs `stopTimer` and `startTimer` are now no-ops and are deprecated. They
|
|
| 8 | + were called at least by the process and unix libraries. No replacement is
|
|
| 9 | + needed.
|
|
| 10 | + |
|
| 11 | + They were used by libraries to temporarily block the RTS's use of the timer
|
|
| 12 | + signal. These functions no longer have a purpose since the RTS interval
|
|
| 13 | + timer no longer uses signals.
|
|
| 14 | +} |
| ... | ... | @@ -443,13 +443,6 @@ void |
| 443 | 443 | moreCapabilities (uint32_t from USED_IF_THREADS, uint32_t to USED_IF_THREADS)
|
| 444 | 444 | {
|
| 445 | 445 | #if defined(THREADED_RTS)
|
| 446 | - // We must disable the timer while we do this since the tick handler may
|
|
| 447 | - // call contextSwitchAllCapabilities, which may see the capabilities array
|
|
| 448 | - // as we free it. The alternative would be to protect the capabilities
|
|
| 449 | - // array with a lock but this seems more expensive than necessary.
|
|
| 450 | - // See #17289.
|
|
| 451 | - stopTimer();
|
|
| 452 | - |
|
| 453 | 446 | if (to == 1) {
|
| 454 | 447 | // THREADED_RTS must work on builds that don't have a mutable
|
| 455 | 448 | // BaseReg (eg. unregisterised), so in this case
|
| ... | ... | @@ -470,8 +463,6 @@ moreCapabilities (uint32_t from USED_IF_THREADS, uint32_t to USED_IF_THREADS) |
| 470 | 463 | }
|
| 471 | 464 | |
| 472 | 465 | debugTrace(DEBUG_sched, "allocated %d more capabilities", to - from);
|
| 473 | - |
|
| 474 | - startTimer();
|
|
| 475 | 466 | #endif
|
| 476 | 467 | }
|
| 477 | 468 |
| ... | ... | @@ -415,8 +415,8 @@ hs_init_ghc(int *argc, char **argv[], RtsConfig rts_config) |
| 415 | 415 | traceInitEvent(dumpIPEToEventLog);
|
| 416 | 416 | initHeapProfiling();
|
| 417 | 417 | |
| 418 | - /* start the virtual timer 'subsystem'. */
|
|
| 419 | - startTimer();
|
|
| 418 | + /* start the timer (after initTimer above) */
|
|
| 419 | + unpauseTimer();
|
|
| 420 | 420 | |
| 421 | 421 | #if defined(RTS_USER_SIGNALS)
|
| 422 | 422 | if (RtsFlags.MiscFlags.install_signal_handlers) {
|
| ... | ... | @@ -512,14 +512,12 @@ hs_exit_(bool wait_foreign) |
| 512 | 512 | }
|
| 513 | 513 | #endif
|
| 514 | 514 | |
| 515 | - /* stop the ticker */
|
|
| 516 | - stopTimer();
|
|
| 517 | - /*
|
|
| 518 | - * it is quite important that we wait here as some timer implementations
|
|
| 519 | - * (e.g. pthread) may fire even after we exit, which may segfault as we've
|
|
| 520 | - * already freed the capabilities.
|
|
| 515 | + /* We rely on the guarantee that exitTimer stops the timer synchronously,
|
|
| 516 | + * which ensures the timer handler does not get run again after this point.
|
|
| 517 | + * We are about to start freeing resources used by the timer handler (like
|
|
| 518 | + * the capabilities, eventlog and profiling data structures).
|
|
| 521 | 519 | */
|
| 522 | - exitTimer(true);
|
|
| 520 | + exitTimer();
|
|
| 523 | 521 | |
| 524 | 522 | /*
|
| 525 | 523 | * Dump the ticky counter definitions
|
| ... | ... | @@ -37,6 +37,7 @@ |
| 37 | 37 | #include "win32/AsyncWinIO.h"
|
| 38 | 38 | #endif
|
| 39 | 39 | #include "Trace.h"
|
| 40 | +#include "eventlog/EventLog.h"
|
|
| 40 | 41 | #include "RaiseAsync.h"
|
| 41 | 42 | #include "Threads.h"
|
| 42 | 43 | #include "Timer.h"
|
| ... | ... | @@ -454,7 +455,7 @@ run_thread: |
| 454 | 455 | prev = setRecentActivity(ACTIVITY_YES);
|
| 455 | 456 | if (prev == ACTIVITY_DONE_GC) {
|
| 456 | 457 | #if !defined(PROFILING)
|
| 457 | - startTimer();
|
|
| 458 | + unpauseTimer();
|
|
| 458 | 459 | #endif
|
| 459 | 460 | }
|
| 460 | 461 | break;
|
| ... | ... | @@ -1935,7 +1936,7 @@ delete_threads_and_gc: |
| 1935 | 1936 | // it will get re-enabled if we run any threads after the GC.
|
| 1936 | 1937 | setRecentActivity(ACTIVITY_DONE_GC);
|
| 1937 | 1938 | #if !defined(PROFILING)
|
| 1938 | - stopTimer();
|
|
| 1939 | + pauseTimer();
|
|
| 1939 | 1940 | #endif
|
| 1940 | 1941 | break;
|
| 1941 | 1942 | }
|
| ... | ... | @@ -2100,24 +2101,31 @@ forkProcess(HsStablePtr *entry |
| 2100 | 2101 | ACQUIRE_LOCK(&all_tasks_mutex);
|
| 2101 | 2102 | #endif
|
| 2102 | 2103 | |
| 2103 | - stopTimer(); // See #4074
|
|
| 2104 | - |
|
| 2105 | 2104 | #if defined(TRACING)
|
| 2106 | - flushAllCapsEventsBufs(); // so that child won't inherit dirty file buffers
|
|
| 2105 | +#if defined(HAVE_PREEMPTION)
|
|
| 2106 | + // We must hold the eventlog global mutex over the fork to prevent the
|
|
| 2107 | + // timer thread from trying to post events. While holding the mutex we need
|
|
| 2108 | + // to flush the eventlogs (global and per-cap) so that child won't inherit
|
|
| 2109 | + // dirty eventlog buffers or file buffers.
|
|
| 2110 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 2111 | +#endif
|
|
| 2112 | + flushAllCapsEventsBufs_();
|
|
| 2107 | 2113 | #endif
|
| 2108 | 2114 | |
| 2109 | 2115 | pid = fork();
|
| 2110 | 2116 | |
| 2111 | 2117 | if (pid) { // parent
|
| 2112 | 2118 | |
| 2113 | - startTimer(); // #4074
|
|
| 2114 | - |
|
| 2115 | 2119 | RELEASE_LOCK(&sched_mutex);
|
| 2116 | 2120 | RELEASE_LOCK(&sm_mutex);
|
| 2117 | 2121 | RELEASE_LOCK(&stable_ptr_mutex);
|
| 2118 | 2122 | RELEASE_LOCK(&stable_name_mutex);
|
| 2119 | 2123 | RELEASE_LOCK(&task->lock);
|
| 2120 | 2124 | |
| 2125 | +#if defined(TRACING) && defined(HAVE_PREEMPTION)
|
|
| 2126 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 2127 | +#endif
|
|
| 2128 | + |
|
| 2121 | 2129 | #if defined(THREADED_RTS)
|
| 2122 | 2130 | /* N.B. releaseCapability_ below may need to take all_tasks_mutex */
|
| 2123 | 2131 | RELEASE_LOCK(&all_tasks_mutex);
|
| ... | ... | @@ -2224,8 +2232,8 @@ forkProcess(HsStablePtr *entry |
| 2224 | 2232 | generations[g].threads = END_TSO_QUEUE;
|
| 2225 | 2233 | }
|
| 2226 | 2234 | |
| 2227 | - // On Unix, all timers are reset in the child, so we need to start
|
|
| 2228 | - // the timer again.
|
|
| 2235 | + // The timer thread is not present in the child process, so we need
|
|
| 2236 | + // to initialise the timer again.
|
|
| 2229 | 2237 | initTimer();
|
| 2230 | 2238 | |
| 2231 | 2239 | // TODO: need to trace various other things in the child
|
| ... | ... | @@ -2236,7 +2244,7 @@ forkProcess(HsStablePtr *entry |
| 2236 | 2244 | |
| 2237 | 2245 | // start timer after the IOManager is initialized
|
| 2238 | 2246 | // (the idle GC may wake up the IOManager)
|
| 2239 | - startTimer();
|
|
| 2247 | + unpauseTimer();
|
|
| 2240 | 2248 | |
| 2241 | 2249 | // Install toplevel exception handlers, so interruption
|
| 2242 | 2250 | // signal will be sent to the main thread.
|
| ... | ... | @@ -2303,12 +2311,6 @@ setNumCapabilities (uint32_t new_n_capabilities USED_IF_THREADS) |
| 2303 | 2311 | cap = rts_lock();
|
| 2304 | 2312 | task = cap->running_task;
|
| 2305 | 2313 | |
| 2306 | - |
|
| 2307 | - // N.B. We must stop the interval timer while we are changing the
|
|
| 2308 | - // capabilities array lest handle_tick may try to context switch
|
|
| 2309 | - // an old capability. See #17289.
|
|
| 2310 | - stopTimer();
|
|
| 2311 | - |
|
| 2312 | 2314 | stopAllCapabilities(&cap, task);
|
| 2313 | 2315 | |
| 2314 | 2316 | if (new_n_capabilities < enabled_capabilities)
|
| ... | ... | @@ -2364,9 +2366,7 @@ setNumCapabilities (uint32_t new_n_capabilities USED_IF_THREADS) |
| 2364 | 2366 | tracingAddCapabilities(n_capabilities, new_n_capabilities);
|
| 2365 | 2367 | #endif
|
| 2366 | 2368 | |
| 2367 | - // Resize the capabilities array
|
|
| 2368 | - // NB. after this, capabilities points somewhere new. Any pointers
|
|
| 2369 | - // of type (Capability *) are now invalid.
|
|
| 2369 | + // Allocate and initialise the extra capabilities
|
|
| 2370 | 2370 | moreCapabilities(n_capabilities, new_n_capabilities);
|
| 2371 | 2371 | |
| 2372 | 2372 | // Resize and update storage manager data structures
|
| ... | ... | @@ -2394,8 +2394,6 @@ setNumCapabilities (uint32_t new_n_capabilities USED_IF_THREADS) |
| 2394 | 2394 | // Notify IO manager that the number of capabilities has changed.
|
| 2395 | 2395 | notifyIOManagerCapabilitiesChanged(&cap);
|
| 2396 | 2396 | |
| 2397 | - startTimer();
|
|
| 2398 | - |
|
| 2399 | 2397 | rts_unlock(cap);
|
| 2400 | 2398 | |
| 2401 | 2399 | #endif // THREADED_RTS
|
| ... | ... | @@ -12,9 +12,44 @@ |
| 12 | 12 | |
| 13 | 13 | typedef void (*TickProc)(int);
|
| 14 | 14 | |
| 15 | -void initTicker (Time interval, TickProc handle_tick);
|
|
| 16 | -void startTicker (void);
|
|
| 17 | -void stopTicker (void);
|
|
| 18 | -void exitTicker (bool wait);
|
|
| 15 | +/* The ticker is initialised in a paused state. Use unpauseTicker to start. */
|
|
| 16 | +void initTicker(Time interval, TickProc handle_tick);
|
|
| 17 | + |
|
| 18 | +/* Stop and terminate the ticker. It does not need to be stopped first.
|
|
| 19 | + * The exitTicker action is *synchronous*. When it returns the caller is
|
|
| 20 | + * guaranteed that the tick action is blocked.
|
|
| 21 | + */
|
|
| 22 | +void exitTicker(void);
|
|
| 23 | + |
|
| 24 | +/* Pause and unpause (resume) the ticker.
|
|
| 25 | + *
|
|
| 26 | + * The pauseTicker and unpauseTicker actions are *asynchronous*. After calling
|
|
| 27 | + * pauseTicker, the ticker will pause eventually, but there may be another tick
|
|
| 28 | + * action before it does pause (and theoretically there could be several but
|
|
| 29 | + * in practice this is unlikely). Similarly, after calling unpauseTicker the
|
|
| 30 | + * ticker will start up again eventually, but there is an unspecified delay
|
|
| 31 | + * between the unpause and the next tick action (but in practice it is short).
|
|
| 32 | + *
|
|
| 33 | + * This should be used for the purpose of *efficiency*: to avoid unnecessary
|
|
| 34 | + * OS thread wakeups caused by the ticker.
|
|
| 35 | + *
|
|
| 36 | + * These should *not* be used for the purpose of *concurrency safety*: to
|
|
| 37 | + * prevent the tick action from running concurrently with some other critical
|
|
| 38 | + * section. The synchronous case is not provided because it is not currently
|
|
| 39 | + * needed (and proper locking is often a better solution anyway).
|
|
| 40 | + *
|
|
| 41 | + * The pairing of unpauseTicker and the handle_tick action form a
|
|
| 42 | + * synchonises-with relation: values written before unpauseTicker can be
|
|
| 43 | + * read from the resulting handle_tick action.
|
|
| 44 | + *
|
|
| 45 | + * It *is* safe to call these functions from within the tick handler itself.
|
|
| 46 | + *
|
|
| 47 | + * It is safe to use these functions concurrently from multiple threads, but
|
|
| 48 | + * note that they *are* idempotent. This means it is not appropriate to use
|
|
| 49 | + * paired pause/unpause calls concurrently. They can be used by threads based
|
|
| 50 | + * on consistent use of some shared state or observation.
|
|
| 51 | + */
|
|
| 52 | +void pauseTicker(void);
|
|
| 53 | +void unpauseTicker(void);
|
|
| 19 | 54 | |
| 20 | 55 | #include "EndPrivate.h" |
| ... | ... | @@ -28,20 +28,6 @@ |
| 28 | 28 | #include "RtsSignals.h"
|
| 29 | 29 | #include "rts/EventLogWriter.h"
|
| 30 | 30 | |
| 31 | -// See Note [No timer on wasm32]
|
|
| 32 | -#if !defined(wasm32_HOST_ARCH)
|
|
| 33 | -#define HAVE_PREEMPTION
|
|
| 34 | -#endif
|
|
| 35 | - |
|
| 36 | -// This global counter is used to allow multiple threads to stop the
|
|
| 37 | -// timer temporarily with a stopTimer()/startTimer() pair. If
|
|
| 38 | -// timer_enabled == 0 timer is enabled
|
|
| 39 | -// timer_disabled == N, N > 0 timer is disabled by N threads
|
|
| 40 | -// When timer_enabled makes a transition to 0, we enable the timer,
|
|
| 41 | -// and when it makes a transition to non-0 we disable it.
|
|
| 42 | - |
|
| 43 | -static StgWord timer_disabled;
|
|
| 44 | - |
|
| 45 | 31 | /* ticks left before next pre-emptive context switch */
|
| 46 | 32 | static int ticks_to_ctxt_switch = 0;
|
| 47 | 33 | |
| ... | ... | @@ -112,9 +98,9 @@ static |
| 112 | 98 | void
|
| 113 | 99 | handle_tick(int unused STG_UNUSED)
|
| 114 | 100 | {
|
| 115 | - handleProfTick();
|
|
| 116 | - if (RtsFlags.ConcFlags.ctxtSwitchTicks > 0
|
|
| 117 | - && SEQ_CST_LOAD_ALWAYS(&timer_disabled) == 0)
|
|
| 101 | + handleProfTick(); // Bad or worse: see issue #27250.
|
|
| 102 | + |
|
| 103 | + if (RtsFlags.ConcFlags.ctxtSwitchTicks > 0)
|
|
| 118 | 104 | {
|
| 119 | 105 | ticks_to_ctxt_switch--;
|
| 120 | 106 | if (ticks_to_ctxt_switch <= 0) {
|
| ... | ... | @@ -128,7 +114,7 @@ handle_tick(int unused STG_UNUSED) |
| 128 | 114 | ticks_to_eventlog_flush--;
|
| 129 | 115 | if (ticks_to_eventlog_flush <= 0) {
|
| 130 | 116 | ticks_to_eventlog_flush = RtsFlags.TraceFlags.eventlogFlushTicks;
|
| 131 | - flushEventLog(NULL);
|
|
| 117 | + flushEventLog(NULL); // Bad or worse: see issue #27250.
|
|
| 132 | 118 | }
|
| 133 | 119 | }
|
| 134 | 120 | #endif
|
| ... | ... | @@ -153,7 +139,7 @@ handle_tick(int unused STG_UNUSED) |
| 153 | 139 | RtsFlags.MiscFlags.tickInterval;
|
| 154 | 140 | #if defined(THREADED_RTS)
|
| 155 | 141 | wakeUpRts();
|
| 156 | - // The scheduler will call stopTimer() when it has done
|
|
| 142 | + // The scheduler will call pauseTimer() when it has done
|
|
| 157 | 143 | // the GC.
|
| 158 | 144 | #endif
|
| 159 | 145 | } else {
|
| ... | ... | @@ -165,10 +151,10 @@ handle_tick(int unused STG_UNUSED) |
| 165 | 151 | #if defined(PROFILING)
|
| 166 | 152 | if (!(RtsFlags.ProfFlags.doHeapProfile
|
| 167 | 153 | || RtsFlags.CcFlags.doCostCentres)) {
|
| 168 | - stopTimer();
|
|
| 154 | + pauseTimer();
|
|
| 169 | 155 | }
|
| 170 | 156 | #else
|
| 171 | - stopTimer();
|
|
| 157 | + pauseTimer();
|
|
| 172 | 158 | #endif
|
| 173 | 159 | }
|
| 174 | 160 | } else {
|
| ... | ... | @@ -181,48 +167,49 @@ handle_tick(int unused STG_UNUSED) |
| 181 | 167 | }
|
| 182 | 168 | }
|
| 183 | 169 | |
| 184 | -void
|
|
| 185 | -initTimer(void)
|
|
| 170 | +void initTimer(void)
|
|
| 186 | 171 | {
|
| 187 | 172 | #if defined(HAVE_PREEMPTION)
|
| 188 | 173 | initProfTimer();
|
| 189 | 174 | if (RtsFlags.MiscFlags.tickInterval != 0) {
|
| 190 | 175 | initTicker(RtsFlags.MiscFlags.tickInterval, handle_tick);
|
| 191 | 176 | }
|
| 192 | - SEQ_CST_STORE_ALWAYS(&timer_disabled, 1);
|
|
| 193 | 177 | #endif
|
| 194 | 178 | }
|
| 195 | 179 | |
| 196 | -void
|
|
| 197 | -startTimer(void)
|
|
| 180 | +/* Deprecated exported functions. Now no-ops.
|
|
| 181 | + * Historically they were used by the process and unix libraries to disable
|
|
| 182 | + * the signal-based interval timer, since otherwise the timer signal would
|
|
| 183 | + * keep going off in the child process and confusing everything. The interval
|
|
| 184 | + * timer no longer uses signals, so there is no need any more for libraries to
|
|
| 185 | + * disable the timer. Also, the timer internal API has changed.
|
|
| 186 | + */
|
|
| 187 | +void stopTimer(void) { /* no-op */ }
|
|
| 188 | +void startTimer(void) { /* no-op */ }
|
|
| 189 | + |
|
| 190 | +void pauseTimer(void)
|
|
| 198 | 191 | {
|
| 199 | 192 | #if defined(HAVE_PREEMPTION)
|
| 200 | - if (SEQ_CST_SUB_ALWAYS(&timer_disabled, 1) == 0) {
|
|
| 201 | - if (RtsFlags.MiscFlags.tickInterval != 0) {
|
|
| 202 | - startTicker();
|
|
| 203 | - }
|
|
| 193 | + if (RtsFlags.MiscFlags.tickInterval != 0) {
|
|
| 194 | + pauseTicker();
|
|
| 204 | 195 | }
|
| 205 | 196 | #endif
|
| 206 | 197 | }
|
| 207 | 198 | |
| 208 | -void
|
|
| 209 | -stopTimer(void)
|
|
| 199 | +void unpauseTimer(void)
|
|
| 210 | 200 | {
|
| 211 | 201 | #if defined(HAVE_PREEMPTION)
|
| 212 | - if (SEQ_CST_ADD_ALWAYS(&timer_disabled, 1) == 1) {
|
|
| 213 | - if (RtsFlags.MiscFlags.tickInterval != 0) {
|
|
| 214 | - stopTicker();
|
|
| 215 | - }
|
|
| 202 | + if (RtsFlags.MiscFlags.tickInterval != 0) {
|
|
| 203 | + unpauseTicker();
|
|
| 216 | 204 | }
|
| 217 | 205 | #endif
|
| 218 | 206 | }
|
| 219 | 207 | |
| 220 | -void
|
|
| 221 | -exitTimer (bool wait)
|
|
| 208 | +void exitTimer (void)
|
|
| 222 | 209 | {
|
| 223 | 210 | #if defined(HAVE_PREEMPTION)
|
| 224 | 211 | if (RtsFlags.MiscFlags.tickInterval != 0) {
|
| 225 | - exitTicker(wait);
|
|
| 212 | + exitTicker();
|
|
| 226 | 213 | }
|
| 227 | 214 | #endif
|
| 228 | 215 | } |
| ... | ... | @@ -8,5 +8,12 @@ |
| 8 | 8 | |
| 9 | 9 | #pragma once
|
| 10 | 10 | |
| 11 | -RTS_PRIVATE void initTimer (void);
|
|
| 12 | -RTS_PRIVATE void exitTimer (bool wait); |
|
| 11 | +#include "BeginPrivate.h"
|
|
| 12 | + |
|
| 13 | +void initTimer(void);
|
|
| 14 | +void exitTimer(void);
|
|
| 15 | + |
|
| 16 | +void pauseTimer(void);
|
|
| 17 | +void unpauseTimer(void);
|
|
| 18 | + |
|
| 19 | +#include "EndPrivate.h" |
| ... | ... | @@ -129,8 +129,11 @@ typedef struct _EventsBuf { |
| 129 | 129 | static EventsBuf *capEventBuf; // one EventsBuf for each Capability
|
| 130 | 130 | |
| 131 | 131 | static EventsBuf eventBuf; // an EventsBuf not associated with any Capability
|
| 132 | -#if defined(THREADED_RTS)
|
|
| 133 | -static Mutex eventBufMutex; // protected by this mutex
|
|
| 132 | +#if defined(HAVE_PREEMPTION)
|
|
| 133 | +// Note that this mutex is used even in the non-threaded RTS, since the timer
|
|
| 134 | +// thread posts events and flushes. So _all_ uses of this mutex must use
|
|
| 135 | +// ACQUIRE_LOCK_ALWAYS/RELEASE_LOCK_ALWAYS.
|
|
| 136 | +Mutex eventBufMutex; // protects eventBuf above
|
|
| 134 | 137 | #endif
|
| 135 | 138 | |
| 136 | 139 | // Event type
|
| ... | ... | @@ -393,8 +396,10 @@ initEventLogging(void) |
| 393 | 396 | moreCapEventBufs(0, get_n_capabilities());
|
| 394 | 397 | |
| 395 | 398 | initEventsBuf(&eventBuf, EVENT_LOG_SIZE, (EventCapNo)(-1));
|
| 396 | -#if defined(THREADED_RTS)
|
|
| 399 | +#if defined(HAVE_PREEMPTION)
|
|
| 397 | 400 | initMutex(&eventBufMutex);
|
| 401 | +#endif
|
|
| 402 | +#if defined(THREADED_RTS)
|
|
| 398 | 403 | initMutex(&state_change_mutex);
|
| 399 | 404 | #endif
|
| 400 | 405 | }
|
| ... | ... | @@ -416,7 +421,7 @@ startEventLogging_(void) |
| 416 | 421 | {
|
| 417 | 422 | initEventLogWriter();
|
| 418 | 423 | |
| 419 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 424 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 420 | 425 | postHeaderEvents();
|
| 421 | 426 | |
| 422 | 427 | /*
|
| ... | ... | @@ -425,7 +430,7 @@ startEventLogging_(void) |
| 425 | 430 | */
|
| 426 | 431 | printAndClearEventBuf(&eventBuf);
|
| 427 | 432 | |
| 428 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 433 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 429 | 434 | |
| 430 | 435 | return true;
|
| 431 | 436 | }
|
| ... | ... | @@ -495,7 +500,7 @@ endEventLogging(void) |
| 495 | 500 | |
| 496 | 501 | flushEventLog_(NULL);
|
| 497 | 502 | |
| 498 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 503 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 499 | 504 | |
| 500 | 505 | // Mark end of events (data).
|
| 501 | 506 | postEventTypeNum(&eventBuf, EVENT_DATA_END);
|
| ... | ... | @@ -503,7 +508,7 @@ endEventLogging(void) |
| 503 | 508 | // Flush the end of data marker.
|
| 504 | 509 | printAndClearEventBuf(&eventBuf);
|
| 505 | 510 | |
| 506 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 511 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 507 | 512 | |
| 508 | 513 | stopEventLogWriter();
|
| 509 | 514 | event_log_writer = NULL;
|
| ... | ... | @@ -666,7 +671,7 @@ void |
| 666 | 671 | postCapEvent (EventTypeNum tag,
|
| 667 | 672 | EventCapNo capno)
|
| 668 | 673 | {
|
| 669 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 674 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 670 | 675 | ensureRoomForEvent(&eventBuf, tag);
|
| 671 | 676 | |
| 672 | 677 | postEventHeader(&eventBuf, tag);
|
| ... | ... | @@ -685,14 +690,14 @@ postCapEvent (EventTypeNum tag, |
| 685 | 690 | barf("postCapEvent: unknown event tag %d", tag);
|
| 686 | 691 | }
|
| 687 | 692 | |
| 688 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 693 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 689 | 694 | }
|
| 690 | 695 | |
| 691 | 696 | void postCapsetEvent (EventTypeNum tag,
|
| 692 | 697 | EventCapsetID capset,
|
| 693 | 698 | StgWord info)
|
| 694 | 699 | {
|
| 695 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 700 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 696 | 701 | ensureRoomForEvent(&eventBuf, tag);
|
| 697 | 702 | |
| 698 | 703 | postEventHeader(&eventBuf, tag);
|
| ... | ... | @@ -726,7 +731,7 @@ void postCapsetEvent (EventTypeNum tag, |
| 726 | 731 | barf("postCapsetEvent: unknown event tag %d", tag);
|
| 727 | 732 | }
|
| 728 | 733 | |
| 729 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 734 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 730 | 735 | }
|
| 731 | 736 | |
| 732 | 737 | void postCapsetStrEvent (EventTypeNum tag,
|
| ... | ... | @@ -740,14 +745,14 @@ void postCapsetStrEvent (EventTypeNum tag, |
| 740 | 745 | return;
|
| 741 | 746 | }
|
| 742 | 747 | |
| 743 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 748 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 744 | 749 | |
| 745 | 750 | if (!hasRoomForVariableEvent(&eventBuf, size)){
|
| 746 | 751 | printAndClearEventBuf(&eventBuf);
|
| 747 | 752 | |
| 748 | 753 | if (!hasRoomForVariableEvent(&eventBuf, size)){
|
| 749 | 754 | errorBelch("Event size exceeds buffer size, bail out");
|
| 750 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 755 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 751 | 756 | return;
|
| 752 | 757 | }
|
| 753 | 758 | }
|
| ... | ... | @@ -758,7 +763,7 @@ void postCapsetStrEvent (EventTypeNum tag, |
| 758 | 763 | |
| 759 | 764 | postBuf(&eventBuf, (StgWord8*) msg, strsize);
|
| 760 | 765 | |
| 761 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 766 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 762 | 767 | }
|
| 763 | 768 | |
| 764 | 769 | void postCapsetVecEvent (EventTypeNum tag,
|
| ... | ... | @@ -783,14 +788,14 @@ void postCapsetVecEvent (EventTypeNum tag, |
| 783 | 788 | }
|
| 784 | 789 | }
|
| 785 | 790 | |
| 786 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 791 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 787 | 792 | |
| 788 | 793 | if (!hasRoomForVariableEvent(&eventBuf, size)){
|
| 789 | 794 | printAndClearEventBuf(&eventBuf);
|
| 790 | 795 | |
| 791 | 796 | if(!hasRoomForVariableEvent(&eventBuf, size)){
|
| 792 | 797 | errorBelch("Event size exceeds buffer size, bail out");
|
| 793 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 798 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 794 | 799 | return;
|
| 795 | 800 | }
|
| 796 | 801 | }
|
| ... | ... | @@ -804,7 +809,7 @@ void postCapsetVecEvent (EventTypeNum tag, |
| 804 | 809 | postBuf(&eventBuf, (StgWord8*) argv[i], 1 + strlen(argv[i]));
|
| 805 | 810 | }
|
| 806 | 811 | |
| 807 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 812 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 808 | 813 | }
|
| 809 | 814 | |
| 810 | 815 | void postWallClockTime (EventCapsetID capset)
|
| ... | ... | @@ -813,7 +818,7 @@ void postWallClockTime (EventCapsetID capset) |
| 813 | 818 | StgWord64 sec;
|
| 814 | 819 | StgWord32 nsec;
|
| 815 | 820 | |
| 816 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 821 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 817 | 822 | |
| 818 | 823 | /* The EVENT_WALL_CLOCK_TIME event is intended to allow programs
|
| 819 | 824 | reading the eventlog to match up the event timestamps with wall
|
| ... | ... | @@ -846,7 +851,7 @@ void postWallClockTime (EventCapsetID capset) |
| 846 | 851 | postWord64(&eventBuf, sec);
|
| 847 | 852 | postWord32(&eventBuf, nsec);
|
| 848 | 853 | |
| 849 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 854 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 850 | 855 | }
|
| 851 | 856 | |
| 852 | 857 | /*
|
| ... | ... | @@ -885,7 +890,7 @@ void postEventHeapInfo (EventCapsetID heap_capset, |
| 885 | 890 | W_ mblockSize,
|
| 886 | 891 | W_ blockSize)
|
| 887 | 892 | {
|
| 888 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 893 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 889 | 894 | ensureRoomForEvent(&eventBuf, EVENT_HEAP_INFO_GHC);
|
| 890 | 895 | |
| 891 | 896 | postEventHeader(&eventBuf, EVENT_HEAP_INFO_GHC);
|
| ... | ... | @@ -899,7 +904,7 @@ void postEventHeapInfo (EventCapsetID heap_capset, |
| 899 | 904 | postWord64(&eventBuf, mblockSize);
|
| 900 | 905 | postWord64(&eventBuf, blockSize);
|
| 901 | 906 | |
| 902 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 907 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 903 | 908 | }
|
| 904 | 909 | |
| 905 | 910 | void postEventGcStats (Capability *cap,
|
| ... | ... | @@ -952,7 +957,7 @@ void postTaskCreateEvent (EventTaskId taskId, |
| 952 | 957 | EventCapNo capno,
|
| 953 | 958 | EventKernelThreadId tid)
|
| 954 | 959 | {
|
| 955 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 960 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 956 | 961 | ensureRoomForEvent(&eventBuf, EVENT_TASK_CREATE);
|
| 957 | 962 | |
| 958 | 963 | postEventHeader(&eventBuf, EVENT_TASK_CREATE);
|
| ... | ... | @@ -961,14 +966,14 @@ void postTaskCreateEvent (EventTaskId taskId, |
| 961 | 966 | postCapNo(&eventBuf, capno);
|
| 962 | 967 | postKernelThreadId(&eventBuf, tid);
|
| 963 | 968 | |
| 964 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 969 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 965 | 970 | }
|
| 966 | 971 | |
| 967 | 972 | void postTaskMigrateEvent (EventTaskId taskId,
|
| 968 | 973 | EventCapNo capno,
|
| 969 | 974 | EventCapNo new_capno)
|
| 970 | 975 | {
|
| 971 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 976 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 972 | 977 | ensureRoomForEvent(&eventBuf, EVENT_TASK_MIGRATE);
|
| 973 | 978 | |
| 974 | 979 | postEventHeader(&eventBuf, EVENT_TASK_MIGRATE);
|
| ... | ... | @@ -977,28 +982,28 @@ void postTaskMigrateEvent (EventTaskId taskId, |
| 977 | 982 | postCapNo(&eventBuf, capno);
|
| 978 | 983 | postCapNo(&eventBuf, new_capno);
|
| 979 | 984 | |
| 980 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 985 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 981 | 986 | }
|
| 982 | 987 | |
| 983 | 988 | void postTaskDeleteEvent (EventTaskId taskId)
|
| 984 | 989 | {
|
| 985 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 990 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 986 | 991 | ensureRoomForEvent(&eventBuf, EVENT_TASK_DELETE);
|
| 987 | 992 | |
| 988 | 993 | postEventHeader(&eventBuf, EVENT_TASK_DELETE);
|
| 989 | 994 | /* EVENT_TASK_DELETE (taskID) */
|
| 990 | 995 | postTaskId(&eventBuf, taskId);
|
| 991 | 996 | |
| 992 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 997 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 993 | 998 | }
|
| 994 | 999 | |
| 995 | 1000 | void
|
| 996 | 1001 | postEventNoCap (EventTypeNum tag)
|
| 997 | 1002 | {
|
| 998 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1003 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 999 | 1004 | ensureRoomForEvent(&eventBuf, tag);
|
| 1000 | 1005 | postEventHeader(&eventBuf, tag);
|
| 1001 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1006 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1002 | 1007 | }
|
| 1003 | 1008 | |
| 1004 | 1009 | void
|
| ... | ... | @@ -1042,9 +1047,9 @@ void postLogMsg(EventsBuf *eb, EventTypeNum type, char *msg, va_list ap) |
| 1042 | 1047 | |
| 1043 | 1048 | void postMsg(char *msg, va_list ap)
|
| 1044 | 1049 | {
|
| 1045 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1050 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1046 | 1051 | postLogMsg(&eventBuf, EVENT_LOG_MSG, msg, ap);
|
| 1047 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1052 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1048 | 1053 | }
|
| 1049 | 1054 | |
| 1050 | 1055 | void postCapMsg(Capability *cap, char *msg, va_list ap)
|
| ... | ... | @@ -1138,32 +1143,32 @@ void postConcUpdRemSetFlush(Capability *cap) |
| 1138 | 1143 | |
| 1139 | 1144 | void postConcMarkEnd(StgWord32 marked_obj_count)
|
| 1140 | 1145 | {
|
| 1141 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1146 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1142 | 1147 | ensureRoomForEvent(&eventBuf, EVENT_CONC_MARK_END);
|
| 1143 | 1148 | postEventHeader(&eventBuf, EVENT_CONC_MARK_END);
|
| 1144 | 1149 | postWord32(&eventBuf, marked_obj_count);
|
| 1145 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1150 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1146 | 1151 | }
|
| 1147 | 1152 | |
| 1148 | 1153 | void postNonmovingHeapCensus(uint16_t blk_size,
|
| 1149 | 1154 | const struct NonmovingAllocCensus *census)
|
| 1150 | 1155 | {
|
| 1151 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1156 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1152 | 1157 | postEventHeader(&eventBuf, EVENT_NONMOVING_HEAP_CENSUS);
|
| 1153 | 1158 | postWord16(&eventBuf, blk_size);
|
| 1154 | 1159 | postWord32(&eventBuf, census->n_active_segs);
|
| 1155 | 1160 | postWord32(&eventBuf, census->n_filled_segs);
|
| 1156 | 1161 | postWord32(&eventBuf, census->n_live_blocks);
|
| 1157 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1162 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1158 | 1163 | }
|
| 1159 | 1164 | |
| 1160 | 1165 | void postNonmovingPrunedSegments(uint32_t pruned_segments, uint32_t free_segments)
|
| 1161 | 1166 | {
|
| 1162 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1167 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1163 | 1168 | postEventHeader(&eventBuf, EVENT_NONMOVING_PRUNED_SEGMENTS);
|
| 1164 | 1169 | postWord32(&eventBuf, pruned_segments);
|
| 1165 | 1170 | postWord32(&eventBuf, free_segments);
|
| 1166 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1171 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1167 | 1172 | }
|
| 1168 | 1173 | |
| 1169 | 1174 | void closeBlockMarker (EventsBuf *ebuf)
|
| ... | ... | @@ -1224,7 +1229,7 @@ static HeapProfBreakdown getHeapProfBreakdown(void) |
| 1224 | 1229 | |
| 1225 | 1230 | void postHeapProfBegin(void)
|
| 1226 | 1231 | {
|
| 1227 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1232 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1228 | 1233 | PROFILING_FLAGS *flags = &RtsFlags.ProfFlags;
|
| 1229 | 1234 | StgWord modSelector_len =
|
| 1230 | 1235 | flags->modSelector ? strlen(flags->modSelector) : 0;
|
| ... | ... | @@ -1258,42 +1263,42 @@ void postHeapProfBegin(void) |
| 1258 | 1263 | postStringLen(&eventBuf, flags->ccsSelector, ccsSelector_len);
|
| 1259 | 1264 | postStringLen(&eventBuf, flags->retainerSelector, retainerSelector_len);
|
| 1260 | 1265 | postStringLen(&eventBuf, flags->bioSelector, bioSelector_len);
|
| 1261 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1266 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1262 | 1267 | }
|
| 1263 | 1268 | |
| 1264 | 1269 | void postHeapProfSampleBegin(StgInt era)
|
| 1265 | 1270 | {
|
| 1266 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1271 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1267 | 1272 | ensureRoomForEvent(&eventBuf, EVENT_HEAP_PROF_SAMPLE_BEGIN);
|
| 1268 | 1273 | postEventHeader(&eventBuf, EVENT_HEAP_PROF_SAMPLE_BEGIN);
|
| 1269 | 1274 | postWord64(&eventBuf, era);
|
| 1270 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1275 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1271 | 1276 | }
|
| 1272 | 1277 | |
| 1273 | 1278 | |
| 1274 | 1279 | void postHeapBioProfSampleBegin(StgInt era, StgWord64 time)
|
| 1275 | 1280 | {
|
| 1276 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1281 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1277 | 1282 | ensureRoomForEvent(&eventBuf, EVENT_HEAP_BIO_PROF_SAMPLE_BEGIN);
|
| 1278 | 1283 | postEventHeader(&eventBuf, EVENT_HEAP_BIO_PROF_SAMPLE_BEGIN);
|
| 1279 | 1284 | postWord64(&eventBuf, era);
|
| 1280 | 1285 | postWord64(&eventBuf, time);
|
| 1281 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1286 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1282 | 1287 | }
|
| 1283 | 1288 | |
| 1284 | 1289 | void postHeapProfSampleEnd(StgInt era)
|
| 1285 | 1290 | {
|
| 1286 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1291 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1287 | 1292 | ensureRoomForEvent(&eventBuf, EVENT_HEAP_PROF_SAMPLE_END);
|
| 1288 | 1293 | postEventHeader(&eventBuf, EVENT_HEAP_PROF_SAMPLE_END);
|
| 1289 | 1294 | postWord64(&eventBuf, era);
|
| 1290 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1295 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1291 | 1296 | }
|
| 1292 | 1297 | |
| 1293 | 1298 | void postHeapProfSampleString(const char *label,
|
| 1294 | 1299 | StgWord64 residency)
|
| 1295 | 1300 | {
|
| 1296 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1301 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1297 | 1302 | StgWord label_len = strlen(label);
|
| 1298 | 1303 | StgWord len = 1+8+label_len+1;
|
| 1299 | 1304 | CHECK(!ensureRoomForVariableEvent(&eventBuf, len));
|
| ... | ... | @@ -1303,7 +1308,7 @@ void postHeapProfSampleString(const char *label, |
| 1303 | 1308 | postWord8(&eventBuf, 0);
|
| 1304 | 1309 | postWord64(&eventBuf, residency);
|
| 1305 | 1310 | postStringLen(&eventBuf, label, label_len);
|
| 1306 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1311 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1307 | 1312 | }
|
| 1308 | 1313 | |
| 1309 | 1314 | #if defined(PROFILING)
|
| ... | ... | @@ -1313,7 +1318,7 @@ void postHeapProfCostCentre(StgWord32 ccID, |
| 1313 | 1318 | const char *srcloc,
|
| 1314 | 1319 | StgBool is_caf)
|
| 1315 | 1320 | {
|
| 1316 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1321 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1317 | 1322 | StgWord label_len = strlen(label);
|
| 1318 | 1323 | StgWord module_len = strlen(module);
|
| 1319 | 1324 | StgWord srcloc_len = strlen(srcloc);
|
| ... | ... | @@ -1326,13 +1331,13 @@ void postHeapProfCostCentre(StgWord32 ccID, |
| 1326 | 1331 | postStringLen(&eventBuf, module, module_len);
|
| 1327 | 1332 | postStringLen(&eventBuf, srcloc, srcloc_len);
|
| 1328 | 1333 | postWord8(&eventBuf, is_caf);
|
| 1329 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1334 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1330 | 1335 | }
|
| 1331 | 1336 | |
| 1332 | 1337 | void postHeapProfSampleCostCentre(CostCentreStack *stack,
|
| 1333 | 1338 | StgWord64 residency)
|
| 1334 | 1339 | {
|
| 1335 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1340 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1336 | 1341 | StgWord depth = 0;
|
| 1337 | 1342 | CostCentreStack *ccs;
|
| 1338 | 1343 | for (ccs = stack; ccs != NULL && ccs != CCS_MAIN; ccs = ccs->prevStack)
|
| ... | ... | @@ -1351,7 +1356,7 @@ void postHeapProfSampleCostCentre(CostCentreStack *stack, |
| 1351 | 1356 | depth>0 && ccs != NULL && ccs != CCS_MAIN;
|
| 1352 | 1357 | ccs = ccs->prevStack, depth--)
|
| 1353 | 1358 | postWord32(&eventBuf, ccs->cc->ccID);
|
| 1354 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1359 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1355 | 1360 | }
|
| 1356 | 1361 | |
| 1357 | 1362 | |
| ... | ... | @@ -1359,7 +1364,7 @@ void postProfSampleCostCentre(Capability *cap, |
| 1359 | 1364 | CostCentreStack *stack,
|
| 1360 | 1365 | StgWord64 tick)
|
| 1361 | 1366 | {
|
| 1362 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1367 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1363 | 1368 | StgWord depth = 0;
|
| 1364 | 1369 | CostCentreStack *ccs;
|
| 1365 | 1370 | for (ccs = stack; ccs != NULL && ccs != CCS_MAIN; ccs = ccs->prevStack)
|
| ... | ... | @@ -1377,7 +1382,7 @@ void postProfSampleCostCentre(Capability *cap, |
| 1377 | 1382 | depth>0 && ccs != NULL && ccs != CCS_MAIN;
|
| 1378 | 1383 | ccs = ccs->prevStack, depth--)
|
| 1379 | 1384 | postWord32(&eventBuf, ccs->cc->ccID);
|
| 1380 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1385 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1381 | 1386 | }
|
| 1382 | 1387 | |
| 1383 | 1388 | // This event is output at the start of profiling so the tick interval can
|
| ... | ... | @@ -1385,11 +1390,11 @@ void postProfSampleCostCentre(Capability *cap, |
| 1385 | 1390 | // can be calculated from how many samples there are.
|
| 1386 | 1391 | void postProfBegin(void)
|
| 1387 | 1392 | {
|
| 1388 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1393 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1389 | 1394 | postEventHeader(&eventBuf, EVENT_PROF_BEGIN);
|
| 1390 | 1395 | // The interval that each tick was sampled, in nanoseconds
|
| 1391 | 1396 | postWord64(&eventBuf, TimeToNS(RtsFlags.MiscFlags.tickInterval));
|
| 1392 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1397 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1393 | 1398 | }
|
| 1394 | 1399 | #endif /* PROFILING */
|
| 1395 | 1400 | |
| ... | ... | @@ -1415,11 +1420,11 @@ static void postTickyCounterDef(EventsBuf *eb, StgEntCounter *p) |
| 1415 | 1420 | |
| 1416 | 1421 | void postTickyCounterDefs(StgEntCounter *counters)
|
| 1417 | 1422 | {
|
| 1418 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1423 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1419 | 1424 | for (StgEntCounter *p = counters; p != NULL; p = p->link) {
|
| 1420 | 1425 | postTickyCounterDef(&eventBuf, p);
|
| 1421 | 1426 | }
|
| 1422 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1427 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1423 | 1428 | }
|
| 1424 | 1429 | |
| 1425 | 1430 | static void postTickyCounterSample(EventsBuf *eb, StgEntCounter *p)
|
| ... | ... | @@ -1443,13 +1448,13 @@ static void postTickyCounterSample(EventsBuf *eb, StgEntCounter *p) |
| 1443 | 1448 | |
| 1444 | 1449 | void postTickyCounterSamples(StgEntCounter *counters)
|
| 1445 | 1450 | {
|
| 1446 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1451 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1447 | 1452 | ensureRoomForEvent(&eventBuf, EVENT_TICKY_COUNTER_SAMPLE);
|
| 1448 | 1453 | postEventHeader(&eventBuf, EVENT_TICKY_COUNTER_BEGIN_SAMPLE);
|
| 1449 | 1454 | for (StgEntCounter *p = counters; p != NULL; p = p->link) {
|
| 1450 | 1455 | postTickyCounterSample(&eventBuf, p);
|
| 1451 | 1456 | }
|
| 1452 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1457 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1453 | 1458 | }
|
| 1454 | 1459 | #endif /* TICKY_TICKY */
|
| 1455 | 1460 | void postIPE(const InfoProvEnt *ipe)
|
| ... | ... | @@ -1459,7 +1464,7 @@ void postIPE(const InfoProvEnt *ipe) |
| 1459 | 1464 | |
| 1460 | 1465 | // See Note [Maximum event length].
|
| 1461 | 1466 | const StgWord MAX_IPE_STRING_LEN = 65535;
|
| 1462 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1467 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1463 | 1468 | StgWord table_name_len = MIN(strlen(ipe->prov.table_name), MAX_IPE_STRING_LEN);
|
| 1464 | 1469 | StgWord closure_desc_len = MIN(strlen(closure_desc_buf), MAX_IPE_STRING_LEN);
|
| 1465 | 1470 | StgWord ty_desc_len = MIN(strlen(ipe->prov.ty_desc), MAX_IPE_STRING_LEN);
|
| ... | ... | @@ -1489,7 +1494,7 @@ void postIPE(const InfoProvEnt *ipe) |
| 1489 | 1494 | postBuf(&eventBuf, &colon, 1);
|
| 1490 | 1495 | postStringLen(&eventBuf, ipe->prov.src_span, src_span_len);
|
| 1491 | 1496 | |
| 1492 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1497 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1493 | 1498 | }
|
| 1494 | 1499 | |
| 1495 | 1500 | void printAndClearEventBuf (EventsBuf *ebuf)
|
| ... | ... | @@ -1601,14 +1606,21 @@ void flushLocalEventsBuf(Capability *cap) |
| 1601 | 1606 | // Flush all capabilities' event buffers when we already hold all capabilities.
|
| 1602 | 1607 | // Used during forkProcess.
|
| 1603 | 1608 | void flushAllCapsEventsBufs(void)
|
| 1609 | +{
|
|
| 1610 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1611 | + flushAllCapsEventsBufs_();
|
|
| 1612 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1613 | +}
|
|
| 1614 | + |
|
| 1615 | +// Unafe version that does not acquire/release eventBufMutex. You must
|
|
| 1616 | +// already hold the eventBufMutex, which you must do with ACQUIRE_LOCK_ALWAYS!
|
|
| 1617 | +void flushAllCapsEventsBufs_(void)
|
|
| 1604 | 1618 | {
|
| 1605 | 1619 | if (!event_log_writer) {
|
| 1606 | 1620 | return;
|
| 1607 | 1621 | }
|
| 1608 | 1622 | |
| 1609 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1610 | 1623 | printAndClearEventBuf(&eventBuf);
|
| 1611 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1612 | 1624 | |
| 1613 | 1625 | for (unsigned int i=0; i < getNumCapabilities(); i++) {
|
| 1614 | 1626 | flushLocalEventsBuf(getCapability(i));
|
| ... | ... | @@ -1641,9 +1653,9 @@ static void flushEventLog_(Capability **cap USED_IF_THREADS) |
| 1641 | 1653 | return;
|
| 1642 | 1654 | }
|
| 1643 | 1655 | |
| 1644 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1656 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1645 | 1657 | printAndClearEventBuf(&eventBuf);
|
| 1646 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1658 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1647 | 1659 | |
| 1648 | 1660 | #if defined(THREADED_RTS)
|
| 1649 | 1661 | Task *task = newBoundTask();
|
| ... | ... | @@ -18,6 +18,13 @@ |
| 18 | 18 | #if defined(TRACING)
|
| 19 | 19 | |
| 20 | 20 | extern bool eventlog_enabled;
|
| 21 | +#if defined(HAVE_PREEMPTION)
|
|
| 22 | +// Avoid using this mutex directly if at all possible. It is needed in the
|
|
| 23 | +// implementation of forkProcess.
|
|
| 24 | +//
|
|
| 25 | +// All uses of this mutex must use ACQUIRE_LOCK_ALWAYS/RELEASE_LOCK_ALWAYS.
|
|
| 26 | +extern Mutex eventBufMutex;
|
|
| 27 | +#endif
|
|
| 21 | 28 | |
| 22 | 29 | void initEventLogging(void);
|
| 23 | 30 | void restartEventLogging(void);
|
| ... | ... | @@ -27,6 +34,7 @@ void abortEventLogging(void); // #4512 - after fork child needs to abort |
| 27 | 34 | void moreCapEventBufs (uint32_t from, uint32_t to);
|
| 28 | 35 | void flushLocalEventsBuf(Capability *cap);
|
| 29 | 36 | void flushAllCapsEventsBufs(void);
|
| 37 | +void flushAllCapsEventsBufs_(void);
|
|
| 30 | 38 | void flushAllEventsBufs(Capability *cap);
|
| 31 | 39 | |
| 32 | 40 | typedef void (*EventlogInitPost)(void);
|
| ... | ... | @@ -14,6 +14,40 @@ |
| 14 | 14 | |
| 15 | 15 | #pragma once
|
| 16 | 16 | |
| 17 | +/* Note [Threads and preemption]
|
|
| 18 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 19 | + All full-fat OSs that GHC works on have OS threads, and we use them even in
|
|
| 20 | + the non-threaded RTS for a few features:
|
|
| 21 | + * Haskell thread preemption;
|
|
| 22 | + * sample-based profiling;
|
|
| 23 | + * idle GC;
|
|
| 24 | + * periodic eventlog flushing.
|
|
| 25 | + |
|
| 26 | + We use defined(HAVE_PREEMPTION) to decide if these features are implemented
|
|
| 27 | + via OS threads.
|
|
| 28 | + |
|
| 29 | + On platforms like WASM/js we do not have OS threads in any conventional
|
|
| 30 | + sense, and the features above are either not available or are implemented
|
|
| 31 | + differently. See Note [No timer on wasm32].
|
|
| 32 | + |
|
| 33 | + In future if GHC is ported to platforms like bare-metal micro-controllers,
|
|
| 34 | + RTOSs or to run directly under hypervisors then such platforms may also not
|
|
| 35 | + have threads available and they should not define HAVE_PREEMPTION here. Or
|
|
| 36 | + for some micro-controller RTOSs like Zeypher one may have a choice about
|
|
| 37 | + whether to use threads or not (at a size cost). Here would be the right
|
|
| 38 | + place to control whether the feature list above is supported.
|
|
| 39 | + */
|
|
| 40 | +#if defined(wasm32_HOST_ARCH)
|
|
| 41 | + // See Note [No timer on wasm32]
|
|
| 42 | + // To confuse matters, WASM _does_ have pthread.h but it doesnt work.
|
|
| 43 | +#elif defined(HAVE_PTHREAD_H) || defined(HAVE_WINDOWS_H)
|
|
| 44 | +#define HAVE_PREEMPTION
|
|
| 45 | +#else
|
|
| 46 | +#error Decide if this platform has threads and pre-emption or not.
|
|
| 47 | +#endif
|
|
| 48 | +// And JS does all of this differently, without using this bit of the RTS.
|
|
| 49 | + |
|
| 50 | + |
|
| 17 | 51 | #if defined(HAVE_PTHREAD_H) && !defined(mingw32_HOST_OS)
|
| 18 | 52 | |
| 19 | 53 | #if defined(CMINUSMINUS)
|
| ... | ... | @@ -210,9 +244,29 @@ extern bool timedWaitCondition ( Condition* pCond, Mutex* pMut, Time timeout) |
| 210 | 244 | //
|
| 211 | 245 | // Mutexes
|
| 212 | 246 | //
|
| 247 | +// Even in the non-threaded RTS we use threads and mutexes! In particular the
|
|
| 248 | +// timer/ticker is implemented using a thread. And using threads needs locks.
|
|
| 249 | +// In particular we need locks for the data shared between the timer/ticker
|
|
| 250 | +// thread and the thread running the main capability.
|
|
| 251 | +#if defined(HAVE_PREEMPTION)
|
|
| 213 | 252 | extern void initMutex ( Mutex* pMut );
|
| 214 | 253 | extern void closeMutex ( Mutex* pMut );
|
| 215 | 254 | |
| 255 | +// The "always" variants do locking in the threaded and non-threaded RTS.
|
|
| 256 | +// The normal variants below are no-ops in the non-threaded RTS.
|
|
| 257 | +#define ACQUIRE_LOCK_ALWAYS(l) OS_ACQUIRE_LOCK(l)
|
|
| 258 | +#define TRY_ACQUIRE_LOCK_ALWAYS(l) OS_TRY_ACQUIRE_LOCK(l)
|
|
| 259 | +#define RELEASE_LOCK_ALWAYS(l) OS_RELEASE_LOCK(l)
|
|
| 260 | +#define ASSERT_LOCK_HELD_ALWAYS(l) OS_ASSERT_LOCK_HELD(l)
|
|
| 261 | +#else
|
|
| 262 | +// And just to be a bit confusing, the always variants are still no-ops when we
|
|
| 263 | +// do not HAVE_PREEMPTION, since then we don't have threads or mutexes at all.
|
|
| 264 | +#define ACQUIRE_LOCK_ALWAYS(l)
|
|
| 265 | +#define TRY_ACQUIRE_LOCK_ALWAYS(l) 0
|
|
| 266 | +#define RELEASE_LOCK_ALWAYS(l)
|
|
| 267 | +#define ASSERT_LOCK_HELD_ALWAYS(l)
|
|
| 268 | +#endif
|
|
| 269 | + |
|
| 216 | 270 | // Processors and affinity
|
| 217 | 271 | void setThreadAffinity (uint32_t n, uint32_t m);
|
| 218 | 272 | void setThreadNode (uint32_t node);
|
| ... | ... | @@ -228,6 +282,7 @@ void releaseThreadNode (void); |
| 228 | 282 | |
| 229 | 283 | #else
|
| 230 | 284 | |
| 285 | +// No-ops in the non-threaded RTS. See also the _ALWAYS variants above.
|
|
| 231 | 286 | #define ACQUIRE_LOCK(l)
|
| 232 | 287 | #define TRY_ACQUIRE_LOCK(l) 0
|
| 233 | 288 | #define RELEASE_LOCK(l)
|
| ... | ... | @@ -13,6 +13,6 @@ |
| 13 | 13 | |
| 14 | 14 | #pragma once
|
| 15 | 15 | |
| 16 | -void startTimer (void);
|
|
| 17 | -void stopTimer (void);
|
|
| 16 | +void startTimer (void); // Deprecated: see issue #27086
|
|
| 17 | +void stopTimer (void); // Deprecated: see issue #27086
|
|
| 18 | 18 | int rtsTimerSignal (void); // Deprecated: see issue #27073 |
| ... | ... | @@ -103,120 +103,139 @@ |
| 103 | 103 | #include <unistd.h>
|
| 104 | 104 | #include <fcntl.h>
|
| 105 | 105 | |
| 106 | -static Time itimer_interval = DEFAULT_TICK_INTERVAL;
|
|
| 106 | +static Time ticker_interval = DEFAULT_TICK_INTERVAL;
|
|
| 107 | 107 | |
| 108 | -// Should we be firing ticks?
|
|
| 109 | -// Writers to this must hold the mutex below.
|
|
| 110 | -static bool stopped = false;
|
|
| 108 | +// Atomic variable used by client threads to communicate that they want the
|
|
| 109 | +// ticker thread to pause. This communication is one-way, with no
|
|
| 110 | +// acknowledgement.
|
|
| 111 | +static bool pause_request;
|
|
| 111 | 112 | |
| 112 | -// should the ticker thread exit?
|
|
| 113 | -// This can be set without holding the mutex.
|
|
| 114 | -static bool exited = true;
|
|
| 113 | +// Atomic variable used by other threads to communicate that they want the
|
|
| 114 | +// ticker thread to exit.
|
|
| 115 | +static bool exit_request;
|
|
| 116 | +// Used to wait for the ticker thread to terminate after asking it to exit.
|
|
| 117 | +static OSThreadId ticker_thread_id;
|
|
| 115 | 118 | |
| 116 | -// Signaled when we want to (re)start the timer
|
|
| 117 | -static Condition start_cond;
|
|
| 118 | -static Mutex mutex;
|
|
| 119 | -static OSThreadId thread;
|
|
| 119 | +// Fds used with sendFdWakeup to notify the ticker thread that any of the
|
|
| 120 | +// *_request variables above have been set.
|
|
| 121 | +static int notifyfd_r = -1, notifyfd_w = -1;
|
|
| 120 | 122 | |
| 121 | -// fds for interrupting the ticker
|
|
| 122 | -static int interruptfd_r = -1, interruptfd_w = -1;
|
|
| 123 | 123 | |
| 124 | -static void *itimer_thread_func(void *_handle_tick)
|
|
| 124 | +// Asynchronous request. Idempotent.
|
|
| 125 | +void pauseTicker(void)
|
|
| 125 | 126 | {
|
| 126 | - TickProc handle_tick = _handle_tick;
|
|
| 127 | - |
|
| 128 | -#if defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1
|
|
| 129 | - struct pollfd pollfds[1];
|
|
| 127 | + RELEASE_STORE_ALWAYS(&pause_request, true);
|
|
| 128 | + sendFdWakeup(notifyfd_w);
|
|
| 129 | +}
|
|
| 130 | 130 | |
| 131 | - pollfds[0].fd = interruptfd_r;
|
|
| 132 | - pollfds[0].events = POLLIN;
|
|
| 131 | +// Asynchronous request. Idempotent.
|
|
| 132 | +void unpauseTicker(void)
|
|
| 133 | +{
|
|
| 134 | + RELEASE_STORE_ALWAYS(&pause_request, false);
|
|
| 135 | + sendFdWakeup(notifyfd_w);
|
|
| 136 | +}
|
|
| 133 | 137 | |
| 134 | - struct timespec ts = { .tv_sec = TimeToSeconds(itimer_interval)
|
|
| 135 | - , .tv_nsec = TimeToNS(itimer_interval) % 1000000000
|
|
| 136 | - };
|
|
| 137 | -#else
|
|
| 138 | - fd_set selectfds;
|
|
| 139 | - FD_ZERO(&selectfds);
|
|
| 140 | - FD_SET(interruptfd_r, &selectfds);
|
|
| 141 | - |
|
| 142 | - struct timeval tv = { .tv_sec = TimeToSeconds(itimer_interval)
|
|
| 143 | - /* convert remainder time in nanoseconds
|
|
| 144 | - to microseconds, rounding up: */
|
|
| 145 | - , .tv_usec = ((TimeToNS(itimer_interval) % 1000000000)
|
|
| 146 | - + 999) / 1000
|
|
| 147 | - };
|
|
| 148 | -#endif
|
|
| 138 | +// Synchronous. Not idempotent.
|
|
| 139 | +// The ticker is guaranteed stopped after this.
|
|
| 140 | +void exitTicker(void)
|
|
| 141 | +{
|
|
| 142 | + ASSERT(!RELAXED_LOAD_ALWAYS(&exit_request));
|
|
| 143 | + RELEASE_STORE_ALWAYS(&exit_request, true);
|
|
| 144 | + sendFdWakeup(notifyfd_w);
|
|
| 149 | 145 | |
| 150 | - // Relaxed is sufficient: If we don't see that exited was set in one iteration we will
|
|
| 151 | - // see it next time.
|
|
| 152 | - while (!RELAXED_LOAD_ALWAYS(&exited)) {
|
|
| 146 | + // wait for ticker thread to terminate
|
|
| 147 | + if (pthread_join(ticker_thread_id, NULL)) {
|
|
| 148 | + sysErrorBelch("Ticker: Failed to join: %s", strerror(errno));
|
|
| 149 | + }
|
|
| 150 | + closeFdWakeup(notifyfd_r, notifyfd_w);
|
|
| 151 | +}
|
|
| 153 | 152 | |
| 154 | 153 | #if defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1
|
| 155 | - int nfds = 1;
|
|
| 156 | - int nready = ppoll(pollfds, nfds, &ts, NULL);
|
|
| 154 | +typedef struct timespec timeout; // for ppoll()
|
|
| 155 | +typedef struct { struct pollfd pollfds[1]; } fdset;
|
|
| 157 | 156 | #else
|
| 158 | - struct timeval tv_tmp = tv; // copy since select may change this value.
|
|
| 159 | - int nfds = interruptfd_r+1;
|
|
| 160 | - int nready = select(nfds, &selectfds, NULL, NULL, &tv_tmp);
|
|
| 157 | +typedef struct timeval timeout; // for select()
|
|
| 158 | +typedef struct { int fd; fd_set selectfds; } fdset; // need to stash fd
|
|
| 161 | 159 | #endif
|
| 162 | - // In either case (ppoll or select), the result nready is the number
|
|
| 163 | - // of fds that are ready.
|
|
| 164 | - if (RTS_LIKELY(nready == 0)) {
|
|
| 165 | - // Timer expired, not interrupted, continue.
|
|
| 166 | - } else if (nready > 0) {
|
|
| 167 | - // We only monitor one fd (the interruptfd_r), so we know
|
|
| 168 | - // it is that fd that is ready without any further checks.
|
|
| 169 | - collectFdWakeup(interruptfd_r);
|
|
| 170 | - // No further action needed, continue on to handling the final tick
|
|
| 171 | - // and then stop.
|
|
| 172 | - |
|
| 173 | - // Note that we rely on sendFdWakeup and select/poll to provide the
|
|
| 174 | - // happens-before relation. So if 'exited' was set before calling
|
|
| 175 | - // sendFdWakeup, then we should be able to reliably read it after.
|
|
| 176 | - // And thus reading 'exited' in the while loop guard is ok.
|
|
| 160 | + |
|
| 161 | +// local helpers, to hide the difference between ppoll() and select()
|
|
| 162 | +static void poll_init_timeout(timeout *tv, Time t);
|
|
| 163 | +static void poll_init_fdset(fdset *fds, int fd); // single fd only
|
|
| 164 | +// These two return: >0 if fd ready, ==0 if timeout, <0 if error
|
|
| 165 | +static int poll_no_timeout(fdset *fdset);
|
|
| 166 | +static int poll_with_timeout(fdset *fdset, timeout *t);
|
|
| 167 | + |
|
| 168 | +static void *ticker_thread_func(void *_handle_tick)
|
|
| 169 | +{
|
|
| 170 | + TickProc handle_tick = _handle_tick;
|
|
| 171 | + |
|
| 172 | + // Thread-local view of our state. We compare these with the corresponding
|
|
| 173 | + // atomic shared variables used to request state changes.
|
|
| 174 | + bool paused = true; // updated from atomic shared var pause_request
|
|
| 175 | + bool exit = false; // updated from atomic shared var exit_request
|
|
| 176 | + // Note that we start paused.
|
|
| 177 | + |
|
| 178 | + timeout timeout;
|
|
| 179 | + fdset fdset;
|
|
| 180 | + poll_init_timeout(&timeout, ticker_interval);
|
|
| 181 | + poll_init_fdset(&fdset, notifyfd_r);
|
|
| 182 | + |
|
| 183 | + while (!exit) {
|
|
| 184 | + |
|
| 185 | + int notify;
|
|
| 186 | + if (paused) {
|
|
| 187 | + notify = poll_no_timeout(&fdset);
|
|
| 177 | 188 | } else {
|
| 178 | - // While the RTS attempts to mask signals, some foreign libraries
|
|
| 179 | - // that rely on signal delivery may unmask them. Consequently we
|
|
| 180 | - // may see EINTR. See #24610.
|
|
| 181 | - if (errno != EINTR) {
|
|
| 182 | - sysErrorBelch("Ticker: poll failed: %s", strerror(errno));
|
|
| 183 | - }
|
|
| 189 | + notify = poll_with_timeout(&fdset, &timeout);
|
|
| 184 | 190 | }
|
| 185 | 191 | |
| 186 | - // first try a cheap test
|
|
| 187 | - if (RELAXED_LOAD_ALWAYS(&stopped)) {
|
|
| 188 | - OS_ACQUIRE_LOCK(&mutex);
|
|
| 189 | - // should we really stop?
|
|
| 190 | - if (stopped) {
|
|
| 191 | - waitCondition(&start_cond, &mutex);
|
|
| 192 | - }
|
|
| 193 | - OS_RELEASE_LOCK(&mutex);
|
|
| 194 | - } else {
|
|
| 192 | + if (RTS_LIKELY(notify == 0)) {
|
|
| 193 | + // The time expired, no state change notification.
|
|
| 195 | 194 | handle_tick(0);
|
| 195 | + |
|
| 196 | + } else if (notify > 0) {
|
|
| 197 | + // State change notification, check the request variables.
|
|
| 198 | + |
|
| 199 | + // We rely on sendFdWakeup and select/poll to provide the
|
|
| 200 | + // happens-before relation. So if the request variables are set
|
|
| 201 | + // before calling sendFdWakeup, then we should be able to reliably
|
|
| 202 | + // read them here afterwards.
|
|
| 203 | + collectFdWakeup(notifyfd_r);
|
|
| 204 | + |
|
| 205 | + paused = ACQUIRE_LOAD_ALWAYS(&pause_request);
|
|
| 206 | + exit = RELAXED_LOAD_ALWAYS(&exit_request);
|
|
| 207 | + } else if (errno != EINTR) {
|
|
| 208 | + // While the RTS attempts to mask signals, some foreign libraries
|
|
| 209 | + // that rely on signal delivery may unmask them. Consequently we
|
|
| 210 | + // may see EINTR. See #24610.
|
|
| 211 | + sysErrorBelch("Ticker: poll failed: %s", strerror(errno));
|
|
| 196 | 212 | }
|
| 197 | 213 | }
|
| 198 | 214 | |
| 199 | 215 | return NULL;
|
| 200 | 216 | }
|
| 201 | 217 | |
| 218 | +/* Initialise the ticker on startup or re-initialise the ticker after a fork().
|
|
| 219 | + * In the fork case, the thread will not be present, but fds are inherited.
|
|
| 220 | + *
|
|
| 221 | + * The ticker is started in the paused state. Use unpauseTicker to continue.
|
|
| 222 | + */
|
|
| 202 | 223 | void
|
| 203 | 224 | initTicker (Time interval, TickProc handle_tick)
|
| 204 | 225 | {
|
| 205 | - itimer_interval = interval;
|
|
| 206 | - stopped = true;
|
|
| 207 | - exited = false;
|
|
| 226 | + ticker_interval = interval;
|
|
| 227 | + pause_request = true;
|
|
| 228 | + exit_request = false;
|
|
| 229 | + |
|
| 208 | 230 | #if defined(HAVE_SIGNAL_H)
|
| 209 | 231 | sigset_t mask, omask;
|
| 210 | 232 | int sigret;
|
| 211 | 233 | #endif
|
| 212 | 234 | int ret;
|
| 213 | 235 | |
| 214 | - initCondition(&start_cond);
|
|
| 215 | - initMutex(&mutex);
|
|
| 216 | - |
|
| 217 | 236 | /* Open the interrupt fd synchronously.
|
| 218 | 237 | *
|
| 219 | - * We used to do it in itimer_thread_func (i.e. in the timer thread) but it
|
|
| 238 | + * We used to do it in ticker_thread_func (i.e. in the timer thread) but it
|
|
| 220 | 239 | * meant that some user code could run before it and get confused by the
|
| 221 | 240 | * allocation of the timerfd.
|
| 222 | 241 | *
|
| ... | ... | @@ -226,11 +245,11 @@ initTicker (Time interval, TickProc handle_tick) |
| 226 | 245 | * descriptor closed by the first call! (see #20618)
|
| 227 | 246 | */
|
| 228 | 247 | |
| 229 | - if (interruptfd_r != -1) {
|
|
| 248 | + if (notifyfd_r != -1) {
|
|
| 230 | 249 | // don't leak the old file descriptors after a fork (#25280)
|
| 231 | - closeFdWakeup(interruptfd_r, interruptfd_w);
|
|
| 250 | + closeFdWakeup(notifyfd_r, notifyfd_w);
|
|
| 232 | 251 | }
|
| 233 | - newFdWakeup(&interruptfd_r, &interruptfd_w);
|
|
| 252 | + newFdWakeup(¬ifyfd_r, ¬ifyfd_w);
|
|
| 234 | 253 | |
| 235 | 254 | /*
|
| 236 | 255 | * Create the thread with all blockable signals blocked, leaving signal
|
| ... | ... | @@ -242,7 +261,7 @@ initTicker (Time interval, TickProc handle_tick) |
| 242 | 261 | sigfillset(&mask);
|
| 243 | 262 | sigret = pthread_sigmask(SIG_SETMASK, &mask, &omask);
|
| 244 | 263 | #endif
|
| 245 | - ret = createAttachedOSThread(&thread, "ghc_ticker", itimer_thread_func, (void*)handle_tick);
|
|
| 264 | + ret = createAttachedOSThread(&ticker_thread_id, "ghc_ticker", ticker_thread_func, (void*)handle_tick);
|
|
| 246 | 265 | #if defined(HAVE_SIGNAL_H)
|
| 247 | 266 | if (sigret == 0)
|
| 248 | 267 | pthread_sigmask(SIG_SETMASK, &omask, NULL);
|
| ... | ... | @@ -253,47 +272,65 @@ initTicker (Time interval, TickProc handle_tick) |
| 253 | 272 | }
|
| 254 | 273 | }
|
| 255 | 274 | |
| 256 | -void
|
|
| 257 | -startTicker(void)
|
|
| 275 | +/* Implementation of the local helpers, to hide the difference between ppoll()
|
|
| 276 | + * and select().
|
|
| 277 | + */
|
|
| 278 | +#if defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1
|
|
| 279 | +static void poll_init_timeout(timeout *tv, Time t)
|
|
| 258 | 280 | {
|
| 259 | - OS_ACQUIRE_LOCK(&mutex);
|
|
| 260 | - RELAXED_STORE(&stopped, false);
|
|
| 261 | - signalCondition(&start_cond);
|
|
| 262 | - OS_RELEASE_LOCK(&mutex);
|
|
| 281 | + tv->tv_sec = TimeToSeconds(t);
|
|
| 282 | + tv->tv_nsec = TimeToNS(t) % 1000000000;
|
|
| 263 | 283 | }
|
| 264 | 284 | |
| 265 | -/* There may be at most one additional tick fired after a call to this */
|
|
| 266 | -void
|
|
| 267 | -stopTicker(void)
|
|
| 285 | +static void poll_init_fdset(fdset *fds, int fd)
|
|
| 268 | 286 | {
|
| 269 | - OS_ACQUIRE_LOCK(&mutex);
|
|
| 270 | - RELAXED_STORE(&stopped, true);
|
|
| 271 | - OS_RELEASE_LOCK(&mutex);
|
|
| 287 | + fds->pollfds[0].fd = fd;
|
|
| 288 | + fds->pollfds[0].events = POLLIN;
|
|
| 272 | 289 | }
|
| 273 | 290 | |
| 274 | -/* There may be at most one additional tick fired after a call to this */
|
|
| 275 | -void
|
|
| 276 | -exitTicker (bool wait)
|
|
| 291 | +static int poll_no_timeout(fdset *fds)
|
|
| 277 | 292 | {
|
| 278 | - ASSERT(!SEQ_CST_LOAD(&exited));
|
|
| 279 | - SEQ_CST_STORE(&exited, true);
|
|
| 280 | - // ensure that ticker wakes up if stopped
|
|
| 281 | - startTicker();
|
|
| 282 | - sendFdWakeup(interruptfd_w);
|
|
| 283 | - |
|
| 284 | - // wait for ticker to terminate if necessary
|
|
| 285 | - if (wait) {
|
|
| 286 | - if (pthread_join(thread, NULL)) {
|
|
| 287 | - sysErrorBelch("Ticker: Failed to join: %s", strerror(errno));
|
|
| 288 | - }
|
|
| 289 | - closeFdWakeup(interruptfd_r, interruptfd_w);
|
|
| 290 | - closeMutex(&mutex);
|
|
| 291 | - closeCondition(&start_cond);
|
|
| 292 | - } else {
|
|
| 293 | - pthread_detach(thread);
|
|
| 294 | - }
|
|
| 293 | + int nfds = 1;
|
|
| 294 | + return ppoll(fds->pollfds, nfds, NULL, NULL);
|
|
| 295 | +}
|
|
| 296 | + |
|
| 297 | +static int poll_with_timeout(fdset *fds, timeout *ts)
|
|
| 298 | +{
|
|
| 299 | + int nfds = 1;
|
|
| 300 | + return ppoll(fds->pollfds, nfds, ts, NULL);
|
|
| 295 | 301 | }
|
| 296 | 302 | |
| 303 | +#else // select()
|
|
| 304 | + |
|
| 305 | +static void poll_init_timeout(timeout *tv, Time t)
|
|
| 306 | +{
|
|
| 307 | + tv->tv_sec = TimeToSeconds(t);
|
|
| 308 | + /* convert remainder time in nanoseconds to microseconds, rounding up: */
|
|
| 309 | + tv->tv_usec = ((TimeToNS(t) % 1000000000) + 999) / 1000;
|
|
| 310 | +}
|
|
| 311 | + |
|
| 312 | +static void poll_init_fdset(fdset *fds, int fd)
|
|
| 313 | +{
|
|
| 314 | + FD_ZERO(&fds->selectfds);
|
|
| 315 | + FD_SET(fd, &fds->selectfds);
|
|
| 316 | + fds->fd = fd;
|
|
| 317 | +}
|
|
| 318 | + |
|
| 319 | +static int poll_no_timeout(fdset *fds)
|
|
| 320 | +{
|
|
| 321 | + int nfds = fds->fd+1;
|
|
| 322 | + return select(nfds, &fds->selectfds, NULL, NULL, NULL);
|
|
| 323 | +}
|
|
| 324 | + |
|
| 325 | +static int poll_with_timeout(fdset *fds, timeout *tv)
|
|
| 326 | +{
|
|
| 327 | + struct timeval tv_tmp = *tv; // copy since select may change this value.
|
|
| 328 | + int nfds = fds->fd+1;
|
|
| 329 | + return select(nfds, &fds->selectfds, NULL, NULL, &tv_tmp);
|
|
| 330 | +}
|
|
| 331 | +#endif
|
|
| 332 | + |
|
| 333 | +/* This is obsolete, but is used in the unix package for now */
|
|
| 297 | 334 | int
|
| 298 | 335 | rtsTimerSignal(void)
|
| 299 | 336 | {
|
| ... | ... | @@ -9,10 +9,13 @@ |
| 9 | 9 | #include <stdio.h>
|
| 10 | 10 | #include <process.h>
|
| 11 | 11 | |
| 12 | +static Time tick_interval = 0;
|
|
| 12 | 13 | static TickProc tick_proc = NULL;
|
| 14 | + |
|
| 15 | +static Mutex lock; // To protect the timer and state vars below
|
|
| 13 | 16 | static HANDLE timer_queue = NULL;
|
| 14 | 17 | static HANDLE timer = NULL;
|
| 15 | -static Time tick_interval = 0;
|
|
| 18 | +static bool paused;
|
|
| 16 | 19 | |
| 17 | 20 | static VOID CALLBACK tick_callback(
|
| 18 | 21 | PVOID lpParameter STG_UNUSED,
|
| ... | ... | @@ -39,9 +42,12 @@ static VOID CALLBACK tick_callback( |
| 39 | 42 | void
|
| 40 | 43 | initTicker (Time interval, TickProc handle_tick)
|
| 41 | 44 | {
|
| 45 | + ASSERT(timer_queue == NULL);
|
|
| 42 | 46 | tick_interval = interval;
|
| 43 | 47 | tick_proc = handle_tick;
|
| 44 | 48 | |
| 49 | + OS_INIT_LOCK(&lock);
|
|
| 50 | + paused = true; // starts paused
|
|
| 45 | 51 | timer_queue = CreateTimerQueue();
|
| 46 | 52 | if (timer_queue == NULL) {
|
| 47 | 53 | sysErrorBelch("CreateTimerQueue");
|
| ... | ... | @@ -49,39 +55,78 @@ initTicker (Time interval, TickProc handle_tick) |
| 49 | 55 | }
|
| 50 | 56 | }
|
| 51 | 57 | |
| 52 | -void
|
|
| 53 | -startTicker(void)
|
|
| 54 | -{
|
|
| 55 | - BOOL r;
|
|
| 56 | - |
|
| 57 | - r = CreateTimerQueueTimer(&timer,
|
|
| 58 | - timer_queue,
|
|
| 59 | - tick_callback,
|
|
| 60 | - 0,
|
|
| 61 | - 0,
|
|
| 62 | - TimeToMS(tick_interval), // ms
|
|
| 63 | - WT_EXECUTEINTIMERTHREAD);
|
|
| 58 | +static void startTicker(void) {
|
|
| 59 | + ASSERT(timer_queue != NULL && timer == NULL);
|
|
| 60 | + DWORD interval = TimeToMS(tick_interval); // ms
|
|
| 61 | + BOOL r = CreateTimerQueueTimer(&timer,
|
|
| 62 | + timer_queue,
|
|
| 63 | + tick_callback,
|
|
| 64 | + NULL, // callback param
|
|
| 65 | + interval, // inital interval
|
|
| 66 | + interval, // recurrant interval
|
|
| 67 | + WT_EXECUTEINTIMERTHREAD);
|
|
| 68 | + //TODO: using WT_EXECUTEINTIMERTHREAD is fine for context switching, and
|
|
| 69 | + // plausibly also ok for profile sampling but is way out for eventlog
|
|
| 70 | + // flushing. The eventlog flush does a global synchronisation of all
|
|
| 71 | + // capabilities and I/O! And with eventlog providers, it calls arbitrary
|
|
| 72 | + // user code. This is not ok! See issue #27250.
|
|
| 64 | 73 | if (r == 0) {
|
| 65 | 74 | sysErrorBelch("CreateTimerQueueTimer");
|
| 66 | 75 | stg_exit(EXIT_FAILURE);
|
| 67 | 76 | }
|
| 77 | + ASSERT(timer != NULL);
|
|
| 68 | 78 | }
|
| 69 | 79 | |
| 70 | -void
|
|
| 71 | -stopTicker(void)
|
|
| 80 | +static void stopTicker(bool synchronous) {
|
|
| 81 | + ASSERT(timer_queue != NULL && timer != NULL);
|
|
| 82 | + // From the docs for DeleteTimerQueueTimer:
|
|
| 83 | + // If this parameter is INVALID_HANDLE_VALUE, the function waits for any
|
|
| 84 | + // running timer callback functions to complete before returning.
|
|
| 85 | + HANDLE completion = synchronous ? INVALID_HANDLE_VALUE : NULL;
|
|
| 86 | + DeleteTimerQueueTimer(timer_queue, timer, completion);
|
|
| 87 | + timer = NULL;
|
|
| 88 | +}
|
|
| 89 | + |
|
| 90 | +// Asynchronous. Idempotent.
|
|
| 91 | +void pauseTicker(void)
|
|
| 72 | 92 | {
|
| 73 | - if (timer_queue != NULL && timer != NULL) {
|
|
| 74 | - DeleteTimerQueueTimer(timer_queue, timer, NULL);
|
|
| 75 | - timer = NULL;
|
|
| 93 | + OS_ACQUIRE_LOCK(&lock);
|
|
| 94 | + if (!paused) {
|
|
| 95 | + /* pauseTicker is called from within the handle_tick, so stopping
|
|
| 96 | + * the ticker here /must/ be asynchronous or we will deadlock! */
|
|
| 97 | + stopTicker(false /* asynchronous */);
|
|
| 76 | 98 | }
|
| 99 | + paused = true;
|
|
| 100 | + OS_RELEASE_LOCK(&lock);
|
|
| 77 | 101 | }
|
| 78 | 102 | |
| 79 | -void
|
|
| 80 | -exitTicker (bool wait)
|
|
| 103 | +// Asynchronous. Idempotent.
|
|
| 104 | +void unpauseTicker(void)
|
|
| 105 | +{
|
|
| 106 | + OS_ACQUIRE_LOCK(&lock);
|
|
| 107 | + if (paused) {
|
|
| 108 | + startTicker();
|
|
| 109 | + }
|
|
| 110 | + paused = false;
|
|
| 111 | + OS_RELEASE_LOCK(&lock);
|
|
| 112 | +}
|
|
| 113 | + |
|
| 114 | +void exitTicker(void)
|
|
| 81 | 115 | {
|
| 82 | - stopTicker();
|
|
| 83 | - if (timer_queue != NULL) {
|
|
| 84 | - DeleteTimerQueueEx(timer_queue, wait ? INVALID_HANDLE_VALUE : NULL);
|
|
| 85 | - timer_queue = NULL;
|
|
| 116 | + ASSERT(timer_queue != NULL);
|
|
| 117 | + |
|
| 118 | + OS_ACQUIRE_LOCK(&lock);
|
|
| 119 | + if (!paused) {
|
|
| 120 | + stopTicker(true /* synchronous */);
|
|
| 86 | 121 | }
|
| 122 | + OS_RELEASE_LOCK(&lock);
|
|
| 123 | + |
|
| 124 | + // From the docs for DeleteTimerQueueEx:
|
|
| 125 | + // If this parameter is INVALID_HANDLE_VALUE, the function waits
|
|
| 126 | + // for all callback functions to complete before returning.
|
|
| 127 | + // This is a belt-and-braces approach to ensuring exitTicker is synchronous,
|
|
| 128 | + // since stopTicker(true) is already synchronous and there's only one timer.
|
|
| 129 | + HANDLE completion = INVALID_HANDLE_VALUE;
|
|
| 130 | + DeleteTimerQueueEx(timer_queue, completion);
|
|
| 131 | + timer_queue = NULL;
|
|
| 87 | 132 | } |
| 1 | +{-# OPTIONS_GHC -fno-omit-yields #-}
|
|
| 2 | + |
|
| 3 | +import Control.Monad
|
|
| 4 | +import Control.Monad.ST
|
|
| 5 | +import Control.Concurrent
|
|
| 6 | +import Control.Exception
|
|
| 7 | +import System.Exit
|
|
| 8 | +import System.Mem
|
|
| 9 | +import GHC.Arr
|
|
| 10 | +import Prelude hiding (init)
|
|
| 11 | + |
|
| 12 | +-- Test thread fairness:
|
|
| 13 | +-- run two cpu-bound threads concurrently for a second,
|
|
| 14 | +-- each counts how many operations it can perform until signaled to stop
|
|
| 15 | +-- expect a balance between the two with no more than a 75% imperfection.
|
|
| 16 | +-- Yes, 75%! On the CI machines we occasionally observe extraordinary levels
|
|
| 17 | +-- of unfairness: nearly 60% in some cases. We don't want this to become a
|
|
| 18 | +-- fragile test that is ignored, so we use an extreme bound. This should still
|
|
| 19 | +-- catch gross breakage.
|
|
| 20 | +--
|
|
| 21 | +-- This _should_ detect if the interval timer is not working, or if thread
|
|
| 22 | +-- context switching is messed up. We can expect failure if we force a
|
|
| 23 | +-- contex switch interval of more than half the test time, i.e. more than 0.5s
|
|
| 24 | +--
|
|
| 25 | +-- We run the test twice, with allocating and non-allocating worker threads.
|
|
| 26 | +-- The -fno-omit-yields above is crucial for worker_nonalloc below, or it never
|
|
| 27 | +-- gets interrupted and thus no context switches.
|
|
| 28 | + |
|
| 29 | +main :: IO ()
|
|
| 30 | +main = do
|
|
| 31 | + test worker_alloc
|
|
| 32 | + performMajorGC
|
|
| 33 | + test worker_nonalloc
|
|
| 34 | + |
|
| 35 | +test :: Worker -> IO ()
|
|
| 36 | +test worker = do
|
|
| 37 | + stop <- newEmptyMVar
|
|
| 38 | + res1 <- newEmptyMVar
|
|
| 39 | + res2 <- newEmptyMVar
|
|
| 40 | + _ <- forkIO (worker stop >>= putMVar res1)
|
|
| 41 | + _ <- forkIO (worker stop >>= putMVar res2)
|
|
| 42 | + threadDelay 300_000
|
|
| 43 | + -- Let them run for 300ms. The default context switch interval is 20ms.
|
|
| 44 | + -- This gives time for 15 context switches, so this _should_ be enough
|
|
| 45 | + -- to get less than 10% unfairness. And on most platforms it is enough.
|
|
| 46 | + -- But OSX! Oh OSX! How do I loath thee? Let me count++ the ways.
|
|
| 47 | + -- To avoid a fragile test, we use a 75% unfairness threshold.
|
|
| 48 | + putMVar stop ()
|
|
| 49 | + count1 <- takeMVar res1
|
|
| 50 | + count2 <- takeMVar res2
|
|
| 51 | + let balance :: Double
|
|
| 52 | + balance = abs ((fromIntegral count1 - fromIntegral count2)
|
|
| 53 | + / fromIntegral count2)
|
|
| 54 | + when (balance > 0.75) $ do
|
|
| 55 | + putStrLn "Schedule fairness more than 75% tolerance:"
|
|
| 56 | + putStrLn $ "imperfection: " ++ show (balance * 100) ++ "%"
|
|
| 57 | + putStrLn $ "work counts: " ++ show (count1, count2)
|
|
| 58 | + exitFailure
|
|
| 59 | + |
|
| 60 | +type Worker = MVar () -> IO Int
|
|
| 61 | + |
|
| 62 | +-- count how many iterations we can calculate until we're signaled to stop
|
|
| 63 | +worker_template :: IO a -> (a -> IO ()) -> MVar () -> IO Int
|
|
| 64 | +worker_template init iter stop = do
|
|
| 65 | + a <- init
|
|
| 66 | + go a 0
|
|
| 67 | + where
|
|
| 68 | + go a !count = do
|
|
| 69 | + ok <- tryReadMVar stop
|
|
| 70 | + case ok of
|
|
| 71 | + Just () -> return count
|
|
| 72 | + Nothing -> do
|
|
| 73 | + iter a
|
|
| 74 | + go a (count + 1)
|
|
| 75 | + |
|
| 76 | + |
|
| 77 | +-- the allocating worker
|
|
| 78 | +{-# NOINLINE worker_alloc #-}
|
|
| 79 | +worker_alloc :: Worker
|
|
| 80 | +worker_alloc =
|
|
| 81 | + worker_template
|
|
| 82 | + (return 18)
|
|
| 83 | + (\n -> evaluate (fib n) >> return ())
|
|
| 84 | + |
|
| 85 | +-- by forcing this to be Integer we cause lots of allocation!
|
|
| 86 | +fib :: Integer -> Integer
|
|
| 87 | +fib 0 = 0
|
|
| 88 | +fib 1 = 1
|
|
| 89 | +fib n = fib (n-1) + fib (n-2)
|
|
| 90 | + |
|
| 91 | + |
|
| 92 | +-- the non-allocating worker
|
|
| 93 | +{-# NOINLINE worker_nonalloc #-}
|
|
| 94 | +worker_nonalloc :: Worker
|
|
| 95 | +worker_nonalloc =
|
|
| 96 | + worker_template
|
|
| 97 | + (stToIO $ newSTArray (0,50_000) 42)
|
|
| 98 | + (\arr -> stToIO $ arrrev arr)
|
|
| 99 | + |
|
| 100 | +arrrev :: STArray s Int Int -> ST s ()
|
|
| 101 | +arrrev arr =
|
|
| 102 | + let (i,j) = boundsSTArray arr
|
|
| 103 | + in arrrev_go arr i j
|
|
| 104 | + |
|
| 105 | +{-# NOINLINE arrrev_go #-}
|
|
| 106 | +arrrev_go :: STArray s Int Int -> Int -> Int -> ST s ()
|
|
| 107 | +arrrev_go !_ !i !j | i >= j = return ()
|
|
| 108 | +arrrev_go !arr !i !j = do
|
|
| 109 | + x <- readSTArray arr i
|
|
| 110 | + y <- readSTArray arr j
|
|
| 111 | + writeSTArray arr i y
|
|
| 112 | + writeSTArray arr j x
|
|
| 113 | + arrrev_go arr (i+1) (j-1)
|
|
| 114 | + |
| ... | ... | @@ -325,3 +325,15 @@ test('T26341b' |
| 325 | 325 | # test uses pipe operations which are not supported by the JS/wasm backends
|
| 326 | 326 | , when(arch('wasm32') or arch('javascript'), skip)
|
| 327 | 327 | , compile_and_run, ['-package process'])
|
| 328 | + |
|
| 329 | +# Scheduler (very rough) fairness
|
|
| 330 | +test('T27105',
|
|
| 331 | + [when(arch('wasm32'), skip), # same reason as T367_letnoescape
|
|
| 332 | + run_timeout_multiplier(0.05)], # we expect this to run for ~2s
|
|
| 333 | + compile_and_run, [''])
|
|
| 334 | +test('T27105_fail',
|
|
| 335 | + [when(arch('wasm32'), skip),
|
|
| 336 | + # And we can expect it to fail if we context switch too coarsely
|
|
| 337 | + extra_run_opts('+RTS -C0.2 -RTS'), expect_fail,
|
|
| 338 | + run_timeout_multiplier(0.05)],
|
|
| 339 | + multimod_compile_and_run, ['T27105.hs', '']) |