Skip to content

Commit e75af91

Browse files
committed
lock-free Values
1 parent 2abe7d0 commit e75af91

File tree

6 files changed

+102
-91
lines changed

6 files changed

+102
-91
lines changed

ghcide/ghcide.cabal

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ library
5454
fuzzy,
5555
filepath,
5656
fingertree,
57+
focus,
5758
ghc-exactprint,
5859
ghc-trace-events,
5960
Glob,
@@ -62,6 +63,7 @@ library
6263
hie-compat ^>= 0.2.0.0,
6364
hls-plugin-api ^>= 1.2.0.2,
6465
lens,
66+
list-t,
6567
hiedb == 0.4.1.*,
6668
lsp-types >= 1.3.0.1 && < 1.4,
6769
lsp == 1.2.*,
@@ -81,6 +83,7 @@ library
8183
sorted-list,
8284
sqlite-simple,
8385
stm,
86+
stm-containers,
8487
syb,
8588
text,
8689
time,

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

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,13 @@ import Data.String (fromString)
161161
import Data.Text (pack)
162162
import Debug.Trace.Flags (userTracingEnabled)
163163
import qualified Development.IDE.Types.Exports as ExportsMap
164+
import qualified Focus
164165
import HieDb.Types
165166
import Ide.Plugin.Config
166167
import qualified Ide.PluginUtils as HLS
167168
import Ide.Types (PluginId)
169+
import qualified ListT
170+
import qualified StmContainers.Map as STM
168171

169172
-- | We need to serialize writes to the database, so we send any function that
170173
-- needs to write to the database over the channel, where it will be picked up by
@@ -188,7 +191,7 @@ data ShakeExtras = ShakeExtras
188191
,debouncer :: Debouncer NormalizedUri
189192
,logger :: Logger
190193
,globals :: Var (HMap.HashMap TypeRep Dynamic)
191-
,state :: Var Values
194+
,state :: Values
192195
,diagnostics :: Var DiagnosticStore
193196
,hiddenDiagnostics :: Var DiagnosticStore
194197
,publishedDiagnostics :: Var (HMap.HashMap NormalizedUri [Diagnostic])
@@ -319,7 +322,6 @@ getIdeOptionsIO ide = do
319322
-- for the version of that value.
320323
lastValueIO :: IdeRule k v => ShakeExtras -> k -> NormalizedFilePath -> IO (Maybe (v, PositionMapping))
321324
lastValueIO s@ShakeExtras{positionMapping,persistentKeys,state} k file = do
322-
hm <- readVar state
323325
allMappings <- readVar positionMapping
324326

325327
let readPersistent
@@ -334,10 +336,10 @@ lastValueIO s@ShakeExtras{positionMapping,persistentKeys,state} k file = do
334336
MaybeT $ pure $ (,del,ver) <$> fromDynamic dv
335337
case mv of
336338
Nothing -> do
337-
void $ modifyVar' state $ HMap.alter (alterValue $ Failed True) (toKey k file)
339+
void $ atomically $ STM.focus (Focus.alter (alterValue $ Failed True)) (toKey k file) state
338340
return Nothing
339341
Just (v,del,ver) -> do
340-
void $ modifyVar' state $ HMap.alter (alterValue $ Stale (Just del) ver (toDyn v)) (toKey k file)
342+
void $ atomically $ STM.focus (Focus.alter (alterValue $ Stale (Just del) ver (toDyn v))) (toKey k file) state
341343
return $ Just (v,addDelta del $ mappingForVersion allMappings file ver)
342344

343345
-- We got a new stale value from the persistent rule, insert it in the map without affecting diagnostics
@@ -348,7 +350,7 @@ lastValueIO s@ShakeExtras{positionMapping,persistentKeys,state} k file = do
348350
-- Something already succeeded before, leave it alone
349351
_ -> old
350352

351-
case HMap.lookup (toKey k file) hm of
353+
atomically (STM.lookup (toKey k file) state) >>= \case
352354
Nothing -> readPersistent
353355
Just (ValueWithDiagnostics v _) -> case v of
354356
Succeeded ver (fromDynamic -> Just v) -> pure (Just (v, mappingForVersion allMappings file ver))
@@ -413,14 +415,14 @@ shakeDatabaseProfileIO mbProfileDir = do
413415
return (dir </> file)
414416

415417
setValues :: IdeRule k v
416-
=> Var Values
418+
=> Values
417419
-> k
418420
-> NormalizedFilePath
419421
-> Value v
420422
-> Vector FileDiagnostic
421423
-> IO ()
422424
setValues state key file val diags =
423-
void $ modifyVar' state $ HMap.insert (toKey key file) (ValueWithDiagnostics (fmap toDyn val) diags)
425+
atomically $ STM.insert (ValueWithDiagnostics (fmap toDyn val) diags) (toKey key file) state
424426

425427

426428
-- | Delete the value stored for a given ide build key
@@ -431,7 +433,7 @@ deleteValue
431433
-> NormalizedFilePath
432434
-> IO ()
433435
deleteValue ShakeExtras{dirtyKeys, state} key file = do
434-
void $ modifyVar' state $ HMap.delete (toKey key file)
436+
atomically $ STM.delete (toKey key file) state
435437
atomicModifyIORef_ dirtyKeys $ HSet.insert (toKey key file)
436438

437439
recordDirtyKeys
@@ -449,13 +451,12 @@ recordDirtyKeys ShakeExtras{dirtyKeys} key file = withEventTrace "recordDirtyKey
449451
getValues ::
450452
forall k v.
451453
IdeRule k v =>
452-
Var Values ->
454+
Values ->
453455
k ->
454456
NormalizedFilePath ->
455457
IO (Maybe (Value v, Vector FileDiagnostic))
456458
getValues state key file = do
457-
vs <- readVar state
458-
case HMap.lookup (toKey key file) vs of
459+
atomically (STM.lookup (toKey key file) state) >>= \case
459460
Nothing -> pure Nothing
460461
Just (ValueWithDiagnostics v diagsV) -> do
461462
let r = fmap (fromJust . fromDynamic @v) v
@@ -500,7 +501,7 @@ shakeOpen lspEnv defaultConfig logger debouncer
500501
ideNc <- newIORef (initNameCache us knownKeyNames)
501502
shakeExtras <- do
502503
globals <- newVar HMap.empty
503-
state <- newVar HMap.empty
504+
state <- STM.newIO
504505
diagnostics <- newVar mempty
505506
hiddenDiagnostics <- newVar mempty
506507
publishedDiagnostics <- newVar mempty
@@ -559,7 +560,7 @@ startTelemetry db extras@ShakeExtras{..}
559560
IdeOptions{optCheckParents} <- getIdeOptionsIO extras
560561
checkParents <- optCheckParents
561562
regularly 1 $ do
562-
readVar state >>= observe countKeys . countRelevantKeys checkParents . HMap.keys
563+
observe countKeys . countRelevantKeys checkParents . map fst =<< (atomically . ListT.toList . STM.listT) state
563564
readIORef dirtyKeys >>= observe countDirty . countRelevantKeys checkParents . HSet.toList
564565
shakeGetBuildStep db >>= observe countBuilds
565566

@@ -779,8 +780,9 @@ garbageCollectKeys :: String -> Int -> CheckParents -> [(Key, Int)] -> Action [K
779780
garbageCollectKeys label maxAge checkParents agedKeys = do
780781
start <- liftIO offsetTime
781782
extras <- getShakeExtras
782-
(n::Int, garbage) <- liftIO $ modifyVar (state extras) $ \vmap ->
783-
evaluate $ foldl' removeDirtyKey (vmap, (0,[])) agedKeys
783+
let values = state extras
784+
(n::Int, garbage) <- liftIO $ atomically $
785+
foldM (removeDirtyKey values) (0,[]) agedKeys
784786
liftIO $ atomicModifyIORef_ (dirtyKeys extras) $ \x ->
785787
foldl' (flip HSet.insert) x garbage
786788
t <- liftIO start
@@ -794,13 +796,13 @@ garbageCollectKeys label maxAge checkParents agedKeys = do
794796

795797
where
796798
showKey = show . Q
797-
removeDirtyKey st@(vmap,(!counter, keys)) (k, age)
799+
removeDirtyKey m st@(!counter, keys) (k, age)
798800
| age > maxAge
799801
, Just (kt,_) <- fromKeyType k
800802
, not(kt `HSet.member` preservedKeys checkParents)
801-
, (True, vmap') <- HMap.alterF (\prev -> (isJust prev, Nothing)) k vmap
802-
= (vmap', (counter+1, k:keys))
803-
| otherwise = st
803+
= do gotIt <- STM.focus (Focus.member <* Focus.delete) k m
804+
return $ if gotIt then (counter+1, k:keys) else st
805+
| otherwise = pure st
804806

805807
countRelevantKeys :: CheckParents -> [Key] -> Int
806808
countRelevantKeys checkParents =

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

Lines changed: 64 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -16,53 +16,56 @@ module Development.IDE.Core.Tracing
1616
)
1717
where
1818

19-
import Control.Concurrent.Async (Async, async)
20-
import Control.Concurrent.Extra (Var, modifyVar_, newVar,
21-
readVar, threadDelay)
22-
import Control.Exception (evaluate)
23-
import Control.Exception.Safe (SomeException, catch,
24-
generalBracket)
25-
import Control.Monad (forM_, forever, void, when,
26-
(>=>))
27-
import Control.Monad.Catch (ExitCase (..), MonadMask)
28-
import Control.Monad.Extra (whenJust)
19+
import Control.Concurrent.Async (Async, async)
20+
import Control.Concurrent.Extra (modifyVar_, newVar, readVar,
21+
threadDelay)
22+
import Control.Exception (evaluate)
23+
import Control.Exception.Safe (SomeException, catch,
24+
generalBracket)
25+
import Control.Monad (forM_, forever, void, when,
26+
(>=>))
27+
import Control.Monad.Catch (ExitCase (..), MonadMask)
28+
import Control.Monad.Extra (whenJust)
2929
import Control.Monad.IO.Unlift
30-
import Control.Seq (r0, seqList, seqTuple2, using)
31-
import Data.ByteString (ByteString)
32-
import Data.ByteString.Char8 (pack)
33-
import Data.Dynamic (Dynamic)
34-
import qualified Data.HashMap.Strict as HMap
35-
import Data.IORef (modifyIORef', newIORef,
36-
readIORef, writeIORef)
37-
import Data.String (IsString (fromString))
38-
import qualified Data.Text as T
39-
import Data.Text.Encoding (encodeUtf8)
40-
import Data.Typeable (TypeRep, typeOf)
41-
import Data.Word (Word16)
42-
import Debug.Trace.Flags (userTracingEnabled)
43-
import Development.IDE.Core.RuleTypes (GhcSession (GhcSession),
44-
GhcSessionDeps (GhcSessionDeps),
45-
GhcSessionIO (GhcSessionIO))
46-
import Development.IDE.Graph (Action)
30+
import Control.Monad.STM (atomically)
31+
import Control.Seq (r0, seqList, seqTuple2,
32+
using)
33+
import Data.ByteString (ByteString)
34+
import Data.ByteString.Char8 (pack)
35+
import qualified Data.HashMap.Strict as HMap
36+
import Data.IORef (modifyIORef', newIORef,
37+
readIORef, writeIORef)
38+
import Data.String (IsString (fromString))
39+
import qualified Data.Text as T
40+
import Data.Text.Encoding (encodeUtf8)
41+
import Data.Typeable (TypeRep, typeOf)
42+
import Data.Word (Word16)
43+
import Debug.Trace.Flags (userTracingEnabled)
44+
import Development.IDE.Core.RuleTypes (GhcSession (GhcSession),
45+
GhcSessionDeps (GhcSessionDeps),
46+
GhcSessionIO (GhcSessionIO))
47+
import Development.IDE.Graph (Action)
4748
import Development.IDE.Graph.Rule
48-
import Development.IDE.Types.Diagnostics (FileDiagnostic, showDiagnostics)
49-
import Development.IDE.Types.Location (Uri (..))
50-
import Development.IDE.Types.Logger (Logger (Logger), logDebug,
51-
logInfo)
52-
import Development.IDE.Types.Shake (Value,
53-
ValueWithDiagnostics (..),
54-
Values, fromKeyType)
55-
import Foreign.Storable (Storable (sizeOf))
56-
import HeapSize (recursiveSize, runHeapsize)
57-
import Ide.PluginUtils (installSigUsr1Handler)
58-
import Ide.Types (PluginId (..))
59-
import Language.LSP.Types (NormalizedFilePath,
60-
fromNormalizedFilePath)
61-
import Numeric.Natural (Natural)
62-
import OpenTelemetry.Eventlog (SpanInFlight (..), addEvent,
63-
beginSpan, endSpan,
64-
mkValueObserver, observe,
65-
setTag, withSpan, withSpan_)
49+
import Development.IDE.Types.Diagnostics (FileDiagnostic,
50+
showDiagnostics)
51+
import Development.IDE.Types.Location (Uri (..))
52+
import Development.IDE.Types.Logger (Logger (Logger), logDebug,
53+
logInfo)
54+
import Development.IDE.Types.Shake (ValueWithDiagnostics (..),
55+
Values, fromKeyType)
56+
import Foreign.Storable (Storable (sizeOf))
57+
import HeapSize (recursiveSize, runHeapsize)
58+
import Ide.PluginUtils (installSigUsr1Handler)
59+
import Ide.Types (PluginId (..))
60+
import Language.LSP.Types (NormalizedFilePath,
61+
fromNormalizedFilePath)
62+
import qualified ListT
63+
import Numeric.Natural (Natural)
64+
import OpenTelemetry.Eventlog (SpanInFlight (..), addEvent,
65+
beginSpan, endSpan,
66+
mkValueObserver, observe,
67+
setTag, withSpan, withSpan_)
68+
import qualified StmContainers.Map as STM
6669

6770
#if MIN_VERSION_ghc(8,8,0)
6871
otTracedProvider :: MonadUnliftIO m => PluginId -> ByteString -> m a -> m a
@@ -174,16 +177,16 @@ otTracedProvider (PluginId pluginName) provider act
174177
| otherwise = act
175178

176179

177-
startProfilingTelemetry :: Bool -> Logger -> Var Values -> IO ()
178-
startProfilingTelemetry allTheTime logger stateRef = do
180+
startProfilingTelemetry :: Bool -> Logger -> Values -> IO ()
181+
startProfilingTelemetry allTheTime logger state = do
179182
instrumentFor <- getInstrumentCached
180183

181184
installSigUsr1Handler $ do
182185
logInfo logger "SIGUSR1 received: performing memory measurement"
183-
performMeasurement logger stateRef instrumentFor
186+
performMeasurement logger state instrumentFor
184187

185188
when allTheTime $ void $ regularly (1 * seconds) $
186-
performMeasurement logger stateRef instrumentFor
189+
performMeasurement logger state instrumentFor
187190
where
188191
seconds = 1000000
189192

@@ -193,17 +196,16 @@ startProfilingTelemetry allTheTime logger stateRef = do
193196

194197
performMeasurement ::
195198
Logger ->
196-
Var Values ->
199+
Values ->
197200
(Maybe String -> IO OurValueObserver) ->
198201
IO ()
199-
performMeasurement logger stateRef instrumentFor = do
200-
201-
values <- readVar stateRef
202+
performMeasurement logger values instrumentFor = do
203+
contents <- atomically $ ListT.toList $ STM.listT values
202204
let keys = typeOf GhcSession
203205
: typeOf GhcSessionDeps
204206
-- TODO restore
205207
: [ kty
206-
| k <- HMap.keys values
208+
| (k,_) <- contents
207209
, Just (kty,_) <- [fromKeyType k]
208210
-- do GhcSessionIO last since it closes over stateRef itself
209211
, kty /= typeOf GhcSession
@@ -212,7 +214,7 @@ performMeasurement logger stateRef instrumentFor = do
212214
]
213215
++ [typeOf GhcSessionIO]
214216
groupedForSharing <- evaluate (keys `using` seqList r0)
215-
measureMemory logger [groupedForSharing] instrumentFor stateRef
217+
measureMemory logger [groupedForSharing] instrumentFor values
216218
`catch` \(e::SomeException) ->
217219
logInfo logger ("MEMORY PROFILING ERROR: " <> fromString (show e))
218220

@@ -243,12 +245,12 @@ measureMemory
243245
:: Logger
244246
-> [[TypeRep]] -- ^ Grouping of keys for the sharing-aware analysis
245247
-> (Maybe String -> IO OurValueObserver)
246-
-> Var Values
248+
-> Values
247249
-> IO ()
248-
measureMemory logger groups instrumentFor stateRef = withSpan_ "Measure Memory" $ do
249-
values <- readVar stateRef
250+
measureMemory logger groups instrumentFor values = withSpan_ "Measure Memory" $ do
251+
contents <- atomically $ ListT.toList $ STM.listT values
250252
valuesSizeRef <- newIORef $ Just 0
251-
let !groupsOfGroupedValues = groupValues values
253+
let !groupsOfGroupedValues = groupValues contents
252254
logDebug logger "STARTING MEMORY PROFILING"
253255
forM_ groupsOfGroupedValues $ \groupedValues -> do
254256
keepGoing <- readIORef valuesSizeRef
@@ -277,12 +279,12 @@ measureMemory logger groups instrumentFor stateRef = withSpan_ "Measure Memory"
277279
logInfo logger "Memory profiling could not be completed: increase the size of your nursery (+RTS -Ax) and try again"
278280

279281
where
280-
groupValues :: Values -> [ [(String, [Value Dynamic])] ]
281-
groupValues values =
282+
-- groupValues :: Values -> [ [(String, [Value Dynamic])] ]
283+
groupValues contents =
282284
let !groupedValues =
283285
[ [ (show ty, vv)
284286
| ty <- groupKeys
285-
, let vv = [ v | (fromKeyType -> Just (kty,_), ValueWithDiagnostics v _) <- HMap.toList values
287+
, let vv = [ v | (fromKeyType -> Just (kty,_), ValueWithDiagnostics v _) <- contents
286288
, kty == ty]
287289
]
288290
| groupKeys <- groups

0 commit comments

Comments
 (0)