concurrency-1.6.2.0: Control/Concurrent/Classy/Lock.hs
--------------------------------------------------------------------------------
-- 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
--------------------------------------------------------------------------------