Skip to content

Commit 08aa58d

Browse files
committed
Only do async session restarts
1 parent 85cbae7 commit 08aa58d

3 files changed

Lines changed: 68 additions & 39 deletions

File tree

ghcide-test/exe/ShakeRestartTests.hs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ tests = testGroup "shake restart merging"
2121
newestVFSModified VFSUnmodified vfs1 @?= vfs1
2222

2323
, testCase "mergePendingRestart Nothing" $ do
24-
let p = PendingRestart VFSUnmodified (pure []) ["reason"] [] []
24+
let p = PendingRestart VFSUnmodified [] ["reason"] [] []
2525
if mergePendingRestart p Nothing == p
2626
then pure ()
2727
else assertFailure "merging with nothing should get new"
@@ -31,18 +31,18 @@ tests = testGroup "shake restart merging"
3131
done2 <- newEmptyTMVarIO
3232
let key1 = newKey ("1" :: String)
3333
key2 = newKey ("2" :: String)
34-
p1 = PendingRestart VFSUnmodified (pure [key1]) ["r1"] [] [done1]
35-
p2 = PendingRestart VFSUnmodified (pure [key2]) ["r2"] [] [done2]
34+
p1 = PendingRestart VFSUnmodified [pure [key1]] ["r1"] [] [done1]
35+
p2 = PendingRestart VFSUnmodified [pure [key2]] ["r2"] [] [done2]
3636
merged = mergePendingRestart p1 (Just p2)
3737

3838
pendingRestartReasons merged @?= ["r1", "r2"]
39-
keys <- pendingRestartActionBetweenSessions merged
40-
keys @?= [key2, key1]
39+
keys <- sequence $ pendingRestartActionBetweenSessions merged
40+
concat keys @?= [key2, key1]
4141

4242
, testCase "RestartSlot coalescing" $ do
4343
slot <- newRestartSlot
44-
let p1 = PendingRestart VFSUnmodified (pure []) ["r1"] [] []
45-
p2 = PendingRestart VFSUnmodified (pure []) ["r2"] [] []
44+
let p1 = PendingRestart VFSUnmodified [] ["r1"] [] []
45+
p2 = PendingRestart VFSUnmodified [] ["r2"] [] []
4646

4747
atomicModifyIORef'_ (queuedRestart slot) $ Just . mergePendingRestart p1
4848
atomicModifyIORef'_ (queuedRestart slot) $ Just . mergePendingRestart p2

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

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -819,11 +819,11 @@ delayedAction a = do
819819
liftIO $ shakeEnqueue extras a
820820

821821
data PendingRestart = PendingRestart
822-
{ pendingRestartVFS :: VFSModified
823-
, pendingRestartActionBetweenSessions :: IO [Key]
824-
, pendingRestartReasons :: [T.Text]
825-
, pendingRestartActions :: [DelayedActionInternal]
826-
, pendingRestartDoneSignals :: [TMVar ()]
822+
{ pendingRestartVFS :: !VFSModified
823+
, pendingRestartActionBetweenSessions :: ![IO [Key]]
824+
, pendingRestartReasons :: ![T.Text]
825+
, pendingRestartActions :: ![DelayedActionInternal]
826+
, pendingRestartDoneSignals :: ![TMVar ()]
827827
}
828828

829829
newestVFSModified :: VFSModified -> VFSModified -> VFSModified
@@ -834,34 +834,51 @@ mergePendingRestart :: PendingRestart -> Maybe PendingRestart -> PendingRestart
834834
mergePendingRestart new Nothing = new
835835
mergePendingRestart new (Just old) = PendingRestart
836836
{ pendingRestartVFS = newestVFSModified (pendingRestartVFS new) (pendingRestartVFS old)
837-
, pendingRestartReasons = pendingRestartReasons new <> pendingRestartReasons old
838-
-- TODO: Contains a quadratic list append on the number of accumulated shake restarts.
839-
, pendingRestartActions = pendingRestartActions old <> pendingRestartActions new
840-
, pendingRestartActionBetweenSessions = pendingRestartActionBetweenSessions old <> pendingRestartActionBetweenSessions new
841-
, pendingRestartDoneSignals = pendingRestartDoneSignals new <> pendingRestartDoneSignals old }
837+
, pendingRestartReasons = pendingRestartReasons new ++ pendingRestartReasons old
838+
, pendingRestartActions = pendingRestartActions new ++ pendingRestartActions old
839+
, pendingRestartActionBetweenSessions = pendingRestartActionBetweenSessions new ++ pendingRestartActionBetweenSessions old
840+
, pendingRestartDoneSignals = pendingRestartDoneSignals new ++ pendingRestartDoneSignals old
841+
}
842842

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

848854
newRestartSlot :: IO RestartSlot
849-
newRestartSlot = RestartSlot <$> newIORef Nothing <*> newEmptyMVar
855+
newRestartSlot = do
856+
initialBarrier <- newTMVarIO () -- starts filled (no pending restart)
857+
RestartSlot <$> newIORef Nothing <*> newEmptyMVar <*> newTVarIO initialBarrier
850858

851859
-- | Restart the current 'ShakeSession' with the given system actions.
852-
-- Any actions running in the current session will be aborted,
853-
-- but actions added via 'shakeEnqueue' will be requeued.
860+
--
861+
-- Any actions running in the current session will be aborted, but actions added
862+
-- via 'shakeEnqueue' will be requeued.
854863
shakeRestart :: IdeState -> VFSModified -> T.Text -> [DelayedAction ()] -> IO [Key] -> IO ()
855864
shakeRestart IdeState{..} vfs reason acts ioActionBetweenShakeSession = do
856865
restartDone <- newEmptyTMVarIO
857-
atomicModifyIORef'_ (queuedRestart (restartSlot shakeExtras)) $ Just . mergePendingRestart PendingRestart
866+
let slot = restartSlot shakeExtras
867+
-- Publish this restart's barrier, that dependents LSP requests can wait on.
868+
atomically $ writeTVar (lastRestartBarrier slot) restartDone
869+
atomicModifyIORef'_ (queuedRestart slot) $ Just . mergePendingRestart PendingRestart
858870
{ pendingRestartVFS = vfs
859-
, pendingRestartActionBetweenSessions = ioActionBetweenShakeSession
871+
, pendingRestartActionBetweenSessions = [ioActionBetweenShakeSession]
860872
, pendingRestartReasons = [reason]
861873
, pendingRestartActions = acts
862874
, pendingRestartDoneSignals = [restartDone]
863875
}
864-
void $ tryPutMVar (restartSignal (restartSlot shakeExtras)) ()
876+
void $ tryPutMVar (restartSignal slot) ()
877+
-- Block until the restart (including ioActionBetweenShakeSession) completes.
878+
-- This preserves the invariant from the original synchronous shakeRestart:
879+
-- callers (e.g. the session loader) must not proceed until their
880+
-- between-session actions have run, otherwise downstream rules can observe
881+
-- stale results (see Note at Session.hs restartSession call site).
865882
atomically $ readTMVar restartDone
866883

867884
-- | Run a worker that asynchronously processes shake restart requests. Will
@@ -880,10 +897,10 @@ processPendingRestart recorder IdeState{..} = do
880897
takeMVar (restartSignal (restartSlot shakeExtras))
881898
pendingRestart <- atomicModifyIORef' (queuedRestart (restartSlot shakeExtras)) (Nothing,)
882899
void $ forM pendingRestart $ \PendingRestart {..} -> do
883-
flip finally (atomically $ traverse (flip tryPutTMVar ()) pendingRestartDoneSignals) $ do
900+
flip finally (atomically $ traverse (flip tryPutTMVar ()) (reverse pendingRestartDoneSignals)) $ do
884901
let sessionAction runner = do
885902
(stopTime,()) <- duration $ logErrorAfter 10 $ cancelShakeSession runner
886-
keys <- pendingRestartActionBetweenSessions
903+
keys <- fmap concat (sequence (reverse pendingRestartActionBetweenSessions))
887904
-- it is every important to update the dirty keys after we enter the critical section
888905
-- see Note [Housekeeping rule cache and dirty key outside of hls-graph]
889906
atomically $ modifyTVar' (dirtyKeys shakeExtras) $ \x -> foldl' (flip insertKeySet) x keys
@@ -892,13 +909,15 @@ processPendingRestart recorder IdeState{..} = do
892909
queue <- atomicallyNamed "actionQueue - peek" $ peekInProgress $ actionQueue shakeExtras
893910

894911
-- this log is required by tests
895-
logWith recorder Debug $ LogBuildSessionRestart pendingRestartReasons queue backlog stopTime res
912+
logWith recorder Debug $ LogBuildSessionRestart (reverse pendingRestartReasons) queue backlog stopTime res
896913

897914
withMVar' shakeSession sessionAction $ \() ->
898915
-- It is crucial to be masked here, otherwise we can get killed
899916
-- between spawning the new thread and updating shakeSession.
900917
-- See https://github.com/haskell/ghcide/issues/79
901-
(,()) <$> newSession recorder shakeExtras pendingRestartVFS shakeDb pendingRestartActions pendingRestartReasons
918+
(,()) <$> newSession recorder shakeExtras pendingRestartVFS shakeDb
919+
(reverse pendingRestartActions)
920+
(reverse pendingRestartReasons)
902921
pure ()
903922
where
904923
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)