run :: POSIXTime -> POSIXTime -> (POSIXTime -> IO ()) -> MVar Bool -> IO ()
run t0 s0 action m = go
  where
    go = do
      t1 <- getPOSIXTime
      let s_current = s0 + (t1 - t0)
          -- Increase s_next to the next full second.
          s_next = fromIntegral (ceiling (s_current + 0.001) :: Int)

      sleep (s_next - s_current)
      isRunning <- takeMVar m
      when (isRunning) $ do
        -- add 0.001 as a tiebreaker to make sure that `truncate` will work as
        -- expected
        action (s_next + 0.001)
        putMVar m isRunning
        go

    -- | Like `threadDelay`, but takes the delay in seconds.
    sleep :: POSIXTime -> IO ()
    sleep s = threadDelay $ round (s * 1000000)

-- | Stop timer; block until the timer is stopped.
run :: POSIXTime -> POSIXTime -> (POSIXTime -> IO ()) -> MVar Bool -> IO ()
run t0 s0 action m = go
  where
    go = do
      t1 <- getPOSIXTime
      let s_current = s0 + (t1 - t0)
          -- Increase s_next to the next full second.
          s_next = fromIntegral (ceiling (s_current + 0.001) :: Int)

      sleep (s_next - s_current)
      isRunning <- takeMVar m
      when (isRunning) $ do
        -- add 0.001 as a tiebreaker to make sure that `truncate` will work as
        -- expected
        action (s_next + 0.001)
        putMVar m isRunning
        go

    -- | Like `threadDelay`, but takes the delay in seconds.
    sleep :: POSIXTime -> IO ()
    sleep s = threadDelay $ round (s * 1000000)

-- | Stop timer; block until the timer is stopped.
run t0 s0 action m = go
  where
    go = do
      t1 <- getPOSIXTime
      let s_current = s0 + (t1 - t0)
          -- Increase s_next to the next full second.
          s_next = fromIntegral (ceiling (s_current + 0.001) :: Int)

      sleep (s_next - s_current)
      isRunning <- takeMVar m
      when (isRunning) $ do
        -- add 0.001 as a tiebreaker to make sure that `truncate` will work as
        -- expected
        action (s_next + 0.001)
        putMVar m isRunning
        go

    -- | Like `threadDelay`, but takes the delay in seconds.
    sleep :: POSIXTime -> IO ()
    sleep s = threadDelay $ round (s * 1000000)

-- | Stop timer; block until the timer is stopped.