urbit/pkg/hair/lib/Urbit/Behn.hs

97 lines
2.9 KiB
Haskell
Raw Normal View History

2019-05-06 00:46:40 +03:00
{-
# Behn
This provides a timer. To use this,
- Create a new timer with `init`.
- Use `doze` to start the timer.
- Call `wait` to wait until the timer fires.
Then, `wait` will return when the specified time has come.
- If the specified time was in the past, `wait` will return immediately.
- If a timer is set again, the old timer will not fire. The new time
replaces the old one.
- If a timer is unset (with `doze _ Nothing`), the timer will not fire
until a new time has been set.
## Implementation Notes
We use `tryPutMVar` when the timer fires, so that things will continue
to work correctly if the user does not call `wait`. If a timer fires
before `wait` is called, `wait` will return immediatly.
To handle race conditions, the MVar in `bState` is used as a lock. The
code for setting a timer and the thread that runs when the timer fires
(which causes `wait` to return) both take that MVar before acting.
So, if the timer fires conncurently with a call to `doze`,
then one of those threads will get the lock and the other will wait:
- If the `doze` call gets the lock first, it will kill the timer thread
before releasing it.
- If the timer gets the the lock first, it will fire (causeing `wait`
to return) first, and then `doze` action will wait until that finishes.
2019-05-08 23:51:04 +03:00
## TODO
`threadDelay` has low accuracy. Consider using
`GHC.Event.registerTimeout` instead. It's API is very close to what
we want for this anyways.
2019-05-06 00:46:40 +03:00
-}
2019-05-08 21:47:20 +03:00
module Urbit.Behn (Behn, init, wait, doze) where
2019-05-06 00:46:40 +03:00
2019-05-08 21:47:20 +03:00
import Prelude hiding (init)
import Control.Lens
2019-05-06 00:46:40 +03:00
2019-05-08 21:47:20 +03:00
import Data.LargeWord
import Control.Concurrent.MVar
2019-05-06 00:46:40 +03:00
2019-05-08 21:47:20 +03:00
import Control.Concurrent.Async (Async, async, cancel, asyncThreadId)
import Control.Concurrent (threadDelay, killThread)
2019-05-08 23:51:04 +03:00
import Control.Monad (void, when)
2019-05-08 21:47:20 +03:00
import Data.Time.Clock.System (SystemTime(..), getSystemTime)
import Urbit.Time (Wen)
2019-05-06 00:46:40 +03:00
2019-05-08 21:47:20 +03:00
import qualified Control.Concurrent.Async as Async
import qualified Urbit.Time as Time
2019-05-06 00:46:40 +03:00
-- Behn Stuff ------------------------------------------------------------------
data Behn = Behn
2019-05-08 21:47:20 +03:00
{ bState :: MVar (Maybe (Wen, Async ()))
, bSignal :: MVar Wen
2019-05-06 00:46:40 +03:00
}
init :: IO Behn
init = do
st <- newMVar Nothing
sig <- newEmptyMVar
pure (Behn st sig)
2019-05-08 21:47:20 +03:00
wait :: Behn -> IO Wen
2019-05-06 00:46:40 +03:00
wait (Behn _ sig) = takeMVar sig
2019-05-08 21:47:20 +03:00
startTimerThread :: Behn -> Wen -> IO (Async ())
2019-05-06 00:46:40 +03:00
startTimerThread (Behn vSt sig) time =
async $ do
2019-05-08 21:47:20 +03:00
now <- Time.now
2019-05-08 23:51:04 +03:00
Time.sleepUntil time
2019-05-08 21:47:20 +03:00
takeMVar vSt
void $ tryPutMVar sig time
putMVar vSt Nothing
2019-05-06 00:46:40 +03:00
2019-05-08 21:47:20 +03:00
doze :: Behn -> Maybe Wen -> IO ()
2019-05-06 00:46:40 +03:00
doze behn@(Behn vSt sig) mNewTime = do
takeMVar vSt >>= \case Nothing -> pure ()
Just (_,timer) -> cancel timer
newSt <- mNewTime & \case
2019-05-08 21:47:20 +03:00
Nothing -> pure (Nothing :: Maybe (Wen, Async ()))
2019-05-06 00:46:40 +03:00
Just time -> do timer <- startTimerThread behn time
pure (Just (time, timer))
void (putMVar vSt newSt)