{- Copyright (C) 2009-2010 Andrejs Sisojevs All rights reserved. For license and copyright information, see the file COPYRIGHT -} -------------------------------------------------------------------------- -------------------------------------------------------------------------- {- abbr. PCC = PriorityChansConverger Table of content: 1. Let's define system structure first. 1.1. Sources of input to our PCC. 1.2. Output of our PCC reader. 1.3. Put together PCC, it's input sources and it's output reader. 2. Now, when the infrastructure is ready let's define some test cases and run them. 2.1. Test cases set. Not a complete set. If anybody wants to test it seriously, that would be great. 2.2. Running tests. 3. Representations (instances of Show class). Appendix: Some helper functions. ------------------------------------------- Direct PCC usage is to be found only in parts 1.1., 1.2. and 1.3. -} {-# LANGUAGE ScopedTypeVariables, BangPatterns #-} module SimplePCCTestEnvironment where import Control.Concurrent import Control.Concurrent.MVar import Control.Concurrent.ConcurrentUISupport -- this module is provided by PCC package import Control.Concurrent.STM import Control.Concurrent.PriorityChansConverger -- this module is provided by PCC package import Control.Monad import qualified Control.Exception as E import Data.List import qualified Data.Map as M import Data.Map (Map) import qualified Safe.Failure as SF import System.IO import System.IO.Unsafe __useDumps = True ---------------------------------------------------------------------- ---------------------------------------------------------------------- -- 1. Let's define system structure first. ---------------------------------------------------------------------- -- 1.1. Sources of input to our PCC. type PCCFillerID = Int -- PCC requires anything, that is of class Ord type PCCFillerState = Int type PCCFillerOutput = PCCFillerState type PCCFillerCloser = ThreadFinisher type MicrosecondsForDelay = Int type SecondsForDelay = Int data PCCFiller = PCCFiller { pccfInputID :: PCCFillerID -- local type , pccfStartPriority :: StartPriority -- Int , pccfChanCapacity :: ChanCapacity -- Int , pccfPermitToBlock :: PermitToBlock -- Bool , pccfDelayBetweenIterations :: MicrosecondsForDelay , pccfStateMV :: MVar PCCFillerState , pccfStateTransition :: PCCFillerState -> PCCFillerState , pccfStopFiller :: MVar PCCFillerCloser } defaultPCCFiller :: IO PCCFiller defaultPCCFiller = do state_mv <- newMVar 1 stopper <- newMVar $ return () return PCCFiller { pccfInputID = undefined , pccfStartPriority = undefined , pccfChanCapacity = undefined , pccfPermitToBlock = undefined , pccfDelayBetweenIterations = undefined , pccfStateMV = state_mv , pccfStateTransition = (+ 1) , pccfStopFiller = stopper } runPCCFiller :: PCCFiller -> PriorityChansConverger PCCFillerID PCCFillerOutput -> ConcurrentUISupport -> IO () runPCCFiller pccf pcc cuis = do when __useDumps $ dump "" ("PCCFiller" ++ show channelID) WriteMode interrupter_tv <- atomically $ newTVar False cuisWrite cuis ("PCC filler (ID: " ++ show channelID ++ ") started.") modifyMVar_ (pccfStopFiller pccf) (\ _ -> return $ do cuisWrite cuis ("Signal to finish PCC filler (ID: " ++ show channelID ++ ") is sent.") atomically $ modifyTVar_ interrupter_tv (const True) ) _ <- forkIO $ threadCycle interrupter_tv return () where channelID = pccfInputID pccf ------------ threadCycle :: TVar InterruptShouldWe -> IO () threadCycle interrupter_tv = _cycle where _cycle :: IO () -- It's possible to make this thread more STMish, use Control.Concurrent.PriorityChansConverger.PriorityChansConvergerSTM directly. _cycle = do stop <- atomically $ readTVar interrupter_tv case stop of True -> do cuisWrite cuis ("PCC filler (ID: " ++ show channelID ++ ") finished.") return () -- exit False -> do state <- modifyMVar (pccfStateMV pccf) (\ current_state -> return (pccfStateTransition pccf current_state, current_state) ) when __useDumps $ reportExceptionIfAny_2 cuis "runPCCFiller:dump1" $ dump2 (return "\n----Before-write-----------------------------\n") ("PCCFiller" ++ show (pccfInputID pccf)) AppendMode when __useDumps $ reportExceptionIfAny_2 cuis "runPCCFiller:dump2" $ dump2 (showPCC pcc) ("PCCFiller" ++ show (pccfInputID pccf)) AppendMode -- ######################################### br_or_mb_failure <- case pccfPermitToBlock pccf of True -> stmInterruptableWriteInPCC (readTVar interrupter_tv) channelID state pcc False -> Right `liftM` writeInPCC False channelID state pcc when __useDumps $ reportExceptionIfAny_2 cuis "runPCCFiller:dump3" $ dump2 (return "\n----After-write------------------------------\n") ("PCCFiller" ++ show (pccfInputID pccf)) AppendMode when __useDumps $ reportExceptionIfAny_2 cuis "runPCCFiller:dump4" $ dump2 (showPCC pcc) ("PCCFiller" ++ show (pccfInputID pccf)) AppendMode -- ######################################### case br_or_mb_failure of Left () -> cuisWrite cuis ("Writing to channel (ID: " ++ show channelID ++ ") was interrupted." ) Right Nothing -> cuisWrite cuis ("Wrote to channel (ID: " ++ show channelID ++ "): " ++ show (channelID, state) ) Right (Just BadKey_FRWPCC) -> cuisWrite cuis ("Write to channel (ID: " ++ show channelID ++ ") FAILED: bad key." ) Right (Just ChanFull_FRWPCC) -> cuisWrite cuis ("Write to channel (ID: " ++ show channelID ++ ") FAILED: not enough space in channel." ) stop2 <- atomically $ readTVar interrupter_tv unless stop2 $ threadDelay (pccfDelayBetweenIterations pccf) _cycle runPCCFillersArray :: [PCCFiller] -> PriorityChansConverger PCCFillerID PCCFillerOutput -> ConcurrentUISupport -> IO () runPCCFillersArray pccf_list pcc cuis = mapM_ (\ pccf -> runPCCFiller pccf pcc cuis) pccf_list stopPCCFiller :: PCCFiller -> IO () stopPCCFiller = join . readMVar . pccfStopFiller stopPCCFillersArray :: [PCCFiller] -> IO () stopPCCFillersArray = mapM_ stopPCCFiller ---------------------------------------------------------------------- -- 1.2. Output of our PCC reader. ---------------------------------------------------------------------- type PCCReaderID = Int data PCCReader = PCCReader { pccrReaderID :: PCCReaderID , pccrPermitToBlock :: PermitToBlock , pccrMicrosecondsForDelay :: MicrosecondsForDelay , pccrStopFiller :: MVar PCCFillerCloser } defaultPCCReader :: IO PCCReader defaultPCCReader = do stopper <- newMVar $ return () return PCCReader { pccrReaderID = undefined , pccrPermitToBlock = undefined , pccrMicrosecondsForDelay = undefined , pccrStopFiller = stopper } runPCCReader :: PCCReader -> PriorityChansConverger PCCFillerID PCCFillerOutput -> ConcurrentUISupport -> IO () runPCCReader pccr pcc cuis = do when __useDumps $ dump "" f_name WriteMode interrupter_tv <- atomically $ newTVar False cuisWrite cuis ("PCC reader (ID: " ++ show (pccrReaderID pccr) ++ ") started.") _ <- forkIO $ threadCycle interrupter_tv modifyMVar_ (pccrStopFiller pccr) (\ _ -> return $ do cuisWrite cuis ("Signal to finish PCC reader (ID: " ++ show (pccrReaderID pccr) ++ ") is sent.") atomically $ modifyTVar_ interrupter_tv (const True) ) return () where f_name = "PCCReader" ++ show (pccrReaderID pccr) ------------ threadCycle :: TVar InterruptShouldWe -> IO () threadCycle interrupter_tv = _cycle where _cycle :: IO () -- It's possible to make this thread more STMish, use Control.Concurrent.PriorityChansConverger.PriorityChansConvergerSTM directly. _cycle = do stop <- atomically $ readTVar interrupter_tv case stop of True -> do cuisWrite cuis ("PCC reader (ID: " ++ show (pccrReaderID pccr) ++ ") finished.") return () -- exit False -> do when __useDumps $ reportExceptionIfAny_2 cuis "runPCCReader:dump1" $ dump2 (return "\n----Before-read------------------------------\n") f_name AppendMode when __useDumps $ reportExceptionIfAny_2 cuis "runPCCReader:dump2" $ dump2 (showPCC pcc) f_name AppendMode -- ######################################### br_or_elem <- case pccrPermitToBlock pccr of True -> stmInterruptableReadFromPCC (readTVar interrupter_tv) pcc False -> do mb_r <- readFromPCC False pcc return $ case mb_r of Nothing -> Left () Just r -> Right r when __useDumps $ reportExceptionIfAny_2 cuis "runPCCReader:dump3" $ dump2 (return "\n----After-read-------------------------------\n") f_name AppendMode when __useDumps $ reportExceptionIfAny_2 cuis "runPCCReader:dump4" $ dump2 (showPCC pcc) f_name AppendMode -- ######################################### case (pccrPermitToBlock pccr, br_or_elem) of (True , Left ()) -> cuisWrite cuis ("Read from PCC (reader ID: " ++ show (pccrReaderID pccr) ++ ") is interrupred.") (False, Left ()) -> cuisWrite cuis ("Read from PCC (reader ID: " ++ show (pccrReaderID pccr) ++ ") failed: PCC is empty.") (_ , Right (k, e)) -> cuisWrite cuis ("Read from PCC (reader ID: " ++ show (pccrReaderID pccr) ++ "): " ++ show (k, e)) stop2 <- atomically $ readTVar interrupter_tv unless stop2 $ threadDelay (pccrMicrosecondsForDelay pccr) _cycle stopPCCReader :: PCCReader -> IO () stopPCCReader = join . readMVar . pccrStopFiller runPCCReadersArray :: [PCCReader] -> PriorityChansConverger PCCFillerID PCCFillerOutput -> ConcurrentUISupport -> IO () runPCCReadersArray pccr_list pcc cuis = mapM_ (\ pccr -> runPCCReader pccr pcc cuis) pccr_list stopPCCReadersArray :: [PCCReader] -> IO () stopPCCReadersArray = mapM_ stopPCCReader ---------------------------------------------------------------------- -- 1.3. Put together PCC, it's input sources and it's output reader. ---------------------------------------------------------------------- listOfFillers_to_ChansMapToConstructPCC :: [PCCFiller] -> Map PCCFillerID (StartPriority, ChanCapacity) listOfFillers_to_ChansMapToConstructPCC pccf_list = M.fromList $ map (\ pccf -> ( pccfInputID pccf , (pccfStartPriority pccf , pccfChanCapacity pccf ) ) ) pccf_list constructPCC :: [PCCFiller] -> ConcurrentUISupport -> IO (PriorityChansConverger PCCFillerID PCCFillerOutput) constructPCC pccf_list cuis = newPriorityChansConverger_wCUIS (Just cuis) $ listOfFillers_to_ChansMapToConstructPCC pccf_list -- When constructing new PCC, it is possible to use 'newPriorityChansConverger' -- instead of 'newPriorityChansConverger_wCUIS' which isn't using -- any 'ConcurrentUISupport' runFillersAndPCCAndReaders :: [PCCFiller] -> [PCCReader] -> ConcurrentUISupport -> IO ThreadFinisher runFillersAndPCCAndReaders pccf_list pccr_list cuis = do pcc <- constructPCC pccf_list cuis runPCCFillersArray pccf_list pcc cuis runPCCReadersArray pccr_list pcc cuis interrupter_tv <- atomically $ newTVar False _ <- forkIO $ do atomically $ do stop <- readTVar interrupter_tv case stop of True -> return () False -> retry stopPCCFillersArray pccf_list stopPCCReadersArray pccr_list return (atomically $ writeTVar interrupter_tv True) ---------------------------------------------------------------------- ---------------------------------------------------------------------- -- 2. Now, when the infrastructure is ready let's define -- some test cases and run them ---------------------------------------------------------------------- ---------------------------------------------------------------------- -- 2.1. Test cases set. -- Not a complete set. If anybody wants to test it seriously, -- that would be great. type InputMode = Int inputChansSet :: InputMode -> IO [PCCFiller] inputChansSet 1 = sequence [] inputChansSet 2 = sequence [ defaultPCCFiller >>= \ pccf -> return pccf { pccfInputID = 1, pccfStartPriority = 10, pccfChanCapacity = 0, pccfPermitToBlock = False, pccfDelayBetweenIterations = 1000000 } ] inputChansSet 3 = sequence [ defaultPCCFiller >>= \ pccf -> return pccf { pccfInputID = 1, pccfStartPriority = 10, pccfChanCapacity = 1, pccfPermitToBlock = True, pccfDelayBetweenIterations = 1000000 } ] inputChansSet 4 = sequence [ defaultPCCFiller >>= \ pccf -> return pccf { pccfInputID = 1, pccfStartPriority = 5, pccfChanCapacity = 3, pccfPermitToBlock = False, pccfDelayBetweenIterations = 900000 } , defaultPCCFiller >>= \ pccf -> return pccf { pccfInputID = 2, pccfStartPriority = 3, pccfChanCapacity = 5, pccfPermitToBlock = False, pccfDelayBetweenIterations = 1200000 } ] inputChansSet 5 = sequence [ defaultPCCFiller >>= \ pccf -> return pccf { pccfInputID = 1, pccfStartPriority = 10, pccfChanCapacity = 4, pccfPermitToBlock = True, pccfDelayBetweenIterations = 1000000 } , defaultPCCFiller >>= \ pccf -> return pccf { pccfInputID = 2, pccfStartPriority = 8, pccfChanCapacity = 6, pccfPermitToBlock = True, pccfDelayBetweenIterations = 1000000 } , defaultPCCFiller >>= \ pccf -> return pccf { pccfInputID = 3, pccfStartPriority = 6, pccfChanCapacity = 8, pccfPermitToBlock = True, pccfDelayBetweenIterations = 1000000 } , defaultPCCFiller >>= \ pccf -> return pccf { pccfInputID = 4, pccfStartPriority = 4, pccfChanCapacity = 10, pccfPermitToBlock = True, pccfDelayBetweenIterations = 1000000 } ] inputChansSet 6 = sequence [ defaultPCCFiller >>= \ pccf -> return pccf { pccfInputID = 1, pccfStartPriority = 10, pccfChanCapacity = 4, pccfPermitToBlock = True, pccfDelayBetweenIterations = 200000 } , defaultPCCFiller >>= \ pccf -> return pccf { pccfInputID = 2, pccfStartPriority = 8, pccfChanCapacity = 6, pccfPermitToBlock = False, pccfDelayBetweenIterations = 200000 } , defaultPCCFiller >>= \ pccf -> return pccf { pccfInputID = 3, pccfStartPriority = 6, pccfChanCapacity = 8, pccfPermitToBlock = False, pccfDelayBetweenIterations = 1000000 } , defaultPCCFiller >>= \ pccf -> return pccf { pccfInputID = 4, pccfStartPriority = 4, pccfChanCapacity = 10, pccfPermitToBlock = True, pccfDelayBetweenIterations = 1000000 } ] __inputChansSet_lowerBound = 1 __inputChansSet_higherBound = 6 type OutputMode = Int readerSet :: OutputMode -> IO [PCCReader] readerSet 1 = sequence [ defaultPCCReader >>= \ pccr -> return pccr { pccrReaderID = 1, pccrPermitToBlock = False, pccrMicrosecondsForDelay = 1000000 } ] readerSet 2 = sequence [ defaultPCCReader >>= \ pccr -> return pccr { pccrReaderID = 1, pccrPermitToBlock = True, pccrMicrosecondsForDelay = 1000000 } ] readerSet 3 = sequence [ defaultPCCReader >>= \ pccr -> return pccr { pccrReaderID = 1, pccrPermitToBlock = False, pccrMicrosecondsForDelay = 500000 } ] readerSet 4 = sequence [ defaultPCCReader >>= \ pccr -> return pccr { pccrReaderID = 1, pccrPermitToBlock = True, pccrMicrosecondsForDelay = 500000 } ] readerSet 5 = sequence [ defaultPCCReader >>= \ pccr -> return pccr { pccrReaderID = 1, pccrPermitToBlock = False, pccrMicrosecondsForDelay = 1500000 } ] readerSet 6 = sequence [ defaultPCCReader >>= \ pccr -> return pccr { pccrReaderID = 1, pccrPermitToBlock = True, pccrMicrosecondsForDelay = 1500000 } ] readerSet 7 = sequence [ defaultPCCReader >>= \ pccr -> return pccr { pccrReaderID = 1, pccrPermitToBlock = False, pccrMicrosecondsForDelay = 500000 } , defaultPCCReader >>= \ pccr -> return pccr { pccrReaderID = 2, pccrPermitToBlock = True, pccrMicrosecondsForDelay = 500000 } ] readerSet 8 = sequence [ defaultPCCReader >>= \ pccr -> return pccr { pccrReaderID = 1, pccrPermitToBlock = True, pccrMicrosecondsForDelay = 200000 } , defaultPCCReader >>= \ pccr -> return pccr { pccrReaderID = 2, pccrPermitToBlock = True, pccrMicrosecondsForDelay = 1500000 } ] readerSet 9 = sequence [ defaultPCCReader >>= \ pccr -> return pccr { pccrReaderID = 1, pccrPermitToBlock = True, pccrMicrosecondsForDelay = 150000 } , defaultPCCReader >>= \ pccr -> return pccr { pccrReaderID = 2, pccrPermitToBlock = False, pccrMicrosecondsForDelay = 200000 } ] __readerConfig_lowerBound = 1 __readerConfig_higherBound = 9 ---------------------------------------------------------------------- -- 2.2. Running tests. ---------------------------------------------------------------------- type RunTest = Bool runTest :: (RunTest, InputMode, OutputMode, SecondsForDelay) -> ConcurrentUISupport -> IO () runTest (test_shouldwe, input_mode, output_mode, run_time) cuis = E.handle (\ se -> print (se :: E.SomeException)) $ do pccr_list <- readerSet output_mode pccf_list <- inputChansSet input_mode case test_shouldwe of False -> let str = "" ++ "PCCFillers(" ++ show (length pccf_list) ++ "): \n" ++ (join $ intersperse "\n" $ map show pccf_list) ++ "\n-----------------------------------\n" ++ "PCCReaders(" ++ show (length pccf_list) ++ "): \n" ++ (join $ intersperse "\n" $ map show pccr_list) in cuisWrite cuis str True -> do finisher <- runFillersAndPCCAndReaders pccf_list pccr_list cuis threadDelay (run_time * 1000000) finisher main = do cuis <- defaultConcurrentUISupportIO_logInFile "PCCTest.log" WriteMode -- provided by the PCC package, Control.Concurrent.SimpleUI runConcurrentUISupport cuis cuisWrite cuis ("Welcome to SimplePCCTestEnvironment v0.1!\n ") _cycle cuis cuisFinish cuis where _cycle :: ConcurrentUISupport -> IO () _cycle cuis = do cuisWrite cuis ("Please input mode (True/False, inputChansSet #{" ++ show __inputChansSet_lowerBound ++ ".." ++ show __inputChansSet_higherBound ++ "}, readerSet #{" ++ show __readerConfig_lowerBound ++ ".." ++ show __readerConfig_higherBound ++ "}, time (in seconds) for how long to run test). ") cuisWrite cuis ("Or type ':q' for exit.") cuisWrite cuis ("If first is True, then test is run, otherwise chosen inputChansSet and readerSet are shown.") cuisWrite cuis ("Second parameter should be index of inputChansSet, third - of readerSet.") cuisWrite cuis ("Example inputs: (True, 1, 1, 20) or (False,1,2,10), but not (1,2,3,4) nor (True," ++ show (__inputChansSet_lowerBound - 1) ++ "," ++ show (__readerConfig_higherBound + 1) ++ ",-1) nor (True,2,3,4,5)") cuisWrite cuis ("Input > ") mode <- cuisReadLn cuis "main thread" -- (True, 5, 9, 20) -- (True, 6, 6, 30) case mode == ":q" of True -> return () False -> do err_or_mode <- (E.try $ SF.read mode) :: IO (Either E.SomeException (RunTest, InputMode, OutputMode, SecondsForDelay)) case err_or_mode of Left se -> cuisWrite cuis (show se) >> cuisNewPage cuis Right tm@(test_shouldwe, input_mode, output_mode, run_time) -> let cond_1 = input_mode `betweenI` (__inputChansSet_lowerBound, __inputChansSet_higherBound) cond_2 = output_mode `betweenI` ( __readerConfig_lowerBound, __readerConfig_higherBound) cond_3 = run_time > 0 cond = cond_1 && cond_2 && cond_3 in case cond of False -> return () True -> runTest tm cuis >> threadDelay 1000000 >> cuisNewPage cuis _cycle cuis ---------------------------------------------------------------------- ---------------------------------------------------------------------- -- 3. Representations (instances of Show class). ---------------------------------------------------------------------- ---------------------------------------------------------------------- instance Show PCCFiller where show pccf = "PCCFiller {" ++ "\n ID = " ++ (show $ pccfInputID pccf) ++ "\n StartPriority = " ++ (show $ pccfInputID pccf) ++ "\n ChanCapacity = " ++ (show $ pccfChanCapacity pccf) ++ "\n PermitToBlock = " ++ (show $ pccfPermitToBlock pccf) ++ "\n MicrosecondsForDelay = " ++ (show $ pccfDelayBetweenIterations pccf) ++ "\n PCCFillerState = " ++ (show $ unsafePerformIO $ readMVar $ pccfStateMV pccf) ++ "\n}" instance Show PCCReader where show pccr = "PCCReader {" ++ "\n ID = " ++ (show $ pccrReaderID pccr) ++ "\n MicrosecondsForDelay = " ++ (show $ pccrMicrosecondsForDelay pccr) ++ "\n PermitToBlock = " ++ (show $ pccrPermitToBlock pccr) ++ "}" ---------------------------------------------------------------------- -- Appendix: Some helper functions. ---------------------------------------------------------------------- infixr 1 << (<<) :: Monad m => m b -> m a -> m b f << x = x >> f betweenI :: Ord a => a -> (a, a) -> Bool betweenNI :: Ord a => a -> (a, a) -> Bool betweenI a (l,h) = a >= l && a <= h betweenNI a (l,h) = a > l && a < h infixr 1 `countIn` countIn :: Eq a => a -> [a] -> Int countIn a list = _count list 0 where _count [] !i = i _count (h:t) !i = case h == a of True -> _count t (i + 1) False -> _count t i modifyTVar_ :: TVar a -> (a -> a) -> STM () modifyTVar_ tv f = do v <- readTVar tv writeTVar tv (f v) -- data System.IO.IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode type WhatToDump = String type FileSuffix = String dump :: WhatToDump -> FileSuffix -> IOMode -> IO () dump a s m = do h <- openFile ("./dump.out." ++ s ++ ".hs") m hPutStr h $ show a hClose h dump2 :: IO WhatToDump -> FileSuffix -> IOMode -> IO () dump2 a s m = do h <- openFile ("./dump.out." ++ s ++ ".hs") m a >>= hPutStr h hClose h --a >>= putStrLn