{-# LANGUAGE DeriveDataTypeable #-}
module Database.Haskey.Utils.RLock where
import Control.Concurrent (ThreadId, myThreadId)
import Control.Concurrent.MVar
import Control.Exception (Exception, throwIO)
import Control.Monad (unless, when)
import Control.Monad.Catch (MonadMask, bracket_)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Typeable (Typeable)
type RLock = (MVar (Maybe (ThreadId, Integer)), MVar ())
newRLock :: IO RLock
newRLock = do { a <- newMVar Nothing; b <- newMVar (); return (a, b) }
acquireRLock :: RLock -> IO ()
acquireRLock (r, l) = do
myId <- myThreadId
ok <- modifyMVar r $ \state -> case state of
Nothing -> return (state, False)
Just (tId, x) -> if tId == myId
then return (Just (myId, x + 1), True)
else return (state, False)
unless ok $ do
() <- takeMVar l
modifyMVar_ r $ const (return $ Just (myId, 1))
releaseRLock :: RLock -> IO ()
releaseRLock (r, l) = do
myId <- myThreadId
done <- modifyMVar r $ \state -> case state of
Nothing -> throwIO $ RLockError "the lock has no inhabitant"
Just (_, 0) -> throwIO $ RLockError "the lock is already released"
Just (tId, n) -> if tId == myId
then if n == 1
then return (Nothing, True)
else return (Just (myId, n-1), False)
else throwIO $ RLockError "lock not held by releaser"
when done $
putMVar l ()
withRLock :: (MonadIO m, MonadMask m) => RLock -> m a -> m a
withRLock l = bracket_ (liftIO $ acquireRLock l)
(liftIO $ releaseRLock l)
newtype RLockError = RLockError String deriving (Show, Typeable)
instance Exception RLockError where