Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strange behaviour when a test causes a <<loop>> #238

Open
hsenag opened this issue Feb 2, 2019 · 3 comments
Open

Strange behaviour when a test causes a <<loop>> #238

hsenag opened this issue Feb 2, 2019 · 3 comments

Comments

@hsenag
Copy link

hsenag commented Feb 2, 2019

When the RTS detects a loop in a test case, I'm seeing strange behaviour from tasty. Consider the following code, which has a loop that can easily be detected:

module Main where

import Control.Exception
import Test.Tasty
import Test.Tasty.HUnit

loopy =
 catch
  (evaluate (let x = x in x))
  -- (fail "foo")
  (print :: SomeException -> IO())

main = do
  loopy -- verify that the RTS does indeed detect the loop
  defaultMain $ testCase "Loop test" loopy

On Windows (GHC 8.2.2) instead of seeing a NonTermination exception, I get AsyncCancelled
getting caught directly, and then somehow the NonTermination leaks past it and takes down the
whole program:

$ ghc -package tasty -package tasty-hunit -o main Main.hs -O2 && ./main
[1 of 1] Compiling Main             ( Main.hs, Main.o )
Linking main.exe ...
<<loop>>
Loop test: AsyncCancelled
main.exe: <<loop>>

On LInux (GHC 8.2.2 and GHC 8.4.3), the test just hangs, not using CPU:

$ ghc -package tasty -package tasty-hunit -o main Main.hs -O2 && ./main
[1 of 1] Compiling Main             ( Main.hs, Main.o )
Linking main ...
<<loop>>
Loop test: 

In each case I'm using tasty 1.2.1 and tasty-hunit 0.10.0.1.

If I change the loop to some other exception, e.g. by commenting out the evaluate line and uncommenting the fail line in the above code, then it's caught as expected and the test infrastructure continues normally:

$ ghc -package tasty -package tasty-hunit -o main Main.hs -O2 && ./main
[1 of 1] Compiling Main             ( Main.hs, Main.o )
Linking main ...
user error (foo)
user error (foo)
Loop test: OK (0.01s)

All 1 tests passed (0.02s)

I guess it must be something to do with the way tasty runs tests with STM on separate threads. Is there some obvious reason for this I'm missing or is it known already? If not I can try to understand the behaviour better by inlining the tasty test runner code into my example program.

@UnkindPartition
Copy link
Owner

Reminds me of #15 (comment), and I suspect the cause (or even the fix) to be similar.

Would be also nice to add a test for this once we figure it out.

@hsenag
Copy link
Author

hsenag commented Feb 2, 2019

I think I don't understand even the basics of what could be going on. Why is it possible for the calling context to affect the behaviour of the loop detector? I can sort of see in #15 that the actual result being passed across threads was the loop itself so the non-termination was being exported, but in my example I'm being very careful to force it to become an exception inside the test itself.

@UnkindPartition
Copy link
Owner

but in my example I'm being very careful to force it to become an exception inside the test itself

I'm not so sure about that. You are not generating an exception inside the test; you're relying on the RTS/GC to supply it. And the GC analyses the heap as a whole, so the global state of the heap affects the outcome.

Here's a minimal example that doesn't even involve any concurrency:

import Control.Exception
import Foreign.StablePtr

loopy =
 catch
  (evaluate (let x = x in x))
  (print :: SomeException -> IO())
main = do
  newStablePtr loopy
  loopy

The example above just hangs here (8.6.3, Linux x86-64, non-threaded RTS), whereas if you comment the newStablePtr line, it prints the exception. The reason (I think) is because the reference to the blackholed thunk is retained independently, and the GC realizes that there's an opportunity that the thunk will be eventually updated. I suspect that something similar is happening within tasty (the test references the thunk, the main thread references the test tree etc., plus the fact that we have a stable ptr to the main thread as discussed in #15).

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

No branches or pull requests

2 participants