1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
|
-- | This module provides a wrapper around a deque that can enforce additional
-- invariants at runtime for debugging purposes.
module Data.Concurrent.Deque.Debugger
(DebugDeque(DebugDeque))
where
import Data.IORef
import Control.Concurrent
import Data.Concurrent.Deque.Class
-- newtype DebugDeque d = DebugDeque d
-- | Warning, this enforces the excessively STRONG invariant that if any end of the
-- deque is non-threadsafe then it may ever only be touched by one thread during its
-- entire lifetime.
--
-- This extreme form of monagamy is easier to verify, because we don't have enough
-- information to know if two operations on different threads are racing with one
-- another or are properly synchronized.
--
-- The wrapper data structure has two IORefs to track the last thread that touched
-- the left and right end of the deque, respectively.
data DebugDeque d elt = DebugDeque (IORef (Maybe ThreadId), IORef (Maybe ThreadId)) (d elt)
instance DequeClass d => DequeClass (DebugDeque d) where
pushL (DebugDeque (ref,_) q) elt = do
markThread (leftThreadSafe q) ref
pushL q elt
tryPopR (DebugDeque (_,ref) q) = do
markThread (rightThreadSafe q) ref
tryPopR q
newQ = do l <- newIORef Nothing
r <- newIORef Nothing
fmap (DebugDeque (l,r)) newQ
-- FIXME: What are the threadsafe rules for nullQ?
nullQ (DebugDeque _ q) = nullQ q
leftThreadSafe (DebugDeque _ q) = leftThreadSafe q
rightThreadSafe (DebugDeque _ q) = rightThreadSafe q
instance PopL d => PopL (DebugDeque d) where
tryPopL (DebugDeque (ref,_) q) = do
markThread (leftThreadSafe q) ref
tryPopL q
-- | Mark the last thread to use this endpoint.
markThread True _ = return () -- Don't bother tracking.
markThread False ref = do
last <- readIORef ref
tid <- myThreadId
-- putStrLn$"Marking! "++show tid
atomicModifyIORef ref $ \ x ->
case x of
Nothing -> (Just tid, ())
Just tid2
| tid == tid2 -> (Just tid,())
| otherwise -> error$ "DebugDeque: invariant violated, thread safety not allowed but accessed by: "++show (tid,tid2)
|