Skip to content

Commit

Permalink
lock-less debouncer
Browse files Browse the repository at this point in the history
  • Loading branch information
pepeiborra committed Dec 9, 2021
1 parent 369f471 commit baf6714
Showing 1 changed file with 20 additions and 19 deletions.
39 changes: 20 additions & 19 deletions ghcide/src/Development/IDE/Core/Debouncer.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ module Development.IDE.Core.Debouncer
) where

import Control.Concurrent.Async
import Control.Concurrent.Strict
import Control.Concurrent.STM
import Control.Concurrent.STM.Stats (atomicallyNamed)
import Control.Exception
import Control.Monad (join)
import Data.Foldable (traverse_)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as Map
import Control.Monad (join)
import Data.Hashable
import GHC.Conc (unsafeIOToSTM)
import qualified StmContainers.Map as STM
import System.Time.Extra

-- | A debouncer can be used to avoid triggering many events
Expand All @@ -31,28 +31,29 @@ newtype Debouncer k = Debouncer { registerEvent :: Seconds -> k -> IO () -> IO (

-- | Debouncer used in the IDE that delays events as expected.
newAsyncDebouncer :: (Eq k, Hashable k) => IO (Debouncer k)
newAsyncDebouncer = Debouncer . asyncRegisterEvent <$> newVar Map.empty
newAsyncDebouncer = Debouncer . asyncRegisterEvent <$> STM.newIO

-- | Register an event that will fire after the given delay if no other event
-- for the same key gets registered until then.
--
-- If there is a pending event for the same key, the pending event will be killed.
-- Events are run unmasked so it is up to the user of `registerEvent`
-- to mask if required.
asyncRegisterEvent :: (Eq k, Hashable k) => Var (HashMap k (Async ())) -> Seconds -> k -> IO () -> IO ()
asyncRegisterEvent d 0 k fire = do
join $ modifyVar d $ \m -> do
(cancel, !m') <- evaluate $ Map.alterF (\prev -> (traverse_ cancel prev, Nothing)) k m
return (m', cancel)
fire
asyncRegisterEvent :: (Eq k, Hashable k) => STM.Map k (TVar (Seconds, IO())) -> Seconds -> k -> IO () -> IO ()
asyncRegisterEvent d delay k fire = mask_ $ do
a <- asyncWithUnmask $ \unmask -> unmask $ do
sleep delay
fire
modifyVar_ d (evaluate . Map.delete k)
join $ modifyVar d $ \m -> do
(cancel, !m') <- evaluate $ Map.alterF (\prev -> (traverse_ cancel prev, Just a)) k m
return (m', cancel)
prev <- atomically $ STM.lookup k d
case prev of
Just v -> do
atomicallyNamed "debouncer - reset" $ writeTVar v (delay, fire)
Nothing -> do
var <- newTVarIO (delay, fire)
_ <- asyncWithUnmask $ \unmask -> unmask $ do
join $ atomicallyNamed "debouncer - sleep" $ do
(s,act) <- readTVar var
unsafeIOToSTM $ sleep s
STM.delete k d
return act
atomicallyNamed "debouncer2" $ STM.insert var k d

-- | Debouncer used in the DAML CLI compiler that emits events immediately.
noopDebouncer :: Debouncer k
Expand Down

0 comments on commit baf6714

Please sign in to comment.