on master help sign in
Make debouncer configurable (#409)

We have been experiencing a few flaky tests in DAML caused by our CLI compiler losing diagnostics. The reason for that is the debouncer which meant that messages got delayed and not send before the process exited. This PR makes the debouncer abstract and adds a noopDebouncer which doesn’t do any debouncing. This is also what we use in the terminal ghcide test thingy.

commit4aaba4d3e033ad170ff5f9aa040f8d7dbe433b36 authorMoritz Kiefer <moritz.kiefer@purelyfunctional.org> dateThu 6 Feb 2020, 20:57 (7 years ago) parente59d3e2
5 files changed+26−15
exe/Main.hs+4−2 ghcide.cabal+1−1 src/Development/IDE/Core/Debouncer.hs+15−9 src/Development/IDE/Core/Service.hs+4−1 src/Development/IDE/Core/Shake.hs+2−2
exe/Main.hs+4−2
@@ -16,6 +16,7 @@ import Control.Monad.Extra
1616import Control.Monad.IO.Class
1717import Data.Default
1818import System.Time.Extra
19+import Development.IDE.Core.Debouncer
1920import Development.IDE.Core.FileStore
2021import Development.IDE.Core.OfInterest
2122import Development.IDE.Core.Service
@@ -101,7 +102,8 @@ main = do
101102 { optReportProgress = clientSupportsProgress caps
102103 , optShakeProfiling = argsShakeProfiling
103104 }
104− initialise caps (mainRule >> pluginRules plugins >> action kick) getLspId event (logger minBound) options vfs
105+ debouncer <- newAsyncDebouncer
106+ initialise caps (mainRule >> pluginRules plugins >> action kick) getLspId event (logger minBound) debouncer options vfs
105107 else do
106108 putStrLn $ "Ghcide setup tester in " ++ dir ++ "."
107109 putStrLn "Report bugs at https://github.com/digital-asset/ghcide/issues"
@@ -136,7 +138,7 @@ main = do
136138 let options =
137139 (defaultIdeOptions $ return $ return . grab)
138140 { optShakeProfiling = argsShakeProfiling }
139− ide <- initialise def mainRule (pure $ IdInt 0) (showEvent lock) (logger Info) options vfs
141+ ide <- initialise def mainRule (pure $ IdInt 0) (showEvent lock) (logger Info) noopDebouncer options vfs
140142
141143 putStrLn "\nStep 6/6: Type checking the files"
142144 setFilesOfInterest ide $ Set.fromList $ map toNormalizedFilePath files
ghcide.cabal+1−1
@@ -99,6 +99,7 @@ library
9999 include-dirs:
100100 include
101101 exposed-modules:
102+ Development.IDE.Core.Debouncer
102103 Development.IDE.Core.FileStore
103104 Development.IDE.Core.OfInterest
104105 Development.IDE.Core.PositionMapping
@@ -121,7 +122,6 @@ library
121122 Development.IDE.Plugin.Completions
122123 Development.IDE.Plugin.CodeAction
123124 other-modules:
124− Development.IDE.Core.Debouncer
125125 Development.IDE.Core.Compile
126126 Development.IDE.Core.Preprocessor
127127 Development.IDE.Core.FileExists
src/Development/IDE/Core/Debouncer.hs+15−9
@@ -3,8 +3,9 @@
33
44module Development.IDE.Core.Debouncer
55 ( Debouncer
6− , newDebouncer
76 , registerEvent
7+ , newAsyncDebouncer
8+ , noopDebouncer
89 ) where
910
1011import Control.Concurrent.Extra
@@ -22,13 +23,14 @@ import System.Time.Extra
2223-- by delaying each event for a given time. If another event
2324-- is registered for the same key within that timeframe,
2425-- only the new event will fire.
25−newtype Debouncer k = Debouncer (Var (HashMap k (Async ())))
26+--
27+-- We abstract over the debouncer used so we an use a proper debouncer in the IDE but disable
28+-- debouncing in the DAML CLI compiler.
29+newtype Debouncer k = Debouncer { registerEvent :: Seconds -> k -> IO () -> IO () }
2630
27−-- | Create a new empty debouncer.
28−newDebouncer :: IO (Debouncer k)
29−newDebouncer = do
30− m <- newVar Map.empty
31− pure $ Debouncer m
31+-- | Debouncer used in the IDE that delays events as expected.
32+newAsyncDebouncer :: (Eq k, Hashable k) => IO (Debouncer k)
33+newAsyncDebouncer = Debouncer . asyncRegisterEvent <$> newVar Map.empty
3234
3335-- | Register an event that will fire after the given delay if no other event
3436-- for the same key gets registered until then.
@@ -36,11 +38,15 @@ newDebouncer = do
3638-- If there is a pending event for the same key, the pending event will be killed.
3739-- Events are run unmasked so it is up to the user of `registerEvent`
3840-- to mask if required.
39−registerEvent :: (Eq k, Hashable k) => Debouncer k -> Seconds -> k -> IO () -> IO ()
40−registerEvent (Debouncer d) delay k fire = modifyVar_ d $ \m -> mask_ $ do
41+asyncRegisterEvent :: (Eq k, Hashable k) => Var (HashMap k (Async ())) -> Seconds -> k -> IO () -> IO ()
42+asyncRegisterEvent d delay k fire = modifyVar_ d $ \m -> mask_ $ do
4143 whenJust (Map.lookup k m) cancel
4244 a <- asyncWithUnmask $ \unmask -> unmask $ do
4345 sleep delay
4446 fire
4547 modifyVar_ d (pure . Map.delete k)
4648 pure $ Map.insert k a m
49+
50+-- | Debouncer used in the DAML CLI compiler that emits events immediately.
51+noopDebouncer :: Debouncer k
52+noopDebouncer = Debouncer $ \_ _ a -> a
src/Development/IDE/Core/Service.hs+4−1
@@ -23,6 +23,7 @@ import Control.Concurrent.Async
2323import Data.Maybe
2424import Development.IDE.Types.Options (IdeOptions(..))
2525import Control.Monad
26+import Development.IDE.Core.Debouncer
2627import Development.IDE.Core.FileStore (VFSHandle, fileStoreRules)
2728import Development.IDE.Core.FileExists (fileExistsRules)
2829import Development.IDE.Core.OfInterest
@@ -49,14 +50,16 @@ initialise :: LSP.ClientCapabilities
4950 -> IO LSP.LspId
5051 -> (LSP.FromServerMessage -> IO ())
5152 -> Logger
53+ -> Debouncer LSP.NormalizedUri
5254 -> IdeOptions
5355 -> VFSHandle
5456 -> IO IdeState
55−initialise caps mainRule getLspId toDiags logger options vfs =
57+initialise caps mainRule getLspId toDiags logger debouncer options vfs =
5658 shakeOpen
5759 getLspId
5860 toDiags
5961 logger
62+ debouncer
6063 (optShakeProfiling options)
6164 (optReportProgress options)
6265 shakeOptions
src/Development/IDE/Core/Shake.hs+2−2
@@ -294,12 +294,13 @@ seqValue v b = case v of
294294shakeOpen :: IO LSP.LspId
295295 -> (LSP.FromServerMessage -> IO ()) -- ^ diagnostic handler
296296 -> Logger
297+ -> Debouncer NormalizedUri
297298 -> Maybe FilePath
298299 -> IdeReportProgress
299300 -> ShakeOptions
300301 -> Rules ()
301302 -> IO IdeState
302−shakeOpen getLspId eventer logger shakeProfileDir (IdeReportProgress reportProgress) opts rules = do
303+shakeOpen getLspId eventer logger debouncer shakeProfileDir (IdeReportProgress reportProgress) opts rules = do
303304 inProgress <- newVar Map.empty
304305 shakeExtras <- do
305306 globals <- newVar HMap.empty
@@ -307,7 +308,6 @@ shakeOpen getLspId eventer logger shakeProfileDir (IdeReportProgress reportProgr
307308 diagnostics <- newVar mempty
308309 hiddenDiagnostics <- newVar mempty
309310 publishedDiagnostics <- newVar mempty
310− debouncer <- newDebouncer
311311 positionMapping <- newVar Map.empty
312312 pure ShakeExtras{..}
313313 (shakeDb, shakeClose) <-