{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} ----------------------------------------------------------------------------- -- | -- Module : Control.Distributed.Process.Extras.Time -- Copyright : (c) Tim Watson, Jeff Epstein, Alan Zimmerman -- License : BSD3 (see the file LICENSE) -- -- Maintainer : Tim Watson -- Stability : experimental -- Portability : non-portable (requires concurrency) -- -- This module provides facilities for working with time delays and timeouts. -- The type 'Timeout' and the 'timeout' family of functions provide mechanisms -- for working with @threadDelay@-like behaviour that operates on microsecond -- values. -- -- The 'TimeInterval' and 'TimeUnit' related functions provide an abstraction -- for working with various time intervals, whilst the 'Delay' type provides a -- corrolary to 'timeout' that works with these. ----------------------------------------------------------------------------- module Control.Distributed.Process.Extras.Time ( -- * Time interval handling microSeconds , milliSeconds , seconds , minutes , hours , asTimeout , after , within , timeToMicros , TimeInterval , TimeUnit(..) , Delay(..) -- * Conversion To/From NominalDiffTime , timeIntervalToDiffTime , diffTimeToTimeInterval , diffTimeToDelay , delayToDiffTime , microsecondsToNominalDiffTime -- * (Legacy) Timeout Handling , Timeout , TimeoutNotification(..) , timeout , infiniteWait , noWait ) where import Control.Concurrent (threadDelay) import Control.DeepSeq (NFData) import Control.Distributed.Process import Control.Distributed.Process.Extras.Internal.Types import Control.Monad (void) import Data.Binary import Data.Ratio ((%)) import Data.Time.Clock import Data.Typeable (Typeable) import GHC.Generics -------------------------------------------------------------------------------- -- API -- -------------------------------------------------------------------------------- -- | Defines the time unit for a Timeout value data TimeUnit = Days | Hours | Minutes | Seconds | Millis | Micros deriving (Typeable, Generic, Eq, Show) instance Binary TimeUnit where instance NFData TimeUnit where -- | A time interval. data TimeInterval = TimeInterval TimeUnit Int deriving (Typeable, Generic, Eq, Show) instance Binary TimeInterval where instance NFData TimeInterval where -- | Represents either a delay of 'TimeInterval', an infinite wait or no delay -- (i.e., non-blocking). data Delay = Delay TimeInterval | Infinity | NoDelay deriving (Typeable, Generic, Eq, Show) instance Binary Delay where instance NFData Delay where -- | Represents a /timeout/ in terms of microseconds, where 'Nothing' stands for -- infinity and @Just 0@, no-delay. type Timeout = Maybe Int -- | Send to a process when a timeout expires. data TimeoutNotification = TimeoutNotification Tag deriving (Typeable) instance Binary TimeoutNotification where get = fmap TimeoutNotification $ get put (TimeoutNotification n) = put n -- time interval/unit handling -- | converts the supplied @TimeInterval@ to microseconds asTimeout :: TimeInterval -> Int asTimeout (TimeInterval u v) = timeToMicros u v -- | Convenience for making timeouts; e.g., -- -- > receiveTimeout (after 3 Seconds) [ match (\"ok" -> return ()) ] -- after :: Int -> TimeUnit -> Int after n m = timeToMicros m n -- | Convenience for making 'TimeInterval'; e.g., -- -- > let ti = within 5 Seconds in ..... -- within :: Int -> TimeUnit -> TimeInterval within n m = TimeInterval m n -- | given a number, produces a @TimeInterval@ of microseconds microSeconds :: Int -> TimeInterval microSeconds = TimeInterval Micros -- | given a number, produces a @TimeInterval@ of milliseconds milliSeconds :: Int -> TimeInterval milliSeconds = TimeInterval Millis -- | given a number, produces a @TimeInterval@ of seconds seconds :: Int -> TimeInterval seconds = TimeInterval Seconds -- | given a number, produces a @TimeInterval@ of minutes minutes :: Int -> TimeInterval minutes = TimeInterval Minutes -- | given a number, produces a @TimeInterval@ of hours hours :: Int -> TimeInterval hours = TimeInterval Hours -- TODO: is timeToMicros efficient enough? -- | converts the supplied @TimeUnit@ to microseconds {-# INLINE timeToMicros #-} timeToMicros :: TimeUnit -> Int -> Int timeToMicros Micros us = us timeToMicros Millis ms = ms * (10 ^ (3 :: Int)) -- (1000µs == 1ms) timeToMicros Seconds secs = timeToMicros Millis (secs * milliSecondsPerSecond) timeToMicros Minutes mins = timeToMicros Seconds (mins * secondsPerMinute) timeToMicros Hours hrs = timeToMicros Minutes (hrs * minutesPerHour) timeToMicros Days days = timeToMicros Hours (days * hoursPerDay) {-# INLINE hoursPerDay #-} hoursPerDay :: Int hoursPerDay = 24 {-# INLINE minutesPerHour #-} minutesPerHour :: Int minutesPerHour = 60 {-# INLINE secondsPerMinute #-} secondsPerMinute :: Int secondsPerMinute = 60 {-# INLINE milliSecondsPerSecond #-} milliSecondsPerSecond :: Int milliSecondsPerSecond = 1000 {-# INLINE microSecondsPerSecond #-} microSecondsPerSecond :: Int microSecondsPerSecond = 1000000 -- timeouts/delays (microseconds) -- | Constructs an inifinite 'Timeout'. infiniteWait :: Timeout infiniteWait = Nothing -- | Constructs a no-wait 'Timeout' noWait :: Timeout noWait = Just 0 -- | Sends the calling process @TimeoutNotification tag@ after @time@ microseconds timeout :: Int -> Tag -> ProcessId -> Process () timeout time tag p = void $ spawnLocal $ do liftIO $ threadDelay time send p (TimeoutNotification tag) -- Converting to/from Data.Time.Clock NominalDiffTime -- | given a @TimeInterval@, provide an equivalent @NominalDiffTim@ timeIntervalToDiffTime :: TimeInterval -> NominalDiffTime timeIntervalToDiffTime ti = microsecondsToNominalDiffTime (fromIntegral $ asTimeout ti) -- | given a @NominalDiffTim@@, provide an equivalent @TimeInterval@ diffTimeToTimeInterval :: NominalDiffTime -> TimeInterval diffTimeToTimeInterval dt = microSeconds $ (fromIntegral (round (dt * 1000000) :: Integer)) -- | given a @Delay@, provide an equivalent @NominalDiffTim@ delayToDiffTime :: Delay -> NominalDiffTime delayToDiffTime (Delay ti) = timeIntervalToDiffTime ti delayToDiffTime Infinity = error "trying to convert Delay.Infinity to a NominalDiffTime" delayToDiffTime (NoDelay) = microsecondsToNominalDiffTime 0 -- | given a @NominalDiffTim@@, provide an equivalent @Delay@ diffTimeToDelay :: NominalDiffTime -> Delay diffTimeToDelay dt = Delay $ diffTimeToTimeInterval dt -- | Create a 'NominalDiffTime' from a number of microseconds. microsecondsToNominalDiffTime :: Integer -> NominalDiffTime microsecondsToNominalDiffTime x = fromRational (x % (fromIntegral microSecondsPerSecond)) -- tenYearsAsMicroSeconds :: Integer -- tenYearsAsMicroSeconds = 10 * 365 * 24 * 60 * 60 * 1000000 -- | Allow @(+)@ and @(-)@ operations on @TimeInterval@s instance Num TimeInterval where t1 + t2 = microSeconds $ asTimeout t1 + asTimeout t2 t1 - t2 = microSeconds $ asTimeout t1 - asTimeout t2 _ * _ = error "trying to multiply two TimeIntervals" abs t = microSeconds $ abs (asTimeout t) signum t = if (asTimeout t) == 0 then 0 else if (asTimeout t) < 0 then -1 else 1 fromInteger _ = error "trying to call fromInteger for a TimeInterval. Cannot guess units" -- | Allow @(+)@ and @(-)@ operations on @Delay@s instance Num Delay where NoDelay + x = x Infinity + _ = Infinity x + NoDelay = x _ + Infinity = Infinity (Delay t1 ) + (Delay t2) = Delay (t1 + t2) NoDelay - x = x Infinity - _ = Infinity x - NoDelay = x _ - Infinity = Infinity (Delay t1 ) - (Delay t2) = Delay (t1 - t2) _ * _ = error "trying to multiply two Delays" abs NoDelay = NoDelay abs Infinity = Infinity abs (Delay t) = Delay (abs t) signum (NoDelay) = 0 signum Infinity = 1 signum (Delay t) = Delay (signum t) fromInteger 0 = NoDelay fromInteger _ = error "trying to call fromInteger for a Delay. Cannot guess units"