Skip to content

Commit eb5356d

Browse files
committed
new hls-graph runtime
1 parent 08350aa commit eb5356d

File tree

17 files changed

+328
-259
lines changed

17 files changed

+328
-259
lines changed

ghcide/ghcide.cabal

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ library
142142
Development.IDE.Core.Shake
143143
Development.IDE.Core.Tracing
144144
Development.IDE.Core.UseStale
145-
Development.IDE.Core.WorkerThread
146145
Development.IDE.GHC.Compat
147146
Development.IDE.GHC.Compat.Core
148147
Development.IDE.GHC.Compat.CmdLine

ghcide/session-loader/Development/IDE/Session.hs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,12 @@ import qualified Data.HashSet as Set
105105
import qualified Data.Set as OS
106106
import Database.SQLite.Simple
107107
import Development.IDE.Core.Tracing (withTrace)
108-
import Development.IDE.Core.WorkerThread
109108
import qualified Development.IDE.GHC.Compat.Util as Compat
110109
import Development.IDE.Session.Diagnostics (renderCradleError)
111110
import Development.IDE.Types.Shake (WithHieDb,
112111
WithHieDbShield (..),
113112
toNoFileKey)
113+
import Development.IDE.WorkerThread
114114
import GHC.Data.Graph.Directed
115115
import HieDb.Create
116116
import HieDb.Types
@@ -153,6 +153,14 @@ data Log
153153
| LogSessionWorkerThread LogWorkerThread
154154
deriving instance Show Log
155155

156+
instance Pretty LogWorkerThread where
157+
pretty = \case
158+
LogThreadEnding t -> "Worker thread ending:" <+> pretty t
159+
LogThreadEnded t -> "Worker thread ended:" <+> pretty t
160+
LogSingleWorkStarting t -> "Worker starting a unit of work: " <+> pretty t
161+
LogSingleWorkEnded t -> "Worker ended a unit of work: " <+> pretty t
162+
LogMainThreadId t tid -> "Main thread for" <+> pretty t <+> "is" <+> pretty (show tid)
163+
156164
instance Pretty Log where
157165
pretty = \case
158166
LogSessionWorkerThread msg -> pretty msg
@@ -384,7 +392,7 @@ runWithDb recorder fp = ContT $ \k -> do
384392
_ <- withWriteDbRetryable deleteMissingRealFiles
385393
_ <- withWriteDbRetryable garbageCollectTypeNames
386394

387-
runContT (withWorkerQueue (cmapWithPrio LogSessionWorkerThread recorder) "hiedb thread" (writer withWriteDbRetryable))
395+
runContT (withWorkerQueue (logWith (cmapWithPrio LogSessionWorkerThread recorder) Debug) "hiedb thread" (writer withWriteDbRetryable))
388396
$ \chan -> withHieDb fp (\readDb -> k (WithHieDbShield $ makeWithHieDbRetryable recorder rng readDb, chan))
389397
where
390398
writer withHieDbRetryable l = do

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ import Development.IDE.Core.Preprocessor
7575
import Development.IDE.Core.ProgressReporting (progressUpdate)
7676
import Development.IDE.Core.RuleTypes
7777
import Development.IDE.Core.Shake
78-
import Development.IDE.Core.WorkerThread (writeTaskQueue)
78+
import Development.IDE.WorkerThread (writeTaskQueue)
7979
import Development.IDE.Core.Tracing (withTrace)
8080
import qualified Development.IDE.GHC.Compat as Compat
8181
import qualified Development.IDE.GHC.Compat as GHC

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,14 @@ import Development.IDE.Core.IdeConfiguration (isWorkspaceFile)
4545
import Development.IDE.Core.RuleTypes
4646
import Development.IDE.Core.Shake hiding (Log)
4747
import qualified Development.IDE.Core.Shake as Shake
48-
import Development.IDE.Core.WorkerThread
4948
import Development.IDE.GHC.Orphans ()
5049
import Development.IDE.Graph
5150
import Development.IDE.Import.DependencyInformation
5251
import Development.IDE.Types.Diagnostics
5352
import Development.IDE.Types.Location
5453
import Development.IDE.Types.Options
5554
import Development.IDE.Types.Shake (toKey)
55+
import Development.IDE.WorkerThread
5656
import HieDb.Create (deleteMissingRealFiles)
5757
import Ide.Logger (Pretty (pretty),
5858
Priority (Info),

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

Lines changed: 121 additions & 87 deletions
Large diffs are not rendered by default.

ghcide/src/Development/IDE/LSP/LanguageServer.hs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,15 @@ import Development.IDE.Core.IdeConfiguration
4646
import Development.IDE.Core.Service (shutdown)
4747
import Development.IDE.Core.Shake hiding (Log)
4848
import Development.IDE.Core.Tracing
49-
import Development.IDE.Core.WorkerThread
5049
import qualified Development.IDE.Session as Session
5150
import Development.IDE.Types.Shake (WithHieDb,
5251
WithHieDbShield (..))
52+
import Development.IDE.WorkerThread
5353
import Ide.Logger
5454
import Language.LSP.Server (LanguageContextEnv,
5555
LspServerLog,
5656
type (<~>))
57+
import System.Time.Extra (Seconds, sleep)
5758
import System.Timeout (timeout)
5859
data Log
5960
= LogRegisteringIdeConfig !IdeConfiguration
@@ -67,10 +68,13 @@ data Log
6768
| LogShutDownTimeout Int
6869
| LogServerExitWith (Either () Int)
6970
| LogReactorShutdownConfirmed !T.Text
71+
| LogInitializeIdeStateTookTooLong Seconds
7072
deriving Show
7173

7274
instance Pretty Log where
7375
pretty = \case
76+
LogInitializeIdeStateTookTooLong seconds ->
77+
"Building the initial session took more than" <+> pretty seconds <+> "seconds"
7478
LogReactorShutdownRequested b ->
7579
"Requested reactor shutdown; stop signal posted: " <+> pretty b
7680
LogReactorShutdownConfirmed msg ->
@@ -350,8 +354,8 @@ handleInit initParams env (TRequestMessage _ _ m params) = otTracedHandler "Init
350354
runWithWorkerThreads :: Recorder (WithPriority Session.Log) -> FilePath -> (WithHieDb -> ThreadQueue -> IO ()) -> IO ()
351355
runWithWorkerThreads recorder dbLoc f = evalContT $ do
352356
(WithHieDbShield hiedb, threadQueue) <- runWithDb recorder dbLoc
353-
sessionRestartTQueue <- withWorkerQueueSimple (cmapWithPrio Session.LogSessionWorkerThread recorder) "RestartTQueue"
354-
sessionLoaderTQueue <- withWorkerQueueSimple (cmapWithPrio Session.LogSessionWorkerThread recorder) "SessionLoaderTQueue"
357+
sessionRestartTQueue <- withWorkerQueueSimple (logWith (cmapWithPrio Session.LogSessionWorkerThread recorder) Debug) "RestartTQueue"
358+
sessionLoaderTQueue <- withWorkerQueueSimple (logWith (cmapWithPrio Session.LogSessionWorkerThread recorder) Debug) "SessionLoaderTQueue"
355359
liftIO $ f hiedb (ThreadQueue threadQueue sessionRestartTQueue sessionLoaderTQueue)
356360

357361
-- | Runs the action until it ends or until the given MVar is put.

hls-graph/hls-graph.cabal

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,14 @@ library
6565
Development.IDE.Graph.KeyMap
6666
Development.IDE.Graph.KeySet
6767
Development.IDE.Graph.Rule
68+
Development.IDE.WorkerThread
6869
Paths_hls_graph
6970

7071
autogen-modules: Paths_hls_graph
7172
hs-source-dirs: src
7273
build-depends:
74+
, mtl ^>=2.3.1
75+
, safe-exceptions ^>=0.1.7.4
7376
, aeson
7477
, async >=2.0
7578
, base >=4.12 && <5

hls-graph/src/Development/IDE/Graph.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ module Development.IDE.Graph(
1818
-- * Actions for inspecting the keys in the database
1919
getDirtySet,
2020
getKeysAndVisitedAge,
21+
2122
module Development.IDE.Graph.KeyMap,
2223
module Development.IDE.Graph.KeySet,
2324
) where

hls-graph/src/Development/IDE/Graph/Database.hs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ module Development.IDE.Graph.Database(
99
shakeGetDatabaseKeys,
1010
shakeGetDirtySet,
1111
shakeGetCleanKeys
12-
,shakeGetBuildEdges) where
12+
,shakeGetBuildEdges,
13+
shakeShutDatabase) where
1314
import Control.Concurrent.STM.Stats (readTVarIO)
1415
import Data.Dynamic
1516
import Data.Maybe
@@ -21,16 +22,20 @@ import Development.IDE.Graph.Internal.Options
2122
import Development.IDE.Graph.Internal.Profile (writeProfile)
2223
import Development.IDE.Graph.Internal.Rules
2324
import Development.IDE.Graph.Internal.Types
25+
import Development.IDE.WorkerThread (TaskQueue)
2426

2527

2628
-- Placeholder to be the 'extra' if the user doesn't set it
2729
data NonExportedType = NonExportedType
2830

29-
shakeNewDatabase :: ShakeOptions -> Rules () -> IO ShakeDatabase
30-
shakeNewDatabase opts rules = do
31+
shakeShutDatabase :: ShakeDatabase -> IO ()
32+
shakeShutDatabase (ShakeDatabase _ _ db) = shutDatabase db
33+
34+
shakeNewDatabase :: TaskQueue (IO ()) -> ShakeOptions -> Rules () -> IO ShakeDatabase
35+
shakeNewDatabase que opts rules = do
3136
let extra = fromMaybe (toDyn NonExportedType) $ shakeExtra opts
3237
(theRules, actions) <- runRules extra rules
33-
db <- newDatabase extra theRules
38+
db <- newDatabase que extra theRules
3439
pure $ ShakeDatabase (length actions) actions db
3540

3641
shakeRunDatabase :: ShakeDatabase -> [Action a] -> IO [a]

hls-graph/src/Development/IDE/Graph/Internal/Database.hs

Lines changed: 38 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ module Development.IDE.Graph.Internal.Database (compute, newDatabase, incDatabas
1212

1313
import Prelude hiding (unzip)
1414

15-
import Control.Concurrent.Async
16-
import Control.Concurrent.Extra
17-
import Control.Concurrent.STM.Stats (STM, TVar, atomically,
15+
import Control.Concurrent.STM.Stats (STM, atomically,
1816
atomicallyNamed,
1917
modifyTVar', newTVarIO,
2018
readTVar, readTVarIO,
@@ -31,28 +29,29 @@ import Data.IORef.Extra
3129
import Data.Maybe
3230
import Data.Traversable (for)
3331
import Data.Tuple.Extra
34-
import Debug.Trace (traceM)
32+
import Debug.Trace (traceEvent)
3533
import Development.IDE.Graph.Classes
3634
import Development.IDE.Graph.Internal.Key
3735
import Development.IDE.Graph.Internal.Rules
3836
import Development.IDE.Graph.Internal.Types
3937
import qualified Focus
4038
import qualified ListT
4139
import qualified StmContainers.Map as SMap
42-
import System.Time.Extra (duration, sleep)
43-
import UnliftIO (MonadUnliftIO (withRunInIO))
44-
import qualified UnliftIO.Exception as UE
40+
import System.Time.Extra (duration)
4541

4642
#if MIN_VERSION_base(4,19,0)
4743
import Data.Functor (unzip)
4844
#else
4945
import Data.List.NonEmpty (unzip)
5046
#endif
47+
import Development.IDE.WorkerThread (TaskQueue,
48+
awaitRunInThreadStmInNewThread)
5149

5250

53-
newDatabase :: Dynamic -> TheRules -> IO Database
54-
newDatabase databaseExtra databaseRules = do
51+
newDatabase :: TaskQueue (IO ()) -> Dynamic -> TheRules -> IO Database
52+
newDatabase databaseQueue databaseExtra databaseRules = do
5553
databaseStep <- newTVarIO $ Step 0
54+
databaseThreads <- newTVarIO []
5655
databaseValues <- atomically SMap.new
5756
pure Database{..}
5857

@@ -100,8 +99,9 @@ build db stack keys = do
10099
else throw $ AsyncParentKill i $ Step (-1)
101100
where
102101
go = do
103-
step <- readTVarIO $ databaseStep db
104-
!built <- runAIO step $ builder db stack (fmap newKey keys)
102+
-- step <- readTVarIO $ databaseStep db
103+
-- built <- mapConcurrently (builderOne db stack) (fmap newKey keys)
104+
built <- builder db stack (fmap newKey keys)
105105
let (ids, vs) = unzip built
106106
pure (ids, fmap (asV . resultValue) vs)
107107
where
@@ -112,38 +112,39 @@ build db stack keys = do
112112
-- | Build a list of keys and return their results.
113113
-- If none of the keys are dirty, we can return the results immediately.
114114
-- Otherwise, a blocking computation is returned *which must be evaluated asynchronously* to avoid deadlock.
115-
builder :: (Traversable f) => Database -> Stack -> f Key -> AIO (f (Key, Result))
115+
builder :: (Traversable f) => Database -> Stack -> f Key -> IO (f (Key, Result))
116116
-- builder _ st kk | traceShow ("builder", st,kk) False = undefined
117-
builder db stack keys = do
118-
keyWaits <- for keys $ \k -> builderOne db stack k
119-
!res <- for keyWaits $ \(k, waitR) -> do
120-
!v<- liftIO waitR
121-
return (k, v)
122-
return res
117+
builder db stack keys = for keys $ \k -> builderOne db stack k
123118

124-
builderOne :: Database -> Stack -> Key -> AIO (Key, IO Result)
125-
builderOne db@Database {..} stack id = UE.uninterruptibleMask $ \restore -> do
126-
current <- liftIO $ readTVarIO databaseStep
127-
(k, registerWaitResult) <- restore $ liftIO $ atomicallyNamed "builder" $ do
119+
builderOne :: Database -> Stack -> Key -> IO (Key, Result)
120+
builderOne db@Database {..} stack id = do
121+
traceEvent ("builderOne: " ++ show id) return ()
122+
res <- liftIO $ atomicallyNamed "builder" $ do
128123
-- Spawn the id if needed
129124
status <- SMap.lookup id databaseValues
125+
current@(Step cs) <- readTVar databaseStep
126+
let getStep = do
127+
Step current <- readTVar databaseStep
128+
return current
129+
130130
val <- case viewDirty current $ maybe (Dirty Nothing) keyStatus status of
131131
Dirty s -> do
132-
let act =
133-
asyncWithCleanUp
134-
((restore $ refresh db stack id s)
135-
`UE.onException` UE.uninterruptibleMask_ (liftIO (atomicallyNamed "builder - onException" (SMap.focus updateDirty id databaseValues)))
136-
)
137132
SMap.focus (updateStatus $ Running current s) id databaseValues
138-
return act
139-
Clean r -> pure . pure . pure $ r
133+
traceEvent ("Starting build of key: " ++ show id ++ ", step "++ show current)
134+
$ awaitRunInThreadStmInNewThread getStep cs databaseQueue databaseThreads (refresh db stack id s)
135+
$ \e -> atomically $ SMap.focus (updateStatus $ Exception current e s) id databaseValues
136+
return Nothing
137+
Clean r -> return $ Just r
140138
-- force here might contains async exceptions from previous runs
141139
Running _step _s
142140
| memberStack id stack -> throw $ StackException stack
143141
| otherwise -> retry
144-
pure (id, val)
145-
waitR <- registerWaitResult
146-
return (k, waitR)
142+
Exception _ e _s -> throw e
143+
pure val
144+
case res of
145+
Just r -> return (id, r)
146+
Nothing -> builderOne db stack id
147+
147148
-- | isDirty
148149
-- only dirty when it's build time is older than the changed time of one of its dependencies
149150
isDirty :: Foldable t => Result -> t (a, Result) -> Bool
@@ -156,30 +157,27 @@ isDirty me = any (\(_,dep) -> resultBuilt me < resultChanged dep)
156157
-- * If no dirty dependencies and we have evaluated the key previously, then we refresh it in the current thread.
157158
-- This assumes that the implementation will be a lookup
158159
-- * Otherwise, we spawn a new thread to refresh the dirty deps (if any) and the key itself
159-
refreshDeps :: KeySet -> Database -> Stack -> Key -> Result -> [KeySet] -> AIO Result
160+
refreshDeps :: KeySet -> Database -> Stack -> Key -> Result -> [KeySet] -> IO Result
160161
refreshDeps visited db stack key result = \case
161162
-- no more deps to refresh
162-
[] -> compute' db stack key RunDependenciesSame (Just result)
163+
[] -> compute db stack key RunDependenciesSame (Just result)
163164
(dep:deps) -> do
164165
let newVisited = dep <> visited
165166
res <- builder db stack (toListKeySet (dep `differenceKeySet` visited))
166167
if isDirty result res
167168
-- restart the computation if any of the deps are dirty
168-
then compute' db stack key RunDependenciesChanged (Just result)
169+
then compute db stack key RunDependenciesChanged (Just result)
169170
-- else kick the rest of the deps
170171
else refreshDeps newVisited db stack key result deps
171172

172173

173174
-- refresh :: Database -> Stack -> Key -> Maybe Result -> IO Result
174175
-- refresh _ st k _ | traceShow ("refresh", st, k) False = undefined
175-
refresh :: Database -> Stack -> Key -> Maybe Result -> AIO Result
176+
refresh :: Database -> Stack -> Key -> Maybe Result -> IO Result
176177
refresh db stack key result = case (addStack key stack, result) of
177178
(Left e, _) -> throw e
178179
(Right stack, Just me@Result{resultDeps = ResultDeps deps}) -> refreshDeps mempty db stack key me (reverse deps)
179-
(Right stack, _) -> compute' db stack key RunDependenciesChanged result
180-
181-
compute' :: Database -> Stack -> Key -> RunMode -> Maybe Result -> AIO Result
182-
compute' db stack key mode result = liftIO $ compute db stack key mode result
180+
(Right stack, _) -> compute db stack key RunDependenciesChanged result
183181
-- | Compute a key.
184182
compute :: Database -> Stack -> Key -> RunMode -> Maybe Result -> IO Result
185183
-- compute _ st k _ _ | traceShow ("compute", st, k) False = undefined
@@ -284,68 +282,5 @@ transitiveDirtySet database = flip State.execStateT mempty . traverse_ loop
284282
next <- lift $ atomically $ getReverseDependencies database x
285283
traverse_ loop (maybe mempty toListKeySet next)
286284

287-
--------------------------------------------------------------------------------
288-
-- Asynchronous computations with cancellation
289-
290-
-- | A simple monad to implement cancellation on top of 'Async',
291-
-- generalizing 'withAsync' to monadic scopes.
292-
newtype AIO a = AIO { unAIO :: ReaderT (TVar [Async ()]) IO a }
293-
deriving newtype (Applicative, Functor, Monad, MonadIO)
294-
295-
data AsyncParentKill = AsyncParentKill ThreadId Step
296-
deriving (Show, Eq)
297-
298-
instance Exception AsyncParentKill where
299-
toException = asyncExceptionToException
300-
fromException = asyncExceptionFromException
301-
302-
-- | Run the monadic computation, cancelling all the spawned asyncs if an exception arises
303-
runAIO :: Step -> AIO a -> IO a
304-
runAIO s (AIO act) = do
305-
asyncsRef <- newTVarIO []
306-
-- Log the exact exception (including async exceptions) before cleanup,
307-
-- then rethrow to preserve previous semantics.
308-
runReaderT act asyncsRef `onException` do
309-
asyncs <- atomically $ do
310-
r <- readTVar asyncsRef
311-
modifyTVar' asyncsRef $ const []
312-
return r
313-
tid <- myThreadId
314-
cleanupAsync asyncs tid s
315-
316-
-- | Like 'async' but with built-in cancellation.
317-
-- Returns an IO action to wait on the result.
318-
asyncWithCleanUp :: AIO a -> AIO (IO a)
319-
asyncWithCleanUp act = do
320-
st <- AIO ask
321-
io <- unliftAIO act
322-
-- mask to make sure we keep track of the spawned async
323-
liftIO $ uninterruptibleMask $ \restore -> do
324-
a <- async $ restore io
325-
atomically $ modifyTVar' st (void a :)
326-
return $ wait a
327-
328-
unliftAIO :: AIO a -> AIO (IO a)
329-
unliftAIO act = do
330-
st <- AIO ask
331-
return $ runReaderT (unAIO act) st
332285

333-
instance MonadUnliftIO AIO where
334-
withRunInIO k = do
335-
st <- AIO ask
336-
liftIO $ k (\aio -> runReaderT (unAIO aio) st)
337286

338-
cleanupAsync :: [Async a] -> ThreadId -> Step -> IO ()
339-
-- mask to make sure we interrupt all the asyncs
340-
cleanupAsync asyncs tid step = uninterruptibleMask $ \unmask -> do
341-
-- interrupt all the asyncs without waiting
342-
-- mapM_ (\a -> throwTo (asyncThreadId a) AsyncCancelled) asyncs
343-
mapM_ (\a -> throwTo (asyncThreadId a) $ AsyncParentKill tid step) asyncs
344-
-- Wait until all the asyncs are done
345-
-- But if it takes more than 10 seconds, log to stderr
346-
unless (null asyncs) $ do
347-
let warnIfTakingTooLong = unmask $ forever $ do
348-
sleep 10
349-
traceM "cleanupAsync: waiting for asyncs to finish"
350-
withAsync warnIfTakingTooLong $ \_ ->
351-
mapM_ waitCatch asyncs

0 commit comments

Comments
 (0)