module Control.Concurrent.Priority.RoomCore (Room, newRoom, Claim, ClaimMode(..), claimedRoom, claimedThread, userData, approve, claim_, inUse) where import Data.Unique import Data.Set as Set import Data.Map as Map import GHC.Conc import Control.Monad import Control.Exception -- | A resource pool, parameterized against arbitrary user data. data Room u = Room (u,Unique) (TVar (Set ThreadId)) -- | A 'Claim', or attempt to acquire or release a 'Room'. data Claim u = Claim (Room u) ThreadId ClaimMode (TVar Bool) data ClaimMode = Acquire | Release deriving (Eq) instance Eq (Room u) where (==) (Room u1 _) (Room u2 _) = snd u1 == snd u2 instance Ord (Room u) where compare (Room u1 _) (Room u2 _) = compare (snd u1) (snd u2) -- | Create a new Room with some arbitrary user data. newRoom :: u -> IO (Room u) newRoom u = return Room `ap` liftM ((,) u) newUnique `ap` atomically (newTVar Set.empty) -- | Get the user data associated with a 'Room'. userData :: Room u -> u userData (Room (u,_) _) = u -- | Whether a Claim is to acquire or release a room. claimMode :: Claim u -> ClaimMode claimMode (Claim _ _ b _) = b -- | Get the 'Room' target of a 'Claim'. claimedRoom :: Claim u -> Room u claimedRoom (Claim m _ _ _) = m -- | Get the thread attempting a 'Claim'. claimedThread :: Claim u -> ThreadId claimedThread (Claim _ t _ _) = t -- | Approve a claim. This actually acquires a 'Room'. approve :: Claim u -> STM () approve (Claim (Room _ m) me want claim_var) = do claim_state <- readTVar claim_var when (claim_state == False) $ do writeTVar claim_var True writeTVar m . (case want of Acquire -> Set.insert; Release -> Set.delete) me =<< readTVar m -- | Acquire and/or release some rooms for the duration of a critical section. -- -- * which 'Room's to 'Acquire', and later release, or 'Release' and later reacquire for the duration of the critical section. -- -- * a transaction to 'approve' all entering 'Claim's -- -- * a transaction to 'approve' all exiting 'Claim's -- -- * a transaction to run one or more times if and only if this thread is waiting for approval -- -- * the critical section -- -- A separate 'Claim' is generated each time a Room needs to be acquired. The critical -- section will not enter until every claim has been 'approve'd. -- -- When the critical section exits, an inverse group of claims will be generated, and the critical -- section will not exit until those claims have been 'approve'd. -- -- It is guaranteed that when and only when all 'Claim's have been 'approve'd, the waiting thread will enter -- (or exit) the critical section. The lock on each 'Room' is acquired when it's 'Claim' is 'approve'd, -- not when the critical section is entered. -- -- 'Claim's may be 'approve'd from any transaction, even from another thread. -- claim_ :: Map (Room u) ClaimMode -> ([Claim u] -> STM ()) -> ([Claim u] -> STM ()) -> STM () -> IO a -> IO a claim_ entering_rooms_map approveEnteringSTM approveExitingSTM waitingSTM actionIO = do let entering_rooms = Map.toList entering_rooms_map me <- myThreadId -- transition: generate and request (but do not wait for) approval for all claims let transition rooms approveSTM = atomically $ (\claims -> approveSTM (Prelude.filter ((== Acquire) . claimMode) claims) >> return claims) =<< -- request approval of Acquire claims. (mapM $ \c -> when (claimMode c == Release) (approve c) >> return c) =<< --auto-approve Release claims. (mapM $ \(m,want) -> liftM (Claim m me want) (newTVar False)) =<< -- build claims filterM (\(m,want) -> liftM ((/= (want == Acquire)) . Set.member me) $ inUse m) rooms -- get the difference between the rooms we want and the rooms we have -- confirm: wait for all claims to be approved let confirm claims = forM_ claims $ \(Claim _ _ _ claim_var) -> do claim_state <- readTVar claim_var unless claim_state retry -- confirm or perform the user's wait action, return True iff we have confirmed let confirmWithWaitAction claims= do done <- atomically $ (confirm claims >> return True) `orElse` (waitingSTM >> return False) unless done $ confirmWithWaitAction claims -- when entering we signal the claims (transition) and then wait on the approval of the claims (confirm) -- then we generate a list of inverse claims for when it comes time to exit the critical section -- If an exception is thrown from the approve*STM or waitingSTM, this would leave dangling -- room locks, therefore, we force the rooms into their former state, ignoring any -- constraints that might have been placed on them. On the theory that it's better to violate -- constraints than to leave dangling locks. let transitionAndConfirm approveSTM rooms = flip finally (transition rooms $ mapM_ approve) $ do claims <- transition rooms approveSTM confirmWithWaitAction claims return $ Prelude.map (\(Claim m _ want _) -> (m,case want of Acquire -> Release; Release -> Acquire)) claims bracket (transitionAndConfirm approveEnteringSTM entering_rooms) (transitionAndConfirm approveExitingSTM) (const actionIO) -- | Get all 'ThreadId's that are currently claimimg this 'Room'. inUse :: Room u -> STM (Set ThreadId) inUse (Room _ m) = readTVar m