You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The stm package has the rather nice registerDelay API.
registerDelay :: Int -> IO (TVar Bool)
Set the value of returned TVar to True after a given number of microseconds. The caveats associated with threadDelay also apply.
This is nice, but we could provide a more extensive timer API, based on the underlying GHC timer API. The really nice thing about registerDelay is that being based on STM it is composable. We just need a bit more for use cases like network protocols where you need to be able to push back a timeout. Cancelling is also useful. The GHC timer API can do all these things.
I would like to suggest and get feedback on the following API and implementation. If we go for it, it might be best to add to a new module Control.Concurrent.STM.Timer. I can make a PR based on feedback.
API:
data TimerState = TimerPending | TimerFired | TimerCancelled
data Timer
type Microseconds = Int
-- | Create a new timer which will fire at the given time duration in
-- the future.
--
-- The timer will start in the 'TimerPending' state and either
-- fire at or after the given time leaving it in the 'TimerFired' state,
-- or it may be cancelled with 'cancelTimer', leaving it in the
-- 'TimerCancelled' state.
--
-- Timers /cannot/ be reset to the pending state once fired or cancelled
-- (as this would be very racy). You should create a new timer if you need
-- this functionality.
--
newTimer :: Microseconds -> IO Timer
-- | Read the current state of a timer. This does not block, but returns
-- the current state. It is your responsibility to use 'retry' to wait.
--
-- Alternatively you may wish to use the convenience utility 'awaitTimer'
-- to wait for just the fired or cancelled outcomes.
--
-- You should consider the cancelled state if you plan to use 'cancelTimer'.
--
readTimer :: Timer -> STM TimerState
-- Adjust when this timer will fire, to the given duration into the future.
--
-- It is safe to race this concurrently against the timer firing. It will
-- have no effect if the timer fires first.
--
-- The new time can be before or after the original expiry time, though
-- arguably it is an application design flaw to move timers sooner.
--
updateTimer :: Timer -> Microseconds -> STM ()
-- | Cancel a timer (unless it has already fired), putting it into the
-- 'TimerCancelled' state. Code reading and acting on the timer state
-- need to handle such cancellation appropriately.
--
-- It is safe to race this concurrently against the timer firing. It will
-- have no effect if the timer fires first.
--
cancelTimer :: Timer -> m ()
And implementation in terms of the GHC timeout manager (which is what registerDelay uses)
data Timer = Timer !(STM.TVar TimerState) !GHC.TimerKey
readTimer (Timer var _key) = STM.readTVar var
newTimer = \usec -> do
var <- STM.newTVarIO TimerPending
mgr <- GHC.getSystemTimerManager
key <- GHC.registerTimeout mgr usec (STM.atomically (timerAction var))
return (Timer var key)
where
timerAction var = do
x <- STM.readTVar var
case x of
TimerPending -> STM.writeTVar var TimerFired
TimerFired -> error "MonadTimer(IO): invariant violation"
TimerCancelled -> return ()
-- In GHC's TimerManager this has no effect if the timer already fired.
-- It is safe to race against the timer firing.
updateTimer (Timer _var key) usec = do
mgr <- GHC.getSystemTimerManager
GHC.updateTimer mgr key usec
cancelTimer (Timer var key) = do
STM.atomically $ do
x <- STM.readTVar var
case x of
TimerPending -> STM.writeTVar var TimerCancelled
TimerFired -> return ()
TimerCancelled -> return ()
mgr <- GHC.getSystemTimerManager
GHC.unregisterTimeout mgr key
Plus one handy derived utility
-- | Returns @True@ when the timer is fired, or @False@ if it is cancelled.
awaitTimer :: Timer -> STM Bool
awaitTimer t = do
s <- readTimer t
case s of
TimerPending -> retry
TimerFired -> return True
TimerCancelled -> return False
The text was updated successfully, but these errors were encountered:
In general I like it. However I think updateTimer needs to be IO, not STM, right? There's no way to perform a transaction involving updateTimer because the underlying API is IO. Similarly cancelTimer. Does that make the API less useful? You can't do a test-and-update or a test-and-cancel in the same transaction.
The stm package has the rather nice
registerDelay
API.https://hackage.haskell.org/package/stm-2.5.0.0/docs/Control-Concurrent-STM-TVar.html#v:registerDelay
This is nice, but we could provide a more extensive timer API, based on the underlying GHC timer API. The really nice thing about
registerDelay
is that being based on STM it is composable. We just need a bit more for use cases like network protocols where you need to be able to push back a timeout. Cancelling is also useful. The GHC timer API can do all these things.I would like to suggest and get feedback on the following API and implementation. If we go for it, it might be best to add to a new module
Control.Concurrent.STM.Timer
. I can make a PR based on feedback.API:
And implementation in terms of the GHC timeout manager (which is what
registerDelay
uses)Plus one handy derived utility
The text was updated successfully, but these errors were encountered: