module Main where
import Prelude
import Control.Concurrent.STM
import Test.QuickCheck.Instances
import Test.Tasty
import Test.Tasty.Runners
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck
import qualified Test.QuickCheck as QuickCheck
import qualified Test.QuickCheck.Property as QuickCheck
import qualified SlaveThread as S
import qualified Control.Concurrent.SSem as SSem
main =
defaultMain $
testGroup "All" $ [
testCase "Failing in finalizer doesn't break everything" $ do
finalizer1CalledVar <- newTVarIO False
finalizer2CalledVar <- newTVarIO False
result <- let
finalizer1 =
atomically $ writeTVar finalizer1CalledVar True
finalizer2 =
do
atomically $ writeTVar finalizer2CalledVar True
throwIO (userError "finalizer2 failed")
in try @SomeException $ do
S.forkFinally finalizer1 $ do
S.forkFinally finalizer2 $ threadDelay 100
threadDelay (10^4)
assertEqual "" "Left user error (finalizer2 failed)" (show result)
finalizer2Called <- atomically (readTVar finalizer2CalledVar)
finalizer1Called <- atomically (readTVar finalizer1CalledVar)
assertEqual "" True finalizer2Called
assertEqual "" True finalizer1Called
,
testCase "Forked threads run fine" $ do
replicateM_ 100000 $ do
var <- newMVar 0
let increment = modifyMVar_ var (return . succ)
semaphore <- SSem.new 0
S.fork $ do
increment
semaphore' <- SSem.new (-1)
S.fork $ do
increment
SSem.signal semaphore'
S.fork $ do
increment
SSem.signal semaphore'
SSem.wait semaphore'
SSem.signal semaphore
SSem.wait semaphore
assertEqual "" 3 =<< readMVar var
,
testCase "Killing a thread kills deep slaves" $ do
replicateM_ 100000 $ do
var <- newMVar 0
semaphore <- SSem.new 0
thread <-
S.forkFinally (SSem.signal semaphore) $ do
join $ forkWait $ do
join $ forkWait $ do
w <- forkWait $ do
threadDelay $ 10^6
modifyMVar_ var (return . succ)
threadDelay $ 10^6
modifyMVar_ var (return . succ)
w
killThread thread
SSem.wait semaphore
assertEqual "" 0 =<< readMVar var
,
testCase "Dying normally kills slaves" $ do
replicateM_ 100000 $ do
var <- newIORef 0
let increment = modifyIORef var (+1)
semaphore <- SSem.new 0
S.forkFinally (SSem.signal semaphore) $ do
S.fork $ do
threadDelay $ 10^6
increment
S.fork $ do
threadDelay $ 10^6
increment
SSem.wait semaphore
assertEqual "" 0 =<< readIORef var
,
testCase "Finalization is in order" $ do
replicateM_ 100000 $ do
var <- newMVar []
semaphore <- SSem.new 0
S.forkFinally (uninterruptibleMask_ (modifyMVar_ var (return . (1:)) >> SSem.signal semaphore)) $ do
semaphore' <- SSem.new 0
S.forkFinally (uninterruptibleMask_ (modifyMVar_ var (return . (2:)) >> SSem.signal semaphore')) $ do
S.forkFinally (uninterruptibleMask_ (modifyMVar_ var (return . (3:)))) $ return ()
S.forkFinally (uninterruptibleMask_ (modifyMVar_ var (return . (3:)))) $ return ()
SSem.wait semaphore'
SSem.wait semaphore
assertEqual "" [1,2,3,3] =<< readMVar var
,
testCase "Exceptions don't get lost" $ do
replicateM_ 100000 $ do
result <- try @SomeException $ do
S.fork $ do
S.fork $ do
error "!"
threadDelay $ 10^6
threadDelay $ 10^6
assertBool "" (isLeft result)
,
testCase "Slaves are finalized before master" $ do
replicateM_ 100000 $ do
ready <- newEmptyMVar
var <- newEmptyTMVarIO
thread <-
S.forkFinally (atomically (tryPutTMVar var 1)) $ do
S.forkFinally (atomically (tryPutTMVar var 0)) $
threadDelay $ 10^6
putMVar ready ()
threadDelay $ 10^6
takeMVar ready
killThread thread
assertEqual "First finalizer is not slave" 0 =<< atomically (readTMVar var)
]
forkWait :: IO a -> IO (IO ())
forkWait io =
do
v <- newEmptyMVar
S.fork $ do
r <- try io
putMVar v ()
either (throwIO @SomeException) return r
return $ takeMVar v