Newer
Older
{-# LANGUAGE CApiFFI #-}
module Quasar.Timer.TimerFd (
TimerFd,
newTimerFd,
setTimer,
setInterval,
disarm,
) where
import Control.Concurrent
import Control.Monad.Catch
import Foreign
import Foreign.C
import Quasar.Awaitable
import Quasar.Timer.PosixTimer
import System.Posix.Types
foreign import capi "sys/timerfd.h timerfd_create"
c_timerfd_create :: CClockId -> CInt -> IO TimerFd
foreign import capi "sys/timerfd.h timerfd_settime"
c_timerfd_settime :: TimerFd -> CInt -> Ptr CITimerSpec -> Ptr CITimerSpec -> IO CInt
foreign import capi "unistd.h read"
c_timerfd_read :: TimerFd -> Ptr Word64 -> CSize -> IO CInt
foreign import capi "unistd.h close"
c_timerfd_close :: TimerFd -> IO CInt
foreign import capi "sys/timerfd.h value TFD_CLOEXEC"
c_TFD_CLOEXEC :: CInt
newtype TimerFd = TimerFd Fd
deriving stock (Eq, Show)
deriving newtype Num
newTimerFd :: (MonadQuasar m, MonadIO m, MonadMask m) => ClockId -> IO () -> m TimerFd
newTimerFd clockId callback = mask_ do
timer <- liftIO $ runInBoundThread do
throwErrnoIfMinus1 "timerfd_create" do
c_timerfd_create (toCClockId clockId) c_TFD_CLOEXEC
workerTask <- async $ liftIO $ worker timer
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
await $ isDisposed workerTask
timerFdClose timer
pure timer
where
worker :: TimerFd -> IO ()
worker timer@(TimerFd fd) = forever do
-- Block until timer is expired using the GHC runtime.
-- The thread will be unblocked with an `AsyncCancelled`-Exception, so
-- (contrary to the documentation) `closeFdWith` should not be necessary
-- when closing the fd.
threadWaitRead fd
elapsed <- timerFdRead timer
traceShowIO elapsed
callback
setTimer :: TimerFd -> TimerSetTimeMode -> CTimeSpec -> IO ()
setTimer timer mode timeSpec = setInterval timer mode itspec
where
itspec = defaultCITimerSpec {
it_value = timeSpec
}
setInterval :: TimerFd -> TimerSetTimeMode -> CITimerSpec -> IO ()
setInterval timer mode iTimerSpec = do
alloca \newValue -> do
poke newValue iTimerSpec
throwErrnoIfMinus1_ "timer_settime" do
c_timerfd_settime timer 0 newValue nullPtr
disarm :: TimerFd -> IO ()
disarm timer = setInterval timer TimeRelative defaultCITimerSpec
timerFdRead :: TimerFd -> IO Word64
timerFdRead timer = do
alloca \ptr -> do
throwErrnoIfMinus1 "timerfd_read" do
c_timerfd_read timer ptr 8
peek ptr
timerFdClose :: TimerFd -> IO ()
timerFdClose timer = throwErrnoIfMinus1_ "timerfd_close" $ c_timerfd_close timer