mirror of
https://github.com/barrucadu/dejafu.git
synced 2024-11-26 09:20:36 +03:00
Merge pull request #289 from dfinity-lab/gip/boundedchan_locks
BoundedChan and locks primitives
This commit is contained in:
commit
88278ca471
@ -24,19 +24,25 @@
|
||||
module Control.Concurrent.Classy
|
||||
( module Control.Monad.Conc.Class
|
||||
, module Control.Concurrent.Classy.Chan
|
||||
, module Control.Concurrent.Classy.BoundedChan
|
||||
, module Control.Concurrent.Classy.CRef
|
||||
, module Control.Concurrent.Classy.IORef
|
||||
, module Control.Concurrent.Classy.MVar
|
||||
, module Control.Concurrent.Classy.STM
|
||||
, module Control.Concurrent.Classy.QSem
|
||||
, module Control.Concurrent.Classy.QSemN
|
||||
, module Control.Concurrent.Classy.Lock
|
||||
, module Control.Concurrent.Classy.RWLock
|
||||
) where
|
||||
|
||||
import Control.Concurrent.Classy.BoundedChan
|
||||
import Control.Concurrent.Classy.Chan
|
||||
import Control.Concurrent.Classy.CRef
|
||||
import Control.Concurrent.Classy.IORef
|
||||
import Control.Concurrent.Classy.Lock
|
||||
import Control.Concurrent.Classy.MVar
|
||||
import Control.Concurrent.Classy.QSem
|
||||
import Control.Concurrent.Classy.QSemN
|
||||
import Control.Concurrent.Classy.RWLock
|
||||
import Control.Concurrent.Classy.STM
|
||||
import Control.Monad.Conc.Class
|
||||
|
207
concurrency/Control/Concurrent/Classy/BoundedChan.hs
Normal file
207
concurrency/Control/Concurrent/Classy/BoundedChan.hs
Normal 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
226
concurrency/Control/Concurrent/Classy/Lock.hs
Normal file
226
concurrency/Control/Concurrent/Classy/Lock.hs
Normal 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
376
concurrency/Control/Concurrent/Classy/RWLock.hs
Normal file
376
concurrency/Control/Concurrent/Classy/RWLock.hs
Normal 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)
|
||||
|
||||
--------------------------------------------------------------------------------
|
@ -41,11 +41,14 @@ library
|
||||
, Control.Concurrent.Classy
|
||||
, Control.Concurrent.Classy.Async
|
||||
, Control.Concurrent.Classy.Chan
|
||||
, Control.Concurrent.Classy.BoundedChan
|
||||
, Control.Concurrent.Classy.CRef
|
||||
, Control.Concurrent.Classy.IORef
|
||||
, Control.Concurrent.Classy.MVar
|
||||
, Control.Concurrent.Classy.QSem
|
||||
, Control.Concurrent.Classy.QSemN
|
||||
, Control.Concurrent.Classy.Lock
|
||||
, Control.Concurrent.Classy.RWLock
|
||||
, Control.Concurrent.Classy.STM
|
||||
, Control.Concurrent.Classy.STM.TVar
|
||||
, Control.Concurrent.Classy.STM.TMVar
|
||||
|
Loading…
Reference in New Issue
Block a user