Duncan Coutts pushed to branch wip/dcoutts/issue-27105-stopTicker at Glasgow Haskell Compiler / GHC

Commits:

15 changed files:

Changes:

  • changelog.d/T27105
    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
    +}

  • rts/Capability.c
    ... ... @@ -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
     
    

  • rts/RtsStartup.c
    ... ... @@ -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
    

  • rts/Schedule.c
    ... ... @@ -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
    

  • rts/Ticker.h
    ... ... @@ -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"

  • rts/Timer.c
    ... ... @@ -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
     }

  • rts/Timer.h
    ... ... @@ -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"

  • rts/eventlog/EventLog.c
    ... ... @@ -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();
    

  • rts/eventlog/EventLog.h
    ... ... @@ -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);
    

  • rts/include/rts/OSThreads.h
    ... ... @@ -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)
    

  • rts/include/rts/Timer.h
    ... ... @@ -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

  • rts/posix/Ticker.c
    ... ... @@ -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(&notifyfd_r, &notifyfd_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
     {
    

  • rts/win32/Ticker.c
    ... ... @@ -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
     }

  • testsuite/tests/concurrent/should_run/T27105.hs
    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
    +

  • testsuite/tests/concurrent/should_run/all.T
    ... ... @@ -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', ''])