module Control.Time (
delay, delayTill
, timeout, timeoutAt
, CallbackKey
, callbackAfter, callbackAt
, updateCallbackToAfter, updateCallbackTo
, cancelCallback
, AsMicro(..)
) where
import Control.Concurrent
import qualified Control.Concurrent.Thread.Delay as D
import qualified Control.Concurrent.Timeout as T
import qualified Control.Exception as E
import qualified Control.Monad.Catch as MC
import Control.Monad
import Control.Monad.Trans
import Data.Fixed
import Data.Int
import Data.Time
import Data.Typeable
import Data.Unique
import Data.Word
import qualified GHC.Event as Ev
import Numeric.Natural
import Numeric.Units.Dimensional ((/~))
import qualified Numeric.Units.Dimensional as D
import qualified Numeric.Units.Dimensional.SIUnits as D
delay :: (MonadIO m, AsMicro period) => period -> m ()
delay = liftIO . D.delay . toMicro
day :: NominalDiffTime
day = 86400
delayTill :: (MonadIO m) => UTCTime -> m ()
delayTill t = liftIO $ do
n <- getCurrentTime
case n >= t of
True -> return ()
False -> do
delay (realToFrac . min day . diffUTCTime t $ n::Pico)
delayTill t
newtype Timeout = Timeout Unique deriving (Eq, Typeable)
instance Show Timeout where
show _ = "<<timeout>>"
instance E.Exception Timeout where
toException = E.asyncExceptionToException
fromException = E.asyncExceptionFromException
timeout :: (MonadIO m, MC.MonadMask m, AsMicro period) => period -> m a -> m (Maybe a)
timeout p a | 0 >= toMicro p = return Nothing
timeout p a = do
pid <- liftIO myThreadId
ex <- liftIO $ fmap Timeout newUnique
MC.handleJust (\e -> if e == ex then Just () else Nothing)
(\_ -> return Nothing)
(MC.bracket (liftIO $ forkIOWithUnmask $ \unmask ->
unmask $ delay p >> E.throwTo pid ex)
(MC.uninterruptibleMask_ . liftIO . killThread)
(\_ -> fmap Just a))
timeoutAt :: (MonadIO m, MC.MonadMask m) => UTCTime -> m a -> m (Maybe a)
timeoutAt t a = do
now <- liftIO $ getCurrentTime
case now >= t of
True -> return Nothing
False -> do
pid <- liftIO myThreadId
ex <- liftIO $ fmap Timeout newUnique
MC.handleJust (\e -> if e == ex then Just () else Nothing)
(\_ -> return Nothing)
(MC.bracket (liftIO $ forkIOWithUnmask $ \unmask ->
unmask $ delayTill t >> E.throwTo pid ex)
(MC.uninterruptibleMask_ . liftIO . killThread)
(\_ -> fmap Just a))
newtype MicroSeconds = MS Integer
data CallbackHandle =
CallbackCanceled
| CallbackAt Ev.TimeoutKey UTCTime (IO ())
| CallbackAfter Ev.TimeoutKey MicroSeconds (IO ())
type CallbackKey = MVar CallbackHandle
doCallback :: CallbackKey -> IO ()
doCallback ck = do
mngr <- Ev.getSystemTimerManager
delayedAction <- modifyMVarMasked ck $ \case
CallbackCanceled -> return (CallbackCanceled, return ())
CallbackAt _ t act -> do
n <- getCurrentTime
case t `diffUTCTime` n of
w | w <= 0 -> return (CallbackCanceled, act)
w -> do
newKey <- Ev.registerTimeout mngr
(fromInteger . toMicro $ (realToFrac . min day $ w::Pico))
(doCallback ck)
return (CallbackAt newKey t act, return ())
CallbackAfter _ (MS w) act | w <= 0 -> return (CallbackCanceled, act)
CallbackAfter _ (MS w) act -> do
let stepAmount = min (toInteger (maxBound::Int)) w
newKey <- Ev.registerTimeout mngr
(fromInteger stepAmount)
(doCallback ck)
return (CallbackAfter newKey (MS $ w stepAmount) act, return ())
delayedAction
callbackAfter :: (MonadIO m, AsMicro period) => period -> IO () -> m CallbackKey
callbackAfter p act = liftIO $ do
h <- newMVar (CallbackAfter undefined (MS . toMicro $ p) act)
doCallback h
return h
callbackAt :: MonadIO m => UTCTime -> IO () -> m CallbackKey
callbackAt t act = liftIO $ do
h <- newMVar (CallbackAt undefined t act)
doCallback h
return h
updateCallbackToAfter :: (MonadIO m, AsMicro period) => CallbackKey -> period -> m ()
updateCallbackToAfter ck p = liftIO $ do
delayed <- modifyMVarMasked ck $ \case
CallbackCanceled -> return (CallbackCanceled, return ())
CallbackAt tk _ act -> reRegister tk act
CallbackAfter tk _ act -> reRegister tk act
delayed
where
p' :: Integer
p' = toMicro p
reRegister :: Ev.TimeoutKey -> IO () -> IO (CallbackHandle, IO ())
reRegister tk act = do
mngr <- Ev.getSystemTimerManager
Ev.unregisterTimeout mngr tk
let stepAmount = fromInteger . min (toInteger (maxBound::Int)) $ p'
case stepAmount <= 0 of
True -> return (CallbackCanceled, void . forkIO $ act)
False -> do
newKey <- Ev.registerTimeout mngr
(fromInteger stepAmount)
(doCallback ck)
return (CallbackAfter newKey (MS $ p' stepAmount) act, return ())
updateCallbackTo :: MonadIO m => CallbackKey -> UTCTime -> m ()
updateCallbackTo ck t = liftIO $ do
delayed <- modifyMVarMasked ck $ \case
CallbackCanceled -> return (CallbackCanceled, return ())
CallbackAt tk _ act -> reRegister tk act
CallbackAfter tk _ act -> reRegister tk act
delayed
where
reRegister :: Ev.TimeoutKey -> IO () -> IO (CallbackHandle, IO ())
reRegister tk act = do
mngr <- Ev.getSystemTimerManager
Ev.unregisterTimeout mngr tk
n <- getCurrentTime
let w = t `diffUTCTime` n
let stepAmount = fromInteger . toMicro $ (realToFrac . max 0 . min day $ w::Pico)
case stepAmount <= 0 of
True -> return (CallbackCanceled, void . forkIO $ act)
False -> do
newKey <- Ev.registerTimeout mngr stepAmount (doCallback ck)
return (CallbackAt newKey t act, return ())
cancelCallback :: MonadIO m => CallbackKey -> m ()
cancelCallback ck =
liftIO . modifyMVarMasked_ ck $ \case
CallbackCanceled -> return CallbackCanceled
CallbackAt tk _ _ -> cancelCB tk
CallbackAfter tk _ _ -> cancelCB tk
where
cancelCB :: Ev.TimeoutKey -> IO CallbackHandle
cancelCB tk = do
mngr <- Ev.getSystemTimerManager
Ev.unregisterTimeout mngr tk
return CallbackCanceled
microPrecision :: Num n => n
microPrecision = (10^(6::Int))
class AsMicro d where
toMicro :: d -> Integer
instance AsMicro DiffTime where
toMicro = ceiling . (*) microPrecision
instance (Fractional n, AsMicro n) => AsMicro (D.Time n) where
toMicro t = toMicro (t /~ D.second)
instance AsMicro Integer where
toMicro = (*) microPrecision
instance AsMicro Natural where
toMicro = toMicro . toInteger
instance AsMicro Int where
toMicro = toMicro . toInteger
instance AsMicro Int8 where
toMicro = toMicro . toInteger
instance AsMicro Int16 where
toMicro = toMicro . toInteger
instance AsMicro Int32 where
toMicro = toMicro . toInteger
instance AsMicro Int64 where
toMicro = toMicro . toInteger
instance AsMicro Word where
toMicro = toMicro . toInteger
instance AsMicro Word8 where
toMicro = toMicro . toInteger
instance AsMicro Word16 where
toMicro = toMicro . toInteger
instance AsMicro Word32 where
toMicro = toMicro . toInteger
instance AsMicro Word64 where
toMicro = toMicro . toInteger
instance AsMicro Float where
toMicro = ceiling . (*) microPrecision
instance AsMicro Double where
toMicro = ceiling . (*) microPrecision
instance HasResolution d => AsMicro (Fixed d) where
toMicro (d@(MkFixed v)) = (v * microPrecision) `div` (resolution d)