hspec / hspec-hedgehog

A library to integrate hedgehog tests into your hspec test suite.
https://hackage.haskell.org/package/hspec-hedgehog
Other
28 stars 6 forks source link

Background threads can survive ^C #16

Open treeowl opened 3 years ago

treeowl commented 3 years ago

I honestly have no idea whether this is a problem in HSpec, hedgehog, or hspec-hedgehog, so I figured I'd start here. I'm running a small test suite testing pure code (so the code being tested doesn't fork anything). Sometimes, the generated test cases are much too large (a bug in my test suite), causing execution to take longer than I want to wait. When I hit ^C, I get back to my shell immediately. Unfortunately, some background thread keeps going in the background, seemingly indefinitely, burning lots of CPU, and I have to kill it separately. Any guesses?

sol commented 1 year ago

I have seen this many times with processes that start a (ghc compiled Haskell) subprocesses. I don't have all the details available right now, but from what I remember, this happens when you try to write to a redirected/captured handle in the subprocess where the reading end has been closed in the parent process.

Something like:

  1. Parent process A starts a subprocess B, creating a pipe hErr for the subprocesse to use as stderr.
  2. On ^C / UserInterupt, A does two things, in that order:
    1. Close hErr
    2. waitForProcess on B
  3. B also receives a UserInterupt (assuming we started it with delegate_ctlc, which I think is the default). The UserInterupt exception is not explicitly handled by B and propagated to the default exception handler.
  4. The default exception handler of B catches the exception and tries to write to stderr.
  5. Writing to stderr fails with some "broken pipe" exception as the reading end hErr has been closed in the parent (GOTO 4).

This results in an infinite loop: 4/5/4/5/4/5/4/5/...

sol commented 1 year ago

I think it's unlikely that this is a bug in hapec or hspec-hedgehog. @treeowl do you remember how you invoked your tests?

If it was through cabal test, or something, then it's probably worth looking at how they start subprocesses.

Specifically, in the parent only close any pipe reading ends after waitForProcess concluded, so that the child process does not go into an infinite loop when trying to write to a "closed" stderr.