diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Xmobar/App/Timer.hs | 115 |
1 files changed, 68 insertions, 47 deletions
diff --git a/src/Xmobar/App/Timer.hs b/src/Xmobar/App/Timer.hs index 8464fb7..cf59630 100644 --- a/src/Xmobar/App/Timer.hs +++ b/src/Xmobar/App/Timer.hs @@ -23,30 +23,27 @@ import Control.Concurrent.Async (withAsync) import Control.Concurrent.STM import Control.Exception import Control.Monad (forever, forM, guard) -import Data.Foldable (foldrM) -import Data.IORef +import Data.Foldable (foldrM, for_) import Data.Int (Int64) import Data.Map (Map) import qualified Data.Map as M -import Data.Maybe (isJust) +import Data.Maybe (isJust, fromJust) import Data.Time.Clock.POSIX (getPOSIXTime) import Data.Unique import System.IO.Unsafe (unsafePerformIO) -newtype Timer = Timer (TVar Periods) - type Periods = Map Unique Period -data Tick = Tick (TMVar ()) | TimeOut +data Tick = Tick (TMVar ()) | UnCoalesce data Period = Period { rate :: Int64, next :: Int64, tick :: TMVar Tick } -data TimeOutException = TimeOutException deriving Show -instance Exception TimeOutException +data UnCoalesceException = UnCoalesceException deriving Show +instance Exception UnCoalesceException -{-# NOINLINE timer #-} -timer :: IORef (Maybe Timer) -timer = unsafePerformIO (newIORef Nothing) +{-# NOINLINE periodsVar #-} +periodsVar :: TVar (Maybe Periods) +periodsVar = unsafePerformIO $ newTVarIO Nothing now :: IO Int64 now = do @@ -68,24 +65,27 @@ newPeriod r = do -- second or when the next timer is due), coalescing is disabled for it and it -- falls back to periodic sleep. doEveryTenthSeconds :: Int -> IO () -> IO () -doEveryTenthSeconds r action = do - Just t <- readIORef timer - doEveryTenthSecondsCoalesced t r action `catch` \TimeOutException -> +doEveryTenthSeconds r action = + doEveryTenthSecondsCoalesced r action `catch` \UnCoalesceException -> doEveryTenthSecondsSleeping r action -- | Perform a given action every N tenths of a second, -- coalesce with other timers using a given Timer instance. -doEveryTenthSecondsCoalesced :: Timer -> Int -> IO () -> IO () -doEveryTenthSecondsCoalesced (Timer periodsVar) r action = do +doEveryTenthSecondsCoalesced :: Int -> IO () -> IO () +doEveryTenthSecondsCoalesced r action = do (u, p) <- newPeriod (fromIntegral r) bracket_ (push u p) (pop u) $ forever $ bracket (wait p) done $ const action where - push u p = atomically $ modifyTVar periodsVar (M.insert u p) - pop u = atomically $ modifyTVar periodsVar (M.delete u) + push u p = atomically $ modifyTVar' periodsVar $ \case + Just periods -> Just $ M.insert u p periods + Nothing -> throw UnCoalesceException + pop u = atomically $ modifyTVar' periodsVar $ \case + Just periods -> Just $ M.delete u periods + Nothing -> Nothing wait p = atomically (takeTMVar $ tick p) >>= \case Tick doneVar -> return doneVar - TimeOut -> throwIO TimeOutException + UnCoalesce -> throwIO UnCoalesceException done doneVar = atomically $ putTMVar doneVar () -- | Perform a given action every N tenths of a second, @@ -105,25 +105,39 @@ tenthSeconds s | s >= x = do threadDelay (x * 100000) | otherwise = threadDelay (s * 100000) where x = (maxBound :: Int) `div` 100000 --- | Start the timer coordination thread. +-- | Start the timer coordination thread and perform a given IO action (this +-- is meant to surround the entire xmobar execution), terminating the timer +-- thread afterwards. +-- +-- Additionally, if the timer thread fails, individual +-- 'doEveryTenthSecondsCoalesced' invocations that are waiting to be +-- coordinated by it are notified to fall back to periodic sleeping. +-- +-- The timer thread _will_ fail immediately when running in a non-threaded +-- RTS. withTimer :: (IO () -> IO ()) -> IO a -> IO a -withTimer pauseRefresh action = do - periodsVar <- atomically $ newTVar M.empty - withAsync (timerLoop pauseRefresh periodsVar) $ \_ -> - bracket_ - (writeIORef timer (Just (Timer periodsVar))) - (writeIORef timer Nothing) -- TODO: kill all periods? - action - -timerLoop :: (IO () -> IO ()) -> TVar Periods -> IO () -timerLoop pauseRefresh periodsVar = forever $ do +withTimer pauseRefresh action = + withAsync (timerThread `finally` cleanup) $ const action + where + timerThread = do + atomically $ writeTVar periodsVar $ Just M.empty + timerLoop pauseRefresh + + cleanup = atomically $ readTVar periodsVar >>= \case + Just periods -> do + for_ periods unCoalesceTimer' + writeTVar periodsVar Nothing + Nothing -> return () + +timerLoop :: (IO () -> IO ()) -> IO () +timerLoop pauseRefresh = forever $ do tNow <- now (toFire, tMaybeNext) <- atomically $ do - periods <- readTVar periodsVar + periods <- fromJust <$> readTVar periodsVar let toFire = timersToFire tNow periods let periods' = advanceTimers tNow periods let tMaybeNext = nextFireTime periods' - writeTVar periodsVar periods' + writeTVar periodsVar $ Just periods' return (toFire, tMaybeNext) pauseRefresh $ do -- To avoid multiple refreshes, pause refreshing for up to 1 second, @@ -135,8 +149,8 @@ timerLoop pauseRefresh periodsVar = forever $ do Nothing -> 1000000 fired <- fireTimers toFire timeouted <- waitForTimers timeoutVar fired - timeoutTimers timeouted periodsVar - delayUntilNextFire periodsVar + unCoalesceTimers timeouted + delayUntilNextFire advanceTimers :: Int64 -> Periods -> Periods advanceTimers t = M.map advance @@ -168,20 +182,26 @@ waitForTimers timeoutVar fired = atomically $ do return [u | (u, False) <- dones] -- | Handle slow timers (drop and signal them to stop coalescing). -timeoutTimers :: [Unique] -> TVar Periods -> IO () -timeoutTimers timers periodsVar = atomically $ do - periods <- readTVar periodsVar - periods' <- foldrM timeoutTimer periods timers - writeTVar periodsVar periods' - -timeoutTimer :: Unique -> Periods -> STM Periods -timeoutTimer u periods = do - putTMVar (tick (periods M.! u)) TimeOut +unCoalesceTimers :: [Unique] -> IO () +unCoalesceTimers timers = atomically $ do + periods <- fromJust <$> readTVar periodsVar + periods' <- foldrM unCoalesceTimer periods timers + writeTVar periodsVar $ Just periods' + +unCoalesceTimer :: Unique -> Periods -> STM Periods +unCoalesceTimer u periods = do + unCoalesceTimer' (periods M.! u) return $ u `M.delete` periods -delayUntilNextFire :: TVar Periods -> IO () -delayUntilNextFire periodsVar = do - tMaybeNext <- fmap nextFireTime $ readTVarIO periodsVar +unCoalesceTimer' :: Period -> STM () +unCoalesceTimer' p = do + _ <- tryTakeTMVar (tick p) + putTMVar (tick p) UnCoalesce + +delayUntilNextFire :: IO () +delayUntilNextFire = do + Just periods <- readTVarIO periodsVar + let tMaybeNext = nextFireTime periods tNow <- now delayVar <- case tMaybeNext of Just tNext -> do @@ -195,6 +215,7 @@ delayUntilNextFire periodsVar = do Nothing -> atomically $ newTVar False atomically $ do delayOver <- readTVar delayVar - tMaybeNext' <- fmap nextFireTime $ readTVar periodsVar + periods' <- fromJust <$> readTVar periodsVar + let tMaybeNext' = nextFireTime periods' -- Return also if a new period is added (it may fire sooner). guard $ delayOver || tMaybeNext /= tMaybeNext' |