{-# LANGUAGE RecursiveDo #-}
{-# LANGUAGE StrictData #-}
{-# OPTIONS_GHC -funbox-strict-fields #-}
module Data.TimerWheel
(
TimerWheel,
with,
Config (..),
register,
register_,
recurring,
recurring_,
)
where
import Control.Concurrent
import Control.Exception
import Control.Monad (join, void)
import Data.Fixed (E6, Fixed (MkFixed))
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import GHC.Generics (Generic)
import Micros (Micros (Micros))
import qualified Micros
import Supply (Supply)
import qualified Supply
import Wheel (Wheel)
import qualified Wheel
data TimerWheel = TimerWheel
{
TimerWheel -> Supply
wheelSupply :: Supply,
TimerWheel -> Wheel
wheelWheel :: Wheel,
TimerWheel -> ThreadId
wheelThread :: ThreadId
}
data Config = Config
{
Config -> Int
spokes :: Int,
Config -> Fixed E6
resolution :: Fixed E6
}
deriving stock ((forall x. Config -> Rep Config x)
-> (forall x. Rep Config x -> Config) -> Generic Config
forall x. Rep Config x -> Config
forall x. Config -> Rep Config x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep Config x -> Config
$cfrom :: forall x. Config -> Rep Config x
Generic, Int -> Config -> ShowS
[Config] -> ShowS
Config -> String
(Int -> Config -> ShowS)
-> (Config -> String) -> ([Config] -> ShowS) -> Show Config
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Config] -> ShowS
$cshowList :: [Config] -> ShowS
show :: Config -> String
$cshow :: Config -> String
showsPrec :: Int -> Config -> ShowS
$cshowsPrec :: Int -> Config -> ShowS
Show)
newtype TimerWheelDied
= TimerWheelDied SomeException
deriving stock (Int -> TimerWheelDied -> ShowS
[TimerWheelDied] -> ShowS
TimerWheelDied -> String
(Int -> TimerWheelDied -> ShowS)
-> (TimerWheelDied -> String)
-> ([TimerWheelDied] -> ShowS)
-> Show TimerWheelDied
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [TimerWheelDied] -> ShowS
$cshowList :: [TimerWheelDied] -> ShowS
show :: TimerWheelDied -> String
$cshow :: TimerWheelDied -> String
showsPrec :: Int -> TimerWheelDied -> ShowS
$cshowsPrec :: Int -> TimerWheelDied -> ShowS
Show)
instance Exception TimerWheelDied where
toException :: TimerWheelDied -> SomeException
toException = TimerWheelDied -> SomeException
forall e. Exception e => e -> SomeException
asyncExceptionToException
fromException :: SomeException -> Maybe TimerWheelDied
fromException = SomeException -> Maybe TimerWheelDied
forall e. Exception e => SomeException -> Maybe e
asyncExceptionFromException
with :: Config -> (TimerWheel -> IO a) -> IO a
with :: Config -> (TimerWheel -> IO a) -> IO a
with Config
config TimerWheel -> IO a
action =
case Config -> ()
validateConfig Config
config of
() -> Config -> (TimerWheel -> IO a) -> IO a
forall a. Config -> (TimerWheel -> IO a) -> IO a
_with Config
config TimerWheel -> IO a
action
_with :: Config -> (TimerWheel -> IO a) -> IO a
_with :: Config -> (TimerWheel -> IO a) -> IO a
_with Config {Int
spokes :: Int
spokes :: Config -> Int
spokes, Fixed E6
resolution :: Fixed E6
resolution :: Config -> Fixed E6
resolution} TimerWheel -> IO a
action = do
Wheel
wheelWheel <- Int -> Micros -> IO Wheel
Wheel.create Int
spokes (Fixed E6 -> Micros
Micros.fromFixed Fixed E6
resolution)
Supply
wheelSupply <- IO Supply
Supply.new
ThreadId
thread <- IO ThreadId
myThreadId
((forall a. IO a -> IO a) -> IO a) -> IO a
forall b. ((forall a. IO a -> IO a) -> IO b) -> IO b
uninterruptibleMask \forall a. IO a -> IO a
restore -> do
ThreadId
wheelThread <-
((forall a. IO a -> IO a) -> IO ()) -> IO ThreadId
forkIOWithUnmask (((forall a. IO a -> IO a) -> IO ()) -> IO ThreadId)
-> ((forall a. IO a -> IO a) -> IO ()) -> IO ThreadId
forall a b. (a -> b) -> a -> b
$ \forall a. IO a -> IO a
unmask ->
IO () -> IO ()
forall a. IO a -> IO a
unmask (Wheel -> IO ()
Wheel.reap Wheel
wheelWheel)
IO () -> (SomeException -> IO ()) -> IO ()
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
`catch` \SomeException
e ->
case SomeException -> Maybe AsyncException
forall e. Exception e => SomeException -> Maybe e
fromException SomeException
e of
Just AsyncException
ThreadKilled -> () -> IO ()
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
Maybe AsyncException
_ -> ThreadId -> TimerWheelDied -> IO ()
forall e. Exception e => ThreadId -> e -> IO ()
throwTo ThreadId
thread (SomeException -> TimerWheelDied
TimerWheelDied SomeException
e)
let cleanup :: IO ()
cleanup = ThreadId -> IO ()
killThread ThreadId
wheelThread
let handler :: SomeException -> IO void
handler :: SomeException -> IO void
handler SomeException
ex = do
IO ()
cleanup
case SomeException -> Maybe TimerWheelDied
forall e. Exception e => SomeException -> Maybe e
fromException SomeException
ex of
Just (TimerWheelDied SomeException
ex') -> SomeException -> IO void
forall e a. Exception e => e -> IO a
throwIO SomeException
ex'
Maybe TimerWheelDied
_ -> SomeException -> IO void
forall e a. Exception e => e -> IO a
throwIO SomeException
ex
a
result <- IO a -> IO a
forall a. IO a -> IO a
restore (TimerWheel -> IO a
action TimerWheel :: Supply -> Wheel -> ThreadId -> TimerWheel
TimerWheel {Supply
wheelSupply :: Supply
wheelSupply :: Supply
wheelSupply, Wheel
wheelWheel :: Wheel
wheelWheel :: Wheel
wheelWheel, ThreadId
wheelThread :: ThreadId
wheelThread :: ThreadId
wheelThread}) IO a -> (SomeException -> IO a) -> IO a
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
`catch` SomeException -> IO a
forall void. SomeException -> IO void
handler
IO ()
cleanup
a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure a
result
validateConfig :: Config -> ()
validateConfig :: Config -> ()
validateConfig config :: Config
config@Config {Int
spokes :: Int
spokes :: Config -> Int
spokes, Fixed E6
resolution :: Fixed E6
resolution :: Config -> Fixed E6
resolution}
| Int
spokes Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
0 Bool -> Bool -> Bool
|| Fixed E6
resolution Fixed E6 -> Fixed E6 -> Bool
forall a. Ord a => a -> a -> Bool
<= Fixed E6
0 = String -> ()
forall a. HasCallStack => String -> a
error (String
"[timer-wheel] invalid config: " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Config -> String
forall a. Show a => a -> String
show Config
config)
| Bool
otherwise = ()
register ::
TimerWheel ->
Fixed E6 ->
IO () ->
IO (IO Bool)
register :: TimerWheel -> Fixed E6 -> IO () -> IO (IO Bool)
register TimerWheel
wheel (Fixed E6 -> Micros
secondsToMicros -> Micros
delay) =
TimerWheel -> Micros -> IO () -> IO (IO Bool)
_register TimerWheel
wheel Micros
delay
register_ ::
TimerWheel ->
Fixed E6 ->
IO () ->
IO ()
register_ :: TimerWheel -> Fixed E6 -> IO () -> IO ()
register_ TimerWheel
wheel Fixed E6
delay IO ()
action =
IO (IO Bool) -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (TimerWheel -> Fixed E6 -> IO () -> IO (IO Bool)
register TimerWheel
wheel Fixed E6
delay IO ()
action)
_register :: TimerWheel -> Micros -> IO () -> IO (IO Bool)
_register :: TimerWheel -> Micros -> IO () -> IO (IO Bool)
_register TimerWheel
wheel Micros
delay IO ()
action = do
Int
key <- Supply -> IO Int
Supply.next (TimerWheel -> Supply
wheelSupply TimerWheel
wheel)
Wheel -> Int -> Micros -> IO () -> IO (IO Bool)
Wheel.insert (TimerWheel -> Wheel
wheelWheel TimerWheel
wheel) Int
key Micros
delay IO ()
action
_reregister :: TimerWheel -> Micros -> IO () -> IO (IO Bool)
_reregister :: TimerWheel -> Micros -> IO () -> IO (IO Bool)
_reregister TimerWheel
wheel Micros
delay =
TimerWheel -> Micros -> IO () -> IO (IO Bool)
_register TimerWheel
wheel (if Micros
reso Micros -> Micros -> Bool
forall a. Ord a => a -> a -> Bool
> Micros
delay then Word64 -> Micros
Micros Word64
0 else Micros
delay Micros -> Micros -> Micros
`Micros.minus` Micros
reso)
where
reso :: Micros
reso :: Micros
reso = Wheel -> Micros
Wheel.resolution (TimerWheel -> Wheel
wheelWheel TimerWheel
wheel)
recurring ::
TimerWheel ->
Fixed E6 ->
IO () ->
IO (IO ())
recurring :: TimerWheel -> Fixed E6 -> IO () -> IO (IO ())
recurring TimerWheel
wheel (Fixed E6 -> Micros
secondsToMicros -> Micros
delay) IO ()
action = mdo
let doAction :: IO ()
doAction :: IO ()
doAction = do
IORef (IO Bool) -> IO Bool -> IO ()
forall a. IORef a -> a -> IO ()
writeIORef IORef (IO Bool)
cancelRef (IO Bool -> IO ()) -> IO (IO Bool) -> IO ()
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< TimerWheel -> Micros -> IO () -> IO (IO Bool)
_reregister TimerWheel
wheel Micros
delay IO ()
doAction
IO ()
action
IO Bool
cancel :: IO Bool <-
TimerWheel -> Micros -> IO () -> IO (IO Bool)
_register TimerWheel
wheel Micros
delay IO ()
doAction
IORef (IO Bool)
cancelRef :: IORef (IO Bool) <-
IO Bool -> IO (IORef (IO Bool))
forall a. a -> IO (IORef a)
newIORef IO Bool
cancel
IO () -> IO (IO ())
forall (f :: * -> *) a. Applicative f => a -> f a
pure (IO Bool -> IO ()
untilTrue (IO (IO Bool) -> IO Bool
forall (m :: * -> *) a. Monad m => m (m a) -> m a
join (IORef (IO Bool) -> IO (IO Bool)
forall a. IORef a -> IO a
readIORef IORef (IO Bool)
cancelRef)))
recurring_ ::
TimerWheel ->
Fixed E6 ->
IO () ->
IO ()
recurring_ :: TimerWheel -> Fixed E6 -> IO () -> IO ()
recurring_ TimerWheel
wheel (Fixed E6 -> Micros
secondsToMicros -> Micros
delay) IO ()
action =
IO (IO Bool) -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (TimerWheel -> Micros -> IO () -> IO (IO Bool)
_register TimerWheel
wheel Micros
delay IO ()
doAction)
where
doAction :: IO ()
doAction :: IO ()
doAction = do
IO Bool
_ <- TimerWheel -> Micros -> IO () -> IO (IO Bool)
_reregister TimerWheel
wheel Micros
delay IO ()
doAction
IO ()
action
secondsToMicros :: Fixed E6 -> Micros
secondsToMicros :: Fixed E6 -> Micros
secondsToMicros (MkFixed Integer
micros) =
Word64 -> Micros
Micros (Integer -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Integer -> Integer -> Integer
forall a. Ord a => a -> a -> a
max Integer
0 Integer
micros))
untilTrue :: IO Bool -> IO ()
untilTrue :: IO Bool -> IO ()
untilTrue IO Bool
action =
IO Bool
action IO Bool -> (Bool -> IO ()) -> IO ()
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Bool
True -> () -> IO ()
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
Bool
False -> IO Bool -> IO ()
untilTrue IO Bool
action