1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
|
{-# 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 {un :: TMVar ()}
deriving (Typeable, Eq)
--------------------------------------------------------------------------------
-- Creating locks
--------------------------------------------------------------------------------
-- | Create a lock in the \"unlocked\" state.
new :: STM Lock
new = Lock <$> newTMVar ()
-- | Create a lock in the \"locked\" state.
newAcquired :: STM Lock
newAcquired = Lock <$> 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 = takeTMVar . 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 = fmap isJust . tryTakeTMVar . 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 tmv) = do
b <- tryPutTMVar 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 = liftA2 bracket_ (atomically . acquire) (atomically . 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 l a = mask $ \restore -> do
acquired <- atomically (tryAcquire 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 tmv) = readTMVar 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 = isEmptyTMVar . un
|