summaryrefslogtreecommitdiff
path: root/InterruptibleDelay.hs
blob: b26834416917ab103d0ffcaa7f8ea5f4c2e3cd06 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
module InterruptibleDelay where

import Control.Concurrent
import Control.Monad
import Control.Exception ({-evaluate,-}handle,finally,throwIO)
import Data.Time.Clock (NominalDiffTime)
import System.IO.Error

type Microseconds = Int

microseconds :: NominalDiffTime -> Microseconds
microseconds d = round $ 1000000 * d

data InterruptibleDelay = InterruptibleDelay
        { delayThread :: MVar ThreadId
        }

interruptibleDelay :: IO InterruptibleDelay
interruptibleDelay = do
    fmap InterruptibleDelay newEmptyMVar

-- | Delay for the given number of microseconds and return 'True' if the delay
-- is not interrupted.
--
-- Note: If a thread is already waiting on the given 'InterruptibleDelay'
-- object, then this will block until it becomes available and only then start
-- the delay timer.
startDelay :: InterruptibleDelay -> Microseconds -> IO Bool
startDelay d interval = do
    thread <- myThreadId
    handle (\e -> do when (not $ isUserError e) (throwIO e)
                     return False) $ do
        putMVar (delayThread d) thread
        threadDelay interval
        void $ takeMVar (delayThread d)
        return True
     -- The following cleanup shouldn't be necessary, but I'm paranoid.
     `finally` tryTakeMVar (delayThread d)

  where debugNoise str = return ()

-- | Signal the thread waiting on the given 'InterruptibleDelay' object to
-- continue even though the timeout has not elapsed. If no thread is waiting,
-- then this is a no-op.
interruptDelay :: InterruptibleDelay -> IO ()
interruptDelay d = do
    mthread <- tryTakeMVar (delayThread d)
    forM_ mthread $ \thread -> do
    throwTo thread (userError "Interrupted delay")