{-# 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.Async.V2 import Quasar.Monad import Quasar.Prelude import Quasar.Resources 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 registerDisposeAction_ do 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