Skip to content

Commit 0e84982

Browse files
authored
Improve thread contention around diagnostics (#1546)
* tighten up the update diagnostics loop to avoid contention * Tighten the Debouncer * customize the Debouncer * Fix mask scope
1 parent 05f25c9 commit 0e84982

File tree

3 files changed

+27
-20
lines changed

3 files changed

+27
-20
lines changed

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

+10-8
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ module Development.IDE.Core.Debouncer
1111
import Control.Concurrent.Async
1212
import Control.Concurrent.Extra
1313
import Control.Exception
14-
import Control.Monad.Extra
14+
import Control.Monad (join)
15+
import Data.Foldable (traverse_)
1516
import Data.HashMap.Strict (HashMap)
1617
import qualified Data.HashMap.Strict as Map
1718
import Data.Hashable
@@ -40,17 +41,18 @@ newAsyncDebouncer = Debouncer . asyncRegisterEvent <$> newVar Map.empty
4041
-- to mask if required.
4142
asyncRegisterEvent :: (Eq k, Hashable k) => Var (HashMap k (Async ())) -> Seconds -> k -> IO () -> IO ()
4243
asyncRegisterEvent d 0 k fire = do
43-
modifyVar_ d $ \m -> mask_ $ do
44-
whenJust (Map.lookup k m) cancel
45-
pure $ Map.delete k m
44+
join $ modifyVar d $ \m -> do
45+
(cancel, !m') <- evaluate $ Map.alterF (\prev -> (traverse_ cancel prev, Nothing)) k m
46+
return (m', cancel)
4647
fire
47-
asyncRegisterEvent d delay k fire = modifyVar_ d $ \m -> mask_ $ do
48-
whenJust (Map.lookup k m) cancel
48+
asyncRegisterEvent d delay k fire = mask_ $ do
4949
a <- asyncWithUnmask $ \unmask -> unmask $ do
5050
sleep delay
5151
fire
52-
modifyVar_ d (pure . Map.delete k)
53-
pure $ Map.insert k a m
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)
5456

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

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

+9-8
Original file line numberDiff line numberDiff line change
@@ -1100,15 +1100,16 @@ updateFileDiagnostics fp k ShakeExtras{logger, diagnostics, hiddenDiagnostics, p
11001100
let uri = filePathToUri' fp
11011101
let delay = if null newDiags then 0.1 else 0
11021102
registerEvent debouncer delay uri $ do
1103-
mask_ $ modifyVar_ publishedDiagnostics $ \published -> do
1103+
join $ mask_ $ modifyVar publishedDiagnostics $ \published -> do
11041104
let lastPublish = HMap.lookupDefault [] uri published
1105-
when (lastPublish /= newDiags) $ case lspEnv of
1106-
Nothing -> -- Print an LSP event.
1107-
logInfo logger $ showDiagnosticsColored $ map (fp,ShowDiag,) newDiags
1108-
Just env -> LSP.runLspT env $
1109-
LSP.sendNotification LSP.STextDocumentPublishDiagnostics $
1110-
LSP.PublishDiagnosticsParams (fromNormalizedUri uri) ver (List newDiags)
1111-
pure $! HMap.insert uri newDiags published
1105+
!published' = HMap.insert uri newDiags published
1106+
action = when (lastPublish /= newDiags) $ case lspEnv of
1107+
Nothing -> -- Print an LSP event.
1108+
logInfo logger $ showDiagnosticsColored $ map (fp,ShowDiag,) newDiags
1109+
Just env -> LSP.runLspT env $
1110+
LSP.sendNotification LSP.STextDocumentPublishDiagnostics $
1111+
LSP.PublishDiagnosticsParams (fromNormalizedUri uri) ver (List newDiags)
1112+
return (published', action)
11121113

11131114
newtype Priority = Priority Double
11141115

Diff for: ghcide/src/Development/IDE/Main.hs

+8-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import Data.Maybe (catMaybes, fromMaybe,
1515
import qualified Data.Text as T
1616
import qualified Data.Text.IO as T
1717
import Development.IDE (Action, Rules)
18-
import Development.IDE.Core.Debouncer (newAsyncDebouncer)
18+
import Development.IDE.Core.Debouncer (Debouncer,
19+
newAsyncDebouncer)
1920
import Development.IDE.Core.FileStore (makeVFSHandle)
2021
import Development.IDE.Core.IdeConfiguration (IdeConfiguration (..),
2122
registerIdeConfiguration)
@@ -43,7 +44,8 @@ import Development.IDE.Session (SessionLoadingOptions,
4344
loadSessionWithOptions,
4445
runWithDb,
4546
setInitialDynFlags)
46-
import Development.IDE.Types.Location (toNormalizedFilePath')
47+
import Development.IDE.Types.Location (NormalizedUri,
48+
toNormalizedFilePath')
4749
import Development.IDE.Types.Logger (Logger (Logger))
4850
import Development.IDE.Types.Options (IdeGhcSession,
4951
IdeOptions (optCheckParents, optCheckProject, optReportProgress),
@@ -86,6 +88,7 @@ data Arguments = Arguments
8688
, argsLspOptions :: LSP.Options
8789
, argsDefaultHlsConfig :: Config
8890
, argsGetHieDbLoc :: FilePath -> IO FilePath -- ^ Map project roots to the location of the hiedb for the project
91+
, argsDebouncer :: IO (Debouncer NormalizedUri) -- ^ Debouncer used for diagnostics
8992
}
9093

9194
instance Default Arguments where
@@ -101,6 +104,7 @@ instance Default Arguments where
101104
, argsLspOptions = def {LSP.completionTriggerCharacters = Just "."}
102105
, argsDefaultHlsConfig = def
103106
, argsGetHieDbLoc = getHieDbLoc
107+
, argsDebouncer = newAsyncDebouncer
104108
}
105109

106110
-- | Cheap stderr logger that relies on LineBuffering
@@ -123,6 +127,8 @@ defaultMain Arguments{..} = do
123127
argsOnConfigChange _ide = pure . getConfigFromNotification argsDefaultHlsConfig
124128
rules = argsRules >> pluginRules plugins
125129

130+
debouncer <- argsDebouncer
131+
126132
case argFiles of
127133
Nothing -> do
128134
t <- offsetTime
@@ -148,7 +154,6 @@ defaultMain Arguments{..} = do
148154
{ optReportProgress = clientSupportsProgress caps
149155
}
150156
caps = LSP.resClientCapabilities env
151-
debouncer <- newAsyncDebouncer
152157
initialise
153158
argsDefaultHlsConfig
154159
rules
@@ -184,7 +189,6 @@ defaultMain Arguments{..} = do
184189
when (n > 0) $ putStrLn $ " (" ++ intercalate ", " (catMaybes ucradles) ++ ")"
185190
putStrLn "\nStep 3/4: Initializing the IDE"
186191
vfs <- makeVFSHandle
187-
debouncer <- newAsyncDebouncer
188192
sessionLoader <- loadSessionWithOptions argsSessionLoadingOptions dir
189193
let options = (argsIdeOptions Nothing sessionLoader)
190194
{ optCheckParents = pure NeverCheck

0 commit comments

Comments
 (0)