Skip to content

Commit 4aaba4d

Browse files
authored
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.
1 parent e59d3e2 commit 4aaba4d

File tree

5 files changed

+26
-15
lines changed

5 files changed

+26
-15
lines changed

Diff for: exe/Main.hs

+4-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import Control.Monad.Extra
1616
import Control.Monad.IO.Class
1717
import Data.Default
1818
import System.Time.Extra
19+
import Development.IDE.Core.Debouncer
1920
import Development.IDE.Core.FileStore
2021
import Development.IDE.Core.OfInterest
2122
import 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

Diff for: ghcide.cabal

+1-1
Original file line numberDiff line numberDiff line change
@@ -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

Diff for: src/Development/IDE/Core/Debouncer.hs

+15-9
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33

44
module Development.IDE.Core.Debouncer
55
( Debouncer
6-
, newDebouncer
76
, registerEvent
7+
, newAsyncDebouncer
8+
, noopDebouncer
89
) where
910

1011
import Control.Concurrent.Extra
@@ -22,25 +23,30 @@ 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.
3537
--
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

Diff for: src/Development/IDE/Core/Service.hs

+4-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import Control.Concurrent.Async
2323
import Data.Maybe
2424
import Development.IDE.Types.Options (IdeOptions(..))
2525
import Control.Monad
26+
import Development.IDE.Core.Debouncer
2627
import Development.IDE.Core.FileStore (VFSHandle, fileStoreRules)
2728
import Development.IDE.Core.FileExists (fileExistsRules)
2829
import 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

Diff for: src/Development/IDE/Core/Shake.hs

+2-2
Original file line numberDiff line numberDiff line change
@@ -294,20 +294,20 @@ seqValue v b = case v of
294294
shakeOpen :: 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
306307
state <- newVar HMap.empty
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) <-

0 commit comments

Comments
 (0)