Simon Jakobi pushed to branch wip/sjakobi/T27547-testsuite-robustness-reword at Glasgow Haskell Compiler / GHC
Commits:
-
9c373e02
by Simon Jakobi at 2026-08-16T12:27:56+02:00
-
798f43df
by Simon Jakobi at 2026-08-16T12:27:56+02:00
-
14fdef2e
by Simon Jakobi at 2026-08-16T12:37:28+02:00
4 changed files:
- compiler/GHC/Runtime/Interpreter.hs
- testsuite/config/ghc
- testsuite/tests/rts/all.T
- testsuite/timeout/timeout.py
Changes:
| ... | ... | @@ -113,6 +113,7 @@ import qualified GHC.Exts.Heap as Heap |
| 113 | 113 | import GHC.Stack.CCS (CostCentre,CostCentreStack)
|
| 114 | 114 | import System.Directory
|
| 115 | 115 | import System.Process
|
| 116 | +import System.Timeout (timeout)
|
|
| 116 | 117 | import qualified GHC.InfoProv as InfoProv
|
| 117 | 118 | |
| 118 | 119 | import GHC.Builtin.Modules( gHC_PRIM, gHC_PRIMOPWRAPPERS )
|
| ... | ... | @@ -652,10 +653,16 @@ stopInterp interp = case interpInstance interp of |
| 652 | 653 | case state of
|
| 653 | 654 | InterpPending -> pure state -- already stopped
|
| 654 | 655 | InterpRunning i -> do
|
| 655 | - ex <- getProcessExitCode (interpHandle (instProcess i))
|
|
| 656 | - if isJust ex
|
|
| 657 | - then pure ()
|
|
| 658 | - else sendMessage i Shutdown
|
|
| 656 | + let hdl = interpHandle (instProcess i)
|
|
| 657 | + ex <- getProcessExitCode hdl
|
|
| 658 | + unless (isJust ex) $ do
|
|
| 659 | + sendMessage i Shutdown
|
|
| 660 | + -- The interpreter process shares our stdout/stderr; wait for
|
|
| 661 | + -- it to exit (flushing its output), lest output it produced
|
|
| 662 | + -- on our behalf be lost if it is killed once we exit.
|
|
| 663 | + -- Bounded, in case the process is wedged.
|
|
| 664 | + _ <- timeout 5000000 {- 5s -} (waitForProcess hdl)
|
|
| 665 | + pure ()
|
|
| 659 | 666 | pure InterpPending
|
| 660 | 667 | |
| 661 | 668 | -- -----------------------------------------------------------------------------
|
| ... | ... | @@ -197,6 +197,11 @@ debug_ways = [x[0] for x in config.way_flags.items() |
| 197 | 197 | threaded_ways = [x[0] for x in config.way_flags.items()
|
| 198 | 198 | if '-threaded' in x[1] or 'ghci' == x[0] or 'ghci-opt' == x[0]]
|
| 199 | 199 | |
| 200 | +# #27547
|
|
| 201 | +nonmoving_threaded_ways = [name for name, flags in config.way_flags.items()
|
|
| 202 | + if '-threaded' in flags
|
|
| 203 | + if '-xn' in config.way_rts_flags.get(name, [])]
|
|
| 204 | + |
|
| 200 | 205 | # Ways which run with multiple capabilities
|
| 201 | 206 | concurrent_ways = [name for name, flags in config.way_flags.items()
|
| 202 | 207 | if '-threaded' in flags or 'ghci' == name or 'ghci-opt' == name
|
| ... | ... | @@ -668,7 +668,11 @@ test('IOManager', [js_skip, when(arch('wasm32'), skip), when(opsys('mingw32'), s |
| 668 | 668 | test('T24142', [req_target_smp], compile_and_run, ['-threaded -with-rtsopts "-N2"'])
|
| 669 | 669 | |
| 670 | 670 | test('T25232', [unless(have_profiling(), skip), only_ways(['normal','nonmoving','nonmoving_prof','nonmoving_thr_prof']), extra_ways(['nonmoving', 'nonmoving_prof'] + (['nonmoving_thr_prof'] if have_threaded() else []))], compile_and_run, [''])
|
| 671 | -test('T25280', [unless(opsys('linux'),skip),req_process,js_skip], compile_and_run, [''])
|
|
| 671 | +# T25280 waits for its forked children, so it detects the forkProcess
|
|
| 672 | +# child-shutdown deadlock of #27547 (by timing out).
|
|
| 673 | +test('T25280', [unless(opsys('linux'),skip),req_process,js_skip,
|
|
| 674 | + expect_broken_for(27547, nonmoving_threaded_ways)],
|
|
| 675 | + compile_and_run, [''])
|
|
| 672 | 676 | |
| 673 | 677 | # N.B. This will likely issue a warning on stderr but we merely care that the
|
| 674 | 678 | # program doesn't crash.
|
| ... | ... | @@ -27,6 +27,19 @@ try: |
| 27 | 27 | else:
|
| 28 | 28 | raise e
|
| 29 | 29 | |
| 30 | + def killStragglers(pid):
|
|
| 31 | + # A test can leave descendants that outlive its main process (e.g. a
|
|
| 32 | + # deadlocked forkProcess child, #27547). They hold the inherited
|
|
| 33 | + # stdout/stderr pipes open and would hang the testsuite driver, so
|
|
| 34 | + # kill the whole process group.
|
|
| 35 | + try:
|
|
| 36 | + os.killpg(pid, signal.SIGKILL)
|
|
| 37 | + except OSError as e:
|
|
| 38 | + # ESRCH: group already gone. EPERM: macOS quirk — signalling a
|
|
| 39 | + # group whose leader is a reaped zombie.
|
|
| 40 | + if e.errno not in (errno.ESRCH, errno.EPERM):
|
|
| 41 | + raise e
|
|
| 42 | + |
|
| 30 | 43 | pid = os.fork()
|
| 31 | 44 | if pid == 0:
|
| 32 | 45 | # child
|
| ... | ... | @@ -40,6 +53,9 @@ try: |
| 40 | 53 | old = signal.signal(signal.SIGALRM, handler)
|
| 41 | 54 | signal.alarm(secs)
|
| 42 | 55 | (pid2, res) = os.waitpid(pid, 0)
|
| 56 | + # The handler signals pid, so it must not run once pid is reaped.
|
|
| 57 | + signal.alarm(0)
|
|
| 58 | + killStragglers(pid)
|
|
| 43 | 59 | if (os.WIFEXITED(res)):
|
| 44 | 60 | sys.exit(os.WEXITSTATUS(res))
|
| 45 | 61 | elif os.WIFSIGNALED(res):
|