module Database.VCache.RWLock
( RWLock
, newRWLock
, withRWLock
, withRdOnlyLock
) where
import Control.Monad
import Control.Exception
import Control.Concurrent.MVar
import Data.IORef
import Data.IntSet (IntSet)
import qualified Data.IntSet as IntSet
data RWLock = RWLock
{ rwlock_frames :: !(MVar FB)
, rwlock_writer :: !(MVar ())
}
data FB = FB !F !F
type F = IORef Frame
data Frame = Frame
{ frame_reader_next :: !Int
, frame_readers :: !IntSet
, frame_onClear :: ![IO ()]
}
frame0 :: Frame
frame0 = Frame 1 IntSet.empty []
newRWLock :: IO RWLock
newRWLock = liftM2 RWLock (newMVar =<< newF2) newEmptyMVar where
newF2 :: IO FB
newF2 = liftM2 FB newF newF
newF :: IO F
newF = newIORef frame0
withWriterMutex :: RWLock -> IO a -> IO a
withWriterMutex l = bracket_ getLock dropLock where
getLock = putMVar (rwlock_writer l) ()
dropLock = takeMVar (rwlock_writer l)
withRWLock :: RWLock -> IO a -> IO a
withRWLock l action = withWriterMutex l $ do
oldFrame <- rotateReaderFrames l
mvWait <- newEmptyMVar
onFrameCleared oldFrame (putMVar mvWait ())
takeMVar mvWait
action
rotateReaderFrames :: RWLock -> IO F
rotateReaderFrames l = mask_ $ do
let var = rwlock_frames l
f0 <- newF
(FB f1 f2) <- takeMVar var
putMVar var (FB f0 f1)
return f2
onFrameCleared :: F -> IO () -> IO ()
onFrameCleared f action = atomicModifyIORef f addAction >>= id where
addAction frame =
let bAlreadyClear = IntSet.null (frame_readers frame) in
if bAlreadyClear then (frame0,action) else
let onClear' = action : frame_onClear frame in
let frame' = frame { frame_onClear = onClear' } in
(frame', return ())
withRdOnlyLock :: RWLock -> IO a -> IO a
withRdOnlyLock l = bracket (newReader l) releaseReader . const
newtype Reader = Reader { releaseReader :: IO () }
newReader :: RWLock -> IO Reader
newReader l = mask_ $ do
let var = rwlock_frames l
fb@(FB f _) <- takeMVar var
r <- atomicModifyIORef f addReader
putMVar var fb
return (Reader (delReader f r))
addReader :: Frame -> (Frame, Int)
addReader f =
let r = frame_reader_next f in
let rdrs' = IntSet.insert r (frame_readers f) in
let f' = f { frame_reader_next = (r + 1)
, frame_readers = rdrs' } in
(f', r)
delReader :: F -> Int -> IO ()
delReader f r = atomicModifyIORef f del >>= sequence_ where
del frm =
let rdrs' = IntSet.delete r (frame_readers frm) in
if IntSet.null rdrs' then (frame0, frame_onClear frm) else
let frm' = frm { frame_readers = rdrs' } in
(frm', [])