Skip to content

Ignore SessionException during test cleanup from listener thread#637

Closed
crtschin wants to merge 1 commit into
haskell:masterfrom
crtschin:crtschin/ignore-session-exceptions-during-shutdown
Closed

Ignore SessionException during test cleanup from listener thread#637
crtschin wants to merge 1 commit into
haskell:masterfrom
crtschin:crtschin/ignore-session-exceptions-during-shutdown

Conversation

@crtschin
Copy link
Copy Markdown

@crtschin crtschin commented Apr 8, 2026

Follows haskell/haskell-language-server#4884 (comment).

I get quite a few flaky errors when running HLS's ghcide-tests testsuite.

The following is a small investigation into why. This piece of code is the failing bit. We create pipes and pass these to both ghcide and lsp-test for communication. Tests then intermittently fail with the following error:

Test Failure
ghcide
  constructor hover (#2904)
    ...
    ConstructorsLinear.hs
      ...
      C: FAIL
        Exception: Language server unexpectedly terminated
        HasCallStack backtrace:
          collectBacktraces, called at libraries/ghc-internal/src/GHC/Internal/Exception.hs:169:13 in ghc-internal:GHC.Internal.Exception
          toExceptionWithBacktrace, called at libraries/ghc-internal/src/GHC/Internal/IO.hs:260:11 in ghc-internal:GHC.Internal.IO
          throwIO, called at ./Control/Concurrent/Async.hs:110:7 in tasty-1.5.3-5b82fefc56677f15fc945a95865c9329582aa96d81dddfd360d944f99f7a8e90:Control.Concurrent.Async


        Use -p '/constructor hover (#2904)/&&/ConstructorsLinear.hs.C/' to rerun this test only.

Leading me to lsp-test's getHeaders, called when it tries to read from the pipe. Now, I spent quite a bit trying to track down why the handle was closed, but couldn't find anything definitive. The only hClose in this codepath is this one that occurs after the test is done.

Taking a defensive view and accepting that the pipe can be broken, leads to the question when it is acceptable that the pipe can be broken. I think that bit is here.

finally (timeout msgTimeoutMs (runSession' exitServer))
-- Make sure to kill the listener first, before closing
-- handles etc via cleanupProcess
(cancel async >> cleanup)

When the server test session is done, and we're cleaning up the communication threads. The error handler in the listener thread can still throw the EOF error to the main thread though, interrupting the main thread that's doing the gentle shutdown of the language server.

Short aside on the cleanup

I was severely confused why that finally was there in the shutdown code. After all, it's in the after block of a bracket. It should always be ran already as mask is on. Even better, this huge comment on timeout, gives that timeout uses async exceptions to do it's thing, which in a masked setting means that the timeout is deferred until the masking state is dropped.

What I missed is (as always) interruptibility. During interruptible functions, i.e. blocking functions on MVar, async exceptions can occur so the finally is still relevant.

The change in this PR stops the tests from being flaky (I can run the suite 100x without failures, which would have otherwise occurred within 1-5 runs).

@crtschin crtschin changed the title Ignore SessionException during cleanup Ignore SessionException during test cleanup from listener thread Apr 8, 2026
@crtschin
Copy link
Copy Markdown
Author

crtschin commented Apr 9, 2026

Now, I spent quite a bit trying to track down why the handle was closed, but couldn't find anything definitive.

Just had an idea, Handle get closed during GC automatically. The writing end of this bit is given to ghcide. If the graceful shutdown sequence proceeds as normal, the ghcide thread will close down while the lsp-test listener keeps reading. If in that period of time a GC occurs, the write handle will be closed, and the read-end reads a EOF, raising the exception.

@crtschin
Copy link
Copy Markdown
Author

crtschin commented Apr 9, 2026

Nevermind, can't locally reproduce this with my change in haskell/haskell-language-server#4886.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant