{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
module Control.Concurrent.AlarmClock
( AlarmClock()
, newAlarmClock
, newAlarmClock'
, destroyAlarmClock
, withAlarmClock
, setAlarm
, setAlarmSTM
, setAlarmNow
, isAlarmSet
, isAlarmSetSTM
, TimeScale
, MonotonicTime(..)
) where
import Control.Concurrent.Async (async, wait, waitSTM,
withAsync)
import Control.Concurrent.STM (STM, TVar, atomically,
modifyTVar',
newTVarIO, orElse,
readTVar, retry,
writeTVar)
import Control.Concurrent.Thread.Delay (delay)
import Control.Exception (bracket)
import Control.Monad (join)
import Control.Monad.Fix (mfix)
import GHC.Conc (labelThread,
myThreadId)
import Control.Concurrent.AlarmClock.TimeScale
data AlarmSetting t = AlarmNotSet | AlarmSet t | AlarmDestroyed
data AlarmClock t = AlarmClock
{ acWaitForExit :: IO ()
, acNewSetting :: TVar (AlarmSetting t)
}
newAlarmClock
:: TimeScale t
=> (AlarmClock t -> IO ())
-> IO (AlarmClock t)
newAlarmClock onWakeUp = newAlarmClock' $ const . onWakeUp
newAlarmClock'
:: TimeScale t
=> (AlarmClock t -> t -> IO ())
-> IO (AlarmClock t)
newAlarmClock' onWakeUp = mfix $ \ac -> do
acAsync <- async $ runAlarmClock ac (onWakeUp ac)
AlarmClock (wait acAsync) <$> newTVarIO AlarmNotSet
destroyAlarmClock :: AlarmClock t -> IO ()
destroyAlarmClock AlarmClock{..} = atomically (writeTVar acNewSetting AlarmDestroyed) >> acWaitForExit
withAlarmClock :: TimeScale t
=> (AlarmClock t -> t -> IO ())
-> (AlarmClock t -> IO a) -> IO a
withAlarmClock onWakeUp inner = bracket (newAlarmClock' onWakeUp) destroyAlarmClock inner
setAlarm :: TimeScale t => AlarmClock t -> t -> IO ()
setAlarm ac t = atomically $ setAlarmSTM ac t
setAlarmSTM :: TimeScale t => AlarmClock t -> t -> STM ()
setAlarmSTM AlarmClock{..} t = modifyTVar' acNewSetting $ \case
AlarmNotSet -> AlarmSet t
AlarmSet t' -> AlarmSet $! earlierOf t t'
AlarmDestroyed -> AlarmDestroyed
setAlarmNow :: TimeScale t => AlarmClock t -> IO ()
setAlarmNow alarm = getAbsoluteTime >>= setAlarm alarm
isAlarmSet :: AlarmClock t -> IO Bool
isAlarmSet = atomically . isAlarmSetSTM
isAlarmSetSTM :: AlarmClock t -> STM Bool
isAlarmSetSTM AlarmClock{..} = readTVar acNewSetting
>>= \case { AlarmSet _ -> return True; _ -> return False }
labelMyThread :: String -> IO ()
labelMyThread threadLabel = myThreadId >>= flip labelThread threadLabel
runAlarmClock :: TimeScale t => AlarmClock t -> (t -> IO ()) -> IO ()
runAlarmClock AlarmClock{..} wakeUpAction = labelMyThread "alarmclock" >> loop
where
loop :: IO ()
loop = join $ atomically whenNotSet
whenNotSet :: STM (IO ())
whenNotSet = readTVar acNewSetting >>= \case
AlarmNotSet -> retry
AlarmDestroyed -> return $ return ()
AlarmSet wakeUpTime -> return $ whenSet wakeUpTime
whenSet wakeUpTime = do
now <- getAbsoluteTime
let microsecondsTimeout = microsecondsDiff wakeUpTime now
if 0 < microsecondsTimeout
then join $ withAsync (delay microsecondsTimeout) $ \a -> atomically $
(waitSTM a >> return (whenSet wakeUpTime))
`orElse`
(readTVar acNewSetting >>= \case
AlarmSet wakeUpTime' | earlierOf wakeUpTime' wakeUpTime /= wakeUpTime -> return $ whenSet wakeUpTime'
AlarmDestroyed -> return $ return ()
_ -> retry
)
else do
atomically $ modifyTVar' acNewSetting $ \case
AlarmSet _ -> AlarmNotSet
setting -> setting
wakeUpAction now
loop