    {-# LANGUAGE CPP, DeriveDataTypeable, NoImplicitPrelude #-}

    #if __GLASGOW_HASKELL__ >= 800
    {-# LANGUAGE Safe #-}
    #elif __GLASGOW_HASKELL__ >= 704
    {-# LANGUAGE Trustworthy #-}
    #endif

    --------------------------------------------------------------------------------
    -- |
    -- Module     : Control.Concurrent.STM.Lock
    -- Copyright  : (c) 2010-2011 Bas van Dijk & Roel van Dijk
    -- License    : BSD3 (see the file LICENSE)
    -- Maintainer : Bas van Dijk <v.dijk.bas@gmail.com>
    --            , Roel van Dijk <vandijk.roel@gmail.com>
    --
    -- This module provides an 'STM' version of @Control.Concurrent.Lock@.
    --
    -- This module is intended to be imported qualified. We suggest importing it like:
    --
    -- @
    -- import           Control.Concurrent.STM.Lock         ( Lock )
    -- import qualified Control.Concurrent.STM.Lock as Lock ( ... )
    -- @
    --
    --------------------------------------------------------------------------------

    module Control.Concurrent.STM.Lock
        ( Lock

          -- * Creating locks
        , new
        , newAcquired

          -- * Locking and unlocking
        , acquire
        , tryAcquire
        , release

          -- * Convenience functions
        , with
        , tryWith
        , wait

          -- * Querying locks
        , locked
        ) where

    --------------------------------------------------------------------------------
    -- Imports
    --------------------------------------------------------------------------------

    -- from base:
    import Control.Applicative          ( liftA2 )
    import Control.Exception            ( bracket_, onException )
    import Control.Monad                ( return, when )
    import Data.Bool                    ( Bool, not )

    #ifdef __HADDOCK_VERSION__
    import Data.Bool                    ( Bool(False, True) )
    #endif

    import Data.Eq                      ( Eq )
    import Data.Function                ( ($), (.) )
    import Data.Functor                 ( fmap, (<$>) )
    import Data.Maybe                   ( Maybe(Nothing, Just), isJust )
    import Data.Typeable                ( Typeable )
    import Prelude                      ( error )
    import System.IO                    ( IO )

    #if __GLASGOW_HASKELL__ < 700
    import Control.Monad                ( (>>=), fail )
    #endif

    #if __GLASGOW_HASKELL__ < 700
    import Control.Monad                ( Monad )
    #endif

    -- from stm:
    import Control.Concurrent.STM       ( STM, atomically )

    #ifdef __HADDOCK_VERSION__
    import Control.Concurrent.STM       ( retry )
    #endif

    import Control.Concurrent.STM.TMVar ( TMVar, newTMVar, newEmptyTMVar
                                        , takeTMVar, tryTakeTMVar
                                        , tryPutTMVar, readTMVar, isEmptyTMVar
                                        )

    -- from concurrent-extra (this package):
    import Utils                        ( mask )

    --------------------------------------------------------------------------------
    -- Locks
    --------------------------------------------------------------------------------

    -- | A lock is in one of two states: \"locked\" or \"unlocked\".
    newtype Lock = Lock {Lock -> TMVar ()
    un :: TMVar ()}
        deriving (Typeable, Lock -> Lock -> Bool
    (Lock -> Lock -> Bool) -> (Lock -> Lock -> Bool) -> Eq Lock
    forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
    $c== :: Lock -> Lock -> Bool
    == :: Lock -> Lock -> Bool
    $c/= :: Lock -> Lock -> Bool
    /= :: Lock -> Lock -> Bool
    Eq)

    --------------------------------------------------------------------------------
    -- Creating locks
    --------------------------------------------------------------------------------

    -- | Create a lock in the \"unlocked\" state.
    new :: STM Lock
    new :: STM Lock
    new = TMVar () -> Lock
    Lock (TMVar () -> Lock) -> STM (TMVar ()) -> STM Lock
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> () -> STM (TMVar ())
    forall a. a -> STM (TMVar a)
    newTMVar ()

    -- | Create a lock in the \"locked\" state.
    newAcquired :: STM Lock
    newAcquired :: STM Lock
    newAcquired = TMVar () -> Lock
    Lock (TMVar () -> Lock) -> STM (TMVar ()) -> STM Lock
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> STM (TMVar ())
    forall a. STM (TMVar a)
    newEmptyTMVar

    --------------------------------------------------------------------------------
    -- Locking and unlocking
    --------------------------------------------------------------------------------

    {-|
    * When the state is \"locked\" @acquire@ will 'retry' the transaction.

    * When the state is \"unlocked\" @acquire@ will change the state to \"locked\".
    -}
    acquire :: Lock -> STM ()
    acquire :: Lock -> STM ()
    acquire = TMVar () -> STM ()
    forall a. TMVar a -> STM a
    takeTMVar (TMVar () -> STM ()) -> (Lock -> TMVar ()) -> Lock -> STM ()
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Lock -> TMVar ()
    un

    {-|
    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 :: Lock -> STM Bool
    tryAcquire :: Lock -> STM Bool
    tryAcquire = (Maybe () -> Bool) -> STM (Maybe ()) -> STM Bool
    forall a b. (a -> b) -> STM a -> STM b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap Maybe () -> Bool
    forall a. Maybe a -> Bool
    isJust (STM (Maybe ()) -> STM Bool)
    -> (Lock -> STM (Maybe ())) -> Lock -> STM Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . TMVar () -> STM (Maybe ())
    forall a. TMVar a -> STM (Maybe a)
    tryTakeTMVar (TMVar () -> STM (Maybe ()))
    -> (Lock -> TMVar ()) -> Lock -> STM (Maybe ())
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Lock -> TMVar ()
    un

    {-|
    @release@ changes the state to \"unlocked\" and returns immediately.

    Note that it is an error to release a lock in the \"unlocked\" state!
    -}
    release :: Lock -> STM ()
    release :: Lock -> STM ()
    release (Lock TMVar ()
    tmv) = do
      b <- TMVar () -> () -> STM Bool
    forall a. TMVar a -> a -> STM Bool
    tryPutTMVar TMVar ()
    tmv ()
      when (not b) $ error "Control.Concurrent.STM.Lock.release: Can't release unlocked Lock!"

    --------------------------------------------------------------------------------
    -- Convenience functions
    --------------------------------------------------------------------------------

    {-|
    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.
    -}
    with :: Lock -> IO a -> IO a
    with :: forall a. Lock -> IO a -> IO a
    with = (IO () -> IO () -> IO a -> IO a)
    -> (Lock -> IO ()) -> (Lock -> IO ()) -> Lock -> IO a -> IO a
    forall a b c.
    (a -> b -> c) -> (Lock -> a) -> (Lock -> b) -> Lock -> c
    forall (f :: * -> *) a b c.
    Applicative f =>
    (a -> b -> c) -> f a -> f b -> f c
    liftA2 IO () -> IO () -> IO a -> IO a
    forall a b c. IO a -> IO b -> IO c -> IO c
    bracket_ (STM () -> IO ()
    forall a. STM a -> IO a
    atomically (STM () -> IO ()) -> (Lock -> STM ()) -> Lock -> IO ()
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Lock -> STM ()
    acquire) (STM () -> IO ()
    forall a. STM a -> IO a
    atomically (STM () -> IO ()) -> (Lock -> STM ()) -> Lock -> IO ()
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Lock -> STM ()
    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 :: Lock -> IO a -> IO (Maybe a)
    tryWith :: forall a. Lock -> IO a -> IO (Maybe a)
    tryWith Lock
    l IO a
    a = ((forall a. IO a -> IO a) -> IO (Maybe a)) -> IO (Maybe a)
    forall b. ((forall a. IO a -> IO a) -> IO b) -> IO b
    mask (((forall a. IO a -> IO a) -> IO (Maybe a)) -> IO (Maybe a))
    -> ((forall a. IO a -> IO a) -> IO (Maybe a)) -> IO (Maybe a)
    forall a b. (a -> b) -> a -> b
    $ \forall a. IO a -> IO a
    restore -> do
      acquired <- STM Bool -> IO Bool
    forall a. STM a -> IO a
    atomically (Lock -> STM Bool
    tryAcquire Lock
    l)
      if acquired
        then do r <- restore a `onException` atomically (release l)
                atomically (release l)
                return $ Just r
        else return Nothing

    {-|
    * When the state is \"locked\", @wait@ will 'retry' the transaction

    * When the state is \"unlocked\" @wait@ returns immediately.

    @wait@ does not alter the state of the lock.

    Note that @wait@ is just a convenience function which can be defined as:

    @wait l = 'acquire' l '>>' 'release' l@
    -}
    wait :: Lock -> STM ()
    wait :: Lock -> STM ()
    wait (Lock TMVar ()
    tmv) = TMVar () -> STM ()
    forall a. TMVar a -> STM a
    readTMVar TMVar ()
    tmv

    --------------------------------------------------------------------------------
    -- Querying locks
    --------------------------------------------------------------------------------

    {-|
    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 :: Lock -> STM Bool
    locked :: Lock -> STM Bool
    locked = TMVar () -> STM Bool
    forall a. TMVar a -> STM Bool
    isEmptyTMVar (TMVar () -> STM Bool) -> (Lock -> TMVar ()) -> Lock -> STM Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Lock -> TMVar ()
    un