packages feed

concurrency 1.6.1.0 → 1.6.2.0

raw patch · 6 files changed

+904/−2 lines, 6 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ Control.Concurrent.Classy.BoundedChan: data BoundedChan m a
+ Control.Concurrent.Classy.BoundedChan: isEmptyBoundedChan :: MonadConc m => BoundedChan m a -> m Bool
+ Control.Concurrent.Classy.BoundedChan: newBoundedChan :: MonadConc m => Int -> m (BoundedChan m a)
+ Control.Concurrent.Classy.BoundedChan: readBoundedChan :: MonadConc m => BoundedChan m a -> m a
+ Control.Concurrent.Classy.BoundedChan: tryreadBoundedChan :: MonadConc m => BoundedChan m a -> m (Maybe a)
+ Control.Concurrent.Classy.BoundedChan: trywriteBoundedChan :: MonadConc m => BoundedChan m a -> a -> m Bool
+ Control.Concurrent.Classy.BoundedChan: writeBoundedChan :: MonadConc m => BoundedChan m a -> a -> m ()
+ Control.Concurrent.Classy.BoundedChan: writeList2BoundedChan :: MonadConc m => BoundedChan m a -> [a] -> m ()
+ Control.Concurrent.Classy.Lock: acquire :: MonadConc m => Lock m -> m ()
+ Control.Concurrent.Classy.Lock: data Lock m
+ Control.Concurrent.Classy.Lock: instance GHC.Classes.Eq (Control.Monad.Conc.Class.MVar m ()) => GHC.Classes.Eq (Control.Concurrent.Classy.Lock.Lock m)
+ Control.Concurrent.Classy.Lock: locked :: MonadConc m => Lock m -> m Bool
+ Control.Concurrent.Classy.Lock: newAcquired :: MonadConc m => m (Lock m)
+ Control.Concurrent.Classy.Lock: newLock :: MonadConc m => m (Lock m)
+ Control.Concurrent.Classy.Lock: release :: MonadConc m => Lock m -> m ()
+ Control.Concurrent.Classy.Lock: tryAcquire :: MonadConc m => Lock m -> m Bool
+ Control.Concurrent.Classy.Lock: tryWith :: MonadConc m => Lock m -> m a -> m (Maybe a)
+ Control.Concurrent.Classy.Lock: wait :: MonadConc m => Lock m -> m ()
+ Control.Concurrent.Classy.Lock: with :: MonadConc m => Lock m -> m a -> m a
+ Control.Concurrent.Classy.MVar: type family MVar m :: * -> *
+ Control.Concurrent.Classy.RWLock: acquireRead :: MonadConc m => RWLock m -> m ()
+ Control.Concurrent.Classy.RWLock: acquireWrite :: MonadConc m => RWLock m -> m ()
+ Control.Concurrent.Classy.RWLock: data RWLock m
+ Control.Concurrent.Classy.RWLock: instance GHC.Classes.Eq (Control.Monad.Conc.Class.MVar m Control.Concurrent.Classy.RWLock.State) => GHC.Classes.Eq (Control.Concurrent.Classy.RWLock.RWLock m)
+ Control.Concurrent.Classy.RWLock: instance GHC.Classes.Eq Control.Concurrent.Classy.RWLock.State
+ Control.Concurrent.Classy.RWLock: instance GHC.Classes.Ord Control.Concurrent.Classy.RWLock.State
+ Control.Concurrent.Classy.RWLock: instance GHC.Read.Read Control.Concurrent.Classy.RWLock.State
+ Control.Concurrent.Classy.RWLock: instance GHC.Show.Show Control.Concurrent.Classy.RWLock.State
+ Control.Concurrent.Classy.RWLock: newAcquiredRead :: MonadConc m => m (RWLock m)
+ Control.Concurrent.Classy.RWLock: newAcquiredWrite :: MonadConc m => m (RWLock m)
+ Control.Concurrent.Classy.RWLock: newRWLock :: MonadConc m => m (RWLock m)
+ Control.Concurrent.Classy.RWLock: releaseRead :: MonadConc m => RWLock m -> m ()
+ Control.Concurrent.Classy.RWLock: releaseWrite :: MonadConc m => RWLock m -> m ()
+ Control.Concurrent.Classy.RWLock: tryAcquireRead :: MonadConc m => RWLock m -> m Bool
+ Control.Concurrent.Classy.RWLock: tryAcquireWrite :: MonadConc m => RWLock m -> m Bool
+ Control.Concurrent.Classy.RWLock: tryWithRead :: MonadConc m => RWLock m -> m a -> m (Maybe a)
+ Control.Concurrent.Classy.RWLock: tryWithWrite :: MonadConc m => RWLock m -> m a -> m (Maybe a)
+ Control.Concurrent.Classy.RWLock: waitRead :: MonadConc m => RWLock m -> m ()
+ Control.Concurrent.Classy.RWLock: waitWrite :: MonadConc m => RWLock m -> m ()
+ Control.Concurrent.Classy.RWLock: withRead :: MonadConc m => RWLock m -> m a -> m a
+ Control.Concurrent.Classy.RWLock: withWrite :: MonadConc m => RWLock m -> m a -> m a
+ Control.Concurrent.Classy.STM.TVar: type family TVar stm :: * -> *
+ Control.Monad.Conc.Class: -- "full" <tt>MVar</tt> will block until it is empty.
+ Control.Monad.Conc.Class: -- <tt>MVar</tt> will block until it is full, and attempting to put to a
+ Control.Monad.Conc.Class: -- <tt>Ticket</tt> is a proof that a thread observed a specific previous
+ Control.Monad.Conc.Class: -- <tt>atomicWriteIORef</tt> are used.
+ Control.Monad.Conc.Class: -- <tt>readIORef</tt>, <tt>atomicModifyIORef</tt>, and
+ Control.Monad.Conc.Class: -- memory effects if functions outside the set <tt>newIORef</tt>,
+ Control.Monad.Conc.Class: -- value at a time, attempting to read or take from an "empty"
+ Control.Monad.Conc.Class: -- value.
+ Control.Monad.Conc.Class: -- | An abstract handle to a thread.
+ Control.Monad.STM.Class: -- synchronised.
+ Control.Monad.STM.Class: -- they always contain a value and updates are non-blocking and
+ Control.Monad.STM.Class: -- | The mutable reference type. These behave like <a>TVar</a>s, in that
- Control.Concurrent.Classy.Async: asyncThreadId :: Async m a -> (ThreadId m)
+ Control.Concurrent.Classy.Async: asyncThreadId :: Async m a -> ThreadId m

Files

CHANGELOG.rst view
@@ -7,6 +7,22 @@ .. _PVP: https://pvp.haskell.org/  +1.6.2.0 (2018-11-28)+--------------------++* Git: :tag:`concurrency-1.6.2.0`+* Hackage: :hackage:`concurrency-1.6.2.0`++**Contributors:** :u:`gip` (:pull:`289`).++Added+~~~~~++* (:pull:`289`) The ``Control.Concurrent.Classy.BoundedChan`` module.+* (:pull:`289`) The ``Control.Concurrent.Classy.Lock`` module.+* (:pull:`289`) The ``Control.Concurrent.Classy.RWLock`` module.++ 1.6.1.0 (2018-09-23) -------------------- 
Control/Concurrent/Classy.hs view
@@ -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
+ Control/Concurrent/Classy/BoundedChan.hs view
@@ -0,0 +1,223 @@+--------------------------------------------------------------------------------++-- 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.+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+{-# 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.+--+-- @since 1.6.2.0+writeList2BoundedChan :: (MonadConc m) => BoundedChan m a -> [a] -> m ()+writeList2BoundedChan = mapM_ . writeBoundedChan++--------------------------------------------------------------------------------
+ Control/Concurrent/Classy/Lock.hs view
@@ -0,0 +1,246 @@+--------------------------------------------------------------------------------++-- 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\".+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+newLock :: (MonadConc m) => m (Lock m)+newLock = Lock <$> MVar.newMVar ()++-- | Create a lock in the \"locked\" state.+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+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'.+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+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'@.+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+locked :: (MonadConc m) => Lock m -> m Bool+locked = MVar.isEmptyMVar . _fromLock++--------------------------------------------------------------------------------
+ Control/Concurrent/Classy/RWLock.hs view
@@ -0,0 +1,408 @@+--------------------------------------------------------------------------------++-- 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.+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+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@+--+-- @since 1.6.2.0+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\".+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+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.+--+-- @since 1.6.2.0+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@+--+-- @since 1.6.2.0+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)++--------------------------------------------------------------------------------
concurrency.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/  name:                concurrency-version:             1.6.1.0+version:             1.6.2.0 synopsis:            Typeclasses, functions, and data types for concurrency and STM.  description:@@ -32,7 +32,7 @@ source-repository this   type:     git   location: https://github.com/barrucadu/dejafu.git-  tag:      concurrency-1.6.1.0+  tag:      concurrency-1.6.2.0  library   exposed-modules:     Control.Monad.Conc.Class@@ -41,11 +41,14 @@                      , 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