Duncan Coutts pushed to branch wip/io-manager-deadlock-detection at Glasgow Haskell Compiler / GHC

Commits:

10 changed files:

Changes:

  • rts/Capability.c
    ... ... @@ -31,6 +31,7 @@
    31 31
     #include "sm/OSMem.h"
    
    32 32
     #include "sm/BlockAlloc.h" // for countBlocks()
    
    33 33
     #include "IOManager.h"
    
    34
    +#include "IdleGC.h"
    
    34 35
     
    
    35 36
     #include <string.h>
    
    36 37
     
    
    ... ... @@ -88,7 +89,7 @@ STATIC_INLINE bool
    88 89
     globalWorkToDo (void)
    
    89 90
     {
    
    90 91
         return getSchedState() >= SCHED_INTERRUPTING
    
    91
    -      || getRecentActivity() == ACTIVITY_INACTIVE; // need to check for deadlock
    
    92
    +        || isIdleGcPending();
    
    92 93
     }
    
    93 94
     #endif
    
    94 95
     
    

  • rts/IdleGC.c
    1
    +/* -----------------------------------------------------------------------------
    
    2
    + *
    
    3
    + * (c) The GHC Team, 1995-2005
    
    4
    + *
    
    5
    + * Idle GC: tracking of when to perform a GC during idle time.
    
    6
    + *
    
    7
    + * ---------------------------------------------------------------------------*/
    
    8
    +
    
    9
    +#include "rts/PosixSource.h"
    
    10
    +#include "Rts.h"
    
    11
    +
    
    12
    +#include "IdleGC.h"
    
    13
    +#include "Timer.h"
    
    14
    +#include "Schedule.h"
    
    15
    +
    
    16
    +/*
    
    17
    + Note [GC During Idle Time]
    
    18
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    19
    + A major GC can be performed during idle time (i.e. when no Haskell threads
    
    20
    + are ready to run).  This can be beneficial for two reasons.  First, running
    
    21
    + the GC during idle time makes it less likely that a GC will be triggered when
    
    22
    + the process is active, increasing apparent responsiveness. Second, these idle
    
    23
    + time GCs allow finalizers to run, preventing resources from being held
    
    24
    + indefinitely when the process is otherwise idle for extended periods.
    
    25
    +
    
    26
    + There are two runtime RTS options to control idle time GC timing.  The primary
    
    27
    + control is set by `-I<n>`, which specifies the minimum period of time the
    
    28
    + process must be idle before a GC is automatically triggered.  It defaults to
    
    29
    + 0.3 seconds for the threaded runtime and 0 (which disables idle GC entirely)
    
    30
    + for the non-threaded runtime.  For certain workflows, the 0.3 second delay for
    
    31
    + the threaded RTS may be too small.  If an application must process an extended
    
    32
    + burst of short-lived requests occurring a couple of times a second, it may go
    
    33
    + idle for >0.3 seconds frequently, resulting in potentially dozens of major
    
    34
    + GCs triggered every minute (and resulting heavy CPU load) while the burst
    
    35
    + lasts.  Setting the `-I<n>` value higher will prevent this flurry of major GCs,
    
    36
    + though there is a danger that setting the value too high will prevent automatic
    
    37
    + GCs entirely, if the process never gets a chance to go idle for long enough to
    
    38
    + meet the larger threshold.
    
    39
    +
    
    40
    + In this case, the second control, set by `-Iw<n>` may be helpful.  For example,
    
    41
    + setting `-I0.3 -Iw30` triggers automatic GCs after only 0.3 seconds of idle
    
    42
    + time, but subject to a minimum delay *between* automatic GCs of at least 30
    
    43
    + seconds.  This is likely to work well for applications that must process a
    
    44
    + nearly constant stream of frequent, short-lived requests, ensuring that
    
    45
    + automatic GCs are triggered promptly when the process goes idle while limiting
    
    46
    + the overall frequency of such GCs.  (The default is `-Iw0`, meaning no limit on
    
    47
    + frequency of GCs.)
    
    48
    +
    
    49
    + Automatic GC timing is implemented below using two count-down timers.  The
    
    50
    + `idle_ticks_to_gc` timer counts down the `-I<n>` setting: it is initialized
    
    51
    + when the process goes idle and counts down idle time before an automatic GC
    
    52
    + becomes possible.  The `inter_gc_ticks_to_gc` counts down the `-Iw<n>` setting:
    
    53
    + it is initialized when an automatic GC is actually performed, and holds off the
    
    54
    + next automatic GC until its count expires, limiting the overall frequency of
    
    55
    + automatic GCs.  Both timers must expire before an automatic GC is triggered.
    
    56
    +
    
    57
    + See issue #11134 for additional detail.
    
    58
    + 
    
    59
    + See also Note [Deadlock detection].
    
    60
    +*/
    
    61
    +
    
    62
    +
    
    63
    +/* Flag that tracks whether we have done any execution in this time
    
    64
    + * slice, and controls the disabling of the interval timer.
    
    65
    + *
    
    66
    + * The timer interrupt transitions ACTIVITY_YES into
    
    67
    + * ACTIVITY_MAYBE_NO, waits for RtsFlags.GcFlags.idleGCDelayTime,
    
    68
    + * and then:
    
    69
    + *   - if idle GC is on, set ACTIVITY_INACTIVE and wakeUpRts()
    
    70
    + *   - if idle GC is off, set ACTIVITY_DONE_GC and stopTimer()
    
    71
    + *
    
    72
    + * If the scheduler finds ACTIVITY_INACTIVE, then it sets
    
    73
    + * ACTIVITY_DONE_GC, performs the GC and calls stopTimer().
    
    74
    + *
    
    75
    + * If the scheduler finds ACTIVITY_DONE_GC and it has a thread to run,
    
    76
    + * it enables the timer again with startTimer().
    
    77
    + */
    
    78
    +enum RecentActivity {
    
    79
    +    // the RTS is active
    
    80
    +    ACTIVITY_YES      = 0,
    
    81
    +    // no activity since the last timer signal
    
    82
    +    ACTIVITY_MAYBE_NO = 1,
    
    83
    +    // RtsFlags.GcFlags.idleGCDelayTime has passed with no activity
    
    84
    +    ACTIVITY_INACTIVE = 2,
    
    85
    +    // like ACTIVITY_INACTIVE, but we've done a GC too (if idle GC is
    
    86
    +    // enabled) and the interval timer is now turned off.
    
    87
    +    ACTIVITY_DONE_GC  = 3,
    
    88
    +};
    
    89
    +
    
    90
    +/* Recent activity flag.
    
    91
    + *
    
    92
    + * Note we must always use atomic operations for it, even in the non-threaded
    
    93
    + * runtime, since the timer tick runs in a separate thread.
    
    94
    + *
    
    95
    + * We don't need an initIdleGc. All it would do is set
    
    96
    + * recent_activity = ACTIVITY_YES; which we can do statically.
    
    97
    + */
    
    98
    +StgWord recent_activity = ACTIVITY_YES;
    
    99
    +
    
    100
    +static inline enum RecentActivity
    
    101
    +setRecentActivity(enum RecentActivity new_value)
    
    102
    +{
    
    103
    +    StgWord old = SEQ_CST_XCHG_ALWAYS((StgPtr) &recent_activity, (StgWord) new_value);
    
    104
    +    return (enum RecentActivity) old;
    
    105
    +}
    
    106
    +
    
    107
    +static inline enum RecentActivity
    
    108
    +getRecentActivity(void)
    
    109
    +{
    
    110
    +    return (enum RecentActivity) RELAXED_LOAD_ALWAYS(&recent_activity);
    
    111
    +}
    
    112
    +
    
    113
    +bool isIdleGcPending(void)
    
    114
    +{
    
    115
    +    return (getRecentActivity() == ACTIVITY_INACTIVE);
    
    116
    +}
    
    117
    +
    
    118
    +#if defined(THREADED_IDLEGC)
    
    119
    +
    
    120
    +/* - countdown for minimum idle time before we start a GC (set by -I) */
    
    121
    +static int idle_ticks_to_gc = 0;
    
    122
    +
    
    123
    +/* - countdown for minimum time *between* idle GCs (set by -Iw) */
    
    124
    +static int inter_gc_ticks_to_gc = 0;
    
    125
    +
    
    126
    +/*
    
    127
    + * Called from handle_tick().
    
    128
    + */
    
    129
    +void handleIdleGcTick(void)
    
    130
    +{
    
    131
    +  /*
    
    132
    +   * If we've been inactive for idleGCDelayTime (set by +RTS
    
    133
    +   * -I), tell the scheduler to wake up and do a GC, to check
    
    134
    +   * for threads that are deadlocked.  However, ensure we wait
    
    135
    +   * at least interIdleGCWait (+RTS -Iw) between idle GCs.
    
    136
    +   */
    
    137
    +  switch (getRecentActivity()) {
    
    138
    +  case ACTIVITY_YES:
    
    139
    +      setRecentActivity(ACTIVITY_MAYBE_NO);
    
    140
    +      idle_ticks_to_gc = RtsFlags.GcFlags.idleGCDelayTime /
    
    141
    +                         RtsFlags.MiscFlags.tickInterval;
    
    142
    +      break;
    
    143
    +  case ACTIVITY_MAYBE_NO:
    
    144
    +      if (idle_ticks_to_gc == 0 && inter_gc_ticks_to_gc == 0) {
    
    145
    +          if (RtsFlags.GcFlags.doIdleGC) {
    
    146
    +              setRecentActivity(ACTIVITY_INACTIVE);
    
    147
    +              inter_gc_ticks_to_gc = RtsFlags.GcFlags.interIdleGCWait /
    
    148
    +                                     RtsFlags.MiscFlags.tickInterval;
    
    149
    +              wakeUpRts();
    
    150
    +              // The scheduler will call pauseTimer() when it has done
    
    151
    +              // the GC.
    
    152
    +          } else {
    
    153
    +              setRecentActivity(ACTIVITY_DONE_GC);
    
    154
    +              // disable timer signals (see #1623, #5991, #9105)
    
    155
    +              // but only if we're not profiling (e.g. passed -h or -p RTS
    
    156
    +              // flags). If we are profiling we need to keep the timer active
    
    157
    +              // so that samples continue to be collected.
    
    158
    +#if defined(PROFILING)
    
    159
    +              if (!(RtsFlags.ProfFlags.doHeapProfile
    
    160
    +                    || RtsFlags.CcFlags.doCostCentres)) {
    
    161
    +                  pauseTimer();
    
    162
    +              }
    
    163
    +#else
    
    164
    +              pauseTimer();
    
    165
    +#endif
    
    166
    +          }
    
    167
    +      } else {
    
    168
    +          if (idle_ticks_to_gc) idle_ticks_to_gc--;
    
    169
    +          if (inter_gc_ticks_to_gc) inter_gc_ticks_to_gc--;
    
    170
    +      }
    
    171
    +      break;
    
    172
    +  default:
    
    173
    +      break;
    
    174
    +  }
    
    175
    +}
    
    176
    +
    
    177
    +#else // !defined(THREADED_IDLEGC)
    
    178
    +
    
    179
    +/* Note [Idle GC without preemption]
    
    180
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    181
    +
    
    182
    +On non-threaded platforms without pre-emption, e.g. wasm, we have to use a
    
    183
    +different design for scheduling idle GC (and thus deadlock detection, see
    
    184
    +Note [Deadlock detection]).
    
    185
    +
    
    186
    +Instead of a ticker thread that interrupts the idle capability after some delay,
    
    187
    +we arrange for the I/O manager to only sleep for a maximum of the idle GC delay
    
    188
    +time. The function getNextIdleGcDelayTime() returns this time, accounting for
    
    189
    +the interIdleGCWait.
    
    190
    +
    
    191
    +Then if that timeout is reached the I/O manager calls notifyIdleGcIdle(),
    
    192
    +which schedules an idle GC (by seting the recent activity state). The I/O
    
    193
    +manager must then behave as if it had been interrupted (as happens with the
    
    194
    +threaded idle GC design).
    
    195
    +*/
    
    196
    +
    
    197
    +static Time last_idle_gc_time = -TIME_MAX;
    
    198
    +
    
    199
    +Time getNextIdleGcDelayTime()
    
    200
    +{
    
    201
    +    Time now   = getProcessElapsedTime();
    
    202
    +    Time delay =
    
    203
    +        stg_max (last_idle_gc_time + RtsFlags.GcFlags.interIdleGCWait - now,
    
    204
    +                 RtsFlags.GcFlags.idleGCDelayTime);
    
    205
    +    ASSERT(delay >= 0);
    
    206
    +    return delay;
    
    207
    +}
    
    208
    +
    
    209
    +void notifyIdleGcIdle(bool deadlocked)
    
    210
    +{
    
    211
    +    if (RtsFlags.GcFlags.doIdleGC || deadlocked) {
    
    212
    +        setRecentActivity(ACTIVITY_INACTIVE);
    
    213
    +    }
    
    214
    +}
    
    215
    +
    
    216
    +#endif // defined(THREADED_IDLEGC)
    
    217
    +
    
    218
    +void notifyIdleGcActive(void)
    
    219
    +{
    
    220
    +    switch (getRecentActivity())
    
    221
    +    {
    
    222
    +    case ACTIVITY_DONE_GC: {
    
    223
    +        // ACTIVITY_DONE_GC means we turned off the timer signal to
    
    224
    +        // conserve power (see #1623).  Re-enable it here.
    
    225
    +        uint32_t prev;
    
    226
    +        prev = setRecentActivity(ACTIVITY_YES);
    
    227
    +        if (prev == ACTIVITY_DONE_GC) {
    
    228
    +#if !defined(PROFILING)
    
    229
    +            unpauseTimer();
    
    230
    +#endif
    
    231
    +        }
    
    232
    +        break;
    
    233
    +    }
    
    234
    +    case ACTIVITY_INACTIVE:
    
    235
    +        // If we reached ACTIVITY_INACTIVE, then don't reset it until
    
    236
    +        // we've done the GC.  The thread running here might just be
    
    237
    +        // the IO manager thread that handle_tick() woke up via
    
    238
    +        // wakeUpRts().
    
    239
    +        break;
    
    240
    +    default:
    
    241
    +        setRecentActivity(ACTIVITY_YES);
    
    242
    +    }
    
    243
    +}
    
    244
    +
    
    245
    +void notifyIdleGcDone(bool force_major)
    
    246
    +{
    
    247
    +    switch (getRecentActivity()) {
    
    248
    +    case ACTIVITY_INACTIVE:
    
    249
    +        if (force_major) {
    
    250
    +            // We are doing a GC because the system has been idle for a
    
    251
    +            // timeslice and we need to check for deadlock.  Record the
    
    252
    +            // fact that we've done a GC and turn off the timer signal;
    
    253
    +            // it will get re-enabled if we run any threads after the GC.
    
    254
    +            setRecentActivity(ACTIVITY_DONE_GC);
    
    255
    +#if !defined(PROFILING)
    
    256
    +            pauseTimer();
    
    257
    +#endif
    
    258
    +#if !defined(THREADED_IDLEGC)
    
    259
    +            last_idle_gc_time = getProcessElapsedTime();
    
    260
    +#endif
    
    261
    +            break;
    
    262
    +        }
    
    263
    +        // fall through...
    
    264
    +
    
    265
    +    case ACTIVITY_MAYBE_NO:
    
    266
    +        // the GC might have taken long enough for the timer to set
    
    267
    +        // recent_activity = ACTIVITY_MAYBE_NO or ACTIVITY_INACTIVE,
    
    268
    +        // but we aren't necessarily deadlocked:
    
    269
    +        setRecentActivity(ACTIVITY_YES);
    
    270
    +        break;
    
    271
    +
    
    272
    +    case ACTIVITY_DONE_GC:
    
    273
    +        // If we are actually active, the scheduler will reset the
    
    274
    +        // recent_activity flag and re-enable the timer.
    
    275
    +        break;
    
    276
    +    case ACTIVITY_YES:
    
    277
    +        break;
    
    278
    +    }
    
    279
    +}

  • rts/IdleGC.h
    1
    +/* -----------------------------------------------------------------------------
    
    2
    + *
    
    3
    + * (c) The GHC Team, 1995-2005
    
    4
    + *
    
    5
    + * Idle GC: tracking of when to perform a GC during idle time.
    
    6
    + *
    
    7
    + * Interacts closely with the scheduler and timer tick.
    
    8
    + *
    
    9
    + * ---------------------------------------------------------------------------*/
    
    10
    +
    
    11
    +#include "BeginPrivate.h"
    
    12
    +
    
    13
    +/* See Note [GC During Idle Time] */
    
    14
    +
    
    15
    +#if defined(HAVE_PREEMPTION)
    
    16
    +#define THREADED_IDLEGC
    
    17
    +#else
    
    18
    +/* See Note [Idle GC without preemption] */
    
    19
    +#endif
    
    20
    +
    
    21
    +
    
    22
    +/* Are we in a state where we are want an idle GC to occur?
    
    23
    + * Used in Capability globalWorkToDo() and Schedule scheduleDetectDeadlock.
    
    24
    + */
    
    25
    +bool isIdleGcPending(void);
    
    26
    +
    
    27
    +/* Called from schedule() */
    
    28
    +void notifyIdleGcActive(void);
    
    29
    +
    
    30
    +/* Called from schedule(), specifically scheduleDoGC() */
    
    31
    +void notifyIdleGcDone(bool force_major);
    
    32
    +
    
    33
    +#if defined(THREADED_IDLEGC)
    
    34
    +
    
    35
    +/* Called from handle_tick() */
    
    36
    +void handleIdleGcTick(void);
    
    37
    +
    
    38
    +#else // !defined(THREADED_IDLEGC)
    
    39
    +
    
    40
    +/* Called from I/O managers before waiting */
    
    41
    +Time getNextIdleGcDelayTime(void);
    
    42
    +
    
    43
    +/* Called from I/O managers after waiting */
    
    44
    +void notifyIdleGcIdle(bool deadlocked);
    
    45
    +
    
    46
    +#endif
    
    47
    +
    
    48
    +#include "EndPrivate.h"

  • rts/Schedule.c
    ... ... @@ -42,6 +42,7 @@
    42 42
     #include "Threads.h"
    
    43 43
     #include "Timer.h"
    
    44 44
     #include "Ticker.h"
    
    45
    +#include "IdleGC.h"
    
    45 46
     #include "ThreadPaused.h"
    
    46 47
     #include "ThreadLabels.h"
    
    47 48
     #include "Messages.h"
    
    ... ... @@ -84,14 +85,6 @@ uint64_t allocated_bytes_at_heapoverflow = 0;
    84 85
      */
    
    85 86
     bool heap_overflow = false;
    
    86 87
     
    
    87
    -/* flag that tracks whether we have done any execution in this time slice.
    
    88
    - * LOCK: currently none, perhaps we should lock (but needs to be
    
    89
    - * updated in the fast path of the scheduler).
    
    90
    - *
    
    91
    - * NB. must be StgWord, we do xchg() on it.
    
    92
    - */
    
    93
    -StgWord recent_activity = ACTIVITY_YES;
    
    94
    -
    
    95 88
     /* if this flag is set as well, give up execution
    
    96 89
      * LOCK: none (changes monotonically)
    
    97 90
      */
    
    ... ... @@ -437,29 +430,8 @@ run_thread:
    437 430
         dirty_TSO(cap,t);
    
    438 431
         dirty_STACK(cap,t->stackobj);
    
    439 432
     
    
    440
    -    switch (getRecentActivity())
    
    441
    -    {
    
    442
    -    case ACTIVITY_DONE_GC: {
    
    443
    -        // ACTIVITY_DONE_GC means we turned off the timer signal to
    
    444
    -        // conserve power (see #1623).  Re-enable it here.
    
    445
    -        uint32_t prev;
    
    446
    -        prev = setRecentActivity(ACTIVITY_YES);
    
    447
    -        if (prev == ACTIVITY_DONE_GC) {
    
    448
    -#if !defined(PROFILING)
    
    449
    -            unpauseTimer();
    
    450
    -#endif
    
    451
    -        }
    
    452
    -        break;
    
    453
    -    }
    
    454
    -    case ACTIVITY_INACTIVE:
    
    455
    -        // If we reached ACTIVITY_INACTIVE, then don't reset it until
    
    456
    -        // we've done the GC.  The thread running here might just be
    
    457
    -        // the IO manager thread that handle_tick() woke up via
    
    458
    -        // wakeUpRts().
    
    459
    -        break;
    
    460
    -    default:
    
    461
    -        setRecentActivity(ACTIVITY_YES);
    
    462
    -    }
    
    433
    +    // Let the idle gc tracker know that we're running a thread again
    
    434
    +    notifyIdleGcActive();
    
    463 435
     
    
    464 436
         traceEventRunThread(cap, t);
    
    465 437
     
    
    ... ... @@ -986,7 +958,7 @@ static void
    986 958
     scheduleDetectDeadlock (Capability **pcap, Task *task)
    
    987 959
     {
    
    988 960
         /* See Note [Deadlock detection] */
    
    989
    -    if (getRecentActivity() == ACTIVITY_INACTIVE) {
    
    961
    +    if (isIdleGcPending()) {
    
    990 962
     
    
    991 963
             debugTrace(DEBUG_sched, "maybe deadlocked, forcing major GC...");
    
    992 964
     
    
    ... ... @@ -1933,35 +1905,7 @@ delete_threads_and_gc:
    1933 1905
     
    
    1934 1906
         traceSparkCounters(cap);
    
    1935 1907
     
    
    1936
    -    switch (getRecentActivity()) {
    
    1937
    -    case ACTIVITY_INACTIVE:
    
    1938
    -        if (force_major) {
    
    1939
    -            // We are doing a GC because the system has been idle for a
    
    1940
    -            // timeslice and we need to check for deadlock.  Record the
    
    1941
    -            // fact that we've done a GC and turn off the timer signal;
    
    1942
    -            // it will get re-enabled if we run any threads after the GC.
    
    1943
    -            setRecentActivity(ACTIVITY_DONE_GC);
    
    1944
    -#if !defined(PROFILING)
    
    1945
    -            pauseTimer();
    
    1946
    -#endif
    
    1947
    -            break;
    
    1948
    -        }
    
    1949
    -        // fall through...
    
    1950
    -
    
    1951
    -    case ACTIVITY_MAYBE_NO:
    
    1952
    -        // the GC might have taken long enough for the timer to set
    
    1953
    -        // recent_activity = ACTIVITY_MAYBE_NO or ACTIVITY_INACTIVE,
    
    1954
    -        // but we aren't necessarily deadlocked:
    
    1955
    -        setRecentActivity(ACTIVITY_YES);
    
    1956
    -        break;
    
    1957
    -
    
    1958
    -    case ACTIVITY_DONE_GC:
    
    1959
    -        // If we are actually active, the scheduler will reset the
    
    1960
    -        // recent_activity flag and re-enable the timer.
    
    1961
    -        break;
    
    1962
    -    case ACTIVITY_YES:
    
    1963
    -        break;
    
    1964
    -    }
    
    1908
    +    notifyIdleGcDone(force_major);
    
    1965 1909
     
    
    1966 1910
     #if defined(THREADED_RTS)
    
    1967 1911
         // Stable point where we can do a global check on our spark counters
    
    ... ... @@ -2788,8 +2732,6 @@ void
    2788 2732
     initScheduler(void)
    
    2789 2733
     {
    
    2790 2734
       setSchedState(SCHED_RUNNING);
    
    2791
    -  setRecentActivity(ACTIVITY_YES);
    
    2792
    -
    
    2793 2735
     
    
    2794 2736
       /* Initialise the mutex and condition variables used by
    
    2795 2737
        * the scheduler. */
    

  • rts/Schedule.h
    ... ... @@ -80,58 +80,6 @@ INLINE_HEADER enum SchedState getSchedState(void)
    80 80
         return (enum SchedState) SEQ_CST_LOAD_ALWAYS(&sched_state);
    
    81 81
     }
    
    82 82
     
    
    83
    -/*
    
    84
    - * flag that tracks whether we have done any execution in this time
    
    85
    - * slice, and controls the disabling of the interval timer.
    
    86
    - *
    
    87
    - * The timer interrupt transitions ACTIVITY_YES into
    
    88
    - * ACTIVITY_MAYBE_NO, waits for RtsFlags.GcFlags.idleGCDelayTime,
    
    89
    - * and then:
    
    90
    - *   - if idle GC is on, set ACTIVITY_INACTIVE and wakeUpRts()
    
    91
    - *   - if idle GC is off, set ACTIVITY_DONE_GC and stopTimer()
    
    92
    - *
    
    93
    - * If the scheduler finds ACTIVITY_INACTIVE, then it sets
    
    94
    - * ACTIVITY_DONE_GC, performs the GC and calls stopTimer().
    
    95
    - *
    
    96
    - * If the scheduler finds ACTIVITY_DONE_GC and it has a thread to run,
    
    97
    - * it enables the timer again with startTimer().
    
    98
    - */
    
    99
    -enum RecentActivity {
    
    100
    -    // the RTS is active
    
    101
    -    ACTIVITY_YES      = 0,
    
    102
    -    // no activity since the last timer signal
    
    103
    -    ACTIVITY_MAYBE_NO = 1,
    
    104
    -    // RtsFlags.GcFlags.idleGCDelayTime has passed with no activity
    
    105
    -    ACTIVITY_INACTIVE = 2,
    
    106
    -    // like ACTIVITY_INACTIVE, but we've done a GC too (if idle GC is
    
    107
    -    // enabled) and the interval timer is now turned off.
    
    108
    -    ACTIVITY_DONE_GC  = 3,
    
    109
    -};
    
    110
    -
    
    111
    -/* Recent activity flag.
    
    112
    - * Locks required  : Transition from MAYBE_NO to INACTIVE
    
    113
    - * happens in the timer signal, so it is atomic.  Transition from
    
    114
    - * INACTIVE to DONE_GC happens under sched_mutex.  No lock required
    
    115
    - * to set it to ACTIVITY_YES.
    
    116
    - *
    
    117
    - * N.B. we must always use atomics here since even in the non-threaded runtime
    
    118
    - * the timer may be provided via a signal.
    
    119
    - */
    
    120
    -extern StgWord recent_activity;
    
    121
    -
    
    122
    -INLINE_HEADER enum RecentActivity
    
    123
    -setRecentActivity(enum RecentActivity new_value)
    
    124
    -{
    
    125
    -    StgWord old = SEQ_CST_XCHG_ALWAYS((StgPtr) &recent_activity, (StgWord) new_value);
    
    126
    -    return (enum RecentActivity) old;
    
    127
    -}
    
    128
    -
    
    129
    -INLINE_HEADER enum RecentActivity
    
    130
    -getRecentActivity(void)
    
    131
    -{
    
    132
    -    return (enum RecentActivity) RELAXED_LOAD_ALWAYS(&recent_activity);
    
    133
    -}
    
    134
    -
    
    135 83
     extern bool heap_overflow;
    
    136 84
     
    
    137 85
     #if defined(THREADED_RTS)
    

  • rts/Timer.c
    ... ... @@ -22,7 +22,7 @@
    22 22
     
    
    23 23
     #include "Timer.h"
    
    24 24
     #include "Proftimer.h"
    
    25
    -#include "Schedule.h"
    
    25
    +#include "IdleGC.h"
    
    26 26
     #include "Ticker.h"
    
    27 27
     #include "Capability.h"
    
    28 28
     #include "RtsSignals.h"
    
    ... ... @@ -37,57 +37,6 @@ static int ticks_to_eventlog_flush = 0;
    37 37
     #endif
    
    38 38
     
    
    39 39
     
    
    40
    -/*
    
    41
    - Note [GC During Idle Time]
    
    42
    - ~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    43
    - In the threaded RTS, a major GC can be performed during idle time (i.e., when
    
    44
    - no Haskell computations are ready to run).  This can be beneficial for two
    
    45
    - reasons.  First, running the GC during idle time makes it less likely that a GC
    
    46
    - will be triggered when the process is active, increasing apparent
    
    47
    - responsiveness.  Second, these idle time GCs allow finalizers to run,
    
    48
    - preventing resources from being held indefinitely when the process is otherwise
    
    49
    - idle for extended periods.
    
    50
    -
    
    51
    - There are two runtime RTS options to control idle time GC timing.  The primary
    
    52
    - control is set by `-I<n>`, which specifies the minimum period of time the
    
    53
    - process must be idle before a GC is automatically triggered.  It defaults to
    
    54
    - 0.3 seconds for the threaded runtime and 0 (which disables idle GC entirely)
    
    55
    - for the non-threaded runtime.  For certain workflows, the 0.3 second delay for
    
    56
    - the threaded RTS may be too small.  If an application must process an extended
    
    57
    - burst of short-lived requests occurring a couple of times a second, it may go
    
    58
    - idle for >0.3 seconds frequently, resulting in potentially dozens of major
    
    59
    - GCs triggered every minute (and resulting heavy CPU load) while the burst
    
    60
    - lasts.  Setting the `-I<n>` value higher will prevent this flurry of major GCs,
    
    61
    - though there is a danger that setting the value too high will prevent automatic
    
    62
    - GCs entirely, if the process never gets a chance to go idle for long enough to
    
    63
    - meet the larger threshold.
    
    64
    -
    
    65
    - In this case, the second control, set by `-Iw<n>` may be helpful.  For example,
    
    66
    - setting `-I0.3 -Iw30` triggers automatic GCs after only 0.3 seconds of idle
    
    67
    - time, but subject to a minimum delay *between* automatic GCs of at least 30
    
    68
    - seconds.  This is likely to work well for applications that must process a
    
    69
    - nearly constant stream of frequent, short-lived requests, ensuring that
    
    70
    - automatic GCs are triggered promptly when the process goes idle while limiting
    
    71
    - the overall frequency of such GCs.  (The default is `-Iw0`, meaning no limit on
    
    72
    - frequency of GCs.)
    
    73
    -
    
    74
    - Automatic GC timing is implemented below using two count-down timers.  The
    
    75
    - `idle_ticks_to_gc` timer counts down the `-I<n>` setting: it is initialized
    
    76
    - when the process goes idle and counts down idle time before an automatic GC
    
    77
    - becomes possible.  The `inter_gc_ticks_to_gc` counts down the `-Iw<n>` setting:
    
    78
    - it is initialized when an automatic GC is actually performed, and holds off the
    
    79
    - next automatic GC until its count expires, limiting the overall frequency of
    
    80
    - automatic GCs.  Both timers must expire before an automatic GC is triggered.
    
    81
    -
    
    82
    - See issue #11134 for additional detail.
    
    83
    -*/
    
    84
    -
    
    85
    -/* - countdown for minimum idle time before we start a GC (set by -I) */
    
    86
    -static int idle_ticks_to_gc = 0;
    
    87
    -
    
    88
    -/* - countdown for minimum time *between* idle GCs (set by -Iw) */
    
    89
    -static int inter_gc_ticks_to_gc = 0;
    
    90
    -
    
    91 40
     /*
    
    92 41
      * Function: handle_tick()
    
    93 42
      *
    
    ... ... @@ -119,50 +68,9 @@ handle_tick(int unused STG_UNUSED)
    119 68
       }
    
    120 69
     #endif
    
    121 70
     
    
    122
    -  /*
    
    123
    -   * If we've been inactive for idleGCDelayTime (set by +RTS
    
    124
    -   * -I), tell the scheduler to wake up and do a GC, to check
    
    125
    -   * for threads that are deadlocked.  However, ensure we wait
    
    126
    -   * at least interIdleGCWait (+RTS -Iw) between idle GCs.
    
    127
    -   */
    
    128
    -  switch (getRecentActivity()) {
    
    129
    -  case ACTIVITY_YES:
    
    130
    -      setRecentActivity(ACTIVITY_MAYBE_NO);
    
    131
    -      idle_ticks_to_gc = RtsFlags.GcFlags.idleGCDelayTime /
    
    132
    -                         RtsFlags.MiscFlags.tickInterval;
    
    133
    -      break;
    
    134
    -  case ACTIVITY_MAYBE_NO:
    
    135
    -      if (idle_ticks_to_gc == 0 && inter_gc_ticks_to_gc == 0) {
    
    136
    -          if (RtsFlags.GcFlags.doIdleGC) {
    
    137
    -              setRecentActivity(ACTIVITY_INACTIVE);
    
    138
    -              inter_gc_ticks_to_gc = RtsFlags.GcFlags.interIdleGCWait /
    
    139
    -                                     RtsFlags.MiscFlags.tickInterval;
    
    140
    -              wakeUpRts();
    
    141
    -              // The scheduler will call pauseTimer() when it has done
    
    142
    -              // the GC.
    
    143
    -          } else {
    
    144
    -              setRecentActivity(ACTIVITY_DONE_GC);
    
    145
    -              // disable timer signals (see #1623, #5991, #9105)
    
    146
    -              // but only if we're not profiling (e.g. passed -h or -p RTS
    
    147
    -              // flags). If we are profiling we need to keep the timer active
    
    148
    -              // so that samples continue to be collected.
    
    149
    -#if defined(PROFILING)
    
    150
    -              if (!(RtsFlags.ProfFlags.doHeapProfile
    
    151
    -                    || RtsFlags.CcFlags.doCostCentres)) {
    
    152
    -                  pauseTimer();
    
    153
    -              }
    
    154
    -#else
    
    155
    -              pauseTimer();
    
    71
    +#if defined(THREADED_IDLEGC)
    
    72
    +  handleIdleGcTick();
    
    156 73
     #endif
    
    157
    -          }
    
    158
    -      } else {
    
    159
    -          if (idle_ticks_to_gc) idle_ticks_to_gc--;
    
    160
    -          if (inter_gc_ticks_to_gc) inter_gc_ticks_to_gc--;
    
    161
    -      }
    
    162
    -      break;
    
    163
    -  default:
    
    164
    -      break;
    
    165
    -  }
    
    166 74
     }
    
    167 75
     
    
    168 76
     void initTimer(void)
    

  • rts/posix/Poll.c
    ... ... @@ -28,6 +28,11 @@
    28 28
     
    
    29 29
     #include <limits.h>
    
    30 30
     #include <errno.h>
    
    31
    +
    
    32
    +#if !defined(THREADED_IDLEGC)
    
    33
    +#undef HAVE_DECL_PPOLL // for simplicity, always use poll when !THREADED_IDLEGC
    
    34
    +#endif
    
    35
    +
    
    31 36
     #if defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1
    
    32 37
     /* We prefer the ppoll() function if available since it allows more precise
    
    33 38
      * timeouts. It is not in the posix standard however and some platforms
    
    ... ... @@ -505,6 +510,17 @@ bool awaitCompletedTimeoutsOrIOPoll(CapIOManager *iomgr)
    505 510
             int timeout_ms = timeoutInMilliseconds(iomgr, wait, now);
    
    506 511
     #endif
    
    507 512
     
    
    513
    +#if !defined(THREADED_IDLEGC)
    
    514
    +        /* Without threaded idle GC, poll() does not get interrupted for an
    
    515
    +         * idle GC. Instead we just limit our wait time to the idle gc delay
    
    516
    +         * time, and if that timeout is reached then we schedule an idle GC
    
    517
    +         * (see handleTimeoutForIdleGc below).
    
    518
    +         */
    
    519
    +        bool any_pending_io = nfds > 0;
    
    520
    +        int idlegc_status;
    
    521
    +        adjustTimeoutForIdleGc(any_pending_io, &timeout_ms, &idlegc_status);
    
    522
    +#endif
    
    523
    +
    
    508 524
             /* Check for I/O readiness, possibly waiting. */
    
    509 525
     #if defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1
    
    510 526
             int res = ppoll(poll_table, nfds, timeout_ns, NULL);
    
    ... ... @@ -533,6 +549,9 @@ bool awaitCompletedTimeoutsOrIOPoll(CapIOManager *iomgr)
    533 549
     #else
    
    534 550
                 ASSERT(timeout_ms != -1);
    
    535 551
     #endif
    
    552
    +#if !defined(THREADED_IDLEGC)
    
    553
    +            handleIdleGcTimeout(idlegc_status, &interrupt);
    
    554
    +#endif
    
    536 555
     
    
    537 556
             } else if (res > 0) {
    
    538 557
                 int ncompletions = res;
    

  • rts/posix/Timeout.c
    ... ... @@ -17,6 +17,7 @@
    17 17
     #include "Timeout.h"
    
    18 18
     #include "IOManagerInternals.h"
    
    19 19
     #include "TimeoutQueue.h"
    
    20
    +#include "IdleGC.h"
    
    20 21
     
    
    21 22
     #include <limits.h>
    
    22 23
     
    
    ... ... @@ -135,7 +136,8 @@ static void notifyTimeoutCompletion(CapIOManager *iomgr, StgTimeout *timeout)
    135 136
     /* poll() expect a timeout in milliseconds, with special
    
    136 137
      * values of -1 for indefinite wait, and 0 for no waiting.
    
    137 138
      */
    
    138
    -#if !(defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1)
    
    139
    +#if !(defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1) \
    
    140
    + || !defined(THREADED_IDLEGC)
    
    139 141
     int timeoutInMilliseconds(CapIOManager *iomgr, bool wait, Time now)
    
    140 142
     {
    
    141 143
         if (!wait) {
    
    ... ... @@ -220,5 +222,56 @@ struct timespec *timeoutInNanoseconds(CapIOManager *iomgr, bool wait,
    220 222
     }
    
    221 223
     #endif
    
    222 224
     
    
    225
    +#if !defined(THREADED_IDLEGC)
    
    226
    +enum {
    
    227
    +    timeout_is_idlegc_delay   = 1 << 0,
    
    228
    +    system_must_be_deadlocked = 1 << 1
    
    229
    +};
    
    230
    +
    
    231
    +/* Utilities for handling the non-threaded idle GC variation.
    
    232
    + *
    
    233
    + * See Note [Idle GC without preemption]
    
    234
    + *
    
    235
    + * Before calling poll()/select() etc, adjust the timeout to account for the
    
    236
    + * idle GC delay time.
    
    237
    + *
    
    238
    + * If the timeout occurs, call handleIdleGcTimeout, which will schedule an idle
    
    239
    + * GC if the timeout was reached.
    
    240
    + *
    
    241
    + * The idlegc_status must be passed from adjustTimeoutForIdleGc to
    
    242
    + * handleIdleGcTimeout.
    
    243
    + */
    
    244
    +void adjustTimeoutForIdleGc(bool any_pending_io,
    
    245
    +                            int *timeout_ms      /* in/out */,
    
    246
    +                            int *idlegc_status   /* out */)
    
    247
    +{
    
    248
    +    int idlegc_delay_ms = TimeToMS(getNextIdleGcDelayTime());
    
    249
    +
    
    250
    +    if (RTS_UNLIKELY(!any_pending_io && *timeout_ms == -1)) {
    
    251
    +        /* We must be deadlocked. Schedule an idle GC unconditionally. */
    
    252
    +        *timeout_ms    = 0; /* Cause poll() to return res == 0 immediately. */
    
    253
    +        *idlegc_status = timeout_is_idlegc_delay
    
    254
    +                       & system_must_be_deadlocked;
    
    255
    +
    
    256
    +    } else if (RTS_UNLIKELY(RtsFlags.GcFlags.doIdleGC &&
    
    257
    +                 (idlegc_delay_ms < *timeout_ms || *timeout_ms == -1))) {
    
    258
    +        /* The idle GC delay will be the next timeout. */
    
    259
    +        *timeout_ms    = idlegc_delay_ms;
    
    260
    +        *idlegc_status = timeout_is_idlegc_delay;
    
    261
    +
    
    262
    +    } else {
    
    263
    +        *idlegc_status = 0;
    
    264
    +    }
    
    265
    +}
    
    266
    +
    
    267
    +void handleIdleGcTimeout(int idlegc_status, bool *interrupt)
    
    268
    +{
    
    269
    +    if (idlegc_status & timeout_is_idlegc_delay) {
    
    270
    +        notifyIdleGcIdle(idlegc_status & system_must_be_deadlocked);
    
    271
    +        *interrupt = true;
    
    272
    +    }
    
    273
    +}
    
    274
    +#endif
    
    275
    +
    
    223 276
     #endif // defined(IOMGR_ENABLED_POLL)
    
    224 277
     

  • rts/posix/Timeout.h
    ... ... @@ -33,7 +33,8 @@ void processTimeoutCompletions(CapIOManager *iomgr, Time now);
    33 33
      * timeout in milliseconds, with special values of -1 for indefinite wait,
    
    34 34
      * and 0 for no waiting.
    
    35 35
      */
    
    36
    -#if !(defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1)
    
    36
    +#if !(defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1) \
    
    37
    + || !defined(THREADED_IDLEGC)
    
    37 38
     int timeoutInMilliseconds(CapIOManager *iomgr, bool wait, Time now);
    
    38 39
     #endif
    
    39 40
     
    
    ... ... @@ -46,5 +47,17 @@ struct timespec *timeoutInNanoseconds(CapIOManager *iomgr, bool wait,
    46 47
                                           Time now, struct timespec *tv);
    
    47 48
     #endif
    
    48 49
     
    
    50
    +#if !defined(THREADED_IDLEGC)
    
    51
    +/* Utilities for handling the non-threaded idle GC variation.
    
    52
    + *
    
    53
    + * See Note [Idle GC without preemption]
    
    54
    + */
    
    55
    +void adjustTimeoutForIdleGc(bool any_pending_io,
    
    56
    +                            int *timeout_ms      /* in/out */,
    
    57
    +                            int *idlegc_status   /* out */);
    
    58
    +
    
    59
    +void handleIdleGcTimeout(int idlegc_status, bool *interrupt);
    
    60
    +#endif
    
    61
    +
    
    49 62
     #include "EndPrivate.h"
    
    50 63
     

  • rts/rts.cabal
    ... ... @@ -420,6 +420,7 @@ library
    420 420
                      HsFFI.c
    
    421 421
                      Inlines.c
    
    422 422
                      Interpreter.c
    
    423
    +                 IdleGC.c
    
    423 424
                      IOManager.c
    
    424 425
                      LdvProfile.c
    
    425 426
                      Libdw.c