module Control.Concurrency.AlarmClock where
import Control.Concurrent
import Control.Exception
import Control.Monad
import System.Timeout
import Data.Time
data AlarmClock = AlarmClock (MVar UTCTime) ThreadId
mkAlarmClock :: IO (Maybe UTCTime) -> IO AlarmClock
mkAlarmClock onWakeUp = do
mv <- newEmptyMVar
tid <- mask $ \restore -> forkIO $ runAlarmClock mv $ void $ forkIO $
restore onWakeUp >>= \case Nothing -> return ()
Just wakeUpTime -> setAlarmVar mv wakeUpTime
return $ AlarmClock mv tid
destroyAlarmClock :: AlarmClock -> IO ()
destroyAlarmClock (AlarmClock _ tid) = killThread tid
setAlarmVar :: MVar UTCTime -> UTCTime -> IO ()
setAlarmVar mv wakeUpTime = tryTakeMVar mv >>= \case
Nothing -> putMVar mv wakeUpTime
Just wakeUpTime' -> putMVar mv (min wakeUpTime wakeUpTime')
setAlarm :: AlarmClock -> UTCTime -> IO ()
setAlarm (AlarmClock mv _) = setAlarmVar mv
runAlarmClock :: MVar UTCTime -> IO () -> IO ()
runAlarmClock wakeUpTimeVar wakeUpAction = alarmNotSet
where
alarmNotSet = takeMVar wakeUpTimeVar >>= alarmSet
alarmSet wakeUpTime = do
t <- getCurrentTime
let dt = diffUTCTime wakeUpTime t
if dt < 0 then wakeUpAction >> alarmNotSet
else do
let dt_usec = ceiling (1000000 * dt) :: Integer
let dt_usec_int = if dt_usec > fromIntegral (maxBound :: Int) then maxBound else fromIntegral dt_usec
timeout dt_usec_int (takeMVar wakeUpTimeVar) >>= \case
Nothing -> do
t' <- getCurrentTime
if t' < wakeUpTime then alarmSet wakeUpTime else wakeUpAction >> alarmNotSet
Just wakeUpTime' -> alarmSet (min wakeUpTime wakeUpTime')