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

Commits:

11 changed files:

Changes:

  • changelog.d/idle-gc-and-deadlock-detection
    1
    +section: rts
    
    2
    +issues: #27040 #26408
    
    3
    +mrs: !14820
    
    4
    +synopsis: Idle GC and deadlock detection in the non-threaded RTS now behave the same as in the threaded RTS
    
    5
    +description: {
    
    6
    +Idle GC now works in the non-threaded RTS and is enabled by default.
    
    7
    +
    
    8
    +Deadlock detection in the non-threaded RTS now follows the same design and
    
    9
    +behaviour as in the threaded RTS. In particular, it can detect partial
    
    10
    +deadlocks where some but not all threads are deadlocked. The detection is no
    
    11
    +longer prompt however, as the detection runs as part of idle GC, which (by
    
    12
    +default) is performed 0.3 seconds after all capabilities become idle.
    
    13
    +
    
    14
    +The user guide now documents that deadlock detection relies on idle GC,
    
    15
    +and thus turning off idle GC disables deadlock detection. This is true
    
    16
    +of the threaded RTS in previous GHC releases but it was not previously
    
    17
    +documented behaviour.
    
    18
    +
    
    19
    +Internally, the idle GC and deadlock detection now follow the same design and
    
    20
    +use 99% the same code between the non-threaded and threaded RTS. Having a
    
    21
    +single design improves the comprehension and maintainability of the RTS code.
    
    22
    +}

  • docs/users_guide/profiling.rst
    ... ... @@ -691,11 +691,13 @@ enclosed between ``+RTS ... -RTS`` as usual):
    691 691
         order to increase the resolution of the time profiler.
    
    692 692
     
    
    693 693
         Using a value of zero disables the RTS clock completely, and has the
    
    694
    -    effect of disabling timers that depend on it: the context switch
    
    695
    -    timer and the heap profiling timer. Context switches will still
    
    696
    -    happen, but deterministically and at a rate much faster than normal.
    
    697
    -    Disabling the interval timer is useful for debugging, because it
    
    698
    -    eliminates a source of non-determinism at runtime.
    
    694
    +    effect of disabling the timers and features that depend on it: the context
    
    695
    +    switch timer, the heap profiling timer, the eventlog flush timer, and the
    
    696
    +    idle GC timer. Context switches will still happen, but deterministically
    
    697
    +    and at a rate much faster than normal. Disabling the interval timer is
    
    698
    +    useful for debugging, because it eliminates a source of non-determinism at
    
    699
    +    runtime. Note however that disabling the idle GC timer not only disables
    
    700
    +    idle GC but also deadlock detection (see rts-flag:`-I`).
    
    699 701
     
    
    700 702
     
    
    701 703
     .. rts-flag:: -xc
    

  • docs/users_guide/runtime_control.rst
    ... ... @@ -731,61 +731,6 @@ performance.
    731 731
         the default small ``-A`` value is suboptimal, as it can be in
    
    732 732
         programs that create large amounts of long-lived data.
    
    733 733
     
    
    734
    -.. rts-flag:: -I ⟨seconds⟩
    
    735
    -
    
    736
    -    :default: 0.3 seconds in the threaded runtime, 0 in the non-threaded runtime
    
    737
    -
    
    738
    -    .. index::
    
    739
    -       single: idle GC
    
    740
    -
    
    741
    -    Set the amount of idle time which must pass before a idle GC is
    
    742
    -    performed. Setting ``-I0`` disables the idle GC.
    
    743
    -
    
    744
    -    In the threaded and SMP versions of the RTS (see :ghc-flag:`-threaded`,
    
    745
    -    :ref:`options-linker`), a major GC is automatically performed if the
    
    746
    -    runtime has been idle (no Haskell computation has been running) for a
    
    747
    -    period of time.
    
    748
    -
    
    749
    -    For an interactive application, it is probably a good idea to use
    
    750
    -    the idle GC, because this will allow finalizers to run and
    
    751
    -    deadlocked threads to be detected in the idle time when no Haskell
    
    752
    -    computation is happening. Also, it will mean that a GC is less
    
    753
    -    likely to happen when the application is busy, and so responsiveness
    
    754
    -    may be improved. However, if the amount of live data in the heap is
    
    755
    -    particularly large, then the idle GC can cause a significant delay,
    
    756
    -    and too small an interval could adversely affect interactive
    
    757
    -    responsiveness.
    
    758
    -
    
    759
    -    The idle period timer only resets after some activity
    
    760
    -    by a Haskell thread. If your program is doing literally nothing then
    
    761
    -    after the first idle collection is triggered then no more future collections
    
    762
    -    will be scheduled until more work is performed.
    
    763
    -
    
    764
    -    This is an experimental feature, please let us know if it causes
    
    765
    -    problems and/or could benefit from further tuning.
    
    766
    -
    
    767
    -.. rts-flag:: -Iw ⟨seconds⟩
    
    768
    -
    
    769
    -    :default: 0 seconds
    
    770
    -
    
    771
    -    .. index::
    
    772
    -       single: idle GC
    
    773
    -
    
    774
    -    Set the minimum wait time between runs of the idle GC.
    
    775
    -
    
    776
    -    By default, if idle GC is enabled in the threaded runtime, a major
    
    777
    -    GC will be performed every time the process goes idle for a
    
    778
    -    sufficiently long duration (see :rts-flag:`-I ⟨seconds⟩`).  For
    
    779
    -    large server processes accepting regular but infrequent requests
    
    780
    -    (e.g., once per second), an expensive, major GC may run after
    
    781
    -    every request.  As an alternative to shutting off idle GC entirely
    
    782
    -    (with ``-I0``), a minimum wait time between idle GCs can be
    
    783
    -    specified with this flag.  For example, ``-Iw60`` will ensure that
    
    784
    -    an idle GC runs at most once per minute.
    
    785
    -
    
    786
    -    This is an experimental feature, please let us know if it causes
    
    787
    -    problems and/or could benefit from further tuning.
    
    788
    -
    
    789 734
     .. rts-flag:: -ki ⟨size⟩
    
    790 735
     
    
    791 736
         :default: 1k
    
    ... ... @@ -1011,6 +956,116 @@ performance.
    1011 956
         ``GHC.Stats.getRTSStats`` from Haskell.
    
    1012 957
     
    
    1013 958
     
    
    959
    +Idle GC and deadlock detection
    
    960
    +------------------------------
    
    961
    +
    
    962
    +The RTS performs idle GC and deadlock detection. These features are more
    
    963
    +closely related and dependent than one might expect. In particular, deadlock
    
    964
    +detection relies on idle GC.
    
    965
    +
    
    966
    +The primary purpose of idle GC is to reclaim memory and return it to the system
    
    967
    +when the application has been idle for a short time. It also gives an
    
    968
    +opportunity for finalisers to run more promptly. See :rts-flag:`-I ⟨seconds⟩`.
    
    969
    +
    
    970
    +Deadlock detection is a debugging feature to try to discover when some or all
    
    971
    +Haskell threads are deadlocked. For the purpose of this explanation we define:
    
    972
    +
    
    973
    + * a "partial deadlock" to be a set of threads that are deadlocked; and
    
    974
    + * a "system deadlock" is when all threads are deadlocked.
    
    975
    +
    
    976
    +The deadlock detection is implemented by the GC, and it is capable of detecting
    
    977
    +partial deadlocks. If a deadlock is discovered, the RTS will throw an
    
    978
    +asynchronous exception to (some or all of) the blocked threads to enable
    
    979
    +progress and enable better reporting of the problem. In particular it throws
    
    980
    +the async exceptions ``BlockedIndefinitelyOnMVar`` or
    
    981
    +``BlockedIndefinitelyOnSTM``, as appropriate.
    
    982
    +
    
    983
    +The deadlock detection can be run during a major GC, but it is more expensive
    
    984
    +than a normal major GC so it is not run during normal major GCs. The deadlock
    
    985
    +detection mode of GC is run by the idle GC. This is on the basis that when the
    
    986
    +application is idle is when we are most likely to find a deadlock, and also
    
    987
    +that the extra time cost is unlikely to be a problem because the application is
    
    988
    +already idle.
    
    989
    +
    
    990
    +There are a few consequence of this:
    
    991
    +
    
    992
    + * Deadlock detection relies on idle GC and will not run if idle GC is disabled
    
    993
    +   with ``-I0``.
    
    994
    + * Deadlock detection will not run if the application remains busy, and thus
    
    995
    +   idle GC is not invoked.
    
    996
    + * Deadlock detection is not prompt: it will only occur after the idle GC
    
    997
    +   delay, depending on both :rts-flag:`-I ⟨seconds⟩` and
    
    998
    +   :rts-flag:`-Iw ⟨seconds⟩`.
    
    999
    +
    
    1000
    +In the non-threaded RTS there is additional behaviour: system deadlocks will be
    
    1001
    +detected promptly, and this works even if idle GC is otherwise disabled.
    
    1002
    +
    
    1003
    +One cannot rely on deadlock detection as part of the normal program behaviour.
    
    1004
    +It should only be used for debugging. Firstly, deadlock detection can be
    
    1005
    +disabled or indefinately deferred due to activity. Secondly, the set of threads
    
    1006
    +that deadlock detection throws exceptions to is not guaranteed to be minimal:
    
    1007
    +a thread waiting on a deadlocked thread may also have an exception thrown to it
    
    1008
    +even if it could make progress after the deadlocked thread is terminated.
    
    1009
    +
    
    1010
    +Deadlock detection can be invoked manually using ``performDeadlockDetection``
    
    1011
    +from ``System.Mem.Experimental``.
    
    1012
    +
    
    1013
    +
    
    1014
    +.. rts-flag:: -I ⟨seconds⟩
    
    1015
    +
    
    1016
    +    :default: 0.3 seconds
    
    1017
    +
    
    1018
    +    .. index::
    
    1019
    +       single: idle GC
    
    1020
    +
    
    1021
    +    A major GC is automatically performed if the runtime has been idle (no
    
    1022
    +    Haskell computation has been running) for a period of time. Set the amount
    
    1023
    +    of idle time which must pass before a idle GC is performed.
    
    1024
    +
    
    1025
    +    Setting ``-I0`` disables the idle GC. This also has the unfortunate side
    
    1026
    +    effect of disabling thread deadlock detection (the implementation of which
    
    1027
    +    uses the idle GC).
    
    1028
    +
    
    1029
    +    For an interactive application, it is probably a good idea to use
    
    1030
    +    the idle GC, because this will allow finalizers to run and
    
    1031
    +    deadlocked threads to be detected in the idle time when no Haskell
    
    1032
    +    computation is happening. Also, it will mean that a GC is less
    
    1033
    +    likely to happen when the application is busy, and so responsiveness
    
    1034
    +    may be improved. However, if the amount of live data in the heap is
    
    1035
    +    particularly large, then the idle GC can cause a significant delay,
    
    1036
    +    and too small an interval could adversely affect interactive
    
    1037
    +    responsiveness.
    
    1038
    +
    
    1039
    +    The idle period timer only resets after some activity
    
    1040
    +    by a Haskell thread. If your program is doing literally nothing then
    
    1041
    +    after the first idle collection is triggered then no more future collections
    
    1042
    +    will be scheduled until more work is performed.
    
    1043
    +
    
    1044
    +    Please let us know if it causes problems and/or could benefit from further
    
    1045
    +    tuning.
    
    1046
    +
    
    1047
    +.. rts-flag:: -Iw ⟨seconds⟩
    
    1048
    +
    
    1049
    +    :default: 0 seconds
    
    1050
    +
    
    1051
    +    .. index::
    
    1052
    +       single: idle GC
    
    1053
    +
    
    1054
    +    Set the minimum wait time between runs of the idle GC.
    
    1055
    +
    
    1056
    +    By default (and if idle GC is not disabled) a major
    
    1057
    +    GC will be performed every time the process goes idle for a
    
    1058
    +    sufficiently long duration (see :rts-flag:`-I ⟨seconds⟩`).  For
    
    1059
    +    large server processes accepting regular but infrequent requests
    
    1060
    +    (e.g., once per second), an expensive, major GC may run after
    
    1061
    +    every request.  As an alternative to shutting off idle GC entirely
    
    1062
    +    (with ``-I0``), a minimum wait time between idle GCs can be
    
    1063
    +    specified with this flag.  For example, ``-Iw60`` will ensure that
    
    1064
    +    an idle GC runs at most once per minute.
    
    1065
    +
    
    1066
    +    This is an experimental feature, please let us know if it causes
    
    1067
    +    problems and/or could benefit from further tuning.
    
    1068
    +
    
    1014 1069
     
    
    1015 1070
     .. _rts-options-statistics:
    
    1016 1071
     
    

  • testsuite/tests/rts/Makefile
    ... ... @@ -157,3 +157,11 @@ HSC2HS_OPTS = --cc="$(TEST_CC)" $(addprefix --cflag=,$(TEST_CC_OPTS)) --ld=$(TES
    157 157
     
    
    158 158
     IOManager.hs: IOManager.hsc
    
    159 159
     	'$(HSC2HS)' $(HSC2HS_OPTS) $<
    
    160
    +
    
    161
    +.PHONY: T26408c
    
    162
    +T26408c:
    
    163
    +	"$(TEST_HC)" $(TEST_HC_OPTS) -rtsopts T26408c.hs; \
    
    164
    +	./T26408c +RTS -I0 -T & \
    
    165
    +	sleep 1; \
    
    166
    +	kill -s USR1 $$!; \
    
    167
    +	wait $$!

  • testsuite/tests/rts/T26408a.hs
    1
    +import Control.Concurrent
    
    2
    +import Control.Concurrent.STM
    
    3
    +import Control.Exception
    
    4
    +import Control.Monad
    
    5
    +
    
    6
    +-- | Test to make sure that we can detect a /partial deadlock/ that is /not/
    
    7
    +-- also a /system deadlock/: that is we can detect a set of deadlocked threads
    
    8
    +-- even when there are other unrelated threads that are waiting on I\/O or
    
    9
    +-- timeouts.
    
    10
    +--
    
    11
    +-- Historically this did not work in the non-threaded RTS which would only do
    
    12
    +-- deadlock detection if there was a system deadlock: no runnable threads /and/
    
    13
    +-- no pending I\/O or timers.
    
    14
    +--
    
    15
    +-- See Note [Deadlock detection]
    
    16
    +--
    
    17
    +main :: IO ()
    
    18
    +main = do
    
    19
    +
    
    20
    +  -- Set up two threads that are deadlocked on each other
    
    21
    +  aDone <- newTVarIO False
    
    22
    +  bDone <- newTVarIO False
    
    23
    +  let blockingThread theirDone ourDone =
    
    24
    +        atomically $ do
    
    25
    +          done <- readTVar theirDone
    
    26
    +          guard done
    
    27
    +          writeTVar ourDone True
    
    28
    +  _ <- forkIO (blockingThread bDone aDone)
    
    29
    +  _ <- forkIO (blockingThread aDone bDone)
    
    30
    +
    
    31
    +  -- Set up another thread that is blocked on a long timeout.
    
    32
    +  --
    
    33
    +  -- We use a timeout rather than I/O as it's more portable, whereas I/O waits
    
    34
    +  -- are different between posix and windows I/O managers.
    
    35
    +  --
    
    36
    +  -- One gotcha is that when the timeout completes then the deadlock will be
    
    37
    +  -- detected again (since the bug is about I/O or timeouts masking deadlock
    
    38
    +  -- detection). So for a reliable test the timeout used here must be longer
    
    39
    +  -- than the test framework's own timeout. So we use maxBound, and we adjust
    
    40
    +  -- the test framework's timeout to be short (see run_timeout_multiplier).
    
    41
    +  _ <- forkIO (threadDelay maxBound)
    
    42
    +
    
    43
    +  -- Wait on the two deadlocked threads to terminate. This is now a partial
    
    44
    +  -- deadlock but not a system deadlock (the main thread and the first two
    
    45
    +  -- threads are deadlocked, but the one waiting on the timeout is ok).
    
    46
    +  --
    
    47
    +  -- We now expect that the threads that are deadlocked are detected as such
    
    48
    +  -- and an exception is thrown to some or all of them.
    
    49
    +  --
    
    50
    +  -- Note that if this fails, the test itself will effectively deadlock and
    
    51
    +  -- will rely on the test framework's timeout.
    
    52
    +  atomically $ do
    
    53
    +    status <- mapM readTVar [aDone, bDone]
    
    54
    +    guard (or status)

  • testsuite/tests/rts/T26408a.stderr
    1
    +T26408a: Uncaught exception ghc-internal:GHC.Internal.IO.Exception.BlockedIndefinitelyOnSTM:
    
    2
    +
    
    3
    +thread blocked indefinitely in an STM transaction

  • testsuite/tests/rts/T26408b.hs
    1
    +import Control.Concurrent
    
    2
    +import Control.Concurrent.STM
    
    3
    +import Control.Exception
    
    4
    +import Control.Monad
    
    5
    +
    
    6
    +-- | Test to make sure that we can detect a /system deadlock/ promptly which
    
    7
    +-- means without relying on idle GC. To check this we have to turn off idle GC,
    
    8
    +-- (+RTS -I0) which is what normally triggers deadlock detection.
    
    9
    +--
    
    10
    +-- This is only expected to work in the non-threaded RTS. In principle this
    
    11
    +-- test could also work with the threaded RTS when using only a single
    
    12
    +-- capability, if the I\/O managers can support it. Currently that is not the
    
    13
    +-- case.
    
    14
    +--
    
    15
    +-- See Note [Deadlock detection]
    
    16
    +--
    
    17
    +main :: IO ()
    
    18
    +main = do
    
    19
    +
    
    20
    +  -- Set up two threads that are deadlocked on each other
    
    21
    +  aDone <- newTVarIO False
    
    22
    +  bDone <- newTVarIO False
    
    23
    +  let blockingThread theirDone ourDone =
    
    24
    +        atomically $ do
    
    25
    +          done <- readTVar theirDone
    
    26
    +          guard done
    
    27
    +          writeTVar ourDone True
    
    28
    +  _ <- forkIO (blockingThread bDone aDone)
    
    29
    +  _ <- forkIO (blockingThread aDone bDone)
    
    30
    +
    
    31
    +  -- Wait on the two deadlocked threads to terminate. This is now a system
    
    32
    +  -- deadlock, involving all three threads (the main thread and the other two).
    
    33
    +  --
    
    34
    +  -- We now expect that the threads that are deadlocked are detected as such
    
    35
    +  -- and an exception is thrown to some or all of them.
    
    36
    +  --
    
    37
    +  -- Note that if this fails, the test itself will effectively deadlock and
    
    38
    +  -- will rely on the test framework's timeout.
    
    39
    +  atomically $ do
    
    40
    +    status <- mapM readTVar [aDone, bDone]
    
    41
    +    guard (or status)

  • testsuite/tests/rts/T26408b.stderr
    1
    +T26408b: Uncaught exception ghc-internal:GHC.Internal.IO.Exception.BlockedIndefinitelyOnSTM:
    
    2
    +
    
    3
    +thread blocked indefinitely in an STM transaction

  • testsuite/tests/rts/T26408c.hs
    1
    +import Control.Concurrent
    
    2
    +import Control.Concurrent.STM
    
    3
    +import Control.Exception
    
    4
    +import Control.Monad
    
    5
    +
    
    6
    +import System.Posix.Signals
    
    7
    +import GHC.Stats.Experimental
    
    8
    +
    
    9
    +-- | Test to make sure that we do not falsely detect a deadlock when there is
    
    10
    +-- nothing to do except wait on signals. This is to make sure that our prompt
    
    11
    +-- system deadlock detection is not too eager.
    
    12
    +--
    
    13
    +-- We arrange a partial deadlock that can be broken if a signal arrives and
    
    14
    +-- the signal handler can unblock things.
    
    15
    +--
    
    16
    +-- This should /not/ result in any deadlock being detected, and no other odd
    
    17
    +-- behaviour, like getting caught in a loop and doing too many idle GCs. So we
    
    18
    +-- count and report the number of major GCs.
    
    19
    +--
    
    20
    +main :: IO ()
    
    21
    +main = do
    
    22
    +
    
    23
    +  -- Set up two threads that are deadlocked on each other
    
    24
    +  aDone <- newTVarIO False
    
    25
    +  bDone <- newTVarIO False
    
    26
    +  let blockingThread theirDone ourDone =
    
    27
    +        atomically $ do
    
    28
    +          done <- readTVar theirDone
    
    29
    +          guard done
    
    30
    +          writeTVar ourDone True
    
    31
    +
    
    32
    +  installHandler userDefinedSignal1
    
    33
    +                 (Catch $ do
    
    34
    +                    atomically $ writeTVar aDone True
    
    35
    +                              >> writeTVar bDone True
    
    36
    +                    putStrLn "caught SIGUSR1")
    
    37
    +                 Nothing
    
    38
    +
    
    39
    +  _ <- forkIO (blockingThread bDone aDone)
    
    40
    +  _ <- forkIO (blockingThread aDone bDone)
    
    41
    +  yield
    
    42
    +
    
    43
    +  -- Wait on the deadlocked threads to terminate. We now expect that the threads
    
    44
    +  -- that are deadlocked are detected as such and an exception is raised.
    
    45
    +  -- Note that if this fails, the test itself will effectively deadlock and
    
    46
    +  -- will rely on the test framework's timeout.
    
    47
    +  putStrLn "waiting on deadlocked threads..."
    
    48
    +  atomically $ do
    
    49
    +    status <- mapM readTVar [aDone, bDone]
    
    50
    +    guard (or status)
    
    51
    +
    
    52
    +  -- We expect exactly 1 major GC: the one triggered for deadlock detection
    
    53
    +  -- where we find no deadlock. We should then sleep without doing more GC.
    
    54
    +  -- An easy mistake would be looping: detecting probable deadlock, do idle GC.
    
    55
    +  -- See notifyIdleGcDeadlock().
    
    56
    +  stats <- getRTSStats
    
    57
    +  putStrLn ("terminated normally, major GCs = " ++ show (major_gcs stats))
    
    58
    +  -- This test _could_ be sensitive to changes in default heap sizes etc. If
    
    59
    +  -- this number ends up as 2 for example, then adjust the RTS opts (in the
    
    60
    +  -- makefile) so that the heap is big enough that normally no major GC would
    
    61
    +  -- be needed.

  • testsuite/tests/rts/T26408c.stdout
    1
    +[1 of 2] Compiling Main             ( T26408c.hs, T26408c.o )
    
    2
    +[2 of 2] Linking T26408c
    
    3
    +waiting on deadlocked threads...
    
    4
    +caught SIGUSR1
    
    5
    +terminated normally, major GCs = 1

  • testsuite/tests/rts/all.T
    ... ... @@ -682,6 +682,24 @@ test('T22859',
    682 682
           omit_ways(llvm_ways + ['nonmoving_thr', 'nonmoving_thr_sanity'])],
    
    683 683
          compile_and_run, ['-with-rtsopts -A8K'])
    
    684 684
     
    
    685
    +# Deadlock detection testsL
    
    686
    +# A: check we can detect a partial deadlock that is not a system deadlock
    
    687
    +test('T26408a', [exit_code(1), run_timeout_multiplier(0.1)], compile_and_run, [''])
    
    688
    +# B: check we can detect system deadlock with idle GC turned off (+RTS -I0)
    
    689
    +test('T26408b', [omit_ways(threaded_ways), extra_run_opts('+RTS -I0 -RTS'),
    
    690
    +                 exit_code(1), run_timeout_multiplier(0.1)],
    
    691
    +                 compile_and_run, ['-rtsopts'])
    
    692
    +# C: check we do not falsely detect deadlock when blocked only on signals
    
    693
    +# Skip for win32, wasm and js since this test requires posix signals
    
    694
    +test('T26408c', [when(opsys('mingw32'), skip),
    
    695
    +                 when(arch('wasm32'), skip), js_skip,
    
    696
    +                 run_timeout_multiplier(0.1)
    
    697
    +                ], makefile_test, ['T26408c'])
    
    698
    +# TODO: change omit_ways(threaded_ways) above
    
    699
    +# to all_io_managers_except['mio', 'winio']
    
    700
    +# and test +RTS -N1 with threaded ways, but will be broken for now.
    
    701
    +
    
    702
    +
    
    685 703
     # These tests need access to the internal RTS headers.
    
    686 704
     # TODO: there is probably some cleaner way to do this, and it should probably
    
    687 705
     # be guarded for in-tree tests, since it cannot work against an arbitrary