Skip to content

Commit baf6714

Browse files
committed
lock-less debouncer
1 parent 369f471 commit baf6714

File tree

1 file changed

+20
-19
lines changed

1 file changed

+20
-19
lines changed

ghcide/src/Development/IDE/Core/Debouncer.hs

+20-19
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ module Development.IDE.Core.Debouncer
99
) where
1010

1111
import Control.Concurrent.Async
12-
import Control.Concurrent.Strict
12+
import Control.Concurrent.STM
13+
import Control.Concurrent.STM.Stats (atomicallyNamed)
1314
import Control.Exception
14-
import Control.Monad (join)
15-
import Data.Foldable (traverse_)
16-
import Data.HashMap.Strict (HashMap)
17-
import qualified Data.HashMap.Strict as Map
15+
import Control.Monad (join)
1816
import Data.Hashable
17+
import GHC.Conc (unsafeIOToSTM)
18+
import qualified StmContainers.Map as STM
1919
import System.Time.Extra
2020

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

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

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

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

0 commit comments

Comments
 (0)