Skip to content

Commit cb7ec6f

Browse files
committed
Timeout manager for not threaded runtime. Work in progress
1 parent 1e22eac commit cb7ec6f

File tree

7 files changed

+622
-25
lines changed

7 files changed

+622
-25
lines changed

Diff for: src/Control/Timeout.hs

+6-25
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323

2424
{-# LANGUAGE Trustworthy #-}
2525
{-# LANGUAGE RecursiveDo #-}
26-
{-# LANGUAGE DeriveDataTypeable #-}
2726

2827
module Control.Timeout
2928
( NominalDiffTime
@@ -33,30 +32,18 @@ module Control.Timeout
3332
, sleep
3433
) where
3534

36-
import Control.Exception (Exception)
37-
import Control.Concurrent (myThreadId, forkIO, killThread, threadDelay, throwTo,
35+
import Control.Concurrent (myThreadId, forkIO, killThread, throwTo,
3836
rtsSupportsBoundThreads)
39-
import Data.Typeable (Typeable)
4037
import Data.Time.Clock (NominalDiffTime)
4138
import Data.Unique (newUnique)
42-
import GHC.Event (TimeoutKey, getSystemTimerManager, registerTimeout, unregisterTimeout)
39+
import GHC.Event (getSystemTimerManager, registerTimeout, unregisterTimeout)
4340
import Unsafe.Coerce (unsafeCoerce)
4441

4542
import Control.Monad.Catch (MonadCatch(..), bracket, handleJust)
4643
import Control.Monad.Trans (MonadIO, liftIO)
4744

48-
-- | Exception used for timeout handling
49-
newtype Timeout = Timeout TimeoutKey
50-
deriving (Eq, Typeable)
51-
52-
instance Show Timeout where
53-
show _ = "<<timeout>>"
54-
55-
instance Exception Timeout
56-
57-
timeToUsecs :: NominalDiffTime -> Int
58-
timeToUsecs t = floor $ (* 1000000) $ toRational t
59-
{-# INLINEABLE timeToUsecs #-}
45+
import Control.Timeout.EventManager.Types
46+
import Control.Timeout.Utils
6047

6148
-- | Wrap an 'MonadIO' computation to time out and return @Nothing@ in case no result
6249
-- is available within @n@ seconds. In case a result
@@ -94,11 +81,11 @@ withTimeout t f | t <= 0 = return Nothing
9481
timer <- liftIO getSystemTimerManager
9582
ex@(Timeout key) <- liftIO $ mdo
9683
pid <- liftIO myThreadId
97-
ex <- return . Timeout =<< (liftIO $ registerTimeout timer (timeToUsecs t) (throwTo pid ex))
84+
ex <- fmap (Timeout . unsafeCoerce) (liftIO $ registerTimeout timer (timeToUsecs t) (throwTo pid ex))
9885
return ex
9986
handleJust (\e -> if e == ex then Just () else Nothing)
10087
(\_ -> return Nothing)
101-
(f ex >>= \r -> (liftIO $ unregisterTimeout timer key) >> (return $ Just r))
88+
(f ex >>= \r -> (liftIO $ unregisterTimeout timer (unsafeCoerce key)) >> (return $ Just r))
10289
| otherwise = do
10390
pid <- liftIO myThreadId
10491
ex <- liftIO newUnique >>= return . Timeout . unsafeCoerce
@@ -107,9 +94,3 @@ withTimeout t f | t <= 0 = return Nothing
10794
(bracket (liftIO $ forkIO (sleep t >> throwTo pid ex))
10895
(liftIO . killThread)
10996
(\_ -> f ex >>= return . Just))
110-
111-
-- | Sleep for 'NominalDiffTime', example:
112-
--
113-
-- > sleep 5 -- Will sleep for 5 seconds
114-
sleep :: (MonadIO m) => NominalDiffTime -> m ()
115-
sleep = liftIO . threadDelay . timeToUsecs

Diff for: src/Control/Timeout/EventManager.hs

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
module Control.Timeout.EventManager
2+
( Timeout(..)
3+
, TimeoutKey
4+
, registerTimeout
5+
, unregisterTimeout
6+
) where
7+
8+
import qualified GHC.Event as Event
9+
import Control.Concurrent (rtsSupportsBoundThreads)
10+
11+
import Control.Timeout.EventManager.Types (Timeout)
12+
13+
getTimerManager :: IO EventManager
14+
getTimerManager
15+
| rtsSupportsBoundThreads = Event.getSystemTimerManager
16+
| otherwise = undefined
17+
18+
registerTimeout :: NominalDiffTime -> IO () -> IO Timeout
19+
registerTimeout t f = do
20+
timer <- Event.getSystemTimerManager
21+
fmap Timeout $ registerTimeout timer (timeToUsecs t) f
22+
23+
unregisterTimeout :: Timeout -> IO ()
24+
unregisterTimeout (Timeout key) = do
25+
timer <- Event.getSystemTimerManager
26+
Event.unregisterTimeout timer key

Diff for: src/Control/Timeout/EventManager/Local.hs

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
{-# LANGUAGE NamedFieldPuns #-}
2+
{-# LANGUAGE DeriveDataTypeable #-}
3+
4+
module Control.Timeout.EventManager.Local () where
5+
6+
import Control.Exception (Exception, handle)
7+
import Control.Concurrent (ThreadId, forkIO, throwTo, rtsSupportsBoundThreads)
8+
import Control.Monad (void)
9+
import Data.Time.Clock (NominalDiffTime, addUTCTime, diffUTCTime, getCurrentTime)
10+
import Data.Typeable (Typeable)
11+
import System.IO.Unsafe (unsafePerformIO)
12+
13+
import Control.Monad.Trans (liftIO)
14+
import Control.Monad.Trans.State.Strict (evalStateT, get, put)
15+
16+
import Control.Timeout.EventManager.Types (Timeout)
17+
import Control.Timeout.EventManager.PSQ (PSQ, Elem(..))
18+
import Control.Timeout.Utils (sleep)
19+
import qualified Control.Timeout.EventManager.PSQ as PSQ
20+
21+
data Event = Register Timeout NominalDiffTime (IO ())
22+
| Unregister Timeout
23+
deriving (Typeable)
24+
25+
instance Show Event where
26+
show _ = "<<event>>"
27+
28+
instance Exception Event
29+
30+
type TimeoutQueue = PSQ (IO ())
31+
type EventManager = TimeoutQueue
32+
33+
tick :: EventManager -> IO EventManager
34+
tick queue = handle eventHandler $ do
35+
let mbNext = PSQ.findMin queue
36+
case mbNext of
37+
Just (E { prio, value }) -> do
38+
now <- getCurrentTime
39+
let diff = diffUTCTime prio now
40+
sleep diff >> value
41+
return $ PSQ.deleteMin queue
42+
Nothing -> sleep 1 >> return queue
43+
where
44+
eventHandler (Register timeout time f) = do
45+
now <- getCurrentTime
46+
let time' = addUTCTime time now
47+
return $ PSQ.insert timeout time' f queue
48+
eventHandler (Unregister timeout) = return $ PSQ.delete timeout queue
49+
50+
loop :: IO ()
51+
loop = void $ evalStateT go PSQ.empty
52+
where
53+
go = do
54+
state <- get
55+
new <- liftIO $ tick state
56+
put new >> go
57+
58+
managerThread :: ThreadId
59+
managerThread
60+
| rtsSupportsBoundThreads = error "manager thread not running"
61+
| otherwise = unsafePerformIO $ forkIO loop
62+
{-# NOINLINE managerThread #-}
63+
64+
registerTimeout :: NominalDiffTime -> IO () -> IO Timeout
65+
registerTimeout = undefined
66+
67+
unregisterTimeout :: Timeout -> IO ()
68+
unregisterTimeout t = throwTo managerThread $ Unregister t

0 commit comments

Comments
 (0)