stackage-server/Control/SingleRun.hs
2016-10-21 10:40:25 +03:00

77 lines
2.6 KiB
Haskell

-- | Ensure that a function is only being run on a given input in one
-- thread at a time. All threads trying to make the call at once
-- return the same result.
module Control.SingleRun
( SingleRun
, mkSingleRun
, singleRun
) where
import Control.Concurrent.MVar
import Control.Exception
import Control.Monad (join)
import Prelude
-- | Captures all of the locking machinery and the function which is
-- run to generate results. Use 'mkSingleRun' to create this value.
data SingleRun k v = SingleRun
{ srVar :: MVar [(k, MVar (Res v))]
, srFunc :: k -> IO v
}
-- | Create a 'SingleRun' value out of a function.
mkSingleRun :: Eq k
=> (k -> IO v)
-> IO (SingleRun k v)
mkSingleRun f = do
var <- newMVar []
return SingleRun
{ srVar = var
, srFunc = f
}
data Res v = SyncException SomeException
| AsyncException SomeException
| Success v
toRes :: SomeException -> Res v
toRes se =
case fromException se of
Just (SomeAsyncException _) -> AsyncException se
Nothing -> SyncException se
-- | Get the result for the given input. If any other thread is
-- currently running this same computation, our thread will block on
-- that thread's result and then return it.
--
-- In the case that the other thread dies from a synchronous
-- exception, we will rethrow that same synchronous exception. If,
-- however, that other thread dies from an asynchronous exception, we
-- will retry.
singleRun :: Eq k => SingleRun k v -> k -> IO v
singleRun sr@(SingleRun var f) k =
mask $ \restore ->
join $ modifyMVar var $ \pairs ->
case lookup k pairs of
Just res -> do
let action = restore $ do
res' <- readMVar res
case res' of
SyncException e -> throwIO e
AsyncException _ -> singleRun sr k
Success v -> return v
return (pairs, action)
Nothing -> do
resVar <- newEmptyMVar
let action = do
eres <- try $ restore $ f k
modifyMVar_ var $ return . filter (\(k', _) -> k /= k')
case eres of
Left e -> do
putMVar resVar $ toRes e
throwIO e
Right v -> do
putMVar resVar $ Success v
return v
return ((k, resVar) : pairs, action)