Skip to content

Commit 42b60e4

Browse files
committed
Only do async session restarts
1 parent dd74487 commit 42b60e4

2 files changed

Lines changed: 56 additions & 33 deletions

File tree

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

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -817,11 +817,11 @@ delayedAction a = do
817817
liftIO $ shakeEnqueue extras a
818818

819819
data PendingRestart = PendingRestart
820-
{ pendingRestartVFS :: VFSModified
821-
, pendingRestartActionBetweenSessions :: IO [Key]
822-
, pendingRestartReasons :: [T.Text]
823-
, pendingRestartActions :: [DelayedActionInternal]
824-
, pendingRestartDoneSignals :: [TMVar ()]
820+
{ pendingRestartVFS :: !VFSModified
821+
, pendingRestartActionBetweenSessions :: ![IO [Key]]
822+
, pendingRestartReasons :: ![T.Text]
823+
, pendingRestartActions :: ![DelayedActionInternal]
824+
, pendingRestartDoneSignals :: ![TMVar ()]
825825
}
826826

827827
newestVFSModified :: VFSModified -> VFSModified -> VFSModified
@@ -832,35 +832,46 @@ mergePendingRestart :: PendingRestart -> Maybe PendingRestart -> PendingRestart
832832
mergePendingRestart new Nothing = new
833833
mergePendingRestart new (Just old) = PendingRestart
834834
{ pendingRestartVFS = newestVFSModified (pendingRestartVFS new) (pendingRestartVFS old)
835-
, pendingRestartReasons = pendingRestartReasons new <> pendingRestartReasons old
836-
-- TODO: Contains a quadratic list append on the number of accumulated shake restarts.
837-
, pendingRestartActions = pendingRestartActions old <> pendingRestartActions new
838-
, pendingRestartActionBetweenSessions = pendingRestartActionBetweenSessions old <> pendingRestartActionBetweenSessions new
839-
, pendingRestartDoneSignals = pendingRestartDoneSignals new <> pendingRestartDoneSignals old }
835+
, pendingRestartReasons = pendingRestartReasons new ++ pendingRestartReasons old
836+
, pendingRestartActions = pendingRestartActions new ++ pendingRestartActions old
837+
, pendingRestartActionBetweenSessions = pendingRestartActionBetweenSessions new ++ pendingRestartActionBetweenSessions old
838+
, pendingRestartDoneSignals = pendingRestartDoneSignals new ++ pendingRestartDoneSignals old
839+
}
840840

841841
data RestartSlot = RestartSlot
842-
{ queuedRestart :: IORef (Maybe (PendingRestart))
843-
, restartSignal :: MVar ()
842+
{ queuedRestart :: IORef (Maybe PendingRestart)
843+
, restartSignal :: MVar ()
844+
, lastRestartBarrier :: TVar (TMVar ())
845+
-- ^ A barrier that is filled when the most recent shake restart completes.
846+
--
847+
-- Each call to 'shakeRestart' replaces this with a fresh empty TMVar. The
848+
-- restart worker fills it when the restart finishes. Dependents on the
849+
-- restart can then wait on this.
844850
}
845851

846852
newRestartSlot :: IO RestartSlot
847-
newRestartSlot = RestartSlot <$> newIORef Nothing <*> newEmptyMVar
853+
newRestartSlot = do
854+
initialBarrier <- newTMVarIO () -- starts filled (no pending restart)
855+
RestartSlot <$> newIORef Nothing <*> newEmptyMVar <*> newTVarIO initialBarrier
848856

849857
-- | Restart the current 'ShakeSession' with the given system actions.
850-
-- Any actions running in the current session will be aborted,
851-
-- but actions added via 'shakeEnqueue' will be requeued.
858+
--
859+
-- Any actions running in the current session will be aborted, but actions added
860+
-- via 'shakeEnqueue' will be requeued.
852861
shakeRestart :: IdeState -> VFSModified -> T.Text -> [DelayedAction ()] -> IO [Key] -> IO ()
853862
shakeRestart IdeState{..} vfs reason acts ioActionBetweenShakeSession = do
854863
restartDone <- newEmptyTMVarIO
855-
atomicModifyIORef'_ (queuedRestart (restartSlot shakeExtras)) $ Just . mergePendingRestart PendingRestart
864+
let slot = restartSlot shakeExtras
865+
-- Publish this restart's barrier, that dependents LSP requests can wait on.
866+
atomically $ writeTVar (lastRestartBarrier slot) restartDone
867+
atomicModifyIORef'_ (queuedRestart slot) $ Just . mergePendingRestart PendingRestart
856868
{ pendingRestartVFS = vfs
857-
, pendingRestartActionBetweenSessions = ioActionBetweenShakeSession
869+
, pendingRestartActionBetweenSessions = [ioActionBetweenShakeSession]
858870
, pendingRestartReasons = [reason]
859871
, pendingRestartActions = acts
860872
, pendingRestartDoneSignals = [restartDone]
861873
}
862-
void $ tryPutMVar (restartSignal (restartSlot shakeExtras)) ()
863-
atomically $ readTMVar restartDone
874+
void $ tryPutMVar (restartSignal slot) ()
864875

865876
-- | Run a worker that asynchronously processes shake restart requests. Will
866877
-- only ever queue upto 1 additional restart, accumulating data while processing
@@ -878,10 +889,10 @@ processPendingRestart recorder IdeState{..} = do
878889
takeMVar (restartSignal (restartSlot shakeExtras))
879890
pendingRestart <- atomicModifyIORef' (queuedRestart (restartSlot shakeExtras)) (Nothing,)
880891
void $ forM pendingRestart $ \PendingRestart {..} -> do
881-
flip finally (atomically $ traverse (flip tryPutTMVar ()) pendingRestartDoneSignals) $ do
892+
flip finally (atomically $ traverse (flip tryPutTMVar ()) (reverse pendingRestartDoneSignals)) $ do
882893
let sessionAction runner = do
883894
(stopTime,()) <- duration $ logErrorAfter 10 $ cancelShakeSession runner
884-
keys <- pendingRestartActionBetweenSessions
895+
keys <- fmap concat (sequence (reverse pendingRestartActionBetweenSessions))
885896
-- it is every important to update the dirty keys after we enter the critical section
886897
-- see Note [Housekeeping rule cache and dirty key outside of hls-graph]
887898
atomically $ modifyTVar' (dirtyKeys shakeExtras) $ \x -> foldl' (flip insertKeySet) x keys
@@ -890,13 +901,15 @@ processPendingRestart recorder IdeState{..} = do
890901
queue <- atomicallyNamed "actionQueue - peek" $ peekInProgress $ actionQueue shakeExtras
891902

892903
-- this log is required by tests
893-
logWith recorder Debug $ LogBuildSessionRestart pendingRestartReasons queue backlog stopTime res
904+
logWith recorder Debug $ LogBuildSessionRestart (reverse pendingRestartReasons) queue backlog stopTime res
894905

895906
withMVar' shakeSession sessionAction $ \() ->
896907
-- It is crucial to be masked here, otherwise we can get killed
897908
-- between spawning the new thread and updating shakeSession.
898909
-- See https://github.com/haskell/ghcide/issues/79
899-
(,()) <$> newSession recorder shakeExtras pendingRestartVFS shakeDb pendingRestartActions pendingRestartReasons
910+
(,()) <$> newSession recorder shakeExtras pendingRestartVFS shakeDb
911+
(reverse pendingRestartActions)
912+
(reverse pendingRestartReasons)
900913
pure ()
901914
where
902915
logErrorAfter :: Seconds -> IO () -> IO ()

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

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ handleInit lifecycleCtx env (TRequestMessage _ _ m params) = otTracedHandler "In
343343
-- Completed MVars are pruned when new notifications are added.
344344
notificationLocks <- newTVarIO ([] :: [TMVar ()])
345345
let
346-
consumeChannel = do
346+
consumeChannel threadQueue = do
347347
msg <- readChan $ ctxClientMsgChan lifecycleCtx
348348
case msg of
349349
ReactorNotification act -> do
@@ -352,9 +352,21 @@ handleInit lifecycleCtx env (TRequestMessage _ _ m params) = otTracedHandler "In
352352
old <- readTVar notificationLocks
353353
pruned <- filterM (\m -> isNothing <$> tryReadTMVar m) old
354354
writeTVar notificationLocks (done : pruned)
355-
void $ async $
356-
handle exceptionInHandler act
357-
`finally` atomically (putTMVar done ())
355+
let
356+
slot = tRestartSlot threadQueue
357+
-- After the notification handler returns, check whether
358+
-- a shake restart was triggered.
359+
--
360+
-- If so, wait for it to complete before signaling 'done'
361+
-- so that subsequent requests see the updated VFS /
362+
-- session.
363+
restartDone = do
364+
barrier <- atomically $ readTVar (lastRestartBarrier slot)
365+
async $ atomically $ do
366+
readTMVar barrier
367+
putTMVar done ()
368+
369+
finally (handle exceptionInHandler act) restartDone
358370
ReactorRequest _id act k -> do
359371
currentNotifications <- readTVarIO notificationLocks
360372
void $ async $ do
@@ -365,14 +377,12 @@ handleInit lifecycleCtx env (TRequestMessage _ _ m params) = otTracedHandler "In
365377
ide <- ctxGetIdeState lifecycleCtx env root withHieDb' threadQueue'
366378
registerIdeConfiguration (shakeExtras ide) initConfig
367379
putMVar ideMVar ide
368-
-- Keep this after putMVar ideMVar ide; otherwise shutdown during
369-
-- initialization could leave handleInit blocked indefinitely on readMVar.
370-
withRestartWorker ide $ do
371-
untilReactorStopSignal $ forever consumeChannel
372-
logWith recorder Info LogReactorThreadStopped
380+
381+
withRestartWorker ide $ untilReactorStopSignal $ forever (consumeChannel threadQueue')
382+
logWith recorder Info LogReactorThreadStopped
373383

374384
ide <- readMVar ideMVar
375-
pure $ Right (env,ide)
385+
pure $ Right (env, ide)
376386

377387

378388
-- | runWithWorkerThreads

0 commit comments

Comments
 (0)