2015-01-23 18:11:37 +03:00
|
|
|
-- Modification (to introduce bug) of an example in Parallel and
|
|
|
|
-- Concurrent Programming in Haskell, chapter 7.
|
2016-03-31 20:16:09 +03:00
|
|
|
module Examples.Logger where
|
2015-01-23 18:11:37 +03:00
|
|
|
|
2016-03-23 06:13:25 +03:00
|
|
|
import Control.Concurrent.Classy
|
2015-11-16 05:48:54 +03:00
|
|
|
import Data.Functor (void)
|
|
|
|
import Test.DejaFu hiding (MemType(..))
|
|
|
|
|
2017-05-27 18:43:14 +03:00
|
|
|
import Utils
|
|
|
|
|
|
|
|
tests :: [T]
|
2017-02-20 07:41:36 +03:00
|
|
|
tests =
|
2017-05-27 18:43:14 +03:00
|
|
|
[ T "allowed" raceyLogger validResult
|
|
|
|
, T "correct occurs" raceyLogger isGood
|
|
|
|
, T "bug exists" raceyLogger isBad
|
2015-11-16 05:48:54 +03:00
|
|
|
]
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
2015-01-23 18:11:37 +03:00
|
|
|
|
2016-03-23 06:36:07 +03:00
|
|
|
data Logger m = Logger (MVar m LogCommand) (MVar m [String])
|
2015-01-23 18:11:37 +03:00
|
|
|
|
|
|
|
data LogCommand = Message String | Stop
|
|
|
|
|
|
|
|
-- | Create a new logger with no internal log.
|
2015-01-28 15:10:19 +03:00
|
|
|
initLogger :: MonadConc m => m (Logger m)
|
2015-01-23 18:11:37 +03:00
|
|
|
initLogger = do
|
2016-03-23 06:36:07 +03:00
|
|
|
cmd <- newEmptyMVar
|
|
|
|
logg <- newMVar []
|
2015-08-27 03:05:59 +03:00
|
|
|
let l = Logger cmd logg
|
|
|
|
void . fork $ logger l
|
2015-01-23 18:11:37 +03:00
|
|
|
return l
|
|
|
|
|
2015-01-28 15:10:19 +03:00
|
|
|
logger :: MonadConc m => Logger m -> m ()
|
2015-08-27 03:05:59 +03:00
|
|
|
logger (Logger cmd logg) = loop where
|
2015-01-23 18:11:37 +03:00
|
|
|
loop = do
|
2016-03-23 06:36:07 +03:00
|
|
|
command <- takeMVar cmd
|
2015-01-23 18:11:37 +03:00
|
|
|
case command of
|
|
|
|
Message str -> do
|
2016-03-23 06:36:07 +03:00
|
|
|
strs <- takeMVar logg
|
|
|
|
putMVar logg (strs ++ [str])
|
2015-01-23 18:11:37 +03:00
|
|
|
loop
|
|
|
|
Stop -> return ()
|
|
|
|
|
|
|
|
-- | Add a string to the log.
|
2015-01-28 15:10:19 +03:00
|
|
|
logMessage :: MonadConc m => Logger m -> String -> m ()
|
2016-03-23 06:36:07 +03:00
|
|
|
logMessage (Logger cmd _) str = putMVar cmd $ Message str
|
2015-01-23 18:11:37 +03:00
|
|
|
|
|
|
|
-- | Stop the logger and return the contents of the log.
|
2015-01-28 15:10:19 +03:00
|
|
|
logStop :: MonadConc m => Logger m -> m [String]
|
2015-08-27 03:05:59 +03:00
|
|
|
logStop (Logger cmd logg) = do
|
2016-03-23 06:36:07 +03:00
|
|
|
putMVar cmd Stop
|
|
|
|
readMVar logg
|
2015-01-23 18:11:37 +03:00
|
|
|
|
|
|
|
-- | Race condition! Can you see where?
|
2015-11-16 05:48:54 +03:00
|
|
|
raceyLogger :: MonadConc m => m [String]
|
|
|
|
raceyLogger = do
|
2015-01-23 18:11:37 +03:00
|
|
|
l <- initLogger
|
|
|
|
logMessage l "Hello"
|
|
|
|
logMessage l "World"
|
|
|
|
logMessage l "Foo"
|
|
|
|
logMessage l "Bar"
|
|
|
|
logMessage l "Baz"
|
|
|
|
logStop l
|
|
|
|
|
|
|
|
-- | Test that the result is always in the set of allowed values, and
|
|
|
|
-- doesn't deadlock.
|
2015-02-01 04:21:42 +03:00
|
|
|
validResult :: Predicate [String]
|
|
|
|
validResult = alwaysTrue check where
|
2015-02-06 18:24:47 +03:00
|
|
|
check (Right strs) = strs `elem` [ ["Hello", "World", "Foo", "Bar", "Baz"]
|
|
|
|
, ["Hello", "World", "Foo", "Bar"]
|
|
|
|
]
|
|
|
|
check _ = False
|
2015-01-23 18:11:37 +03:00
|
|
|
|
|
|
|
-- | Test that the "proper" result occurs at least once.
|
2015-02-01 04:21:42 +03:00
|
|
|
isGood :: Predicate [String]
|
|
|
|
isGood = somewhereTrue check where
|
2015-02-06 18:24:47 +03:00
|
|
|
check (Right a) = length a == 5
|
|
|
|
check _ = False
|
2015-01-23 18:11:37 +03:00
|
|
|
|
|
|
|
-- | Test that the erroneous result occurs at least once.
|
2015-02-01 04:21:42 +03:00
|
|
|
isBad :: Predicate [String]
|
|
|
|
isBad = somewhereTrue check where
|
2015-02-06 18:24:47 +03:00
|
|
|
check (Right a) = length a == 4
|
|
|
|
check _ = False
|