packages feed

kazura-queue-0.1.0.0: test/Test/Concurrent.hs

{-# LANGUAGE BangPatterns        #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Test.Concurrent where

import qualified Test.QuickCheck as Q

import qualified Control.Concurrent       as CC
import qualified Control.Concurrent.Async as AS
import qualified Control.Concurrent.MVar  as MV
import qualified Control.Exception        as E
import           Control.Monad            ((>=>))
import qualified Control.Monad            as M
import qualified GHC.Conc                 as CC

import qualified Data.Maybe       as MB
import qualified Data.Traversable as TF
import           Data.Typeable    (Typeable)

class HasThread th where
    threadId     :: th -> IO CC.ThreadId
    throwTo      :: E.Exception e => th -> e -> IO ()
    throwTo th e = threadId th >>= flip E.throwTo e

threadStatus :: HasThread th => th -> IO CC.ThreadStatus
threadStatus = threadId >=> CC.threadStatus

instance HasThread CC.ThreadId where
    threadId = return

instance HasThread (AS.Async x) where
    threadId = return . AS.asyncThreadId

isFinish :: CC.ThreadStatus -> Bool
isFinish CC.ThreadFinished = True
isFinish CC.ThreadDied     = True
isFinish _                 = False

isStop :: CC.ThreadStatus -> Bool
isStop CC.ThreadRunning = False
isStop _                = True

withWaitStart :: (IO () -> IO x) -> IO x
withWaitStart actf = do
    mv    <- MV.newEmptyMVar
    mdelay <- Q.generate $ arbitraryDelay 20000
    async <- AS.async . actf $ MV.readMVar mv
    case mdelay of
        Just delay -> CC.threadDelay delay
        Nothing    -> return ()
    CC.putMVar mv ()
    AS.wait async

concurrently :: IO a -> IO b -> IO (a, b)
concurrently act1 act2 = do
    mdelay1 <- Q.generate $ arbitraryDelay 20000
    mdelay2 <- Q.generate $ arbitraryDelay 20000
    withWaitStart $ \ wait ->
        wrap wait mdelay1 act1 `AS.concurrently` wrap wait mdelay2 act2
    where
        wrap :: IO () -> Maybe Int -> IO a -> IO a
        wrap wait mdelay act = wait >> TF.for mdelay CC.threadDelay >> act

mapConcurrently :: [IO a] -> IO [a]
mapConcurrently acts = do
    let len = length acts
    mds <- Q.generate . Q.vectorOf len $ fmap (`mod` 20000) <$> Q.arbitrary
    withWaitStart $ \ wait -> do
        AS.mapConcurrently id $ wrap wait <$> zip mds acts
    where
        wrap :: IO () -> (Maybe Int, IO a) -> IO a
        wrap wait (mdelay, act) = wait >> TF.for mdelay CC.threadDelay >> act

mapConcurrently_ :: [IO a] -> IO ()
mapConcurrently_ = M.void . mapConcurrently

waitStop :: HasThread th => th -> IO CC.ThreadStatus
waitStop th = snd . head <$> waitAny isStop [th]

waitFinish :: HasThread th => th -> IO CC.ThreadStatus
waitFinish th = snd . head <$> waitFinishAny [th]

waitFinishAny :: HasThread th => [th] -> IO [(Int, CC.ThreadStatus)]
waitFinishAny = waitAny isFinish

waitAny :: HasThread th =>
    (CC.ThreadStatus -> Bool) -> [th] -> IO [(Int, CC.ThreadStatus)]
waitAny = waitAnyAtLeast 1

waitAnyAtLeast :: HasThread th =>
    Int -> (CC.ThreadStatus -> Bool) -> [th] -> IO [(Int, CC.ThreadStatus)]
waitAnyAtLeast num f ths = go
    where
        go = do
            statuses <- M.sequence $ threadStatus <$> ths
            let satisfied = filter (f . snd) $ zip [0..] statuses
            if length satisfied >= num
                then return satisfied
                else CC.threadDelay 1 >> go

data RandomException = RandomException Int String
    deriving (Show, Typeable)
instance E.Exception RandomException

ignoreException :: IO a -> IO (Maybe a)
ignoreException act = (Just <$> act)
    `E.catch` \ (_err :: RandomException) -> do
--        E.uninterruptibleMask_ $ putStrLn $ "---- Exception throwed : " ++ show _err
        return Nothing

ignoreException_ :: IO a -> IO ()
ignoreException_ = M.void . ignoreException

runningThreadId :: HasThread th => th -> IO (Maybe CC.ThreadId)
runningThreadId th = do
    status <- threadStatus th
    if isFinish status
        then return Nothing
        else Just <$> threadId th

throwExceptionRandomly :: HasThread th => [th] -> IO ()
throwExceptionRandomly ths = go (1 :: Int)
    where
        getAlives = fmap MB.catMaybes . M.sequence $ runningThreadId <$> ths
        go !c = do
            mdelay <- Q.generate $ arbitraryDelay $ 20000 * c
            case mdelay of
                Just delay -> CC.threadDelay delay
                Nothing    -> return ()
            alives <- getAlives
            if length alives == 0
                then return ()
                else do
                    alive <- Q.generate $ Q.elements alives
                    throwTo alive . RandomException c $ show mdelay ++ " : " ++ show (length alives)
                    go $ c+1

arbitraryDelay :: Int -> Q.Gen (Maybe Int)
arbitraryDelay limit = do
    mbase <- Q.arbitrary
    multi1 <- (+1) . abs <$> Q.arbitrary
    multi2 <- (+1) . abs <$> Q.arbitrary
    case mbase of
        Just base -> return . Just . (`mod` limit) $ base * multi1 * multi2
        Nothing   -> return Nothing