si-timers-1.0.0.0: src/Control/Monad/Class/MonadTimer/NonStandard.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE TypeFamilies #-}
#if defined(__GLASGOW_HASKELL__) && \
!defined(mingw32_HOST_OS) && \
!defined(__GHCJS__) && \
!defined(js_HOST_ARCH) && \
!defined(wasm32_HOST_ARCH)
#define GHC_TIMERS_API
#endif
-- | A non-standard interface for timer api.
--
-- This module also provides a polyfill which allows to use timer api also on
-- non-threaded RTS regardless of the architecture \/ OS. Currently we support
-- `*nix`, `macOS`, `Windows` (and, unofficially `GHCJS`).
--
-- We use it to provide @'Control.Monad.Class.MonadTimer.MonadTimer' 'IO'@
-- instance and to implement a cancellable timers, see
-- 'Control.Monad.Class.MonadTimer.SI.registerDelayCancellable'.
--
-- You can expect we will deprecate it at some point (e.g. once GHC gets
-- a better support for timers especially across different execution
-- environments).
--
module Control.Monad.Class.MonadTimer.NonStandard
( TimeoutState (..)
, newTimeout
, readTimeout
, cancelTimeout
, awaitTimeout
, NewTimeout
, ReadTimeout
, CancelTimeout
, AwaitTimeout
) where
import qualified Control.Concurrent.STM as STM
#ifndef GHC_TIMERS_API
import Control.Monad (when)
#endif
import Control.Monad.Class.MonadSTM
#ifdef GHC_TIMERS_API
import qualified GHC.Event as GHC (TimeoutKey, getSystemTimerManager,
registerTimeout, unregisterTimeout)
#else
import qualified GHC.Conc.IO as GHC (registerDelay)
#endif
-- | State of a timeout: pending, fired or cancelled.
--
data TimeoutState = TimeoutPending | TimeoutFired | TimeoutCancelled
deriving (Eq, Ord, Show)
-- | The type of the timeout handle, used with 'newTimeout', 'readTimeout', and
-- 'cancelTimeout'.
--
#ifdef GHC_TIMERS_API
data Timeout = TimeoutIO !(STM.TVar TimeoutState) !GHC.TimeoutKey
#else
data Timeout = TimeoutIO !(STM.TVar (STM.TVar Bool)) !(STM.TVar Bool)
#endif
-- | Create a new timeout which will fire at the given time duration in
-- the future.
--
-- The timeout will start in the 'TimeoutPending' state and either
-- fire at or after the given time leaving it in the 'TimeoutFired' state,
-- or it may be cancelled with 'cancelTimeout', leaving it in the
-- 'TimeoutCancelled' state.
--
-- Timeouts /cannot/ be reset to the pending state once fired or cancelled
-- (as this would be very racy). You should create a new timeout if you need
-- this functionality.
--
-- When native timer manager is supported (on `*nix` systems), it only holds
-- a `TVar` with `TimeoutState` and `GHC.TimeoutKey`.
--
newTimeout :: NewTimeout IO Timeout
type NewTimeout m timeout = Int -> m timeout
-- | Read the current state of a timeout. This does not block, but returns
-- the current state. It is your responsibility to use 'retry' to wait.
--
-- Alternatively you may wish to use the convenience utility 'awaitTimeout'
-- to wait for just the fired or cancelled outcomes.
--
-- You should consider the cancelled state if you plan to use 'cancelTimeout'.
--
readTimeout :: ReadTimeout IO Timeout
type ReadTimeout m timeout = timeout -> STM m TimeoutState
-- | Cancel a timeout (unless it has already fired), putting it into the
-- 'TimeoutCancelled' state. Code reading and acting on the timeout state
-- need to handle such cancellation appropriately.
--
-- It is safe to race this concurrently against the timer firing. It will
-- have no effect if the timer fires first.
--
cancelTimeout :: CancelTimeout IO Timeout
type CancelTimeout m timeout = timeout -> m ()
-- | Returns @True@ when the timeout is fired, or @False@ if it is cancelled.
awaitTimeout :: AwaitTimeout IO Timeout
type AwaitTimeout m timeout = timeout -> STM m Bool
#ifdef GHC_TIMERS_API
readTimeout (TimeoutIO var _key) = STM.readTVar var
newTimeout = \d -> do
var <- STM.newTVarIO TimeoutPending
mgr <- GHC.getSystemTimerManager
key <- GHC.registerTimeout mgr d (STM.atomically (timeoutAction var))
return (TimeoutIO var key)
where
timeoutAction var = do
x <- STM.readTVar var
case x of
TimeoutPending -> STM.writeTVar var TimeoutFired
TimeoutFired -> error "MonadTimer(IO): invariant violation"
TimeoutCancelled -> return ()
cancelTimeout (TimeoutIO var key) = do
STM.atomically $ do
x <- STM.readTVar var
case x of
TimeoutPending -> STM.writeTVar var TimeoutCancelled
TimeoutFired -> return ()
TimeoutCancelled -> return ()
mgr <- GHC.getSystemTimerManager
GHC.unregisterTimeout mgr key
#else
readTimeout (TimeoutIO timeoutvarvar cancelvar) = do
canceled <- STM.readTVar cancelvar
fired <- STM.readTVar =<< STM.readTVar timeoutvarvar
case (canceled, fired) of
(True, _) -> return TimeoutCancelled
(_, False) -> return TimeoutPending
(_, True) -> return TimeoutFired
newTimeout d = do
timeoutvar <- GHC.registerDelay d
timeoutvarvar <- STM.newTVarIO timeoutvar
cancelvar <- STM.newTVarIO False
return (TimeoutIO timeoutvarvar cancelvar)
cancelTimeout (TimeoutIO timeoutvarvar cancelvar) =
STM.atomically $ do
fired <- STM.readTVar =<< STM.readTVar timeoutvarvar
when (not fired) $ STM.writeTVar cancelvar True
#endif
awaitTimeout t = do s <- readTimeout t
case s of
TimeoutPending -> retry
TimeoutFired -> return True
TimeoutCancelled -> return False