{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
module Main (main) where
import Control.Applicative ((<|>))
import Control.Concurrent (threadDelay)
import Control.Concurrent.STM (TMVar, putTMVar, readTMVar)
import Control.Exception
import Effectful
import Effectful.Ki
import Test.Tasty
import Test.Tasty.HUnit
main :: IO ()
main =
defaultMain $
testGroup
"ki-effectful"
[ testCase "`fork` works" $ do
res <- runStructuredEff testFork
assertEqual "match" res 42
, testCase "`fork` propagates exceptions" $ do
assertThrow $ runStructuredEff testThrow
, testCase "run a `fork` in a parent scope" $ do
res <- runStructuredEff testScopeLifting
assertEqual "match" res 42
, testCase "`client` works" $ do
res <- runStructuredEff testClient
assertEqual "match" res (Just 42)
, testCase "`client` cancel" $ do
res <- runStructuredEff testClientCancel
assertEqual "match" res Nothing
]
runStructuredEff :: Eff '[StructuredConcurrency, IOE] a -> IO a
runStructuredEff = runEff . runStructuredConcurrency
testFork :: StructuredConcurrency :> es => Eff es Int
testFork = scoped $ \scope -> do
child <- fork scope $ pure 42
atomically $ await child
testThrow :: StructuredConcurrency :> es => Eff es Int
testThrow = scoped $ \scope -> do
_ <- fork scope $ error "oops"
child <- fork scope $ pure 42
atomically $ do
awaitAll scope
await child
testScopeLifting :: StructuredConcurrency :> es => Eff es Int
testScopeLifting = scoped $ \parentScope -> do
child <- scoped $ \_localScope -> do
fork parentScope $ pure 42
atomically $ await child
testClient :: StructuredConcurrency :> es => Eff es (Maybe Int)
testClient = scoped $ \scope -> do
hitman <- newEmptyTMVarIO
child <- fork scope $ client hitman (pure 42)
atomically $ await child
testClientCancel :: (IOE :> es, StructuredConcurrency :> es) => Eff es (Maybe Int)
testClientCancel = scoped $ \scope -> do
hitman <- newEmptyTMVarIO
child <- fork scope $ client hitman $ do
-- liftIO $ putStrLn "running"
liftIO (threadDelay 500000)
pure 42
liftIO (threadDelay 100000)
-- liftIO $ putStrLn "stopping"
atomically $ putTMVar hitman ()
atomically $ await child
-- | cancellable client implementation proposed in https://github.com/awkward-squad/ki/issues/11#issuecomment-1214159154
client :: StructuredConcurrency :> es => TMVar () -> Eff es a -> Eff es (Maybe a)
client doneVar action = scoped $ \scope -> do
thread <- fork scope action
let waitDone = do
() <- readTMVar doneVar
pure Nothing
waitThread = do
result <- await thread
pure (Just result)
atomically $ waitDone <|> waitThread
assertThrow :: IO a -> Assertion
assertThrow action = do
res <- try @SomeException action
case res of
Left _ -> pure ()
Right _ -> assertFailure "Action did not throw"