unliftio-0.2.25.1: test/UnliftIO/PooledAsyncSpec.hs
{-#LANGUAGE DeriveDataTypeable#-}
{-#LANGUAGE BangPatterns#-}
module UnliftIO.PooledAsyncSpec (spec) where
import Test.Hspec
import Control.Concurrent
import Data.List (sort)
import Test.QuickCheck
import qualified Data.Set as Set
import Data.Functor ((<$>))
import UnliftIO
data MyPooledException = PoolHellException
deriving (Show, Typeable)
instance Exception MyPooledException
-- | Strip out duplicates. (Taken from rio)
nubOrd :: Ord a => [a] -> [a]
nubOrd =
loop Set.empty
where
loop _ [] = []
loop !s (a:as)
| a `Set.member` s = loop s as
| otherwise = a : loop (Set.insert a s) as
spec :: Spec
spec = do
let exAction :: Int -> IO Int
exAction x = do
if (x == 2) then throwIO PoolHellException else return ()
return x
action :: Int -> IO ThreadId
action x = do
threadDelay (2 * 10^5)
myThreadId
myVar :: IO (TVar Int)
myVar = atomically $ newTVar 0
maxTVar :: Int -> TVar Int -> IO ()
maxTVar cval tvar = do
atomically $ do
v <- readTVar tvar
if cval >= v
then writeTVar tvar cval
else return ()
poolException :: Selector MyPooledException
poolException = const True
describe "pooled mapConcurrencyN" $ do
it "Throws exception properly" $ do
(pooledMapConcurrentlyN 5 exAction [1..5]) `shouldThrow` poolException
it "total thread should be >= 1" $ do
(pooledMapConcurrentlyN 0 action [1..5]) `shouldThrow` anyErrorCall
it "should not spawn more than five threads for five concurrent tasks" $ do
xs <- (pooledMapConcurrentlyN 5 action [1..5])
(length $ nubOrd xs) `shouldSatisfy` (<= (5 :: Int))
it "should not spawn more than three threads for five concurrent tasks" $ do
xs <- (pooledMapConcurrentlyN 3 action [1..5])
(length $ nubOrd xs) `shouldSatisfy` (<= (3 :: Int))
it "should spawn only one thread" $ do
xs <- (pooledMapConcurrentlyN 1 action [1..5])
(length $ nubOrd xs) `shouldBe` 1
it "never uses more than the given number of pools and doesn't miss any return values" $
forAllShrink ((+ 1) . abs <$> arbitrary) (filter (>= 1) . shrink) $ \threads ->
property $ \list -> do
threadIdsVar <- newTVarIO []
let go :: Int -> IO Int
go i = do
tid <- myThreadId
atomically $ modifyTVar threadIdsVar (tid :)
return i
list' <- pooledMapConcurrentlyN threads go list
sort list' `shouldBe` sort list
tids <- readTVarIO threadIdsVar
length (nubOrd tids) `shouldSatisfy` (<= threads)
describe "pooled mapConcurrencyN_" $ do
it "Throws exception properly" $ do
(pooledMapConcurrentlyN_ 5 exAction [1..5]) `shouldThrow` poolException
it "total thread should be >= 1" $ do
(pooledMapConcurrentlyN_ 0 action [1..5]) `shouldThrow` anyErrorCall
it "find proper maximum value" $ do
var <- myVar
xs <- (pooledMapConcurrentlyN_ 5 (\x -> maxTVar x var) [1..5])
newVar <- atomically $ readTVar var
atomically $ writeTVar var 0
newVar `shouldBe` 5
it "find proper maximum value with 2 threads" $ do
var <- myVar
xs <- (pooledMapConcurrentlyN_ 2 (\x -> maxTVar x var) [1..5])
newVar <- atomically $ readTVar var
atomically $ writeTVar var 0
newVar `shouldBe` 5
it "find proper maximum value with 1 threads" $ do
var <- myVar
xs <- (pooledMapConcurrentlyN_ 1 (\x -> maxTVar x var) [1..5])
newVar <- atomically $ readTVar var
atomically $ writeTVar var 0
newVar `shouldBe` 5
it "make sure activity is happening in different threads" $ do
let myThreads :: IO (TVar [ThreadId])
myThreads = atomically $ newTVar []
collectThreads :: TVar [ThreadId] -> IO ()
collectThreads threadVar = do
tid <- myThreadId
atomically $ do
tvar <- readTVar threadVar
writeTVar threadVar (tid:tvar)
threadDelay $ 2 * 10^5
tid <- myThreads
xs <- pooledMapConcurrentlyN_ 5 (\_ -> collectThreads tid) [1..5]
tids <- atomically $ readTVar tid
(length $ nubOrd tids) `shouldSatisfy` (<= 5)
it "Not more than 5 threads will be spawned even if pooling is set to 8 " $ do
let myThreads :: IO (TVar [ThreadId])
myThreads = atomically $ newTVar []
collectThreads :: TVar [ThreadId] -> IO ()
collectThreads threadVar = do
tid <- myThreadId
atomically $ do
tvar <- readTVar threadVar
writeTVar threadVar (tid:tvar)
threadDelay $ 2 * 10^5
tid <- myThreads
xs <- pooledMapConcurrentlyN_ 8 (\_ -> collectThreads tid) [1..5]
tids <- atomically $ readTVar tid
(length $ nubOrd tids) `shouldSatisfy` (<= 5)
describe "replicate concurrencyN" $ do
it "Throws exception properly" $ do
(pooledReplicateConcurrentlyN 5 1 (exAction 2)) `shouldThrow` poolException
it "total thread should be >= 1" $ do
(pooledReplicateConcurrentlyN 0 1 (action 1)) `shouldThrow` anyErrorCall
it "Read tvar value should be 100" $ do
var <- myVar
xs <- (pooledReplicateConcurrentlyN 5 5 (maxTVar 100 var))
newVar <- atomically $ readTVar var
atomically $ writeTVar var 0
newVar `shouldBe` 100
it "should not spawn more than five threads for five concurrent tasks" $ do
xs <- (pooledReplicateConcurrentlyN 5 5 (action 1))
(length $ nubOrd xs) `shouldSatisfy` (<= (5 :: Int))
it "should not spawn more than three threads for five concurrent tasks" $ do
xs <- (pooledReplicateConcurrentlyN 3 5 (action 1))
(length $ nubOrd xs) `shouldSatisfy` (<= (3 :: Int))
it "should spawn only one thread" $ do
xs <- (pooledReplicateConcurrentlyN 1 5 (action 1))
(length $ nubOrd xs) `shouldBe` 1
it "should give empty list" $ do
xs <- (pooledReplicateConcurrentlyN 3 0 (action 1))
xs `shouldBe` []
it "should give empty list for -ve count" $ do
xs <- (pooledReplicateConcurrentlyN 3 (-3) (action 1))
xs `shouldBe` []
describe "pooled replicateConcurrencyN_" $ do
it "Throws exception properly" $ do
(pooledReplicateConcurrentlyN_ 5 1 (exAction 2)) `shouldThrow` poolException
it "total thread should be >= 1" $ do
(pooledReplicateConcurrentlyN_ 0 2 (action 1)) `shouldThrow` anyErrorCall
it "find proper maximum value" $ do
var <- myVar
pooledReplicateConcurrentlyN_ 5 3 (maxTVar 200 var)
newVar <- atomically $ readTVar var
atomically $ writeTVar var 0
newVar `shouldBe` 200
it "Should be initial value" $ do
var <- myVar
pooledReplicateConcurrentlyN_ 5 (-2) (maxTVar 200 var)
newVar <- atomically $ readTVar var
atomically $ writeTVar var 0
newVar `shouldBe` 0