packages feed

time-manager-0.4.0: System/TimeManager/Internal.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StrictData #-}

module System.TimeManager.Internal where

import Control.Concurrent.MVar (MVar, modifyMVar, newMVar)
import Data.IORef (IORef, readIORef)
import Data.Word (Word64)

#if defined(mingw32_HOST_OS)
import qualified GHC.Event.Windows as EV
#else
import qualified GHC.Event as EV
#endif

----------------------------------------------------------------

-- | A timeout manager
newtype Manager = Manager Int

isNoManager :: Manager -> Bool
isNoManager (Manager 0) = True
isNoManager _ = False

----------------------------------------------------------------

-- | An action (callback) to be performed on timeout.
type TimeoutAction = IO ()

-- | A handle used by a timeout manager.
data Handle = Handle
    { handleTimeout :: Int
    , handleAction :: TimeoutAction
    , handleTimerManager :: ~TimerManager
    -- ^ The system timer manager the timeout key was registered with.
    --   Cached so that per-request operations don't re-fetch it.
    , handleLock :: Lock
    -- ^ Used by 'resume', 'pause' and 'cancel' to determine race conditions.
    --
    -- /We intentionally do not use an @MVar HandleState@ for performance reasons./
    -- /The lock only has to be grabbed to avoid race conditions./
    , handleState :: ~(IORef HandleState)
    -- ^ The current state. Used to decide whether a timeout is still going,
    -- paused, or completely terminated.
    --
    -- /We intentionally do not use an @MVar HandleState@ for performance reasons./
    , handleLastRenewed :: ~(IORef Word64)
    -- ^ Monotonic time (in nanoseconds) when the timeout was last
    --   registered or updated.
    , handleMinRenewGap :: Word64
    -- ^ 'tickle' is a no-op unless at least this many nanoseconds have
    --   passed since the last renewal.
    }

-- | Makes sure the function is only run when there's a key to act on.
withTimeoutKey :: Handle -> (EV.TimeoutKey -> IO ()) -> IO ()
withTimeoutKey h keyF = do
    st <- readIORef $ handleState h
    case st of
        Paused key -> keyF key
        Active key -> keyF key
        _ -> pure ()

-- | Makes sure the function is only run when the state is 'Active'.
withActiveTimeoutKey :: Handle -> (EV.TimeoutKey -> IO ()) -> IO ()
withActiveTimeoutKey h keyF = do
    st <- readIORef $ handleState h
    case st of
        Active key -> keyF key
        _ -> pure ()

-- | Used to avoid race conditions in situations when the state has to be changed.
type Lock = MVar ()

newLock :: IO Lock
newLock = newMVar ()

withLock :: Lock -> IO a -> IO a
withLock lock action =
    -- Not sure whether this should be 'modifyMVarMasked' or not.
    modifyMVar lock $ \l -> do
        a <- action
        pure (l, a)

-- | Tracking the state of a handle.
data HandleState
    = -- Timeout is primed to run
      Active EV.TimeoutKey
    | -- Timeout is paused, but still running
      -- ('resume' will set it back to 'Active' and 'tickle')
      Paused EV.TimeoutKey
    | -- Action ran, but timeout was paused, so it is resumable
      -- ('resume' will reregister the action)
      Stopped
    | -- Action was cancelled or run. 'register' is needed to start a new timeout.
      Terminated

isEmptyHandle :: Handle -> Bool
isEmptyHandle Handle{..} = handleTimeout == 0

withNonEmptyHandle :: Handle -> IO () -> IO ()
withNonEmptyHandle h act =
    if isEmptyHandle h then pure () else act

#if defined(mingw32_HOST_OS)
type TimerManager = EV.Manager

getTimerManager :: IO TimerManager
getTimerManager = EV.getSystemManager
#else
type TimerManager = EV.TimerManager

getTimerManager :: IO TimerManager
getTimerManager = EV.getSystemTimerManager
#endif