jni-0.8.0: src/common/Foreign/JNI/Internal/RWLock.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedLabels #-}
module Foreign.JNI.Internal.RWLock
( RWLock
, new
, tryAcquireReadLock
, releaseReadLock
, acquireWriteLock
) where
import Control.Concurrent.STM
(TVar, atomically, check, newTVarIO, readTVar, stateTVar, writeTVar)
import Control.Monad (when)
import Data.Choice
-- | A read-write lock
--
-- Concurrent readers are allowed, but only one writer is supported.
--
-- Moreover, a writer trying to acquire a write lock has priority over
-- new readers trying to acquire a read lock.
newtype RWLock =
RWLock (TVar (Int, RWWantedState))
-- ^ A count of the held read locks and the wanted state
-- | The wanted state of the RW
data RWWantedState
= Reading -- ^ There are no writers
| Writing (TVar Bool) -- ^ A writer wants to write, grant no more read locks.
-- The TVar is to be written when the last read lock
-- is released.
-- | Creates a new read-write lock.
new :: IO RWLock
new = RWLock <$> newTVarIO (0, Reading)
-- | Tries to acquire a read lock. If this call returns `Do #read`, no writer
-- will be granted a lock before the read lock is released. The lock can be
-- denied if a writer is writing or waiting to write.
tryAcquireReadLock :: RWLock -> IO (Choice "read")
tryAcquireReadLock (RWLock ref) = atomically $
readTVar ref >>= \case
(!readers, Reading) -> do
writeTVar ref (readers + 1, Reading)
return $ Do #read
_ -> return $ Don't #read
-- | Releases a read lock.
releaseReadLock :: RWLock -> IO ()
releaseReadLock (RWLock ref) =
atomically $ do
(readers, aim) <- readTVar ref
writeTVar ref (readers - 1, aim)
case (readers, aim) of
(1, Writing noReadersRef) -> writeTVar noReadersRef True
_ -> return ()
-- | Waits until the current read locks are released and grants a write lock.
-- No new reader locks are granted while the writer is waiting for the lock
-- and while it holds the write lock.
acquireWriteLock :: RWLock -> IO ()
acquireWriteLock (RWLock ref) = do
noReadersRef <- newTVarIO False
readers <- atomically $ stateTVar ref $ \(readers, _) ->
(readers, (readers, Writing noReadersRef))
when (readers > 0) $ atomically $
readTVar noReadersRef >>= check