{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Std.IO.LowResTimer
(
registerLowResTimer
, registerLowResTimer_
, registerLowResTimerOn
, LowResTimer
, queryLowResTimer
, cancelLowResTimer
, cancelLowResTimer_
, timeoutLowRes
, timeoutLowResEx
, throttle
, throttle_
, throttleTrailing_
, LowResTimerManager
, getLowResTimerManager
, isLowResTimerManagerRunning
, lowResTimerManagerCapabilitiesChanged
) where
import Std.Data.Array
#ifndef mingw32_HOST_OS
import GHC.Event
#endif
import Control.Concurrent
import Control.Concurrent.MVar
import Std.IO.Exception
import Control.Monad
import Data.IORef
import Std.Data.PrimIORef
import Data.Word
import GHC.Conc
import System.IO.Unsafe
queueSize :: Int
queueSize = 128
data TimerList = TimerItem {-# UNPACK #-} !Counter (IO ()) TimerList | TimerNil
data LowResTimerManager = LowResTimerManager
{ lrTimerQueue :: Array (IORef TimerList)
, lrIndexLock :: MVar Int
, lrRegisterCount :: Counter
, lrRunningLock :: MVar Bool
}
newLowResTimerManager :: IO LowResTimerManager
newLowResTimerManager = do
indexLock <- newMVar 0
regCounter <- newCounter 0
runningLock <- newMVar False
queue <- newArr queueSize
forM [0..queueSize-1] $ \ i -> do
writeArr queue i =<< newIORef TimerNil
iqueue <- unsafeFreezeArr queue
return (LowResTimerManager iqueue indexLock regCounter runningLock)
lowResTimerManager :: IORef (Array LowResTimerManager)
{-# NOINLINE lowResTimerManager #-}
lowResTimerManager = unsafePerformIO $ do
numCaps <- getNumCapabilities
lrtmArray <- newArr numCaps
forM [0..numCaps-1] $ \ i -> do
writeArr lrtmArray i =<< newLowResTimerManager
ilrtmArray <- unsafeFreezeArr lrtmArray
newIORef ilrtmArray
lowResTimerManagerCapabilitiesChanged :: IO ()
lowResTimerManagerCapabilitiesChanged = do
lrtmArray <- readIORef lowResTimerManager
let oldSize = sizeofArr lrtmArray
numCaps <- getNumCapabilities
when (numCaps /= oldSize) $ do
lrtmArray' <- newArr numCaps
if numCaps < oldSize
then do
forM [0..numCaps-1] $ \ i -> do
writeArr lrtmArray' i =<< indexArrM lrtmArray i
else do
forM [0..oldSize-1] $ \ i -> do
writeArr lrtmArray' i =<< indexArrM lrtmArray i
forM [oldSize..numCaps-1] $ \ i -> do
writeArr lrtmArray' i =<< newLowResTimerManager
ilrtmArray' <- unsafeFreezeArr lrtmArray'
atomicModifyIORef' lowResTimerManager $ \ _ -> (ilrtmArray', ())
getLowResTimerManager :: IO LowResTimerManager
getLowResTimerManager = do
(cap, _) <- threadCapability =<< myThreadId
lrtmArray <- readIORef lowResTimerManager
indexArrM lrtmArray (cap `rem` sizeofArr lrtmArray)
isLowResTimerManagerRunning :: LowResTimerManager -> IO Bool
isLowResTimerManagerRunning (LowResTimerManager _ _ _ runningLock) = readMVar runningLock
registerLowResTimer :: Int
-> IO ()
-> IO LowResTimer
registerLowResTimer t action = do
lrtm <- getLowResTimerManager
registerLowResTimerOn lrtm t action
registerLowResTimer_ :: Int
-> IO ()
-> IO ()
registerLowResTimer_ t action = void (registerLowResTimer t action)
registerLowResTimerOn :: LowResTimerManager
-> Int
-> IO ()
-> IO LowResTimer
registerLowResTimerOn lrtm@(LowResTimerManager queue indexLock regCounter _) t action = do
let (round, tick) = (max 0 t) `quotRem` queueSize
i <- readMVar indexLock
tlistRef <- indexArrM queue ((i + tick) `rem` queueSize)
roundCounter <- newCounter round
mask_ $ do
atomicModifyIORef' tlistRef $ \ tlist ->
let newList = TimerItem roundCounter action tlist
in (newList, ())
atomicAddCounter_ regCounter 1
ensureLowResTimerManager lrtm
return (LowResTimer roundCounter)
newtype LowResTimer = LowResTimer Counter
queryLowResTimer :: LowResTimer -> IO Int
queryLowResTimer (LowResTimer c) = readPrimIORef c
cancelLowResTimer :: LowResTimer -> IO Int
cancelLowResTimer (LowResTimer c) = atomicOrCounter c (-1)
cancelLowResTimer_ :: LowResTimer -> IO ()
cancelLowResTimer_ = void . cancelLowResTimer
timeoutLowRes :: Int
-> IO a
-> IO (Maybe a)
timeoutLowRes timeo io = do
mid <- myThreadId
catch
(do timer <- registerLowResTimer timeo (timeoutAThread mid)
r <- io
cancelLowResTimer timer
return (Just r))
( \ (e :: TimeOutException) -> return Nothing )
where
timeoutAThread id = void . forkIO $ throwTo id (TimeOutException id undefined)
timeoutLowResEx :: HasCallStack
=> Int
-> IO a
-> IO a
timeoutLowResEx timeo io = do
mid <- myThreadId
timer <- registerLowResTimer timeo (timeoutAThread mid)
r <- io
cancelLowResTimer timer
return r
where
timeoutAThread id = void . forkIO $ throwTo id (TimeOutException id callStack)
data TimeOutException = TimeOutException ThreadId CallStack deriving Show
instance Exception TimeOutException
ensureLowResTimerManager :: LowResTimerManager -> IO ()
ensureLowResTimerManager lrtm@(LowResTimerManager _ _ _ runningLock) = do
modifyMVar_ runningLock $ \ running -> do
unless running $ do
tid <- forkIO (startLowResTimerManager lrtm)
labelThread tid "stdio: low resolution time manager"
return True
startLowResTimerManager :: LowResTimerManager ->IO ()
startLowResTimerManager lrtm@(LowResTimerManager _ _ regCounter runningLock) = do
modifyMVar_ runningLock $ \ _ -> do
c <- readPrimIORef regCounter
if c > 0
then do
forkIO (fireLowResTimerQueue lrtm)
case () of
_
#ifndef mingw32_HOST_OS
| rtsSupportsBoundThreads -> do
htm <- getSystemTimerManager
void $ registerTimeout htm 100000 (startLowResTimerManager lrtm)
#endif
| otherwise -> void . forkIO $ do
threadDelay 100000
startLowResTimerManager lrtm
return True
else do
return False
fireLowResTimerQueue :: LowResTimerManager -> IO ()
fireLowResTimerQueue lrtm@(LowResTimerManager queue indexLock regCounter runningLock) = do
(tList, tListRef) <- modifyMVar indexLock $ \ index -> do
tListRef <- indexArrM queue index
tList <- atomicModifyIORef' tListRef $ \ tList -> (TimerNil, tList)
let !index' = (index+1) `rem` queueSize
return (index', (tList, tListRef))
go tList tListRef regCounter
where
go (TimerItem roundCounter action nextList) tListRef regCounter = do
r <- atomicSubCounter roundCounter 1
case r `compare` 0 of
LT -> do
atomicSubCounter_ regCounter 1
go nextList tListRef regCounter
EQ -> do
atomicSubCounter_ regCounter 1
catch action ( \ (_ :: SomeException) -> return () )
go nextList tListRef regCounter
GT -> do
atomicModifyIORef' tListRef $ \ tlist -> (TimerItem roundCounter action tlist, ())
go nextList tListRef regCounter
go TimerNil _ _ = return ()
throttle :: Int
-> IO a
-> IO (IO a)
throttle t action = do
resultCounter <- newCounter 0
resultRef <- newIORef =<< action
return $ do
c <- atomicOrCounter resultCounter (-1)
if c == 0
then do
registerLowResTimer_ t (void $ atomicAndCounter resultCounter 0)
!r <- action
atomicWriteIORef resultRef r
return r
else readIORef resultRef
throttle_ :: Int
-> IO ()
-> IO (IO ())
throttle_ t action = do
resultCounter <- newCounter 0
return $ do
c <- atomicOrCounter resultCounter (-1)
when (c == 0) $ do
registerLowResTimer_ t (void $ atomicAndCounter resultCounter 0)
void action
throttleTrailing_ :: Int
-> IO ()
-> IO (IO ())
throttleTrailing_ t action = do
resultCounter <- newCounter 0
return $ do
c <- atomicOrCounter resultCounter (-1)
when (c == 0) . registerLowResTimer_ t . void . forkIO $ do
atomicAndCounter_ resultCounter 0
action