Merge pull request #289 from dfinity-lab/gip/boundedchan_locks

BoundedChan and locks primitives
This commit is contained in:
Michael Walker 2018-11-28 18:58:03 +00:00 committed by GitHub
commit 88278ca471
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 818 additions and 0 deletions

View File

@ -24,19 +24,25 @@
module Control.Concurrent.Classy module Control.Concurrent.Classy
( module Control.Monad.Conc.Class ( module Control.Monad.Conc.Class
, module Control.Concurrent.Classy.Chan , module Control.Concurrent.Classy.Chan
, module Control.Concurrent.Classy.BoundedChan
, module Control.Concurrent.Classy.CRef , module Control.Concurrent.Classy.CRef
, module Control.Concurrent.Classy.IORef , module Control.Concurrent.Classy.IORef
, module Control.Concurrent.Classy.MVar , module Control.Concurrent.Classy.MVar
, module Control.Concurrent.Classy.STM , module Control.Concurrent.Classy.STM
, module Control.Concurrent.Classy.QSem , module Control.Concurrent.Classy.QSem
, module Control.Concurrent.Classy.QSemN , module Control.Concurrent.Classy.QSemN
, module Control.Concurrent.Classy.Lock
, module Control.Concurrent.Classy.RWLock
) where ) where
import Control.Concurrent.Classy.BoundedChan
import Control.Concurrent.Classy.Chan import Control.Concurrent.Classy.Chan
import Control.Concurrent.Classy.CRef import Control.Concurrent.Classy.CRef
import Control.Concurrent.Classy.IORef import Control.Concurrent.Classy.IORef
import Control.Concurrent.Classy.Lock
import Control.Concurrent.Classy.MVar import Control.Concurrent.Classy.MVar
import Control.Concurrent.Classy.QSem import Control.Concurrent.Classy.QSem
import Control.Concurrent.Classy.QSemN import Control.Concurrent.Classy.QSemN
import Control.Concurrent.Classy.RWLock
import Control.Concurrent.Classy.STM import Control.Concurrent.Classy.STM
import Control.Monad.Conc.Class import Control.Monad.Conc.Class

View File

@ -0,0 +1,207 @@
--------------------------------------------------------------------------------
-- Copyright © 2009, Galois, Inc.
-- Copyright © 2018, DFINITY Stiftung
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions
-- are met:
--
-- * Redistributions of source code must retain the above copyright
-- notice, this list of conditions and the following disclaimer.
-- * Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in
-- the documentation and/or other materials provided with the
-- distribution.
-- * Neither the name of the Galois, Inc. nor the names of its
-- contributors may be used to endorse or promote products derived
-- from this software without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-- FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-- INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-- BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-- ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-- |
-- Module : Control.Concurrent.Classy.BoundedChan
-- Copyright : © 2009 Galois Inc.
-- , © 2018 DFINITY Stiftung
-- Maintainer : DFINITY USA Research <team@dfinity.org>
--
-- Implements bounded channels. These channels differ from normal 'Chan's in
-- that they are guaranteed to contain no more than a certain number of
-- elements. This is ideal when you may be writing to a channel faster than
-- you are able to read from it.
--
-- This module supports all the functions of "Control.Concurrent.Chan" except
-- 'unGetChan' and 'dupChan', which are not supported for bounded channels.
--
-- Extra consistency: This version enforces that if thread Alice writes
-- e1 followed by e2 then e1 will be returned by readBoundedChan before e2.
-- Conversely, if thead Bob reads e1 followed by e2 then it was true that
-- writeBoundedChan e1 preceded writeBoundedChan e2.
--
-- Previous versions did not enforce this consistency: if writeBoundedChan were
-- preempted between putMVars or killThread arrived between putMVars then it
-- can fail. Similarly it might fail if readBoundedChan were stopped after putMVar
-- and before the second takeMVar. An unlucky pattern of several such deaths
-- might actually break the invariants of the array in an unrecoverable way
-- causing all future reads and writes to block.
--------------------------------------------------------------------------------
module Control.Concurrent.Classy.BoundedChan
( BoundedChan
, newBoundedChan
, writeBoundedChan
, trywriteBoundedChan
, readBoundedChan
, tryreadBoundedChan
, isEmptyBoundedChan
, writeList2BoundedChan
) where
--------------------------------------------------------------------------------
import Control.Monad (replicateM)
import Data.Array (Array, listArray, (!))
import qualified Control.Concurrent.Classy.MVar as MVar
import Control.Monad.Catch (mask_, onException)
import Control.Monad.Conc.Class (MonadConc(MVar))
--------------------------------------------------------------------------------
-- | A 'BoundedChan' is an abstract data type representing a bounded channel.
data BoundedChan m a
= BoundedChan
{ _size :: Int
, _contents :: Array Int (MVar m a)
, _writePos :: MVar m Int
, _readPos :: MVar m Int
}
deriving ()
-- TODO: check if the fields of BoundedChan could be strict / unpacked
--------------------------------------------------------------------------------
-- Versions of modifyMVar and withMVar that do not 'restore' the previous mask
-- state when running 'io', with added modification strictness.
-- The lack of 'restore' may make these perform better than the normal version.
-- Moving strictness here makes using them more pleasant.
{-# INLINE modifyMVarMask #-}
modifyMVarMask :: (MonadConc m) => MVar m a -> (a -> m (a, b)) -> m b
modifyMVarMask m callback = mask_ $ do
a <- MVar.takeMVar m
(a', b) <- callback a `onException` MVar.putMVar m a
MVar.putMVar m $! a'
pure b
{-# INLINE modifyMVarMask_ #-}
modifyMVarMask_ :: (MonadConc m) => MVar m a -> (a -> m a) -> m ()
modifyMVarMask_ m callback =
mask_ $ do
a <- MVar.takeMVar m
a' <- callback a `onException` MVar.putMVar m a
MVar.putMVar m $! a'
{-# INLINE withMVarMask #-}
withMVarMask :: (MonadConc m) => MVar m a -> (a -> m b) -> m b
withMVarMask m callback =
mask_ $ do
a <- MVar.takeMVar m
b <- callback a `onException` MVar.putMVar m a
MVar.putMVar m a
pure b
--------------------------------------------------------------------------------
-- |
-- @newBoundedChan n@ returns a channel than can contain no more than @n@
-- elements.
newBoundedChan :: (MonadConc m) => Int -> m (BoundedChan m a)
newBoundedChan x = do
entls <- replicateM x MVar.newEmptyMVar
wpos <- MVar.newMVar 0
rpos <- MVar.newMVar 0
let entries = listArray (0, x - 1) entls
pure (BoundedChan x entries wpos rpos)
-- |
-- Write an element to the channel. If the channel is full, this routine will
-- block until it is able to write. Blockers wait in a fair FIFO queue.
writeBoundedChan :: (MonadConc m) => BoundedChan m a -> a -> m ()
writeBoundedChan (BoundedChan size contents wposMV _) x =
modifyMVarMask_ wposMV $ \wpos -> do
MVar.putMVar (contents ! wpos) x
pure (succ wpos `mod` size) -- only advance when putMVar succeeds
-- |
-- A variant of 'writeBoundedChan' which, instead of blocking when the channel is
-- full, simply aborts and does not write the element. Note that this routine
-- can still block while waiting for write access to the channel.
trywriteBoundedChan :: (MonadConc m) => BoundedChan m a -> a -> m Bool
trywriteBoundedChan (BoundedChan size contents wposMV _) x =
modifyMVarMask wposMV $ \wpos -> do
success <- MVar.tryPutMVar (contents ! wpos) x
-- only advance when putMVar succeeds
let wpos' = if success then succ wpos `mod` size else wpos
pure (wpos', success)
-- |
-- Read an element from the channel. If the channel is empty, this routine
-- will block until it is able to read. Blockers wait in a fair FIFO queue.
readBoundedChan :: (MonadConc m) => BoundedChan m a -> m a
readBoundedChan (BoundedChan size contents _ rposMV) =
modifyMVarMask rposMV $ \rpos -> do
a <- MVar.takeMVar (contents ! rpos)
pure (succ rpos `mod` size, a) -- only advance when takeMVar succeeds
-- |
-- A variant of 'readBoundedChan' which, instead of blocking when the channel is
-- empty, immediately returns 'Nothing'. Otherwise, 'tryreadBoundedChan' returns
-- @'Just' a@ where @a@ is the element read from the channel. Note that this
-- routine can still block while waiting for read access to the channel.
tryreadBoundedChan :: (MonadConc m) => BoundedChan m a -> m (Maybe a)
tryreadBoundedChan (BoundedChan size contents _ rposMV) =
modifyMVarMask rposMV $ \rpos -> do
ma <- MVar.tryTakeMVar (contents ! rpos)
-- only advance when takeMVar succeeds
let rpos' = case ma of
Just _ -> succ rpos `mod` size
Nothing -> rpos
pure (rpos', ma)
-- |
-- Returns 'True' if the supplied channel is empty.
--
-- NOTE: This may block on an empty channel if there is a blocked reader.
-- NOTE: This function is deprecated.
{-# DEPRECATED isEmptyBoundedChan
"This isEmptyBoundedChan can block, no non-blocking substitute yet" #-}
isEmptyBoundedChan :: (MonadConc m) => BoundedChan m a -> m Bool
isEmptyBoundedChan (BoundedChan _ contents _ rposMV) =
withMVarMask rposMV $ \rpos ->
MVar.isEmptyMVar (contents ! rpos)
-- |
-- Write a list of elements to the channel.
-- If the channel becomes full, this routine will block until it can write.
-- Competing writers may interleave with this one.
writeList2BoundedChan :: (MonadConc m) => BoundedChan m a -> [a] -> m ()
writeList2BoundedChan = mapM_ . writeBoundedChan
--------------------------------------------------------------------------------

View File

@ -0,0 +1,226 @@
--------------------------------------------------------------------------------
-- Copyright © 2010-2012 Bas van Dijk & Roel van Dijk
-- Copyright © 2018 DFINITY Stiftung
--
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are
-- met:
--
-- * Redistributions of source code must retain the above copyright
-- notice, this list of conditions and the following disclaimer.
--
-- * Redistributions in binary form must reproduce the above
-- copyright notice, this list of conditions and the following
-- disclaimer in the documentation and/or other materials provided
-- with the distribution.
--
-- * The names of Bas van Dijk, Roel van Dijk and the names of
-- contributors may NOT be used to endorse or promote products
-- derived from this software without specific prior written
-- permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE UndecidableInstances #-}
--------------------------------------------------------------------------------
-- |
-- Module : Control.Concurrent.Classy.Lock
-- Copyright : © 2010-2011 Bas van Dijk & Roel van Dijk
-- , © 2018 DFINITY Stiftung
-- Maintainer : DFINITY USA Research <team@dfinity.org>
--
-- This module provides the 'Lock' synchronisation mechanism. It was inspired by
-- the Python and Java @Lock@ objects and should behave in a similar way. See:
--
-- <http://docs.python.org/3.1/library/threading.html#lock-objects>
--
-- and:
--
-- <http://java.sun.com/javase/7/docs/api/java/util/concurrent/locks/Lock.html>
--
-- All functions are /exception safe/. Throwing asynchronous exceptions will not
-- compromise the internal state of a 'Lock'.
--------------------------------------------------------------------------------
module Control.Concurrent.Classy.Lock
( -- * @Lock@
Lock
-- * Creating locks
, newLock
, newAcquired
-- * Locking and unlocking
, acquire
, tryAcquire
, release
-- * Convenience functions
, with
, tryWith
, wait
-- * Querying locks
, locked
) where
--------------------------------------------------------------------------------
import Control.Applicative (pure, (<*>))
import Control.Monad (when)
import Data.Bool (Bool, not)
import Data.Eq (Eq((==)))
import Data.Function (($), (.))
import Data.Functor (fmap, (<$>))
import Data.Maybe (Maybe(Just, Nothing), isJust)
import Data.Typeable (Typeable)
import Prelude (error)
import qualified Control.Concurrent.Classy.MVar as MVar
import Control.Monad.Catch (bracket_, mask, onException)
import Control.Monad.Conc.Class (MonadConc(MVar))
--------------------------------------------------------------------------------
-- | A lock is in one of two states: \"locked\" or \"unlocked\".
newtype Lock m
= Lock
{ _fromLock :: MVar m ()
}
deriving (Typeable)
instance (Eq (MVar m ())) => Eq (Lock m) where
(==) (Lock a) (Lock b) = a == b
--------------------------------------------------------------------------------
-- | Create a lock in the \"unlocked\" state.
newLock :: (MonadConc m) => m (Lock m)
newLock = Lock <$> MVar.newMVar ()
-- | Create a lock in the \"locked\" state.
newAcquired :: (MonadConc m) => m (Lock m)
newAcquired = Lock <$> MVar.newEmptyMVar
--------------------------------------------------------------------------------
-- |
-- Acquires the 'Lock'. Blocks if another thread has acquired the 'Lock'.
--
-- @acquire@ behaves as follows:
--
-- * When the state is \"unlocked\" @acquire@ changes the state to \"locked\".
--
-- * When the state is \"locked\" @acquire@ /blocks/ until a call to 'release'
-- in another thread wakes the calling thread. Upon awakening it will change
-- the state to \"locked\".
--
-- There are two further important properties of @acquire@:
--
-- * @acquire@ is single-wakeup. That is, if there are multiple threads blocked
-- on @acquire@ and the lock is released, only one thread will be woken up.
-- The runtime guarantees that the woken thread completes its @acquire@
-- operation.
--
-- * When multiple threads are blocked on @acquire@, they are woken up in FIFO
-- order. This is useful for providing fairness properties of abstractions
-- built using locks. Note that this differs from the Python implementation
-- where the wake-up order is undefined.
acquire :: (MonadConc m) => Lock m -> m ()
acquire = MVar.takeMVar . _fromLock
-- |
-- A non-blocking 'acquire'.
--
-- * When the state is \"unlocked\" @tryAcquire@ changes the state to \"locked\"
-- and returns 'True'.
--
-- * When the state is \"locked\" @tryAcquire@ leaves the state unchanged and
-- returns 'False'.
tryAcquire :: (MonadConc m) => Lock m -> m Bool
tryAcquire = fmap isJust . MVar.tryTakeMVar . _fromLock
-- |
-- @release@ changes the state to \"unlocked\" and returns immediately.
--
-- Note that it is an error to release a lock in the \"unlocked\" state!
--
-- If there are any threads blocked on 'acquire' the thread that first called
-- @acquire@ will be woken up.
release :: (MonadConc m) => Lock m -> m ()
release (Lock mv) = do
b <- MVar.tryPutMVar mv ()
when (not b) $
error "Control.Concurrent.Classy.Lock.release: cannot release an unlocked Lock!"
--------------------------------------------------------------------------------
-- |
-- A convenience function which first acquires the lock and then performs the
-- computation. When the computation terminates, whether normally or by raising an
-- exception, the lock is released.
--
-- Note that: @with = 'bracket_' '<$>' 'acquire' '<*>' 'release'@.
with :: (MonadConc m) => Lock m -> m a -> m a
with = bracket_ <$> acquire <*> release
-- |
-- A non-blocking 'with'. @tryWith@ is a convenience function which first tries
-- to acquire the lock. If that fails, 'Nothing' is returned. If it succeeds,
-- the computation is performed. When the computation terminates, whether
-- normally or by raising an exception, the lock is released and 'Just' the
-- result of the computation is returned.
tryWith :: (MonadConc m) => Lock m -> m a -> m (Maybe a)
tryWith l a = mask $ \restore -> do
acquired <- tryAcquire l
if acquired
then do r <- restore a `onException` release l
release l
pure (Just r)
else pure Nothing
-- |
-- * When the state is \"locked\", @wait@ /blocks/ until a call to 'release'
-- in another thread changes it to \"unlocked\".
--
-- * @wait@ is multiple-wakeup, so when multiple waiters are blocked on
-- a @Lock@, all of them are woken up at the same time.
--
-- * When the state is \"unlocked\" @wait@ returns immediately.
--
-- @wait@ does not alter the state of the lock.
wait :: (MonadConc m) => Lock m -> m ()
wait (Lock mv) = MVar.readMVar mv
--------------------------------------------------------------------------------
-- |
-- Determines if the lock is in the \"locked\" state.
--
-- Note that this is only a snapshot of the state. By the time a program reacts
-- on its result it may already be out of date.
locked :: (MonadConc m) => Lock m -> m Bool
locked = MVar.isEmptyMVar . _fromLock
--------------------------------------------------------------------------------

View File

@ -0,0 +1,376 @@
--------------------------------------------------------------------------------
-- Copyright © 2010-2012 Bas van Dijk & Roel van Dijk
-- Copyright © 2018 DFINITY Stiftung
--
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are
-- met:
--
-- * Redistributions of source code must retain the above copyright
-- notice, this list of conditions and the following disclaimer.
--
-- * Redistributions in binary form must reproduce the above
-- copyright notice, this list of conditions and the following
-- disclaimer in the documentation and/or other materials provided
-- with the distribution.
--
-- * The names of Bas van Dijk, Roel van Dijk and the names of
-- contributors may NOT be used to endorse or promote products
-- derived from this software without specific prior written
-- permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-------------------------------------------------------------------------------
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE UndecidableInstances #-}
-------------------------------------------------------------------------------
-- |
-- Module : Control.Concurrent.Classy.RWLock
-- Copyright : © 2010-2011 Bas van Dijk & Roel van Dijk
-- , © 2018 DFINITY Stiftung
-- Maintainer : DFINITY USA Research <team@dfinity.org>
--
-- Multiple-reader, single-writer locks. Used to protect shared resources which
-- may be concurrently read, but only sequentially written.
--
-- All functions are /exception safe/. Throwing asynchronous exceptions will not
-- compromise the internal state of an 'RWLock'. This means it is perfectly safe
-- to kill a thread that is blocking on, for example, 'acquireRead'.
-------------------------------------------------------------------------------
module Control.Concurrent.Classy.RWLock
( -- * @RWLock@
RWLock
-- * Creating locks
, newRWLock
, newAcquiredRead
, newAcquiredWrite
-- * Read access
-- ** Blocking
, acquireRead
, releaseRead
, withRead
, waitRead
-- ** Non-blocking
, tryAcquireRead
, tryWithRead
-- * Write access
-- ** Blocking
, acquireWrite
, releaseWrite
, withWrite
, waitWrite
-- ** Non-blocking
, tryAcquireWrite
, tryWithWrite
) where
-------------------------------------------------------------------------------
import Control.Applicative (pure, (<*>))
import Control.Monad (Monad, (>>))
import Data.Bool (Bool(False, True))
import Data.Eq (Eq, (==))
import Data.Function (on, ($))
import Data.Functor ((<$>))
import Data.Int (Int)
import Data.List ((++))
import Data.Maybe (Maybe(Just, Nothing))
import Data.Ord (Ord)
import Data.Typeable (Typeable)
import Prelude (String, error, pred, succ)
import Text.Read (Read)
import Text.Show (Show)
import qualified Control.Concurrent.Classy.MVar as MVar
import Control.Monad.Catch (bracket_, mask, mask_,
onException)
import Control.Monad.Conc.Class (MonadConc(MVar))
import Control.Concurrent.Classy.Lock (Lock)
import qualified Control.Concurrent.Classy.Lock as Lock
-------------------------------------------------------------------------------
-- |
-- Multiple-reader, single-writer lock. Is in one of three states:
--
-- * \"Free\": Read or write access can be acquired without blocking.
--
-- * \"Read\": One or more threads have acquired read access.
-- Blocks write access.
--
-- * \"Write\": A single thread has acquired write access.
-- Blocks other threads from acquiring both read and write access.
data RWLock m
= RWLock
{ _state :: MVar m State
, _readLock :: Lock m
, _writeLock :: Lock m
}
deriving (Typeable)
-- TODO: could the fields of RWLock be strict / unpacked?
instance (Eq (MVar m State)) => Eq (RWLock m) where
(==) = (==) `on` _state
-------------------------------------------------------------------------------
-- |
-- Internal state of the 'RWLock'.
data State
= Free
| Read !Int
| Write
deriving (Eq, Ord, Show, Read)
-------------------------------------------------------------------------------
-- |
-- Create a new 'RWLock' in the \"free\" state; either read or write access
-- can be acquired without blocking.
newRWLock :: (MonadConc m) => m (RWLock m)
newRWLock = do
state <- MVar.newMVar Free
rlock <- Lock.newLock
RWLock state rlock <$> Lock.newLock
-- |
-- Create a new 'RWLock' in the \"read\" state; only read can be acquired
-- without blocking.
newAcquiredRead :: (MonadConc m) => m (RWLock m)
newAcquiredRead = do
state <- MVar.newMVar (Read 1)
rlock <- Lock.newAcquired
RWLock state rlock <$> Lock.newLock
-- |
-- Create a new 'RWLock' in the \"write\" state; either acquiring read or
-- write will block.
newAcquiredWrite :: (MonadConc m) => m (RWLock m)
newAcquiredWrite = do
state <- MVar.newMVar Write
rlock <- Lock.newLock
RWLock state rlock <$> Lock.newAcquired
-------------------------------------------------------------------------------
-- |
-- Acquire the read lock.
--
-- Blocks if another thread has acquired write access.
-- If @acquireRead@ terminates without throwing an exception the state of
-- the 'RWLock' will be \"read\".
--
-- Implementation note: throws an exception when more than @'maxBound' :: 'Int'@
-- simultaneous threads acquire the read lock. But that is unlikely.
acquireRead :: (MonadConc m) => RWLock m -> m ()
acquireRead RWLock { _state, _readLock, _writeLock } = mask_ go
where
go = do
st <- MVar.takeMVar _state
case st of
Free -> do Lock.acquire _readLock
MVar.putMVar _state $ Read 1
(Read n) -> MVar.putMVar _state $ Read (succ n)
Write -> do MVar.putMVar _state st
Lock.wait _writeLock
go
-- |
-- Try to acquire the read lock; non blocking.
--
-- Like 'acquireRead', but doesn't block. Returns 'True' if the resulting
-- state is \"read\", 'False' otherwise.
tryAcquireRead :: (MonadConc m) => RWLock m -> m Bool
tryAcquireRead RWLock { _state, _readLock } = mask_ $ do
st <- MVar.takeMVar _state
case st of
Free -> do Lock.acquire _readLock
MVar.putMVar _state $ Read 1
pure True
Read n -> do MVar.putMVar _state $ Read (succ n)
pure True
Write -> do MVar.putMVar _state st
pure False
-- |
-- Release the read lock.
--
-- If the calling thread was the last one to relinquish read access the state
-- will revert to \"free\".
--
-- It is an error to release read access to an 'RWLock' which is not in
-- the \"read\" state.
releaseRead :: (MonadConc m) => RWLock m -> m ()
releaseRead RWLock { _state, _readLock } = mask_ $ do
st <- MVar.takeMVar _state
case st of
Read 1 -> do Lock.release _readLock
MVar.putMVar _state Free
Read n -> MVar.putMVar _state $ Read (pred n)
_ -> do MVar.putMVar _state st
throw "releaseRead" "already released"
-- |
-- A convenience function wich first acquires read access and then performs the
-- computation. When the computation terminates, whether normally or by raising
-- an exception, the read lock is released.
withRead :: (MonadConc m) => RWLock m -> m a -> m a
withRead = bracket_ <$> acquireRead <*> releaseRead
-- |
-- A non-blocking 'withRead'. First tries to acquire the lock. If that fails,
-- 'Nothing' is returned. If it succeeds, the computation is performed.
-- When the computation terminates, whether normally or by raising an exception,
-- the lock is released and 'Just' the result of the computation is returned.
tryWithRead :: (MonadConc m) => RWLock m -> m a -> m (Maybe a)
tryWithRead l a = mask $ \restore -> do
acquired <- tryAcquireRead l
if acquired
then do r <- restore a `onException` releaseRead l
releaseRead l
pure $ Just r
else pure Nothing
-- |
-- * When the state is \"write\", @waitRead@ /blocks/ until a call to
-- 'releaseWrite' in another thread changes the state to \"free\".
--
-- * When the state is \"free\" or \"read\" @waitRead@ returns immediately.
--
-- @waitRead@ does not alter the state of the lock.
--
-- Note that @waitRead@ is just a convenience function defined as:
--
-- @waitRead l = 'mask_' '$' 'acquireRead' l '>>' 'releaseRead' l@
waitRead :: (MonadConc m) => RWLock m -> m ()
waitRead l = mask_ (acquireRead l >> releaseRead l)
-------------------------------------------------------------------------------
-- |
-- Acquire the write lock.
--
-- Blocks if another thread has acquired either read or write access.
-- If @acquireWrite@ terminates without throwing an exception the state of
-- the 'RWLock' will be \"write\".
acquireWrite :: (MonadConc m) => RWLock m -> m ()
acquireWrite RWLock { _state, _readLock, _writeLock } = mask_ go'
where
go' = do
st <- MVar.takeMVar _state
case st of
Free -> do Lock.acquire _writeLock
MVar.putMVar _state Write
Read _ -> do MVar.putMVar _state st
Lock.wait _readLock
go'
Write -> do MVar.putMVar _state st
Lock.wait _writeLock
go'
-- |
-- Try to acquire the write lock; non blocking.
--
-- Like 'acquireWrite', but doesn't block.
-- Returns 'True' if the resulting state is \"write\", 'False' otherwise.
tryAcquireWrite :: (MonadConc m) => RWLock m -> m Bool
tryAcquireWrite RWLock { _state, _writeLock } = mask_ $ do
st <- MVar.takeMVar _state
case st of
Free -> do Lock.acquire _writeLock
MVar.putMVar _state Write
pure True
_ -> do MVar.putMVar _state st
pure False
-- |
-- Release the write lock.
--
-- If @releaseWrite@ terminates without throwing an exception the state
-- will be \"free\".
--
-- It is an error to release write access to an 'RWLock' which is not
-- in the \"write\" state.
releaseWrite :: (MonadConc m) => RWLock m -> m ()
releaseWrite RWLock { _state, _writeLock } = mask_ $ do
st <- MVar.takeMVar _state
case st of
Write -> do Lock.release _writeLock
MVar.putMVar _state Free
_ -> do MVar.putMVar _state st
throw "releaseWrite" "already released"
-- |
-- A convenience function wich first acquires write access and then performs
-- the computation. When the computation terminates, whether normally or by
-- raising an exception, the write lock is released.
withWrite :: (MonadConc m) => RWLock m -> m a -> m a
withWrite = bracket_ <$> acquireWrite <*> releaseWrite
-- |
-- A non-blocking 'withWrite'. First tries to acquire the lock. If that fails,
-- 'Nothing' is returned. If it succeeds, the computation is performed.
-- When the computation terminates, whether normally or by raising an exception,
-- the lock is released and 'Just' the result of the computation is returned.
tryWithWrite :: (MonadConc m) => RWLock m -> m a -> m (Maybe a)
tryWithWrite l a = mask $ \restore -> do
acquired <- tryAcquireWrite l
if acquired
then do r <- restore a `onException` releaseWrite l
releaseWrite l
pure $ Just r
else pure Nothing
-- |
-- * When the state is \"write\" or \"read\" @waitWrite@ /blocks/ until a call
-- to 'releaseWrite' or 'releaseRead' in another thread changes the state
-- to \"free\".
--
-- * When the state is \"free\" @waitWrite@ returns immediately.
--
-- @waitWrite@ does not alter the state of the lock.
--
-- Note that @waitWrite@ is just a convenience function defined as:
--
-- @waitWrite l = 'mask_' '$' 'acquireWrite' l '>>' 'releaseWrite' l@
waitWrite :: (MonadConc m) => RWLock m -> m ()
waitWrite l = mask_ (acquireWrite l >> releaseWrite l)
--------------------------------------------------------------------------------
throw :: (Monad m) => String -> String -> m a
throw func msg
= error ("Control.Concurrent.Classy.RWLock." ++ func ++ ": " ++ msg)
--------------------------------------------------------------------------------

View File

@ -41,11 +41,14 @@ library
, Control.Concurrent.Classy , Control.Concurrent.Classy
, Control.Concurrent.Classy.Async , Control.Concurrent.Classy.Async
, Control.Concurrent.Classy.Chan , Control.Concurrent.Classy.Chan
, Control.Concurrent.Classy.BoundedChan
, Control.Concurrent.Classy.CRef , Control.Concurrent.Classy.CRef
, Control.Concurrent.Classy.IORef , Control.Concurrent.Classy.IORef
, Control.Concurrent.Classy.MVar , Control.Concurrent.Classy.MVar
, Control.Concurrent.Classy.QSem , Control.Concurrent.Classy.QSem
, Control.Concurrent.Classy.QSemN , Control.Concurrent.Classy.QSemN
, Control.Concurrent.Classy.Lock
, Control.Concurrent.Classy.RWLock
, Control.Concurrent.Classy.STM , Control.Concurrent.Classy.STM
, Control.Concurrent.Classy.STM.TVar , Control.Concurrent.Classy.STM.TVar
, Control.Concurrent.Classy.STM.TMVar , Control.Concurrent.Classy.STM.TMVar