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

Commits:

6 changed files:

Changes:

  • rts/IdleGC.c
    ... ... @@ -55,6 +55,8 @@
    55 55
      automatic GCs.  Both timers must expire before an automatic GC is triggered.
    
    56 56
     
    
    57 57
      See issue #11134 for additional detail.
    
    58
    +
    
    59
    + See also Note [Deadlock detection].
    
    58 60
     */
    
    59 61
     
    
    60 62
     
    
    ... ... @@ -113,6 +115,8 @@ bool isIdleGcPending(void)
    113 115
         return (getRecentActivity() == ACTIVITY_INACTIVE);
    
    114 116
     }
    
    115 117
     
    
    118
    +#if defined(THREADED_IDLEGC)
    
    119
    +
    
    116 120
     /* - countdown for minimum idle time before we start a GC (set by -I) */
    
    117 121
     static int idle_ticks_to_gc = 0;
    
    118 122
     
    
    ... ... @@ -170,6 +174,47 @@ void handleIdleGcTick(void)
    170 174
       }
    
    171 175
     }
    
    172 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 = 0;
    
    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
    +
    
    173 218
     void notifyIdleGcActive(void)
    
    174 219
     {
    
    175 220
         switch (getRecentActivity())
    
    ... ... @@ -209,6 +254,9 @@ void notifyIdleGcDone(bool force_major)
    209 254
                 setRecentActivity(ACTIVITY_DONE_GC);
    
    210 255
     #if !defined(PROFILING)
    
    211 256
                 pauseTimer();
    
    257
    +#endif
    
    258
    +#if !defined(THREADED_IDLEGC)
    
    259
    +            last_idle_gc_time = getProcessElapsedTime();
    
    212 260
     #endif
    
    213 261
                 break;
    
    214 262
             }
    

  • rts/IdleGC.h
    ... ... @@ -10,6 +10,15 @@
    10 10
     
    
    11 11
     #include "BeginPrivate.h"
    
    12 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
    +
    
    13 22
     /* Are we in a state where we are want an idle GC to occur?
    
    14 23
      * Used in Capability globalWorkToDo() and Schedule scheduleDetectDeadlock.
    
    15 24
      */
    
    ... ... @@ -21,7 +30,19 @@ void notifyIdleGcActive(void);
    21 30
     /* Called from schedule(), specifically scheduleDoGC() */
    
    22 31
     void notifyIdleGcDone(bool force_major);
    
    23 32
     
    
    33
    +#if defined(THREADED_IDLEGC)
    
    34
    +
    
    24 35
     /* Called from handle_tick() */
    
    25 36
     void handleIdleGcTick(void);
    
    26 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
    +
    
    27 48
     #include "EndPrivate.h"

  • rts/Timer.c
    ... ... @@ -68,7 +68,9 @@ handle_tick(int unused STG_UNUSED)
    68 68
       }
    
    69 69
     #endif
    
    70 70
     
    
    71
    +#if defined(THREADED_IDLEGC)
    
    71 72
       handleIdleGcTick();
    
    73
    +#endif
    
    72 74
     }
    
    73 75
     
    
    74 76
     void initTimer(void)
    

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