Skip to content

Commit 1fd46bf

Browse files
committed
1. mergeMultiple restarts if they appear at once.
2. spawns only if need when building a key, see `builderOneCoroutine`
1 parent 8f37379 commit 1fd46bf

File tree

9 files changed

+235
-106
lines changed

9 files changed

+235
-106
lines changed

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

Lines changed: 119 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ module Development.IDE.Core.Shake(
2525
IdeState, shakeSessionInit, shakeExtras, shakeDb, rootDir,
2626
ShakeExtras(..), getShakeExtras, getShakeExtrasRules,
2727
KnownTargets(..), Target(..), toKnownFiles, unionKnownTargets, mkKnownTargets,
28+
ShakeRestartArgs(..),
29+
shakeRestart,
2830
IdeRule, IdeResult, RestartQueue,
2931
GetModificationTime(GetModificationTime, GetModificationTime_, missingFileDiagnostics),
3032
shakeOpen, shakeShut,
@@ -76,7 +78,7 @@ module Development.IDE.Core.Shake(
7678
Log(..),
7779
VFSModified(..), getClientConfigAction,
7880
ThreadQueue(..),
79-
runWithSignal
81+
runWithSignal, runRestartTask, runRestartTaskDync, dynShakeRestart
8082
) where
8183

8284
import Control.Concurrent.Async
@@ -107,8 +109,7 @@ import Data.Hashable
107109
import qualified Data.HashMap.Strict as HMap
108110
import Data.HashSet (HashSet)
109111
import qualified Data.HashSet as HSet
110-
import Data.List.Extra (foldl', partition,
111-
takeEnd)
112+
import Data.List.Extra (partition, takeEnd)
112113
import qualified Data.Map.Strict as Map
113114
import Data.Maybe
114115
import qualified Data.SortedList as SL
@@ -152,7 +153,7 @@ import Development.IDE.Graph.Database (ShakeDatabase,
152153
shakeShutDatabase)
153154
import Development.IDE.Graph.Internal.Action (runActionInDbCb)
154155
import Development.IDE.Graph.Internal.Database (AsyncParentKill (AsyncParentKill))
155-
import Development.IDE.Graph.Internal.Types (Step (..),
156+
import Development.IDE.Graph.Internal.Types (DBQue, Step (..),
156157
getShakeStep)
157158
import Development.IDE.Graph.Rule
158159
import Development.IDE.Types.Action
@@ -194,7 +195,7 @@ import UnliftIO (MonadUnliftIO (withRun
194195
data Log
195196
= LogCreateHieDbExportsMapStart
196197
| LogCreateHieDbExportsMapFinish !Int
197-
| LogBuildSessionRestart !String ![DelayedActionInternal] !KeySet !Seconds !(Maybe FilePath) !Int
198+
| LogBuildSessionRestart !ShakeRestartArgs ![DelayedActionInternal] !KeySet !Seconds !(Maybe FilePath) !Int
198199
| LogBuildSessionRestartTakingTooLong !Seconds
199200
| LogDelayedAction !(DelayedAction ()) !Seconds
200201
| LogBuildSessionFinish !Step !(Either SomeException [Either SomeException ()])
@@ -227,9 +228,10 @@ instance Pretty Log where
227228
"Initializing exports map from hiedb"
228229
LogCreateHieDbExportsMapFinish exportsMapSize ->
229230
"Done initializing exports map from hiedb. Size:" <+> pretty exportsMapSize
230-
LogBuildSessionRestart reason actionQueue keyBackLog abortDuration shakeProfilePath step ->
231+
LogBuildSessionRestart restartArgs actionQueue keyBackLog abortDuration shakeProfilePath step ->
231232
vcat
232-
[ "Restarting build session due to" <+> pretty reason
233+
[ "Restarting build session due to" <+> pretty (sraReason restartArgs)
234+
, "Restarts num:" <+> pretty (sraCount $ restartArgs)
233235
, "Action Queue:" <+> pretty (map actionName actionQueue)
234236
, "Keys:" <+> pretty (map show $ toListKeySet keyBackLog)
235237
, "Current step:" <+> pretty (show step)
@@ -287,7 +289,9 @@ data HieDbWriter
287289
-- The inner `(HieDb -> IO ()) -> IO ()` wraps `HieDb -> IO ()`
288290
-- with (currently) retry functionality
289291
type IndexQueue = TaskQueue (((HieDb -> IO ()) -> IO ()) -> IO ())
290-
type RestartQueue = TaskQueue (IO ())
292+
-- type RestartQueue = TaskQueue ShakeRestartArgs
293+
type ShakeQueue = DBQue
294+
type RestartQueue = ShakeQueue
291295
type LoaderQueue = TaskQueue (IO ())
292296

293297

@@ -716,7 +720,7 @@ shakeOpen recorder lspEnv defaultConfig idePlugins debouncer
716720
semanticTokensCache <- STM.newIO
717721
positionMapping <- STM.newIO
718722
knownTargetsVar <- newTVarIO $ hashed emptyKnownTargets
719-
let restartShakeSession = shakeRestart recorder ideState
723+
let restartShakeSession = shakeRestart restartQueue
720724
persistentKeys <- newTVarIO mempty
721725
indexPending <- newTVarIO HMap.empty
722726
indexCompleted <- newTVarIO 0
@@ -761,11 +765,11 @@ shakeOpen recorder lspEnv defaultConfig idePlugins debouncer
761765
checkParents <- optCheckParents
762766

763767

764-
logMonitoring <- newLogMonitoring recorder
765-
let monitoring = logMonitoring <> argMonitoring
768+
-- logMonitoring <- newLogMonitoring recorder
769+
let monitoring = argMonitoring
766770
-- monitoring
767771
let readValuesCounter = fromIntegral . countRelevantKeys checkParents <$> getStateKeys shakeExtras
768-
readDirtyKeys = fromIntegral . countRelevantKeys checkParents . toListKeySet <$> readTVarIO(dirtyKeys shakeExtras)
772+
readDirtyKeys = fromIntegral . countRelevantKeys checkParents . toListKeySet <$> readTVarIO (dirtyKeys shakeExtras)
769773
readIndexPending = fromIntegral . HMap.size <$> readTVarIO (indexPending $ hiedbWriter shakeExtras)
770774
readExportsMap = fromIntegral . ExportsMap.exportsMapSize <$> readTVarIO (exportsMap shakeExtras)
771775
readDatabaseCount = fromIntegral . countRelevantKeys checkParents . map fst <$> shakeGetDatabaseKeys shakeDb
@@ -784,6 +788,7 @@ shakeOpen recorder lspEnv defaultConfig idePlugins debouncer
784788

785789
let ideState = IdeState{..}
786790
return ideState
791+
787792
newLogMonitoring :: MonadIO m => Recorder (WithPriority Log) -> m Monitoring
788793
newLogMonitoring logger = do
789794
actions <- newIORef []
@@ -849,37 +854,102 @@ delayedAction a = do
849854
liftIO $ shakeEnqueue extras a
850855

851856

857+
data ShakeRestartArgs = ShakeRestartArgs
858+
{ sraVfs :: !VFSModified
859+
, sraReason :: !String
860+
, sraActions :: ![DelayedAction ()]
861+
, sraBetweenSessions :: IO [Key]
862+
, sraReStartQueue :: !RestartQueue
863+
, sraCount :: !Int
864+
, sraWaitMVars :: ![MVar ()]
865+
-- ^ Just for debugging, how many restarts have been requested so far
866+
}
867+
868+
instance Show ShakeRestartArgs where
869+
show ShakeRestartArgs{..} =
870+
"ShakeRestartArgs { sraReason = " ++ show sraReason
871+
++ ", sraActions = " ++ show (map actionName sraActions)
872+
++ ", sraCount = " ++ show sraCount
873+
++ " }"
874+
875+
instance Semigroup ShakeRestartArgs where
876+
a <> b = ShakeRestartArgs
877+
{ sraVfs = sraVfs a <> sraVfs b
878+
, sraReason = sraReason a ++ "; " ++ sraReason b
879+
, sraActions = sraActions a ++ sraActions b
880+
, sraBetweenSessions = (++) <$> sraBetweenSessions a <*> sraBetweenSessions b
881+
, sraReStartQueue = sraReStartQueue a
882+
, sraCount = sraCount a + sraCount b
883+
, sraWaitMVars = sraWaitMVars a ++ sraWaitMVars b
884+
}
885+
852886
-- | Restart the current 'ShakeSession' with the given system actions.
853887
-- Any actions running in the current session will be aborted,
854888
-- but actions added via 'shakeEnqueue' will be requeued.
855-
shakeRestart :: Recorder (WithPriority Log) -> IdeState -> VFSModified -> String -> [DelayedAction ()] -> IO [Key] -> IO ()
856-
shakeRestart recorder IdeState{..} vfs reason acts ioActionBetweenShakeSession =
857-
void $ awaitRunInThreadAtHead (restartQueue shakeExtras) $ do
858-
withMVar'
859-
shakeSession
860-
(\runner -> do
861-
(stopTime,()) <- duration $ logErrorAfter 10 $ cancelShakeSession runner
862-
queue <- atomicallyNamed "actionQueue - peek" $ peekInProgress $ actionQueue shakeExtras
863-
keys <- ioActionBetweenShakeSession
864-
-- it is every important to update the dirty keys after we enter the critical section
865-
-- see Note [Housekeeping rule cache and dirty key outside of hls-graph]
866-
atomically $ modifyTVar' (dirtyKeys shakeExtras) $ \x -> foldl' (flip insertKeySet) x keys
867-
res <- shakeDatabaseProfile shakeDb
868-
backlog <- readTVarIO $ dirtyKeys shakeExtras
869-
-- this log is required by tests
870-
step <- shakeGetBuildStep shakeDb
871-
logWith recorder Debug $ LogBuildSessionRestart reason queue backlog stopTime res step
872-
)
873-
-- It is crucial to be masked here, otherwise we can get killed
874-
-- between spawning the new thread and updating shakeSession.
875-
-- See https://github.com/haskell/ghcide/issues/79
876-
(\() -> do
877-
(,()) <$> newSession recorder shakeExtras vfs shakeDb acts reason)
878-
where
879-
logErrorAfter :: Seconds -> IO () -> IO ()
880-
logErrorAfter seconds action = flip withAsync (const action) $ do
881-
sleep seconds
882-
logWith recorder Error (LogBuildSessionRestartTakingTooLong seconds)
889+
shakeRestart :: RestartQueue -> VFSModified -> String -> [DelayedAction ()] -> IO [Key] -> IO ()
890+
shakeRestart rts vfs reason acts ioActionBetweenShakeSession = do
891+
waitMVar <- newEmptyMVar
892+
void $ submitWork rts $ Left $
893+
toDyn $ ShakeRestartArgs vfs reason acts ioActionBetweenShakeSession rts 1 [waitMVar]
894+
-- Wait until the restart is done
895+
takeMVar waitMVar
896+
897+
dynShakeRestart :: Dynamic -> ShakeRestartArgs
898+
dynShakeRestart dy = case fromDynamic dy of
899+
Just shakeRestartArgs -> shakeRestartArgs
900+
Nothing -> error "Internal error, dynShakeRestart, got invalid dynamic type"
901+
902+
-- runRestartTask :: Recorder (WithPriority Log) -> IdeState -> VFSModified -> String -> [DelayedAction ()] -> IO [Key] -> IO ()
903+
-- runRestartTask recorder IdeState{..} vfs reason acts ioActionBetweenShakeSession =
904+
runRestartTaskDync :: Recorder (WithPriority Log) -> MVar IdeState -> Dynamic -> IO ()
905+
runRestartTaskDync recorder ideStateVar dy = runRestartTask recorder ideStateVar (dynShakeRestart dy)
906+
907+
runRestartTask :: Recorder (WithPriority Log) -> MVar IdeState -> ShakeRestartArgs -> IO ()
908+
runRestartTask recorder ideStateVar shakeRestartArgs = do
909+
IdeState {shakeDb, shakeSession, shakeExtras, shakeDatabaseProfile} <- readMVar ideStateVar
910+
let prepareRestart sra@ShakeRestartArgs {..} = do
911+
keys <- sraBetweenSessions
912+
-- it is every important to update the dirty keys after we enter the critical section
913+
-- see Note [Housekeeping rule cache and dirty key outside of hls-graph]
914+
atomically $ modifyTVar' (dirtyKeys shakeExtras) $ \x -> foldl' (flip insertKeySet) x keys
915+
-- Check if there is another restart request pending, if so, we run that one too
916+
readAndGo sra sraReStartQueue
917+
readAndGo sra sraReStartQueue = do
918+
nextRestartArg <- atomically $ tryReadTaskQueue sraReStartQueue
919+
case nextRestartArg of
920+
Nothing -> return sra
921+
Just (Left dy) -> do
922+
res <- prepareRestart $ dynShakeRestart dy
923+
return $ sra <> res
924+
Just (Right _) -> readAndGo sra sraReStartQueue
925+
withMVar'
926+
shakeSession
927+
( \runner -> do
928+
-- takeShakeLock shakeDb
929+
(stopTime, ()) <- duration $ logErrorAfter 10 $ cancelShakeSession runner
930+
restartArgs <- prepareRestart shakeRestartArgs
931+
queue <- atomicallyNamed "actionQueue - peek" $ peekInProgress $ actionQueue shakeExtras
932+
res <- shakeDatabaseProfile shakeDb
933+
backlog <- readTVarIO $ dirtyKeys shakeExtras
934+
-- this log is required by tests
935+
step <- shakeGetBuildStep shakeDb
936+
logWith recorder Info $ LogBuildSessionRestart restartArgs queue backlog stopTime res step
937+
return restartArgs
938+
)
939+
-- It is crucial to be masked here, otherwise we can get killed
940+
-- between spawning the new thread and updating shakeSession.
941+
-- See https://github.com/haskell/ghcide/issues/79
942+
( \(ShakeRestartArgs {..}) ->
943+
do
944+
(,()) <$> newSession recorder shakeExtras sraVfs shakeDb sraActions sraReason
945+
`finally` for_ sraWaitMVars (`putMVar` ())
946+
)
947+
where
948+
logErrorAfter :: Seconds -> IO () -> IO ()
949+
logErrorAfter seconds action = flip withAsync (const action) $ do
950+
sleep seconds
951+
logWith recorder Error (LogBuildSessionRestartTakingTooLong seconds)
952+
883953

884954
-- | Enqueue an action in the existing 'ShakeSession'.
885955
-- Returns a computation to block until the action is run, propagating exceptions.
@@ -893,7 +963,7 @@ shakeEnqueue ShakeExtras{actionQueue, shakeRecorder} act = do
893963
logWith shakeRecorder Debug $ LogShakeText (T.pack $ "Enqueued action: " <> actionName act)
894964
let wait' barrier =
895965
waitBarrier barrier `catches`
896-
[ Handler(\BlockedIndefinitelyOnMVar ->
966+
[ Handler (\BlockedIndefinitelyOnMVar ->
897967
fail $ "internal bug: forever blocked on MVar for " <>
898968
actionName act)
899969
, Handler (\e@(SomeAsyncException _) -> do
@@ -906,6 +976,10 @@ shakeEnqueue ShakeExtras{actionQueue, shakeRecorder} act = do
906976

907977
data VFSModified = VFSUnmodified | VFSModified !VFS
908978

979+
instance Semigroup VFSModified where
980+
x <> VFSUnmodified = x
981+
_ <> x = x
982+
909983
-- | Set up a new 'ShakeSession' with a set of initial actions
910984
-- Will crash if there is an existing 'ShakeSession' running.
911985
newSession
@@ -1049,7 +1123,7 @@ garbageCollectKeys label maxAge checkParents agedKeys = do
10491123
removeDirtyKey dk values st@(!counter, keys) (k, age)
10501124
| age > maxAge
10511125
, Just (kt,_) <- fromKeyType k
1052-
, not(kt `HSet.member` preservedKeys checkParents)
1126+
, not (kt `HSet.member` preservedKeys checkParents)
10531127
= atomicallyNamed "GC" $ do
10541128
gotIt <- STM.focus (Focus.member <* Focus.delete) k values
10551129
when gotIt $
@@ -1424,12 +1498,12 @@ updateFileDiagnostics :: MonadIO m
14241498
-> [FileDiagnostic] -- ^ current results
14251499
-> m ()
14261500
updateFileDiagnostics recorder fp ver k ShakeExtras{diagnostics, hiddenDiagnostics, publishedDiagnostics, debouncer, lspEnv, ideTesting} current0 = do
1427-
liftIO $ withTrace ("update diagnostics " <> fromString(fromNormalizedFilePath fp)) $ \ addTag -> do
1501+
liftIO $ withTrace ("update diagnostics " <> fromString (fromNormalizedFilePath fp)) $ \ addTag -> do
14281502
addTag "key" (show k)
14291503
let (currentShown, currentHidden) = partition ((== ShowDiag) . fdShouldShowDiagnostic) current
14301504
uri = filePathToUri' fp
14311505
addTagUnsafe :: String -> String -> String -> a -> a
1432-
addTagUnsafe msg t x v = unsafePerformIO(addTag (msg <> t) x) `seq` v
1506+
addTagUnsafe msg t x v = unsafePerformIO (addTag (msg <> t) x) `seq` v
14331507
update :: (forall a. String -> String -> a -> a) -> [FileDiagnostic] -> STMDiagnosticStore -> STM [FileDiagnostic]
14341508
update addTagUnsafeMethod new store = addTagUnsafeMethod "count" (show $ Prelude.length new) $ setStageDiagnostics addTagUnsafeMethod uri ver (renderKey k) new store
14351509
current = map (fdLspDiagnosticL %~ diagsFromRule) current0
@@ -1556,3 +1630,4 @@ runWithSignal msgStart msgEnd files rule = do
15561630
kickSignal testing lspEnv files msgStart
15571631
void $ uses rule files
15581632
kickSignal testing lspEnv files msgEnd
1633+

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

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,13 @@ import Control.Concurrent.Extra (newBarrier,
4141
signalBarrier,
4242
waitBarrier)
4343
import Control.Monad.IO.Unlift (MonadUnliftIO)
44-
import Control.Monad.Trans.Cont (evalContT)
44+
import Control.Monad.Trans.Cont (ContT, evalContT)
4545
import Development.IDE.Core.IdeConfiguration
4646
import Development.IDE.Core.Service (shutdown)
4747
import Development.IDE.Core.Shake hiding (Log)
48+
import qualified Development.IDE.Core.Shake as Shake
4849
import Development.IDE.Core.Tracing
50+
import Development.IDE.Graph.Internal.Types (DBQue)
4951
import qualified Development.IDE.Session as Session
5052
import Development.IDE.Types.Shake (WithHieDb,
5153
WithHieDbShield (..))
@@ -63,6 +65,7 @@ data Log
6365
| LogReactorThreadStopped Int
6466
| LogCancelledRequest !SomeLspId
6567
| LogSession Session.Log
68+
| LogShake Shake.Log
6669
| LogLspServer LspServerLog
6770
| LogReactorShutdownRequested Bool
6871
| LogShutDownTimeout Int
@@ -73,6 +76,7 @@ data Log
7376

7477
instance Pretty Log where
7578
pretty = \case
79+
LogShake msg -> pretty msg
7680
LogInitializeIdeStateTookTooLong seconds ->
7781
"Building the initial session took more than" <+> pretty seconds <+> "seconds"
7882
LogReactorShutdownRequested b ->
@@ -330,7 +334,7 @@ handleInit initParams env (TRequestMessage _ _ m params) = otTracedHandler "Init
330334
exceptionInHandler e
331335
k $ TResponseError (InR ErrorCodes_InternalError) (T.pack $ show e) Nothing
332336
_ <- flip forkFinally handleServerExceptionOrShutDown $ do
333-
runWithWorkerThreads (cmapWithPrio LogSession recorder) dbLoc $ \withHieDb' threadQueue' ->
337+
runWithWorkerThreads recorder ideMVar dbLoc $ \withHieDb' threadQueue' ->
334338
do
335339
ide <- ctxGetIdeState initParams env root withHieDb' threadQueue'
336340
putMVar ideMVar ide
@@ -349,14 +353,20 @@ handleInit initParams env (TRequestMessage _ _ m params) = otTracedHandler "Init
349353
pure $ Right (env,ide)
350354

351355

356+
runShakeThread :: Recorder (WithPriority Log) -> MVar IdeState -> ContT () IO DBQue
357+
runShakeThread recorder mide =
358+
withWorkerQueue
359+
(logWith (cmapWithPrio (LogSession . Session.LogSessionWorkerThread) recorder) Debug)
360+
"ShakeRestartQueue"
361+
(eitherWorker (runRestartTaskDync (cmapWithPrio LogShake recorder) mide) id)
352362
-- | runWithWorkerThreads
353363
-- create several threads to run the session, db and session loader
354364
-- see Note [Serializing runs in separate thread]
355-
runWithWorkerThreads :: Recorder (WithPriority Session.Log) -> FilePath -> (WithHieDb -> ThreadQueue -> IO ()) -> IO ()
356-
runWithWorkerThreads recorder dbLoc f = evalContT $ do
357-
(WithHieDbShield hiedb, threadQueue) <- runWithDb recorder dbLoc
358-
sessionRestartTQueue <- withWorkerQueueSimple (logWith (cmapWithPrio Session.LogSessionWorkerThread recorder) Debug) "RestartTQueue"
359-
sessionLoaderTQueue <- withWorkerQueueSimple (logWith (cmapWithPrio Session.LogSessionWorkerThread recorder) Debug) "SessionLoaderTQueue"
365+
runWithWorkerThreads :: Recorder (WithPriority Log) -> MVar IdeState -> FilePath -> (WithHieDb -> ThreadQueue -> IO ()) -> IO ()
366+
runWithWorkerThreads recorder mide dbLoc f = evalContT $ do
367+
(WithHieDbShield hiedb, threadQueue) <- runWithDb (cmapWithPrio LogSession recorder) dbLoc
368+
sessionRestartTQueue <- runShakeThread recorder mide
369+
sessionLoaderTQueue <- withWorkerQueueSimple (logWith (cmapWithPrio (LogSession . Session.LogSessionWorkerThread) recorder) Debug) "SessionLoaderTQueue"
360370
liftIO $ f hiedb (ThreadQueue threadQueue sessionRestartTQueue sessionLoaderTQueue)
361371

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

0 commit comments

Comments
 (0)