Skip to content

Commit 8c17daa

Browse files
committed
Update reactor shutdown logging, and improve shutdown handling
1 parent 7bf694a commit 8c17daa

File tree

3 files changed

+68
-56
lines changed

3 files changed

+68
-56
lines changed

.github/workflows/flakiness.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,6 @@ jobs:
7070
run: |
7171
cabal --version
7272
ghc --version
73-
- name: Build ghcide-tests
74-
run: cabal build ghcide-tests
7573
7674
- name: Run flakiness loop
7775
id: run-loop
@@ -83,7 +81,6 @@ jobs:
8381
LOG_STDERR: '1'
8482
TEST_PATTERNS: ${{ github.event.inputs.test_patterns }}
8583
PATTERN_FILE: ${{ github.event.inputs.pattern_file }}
86-
NO_BUILD_ONCE: '1'
8784
run: |
8885
# Run with a sensible default of 1000 iterations on PRs;
8986
max_iter="${{ github.event.inputs.max_iter }}"

cabal.project

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,5 @@ if impl(ghc >= 9.11)
6060
source-repository-package
6161
type: git
6262
location: https://github.com/soulomoon/lsp.git
63-
tag: a747f731214ac269806df2ecad13044e094c96bf
63+
tag: df83bb0fe7ea3f09339dae4593efe6b4a5284413
6464
subdir: lsp lsp-types lsp-test

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

Lines changed: 67 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -59,25 +59,30 @@ data Log
5959
= LogRegisteringIdeConfig !IdeConfiguration
6060
| LogReactorThreadException !SomeException
6161
| LogReactorMessageActionException !SomeException
62-
| LogReactorThreadStopped
62+
| LogReactorThreadStopped Int
6363
| LogCancelledRequest !SomeLspId
6464
| LogSession Session.Log
6565
| LogLspServer LspServerLog
66-
| LogServerShutdownMessage
66+
| LogReactorShutdownRequested Bool
6767
| LogShutDownTimeout Int
6868
| LogServerExitWith (Either () Int)
69+
| LogReactorShutdownConfirmed !T.Text
6970
deriving Show
7071

7172
instance Pretty Log where
7273
pretty = \case
74+
LogReactorShutdownRequested b ->
75+
"Requested reactor shutdown; stop signal posted: " <+> pretty b
76+
LogReactorShutdownConfirmed msg ->
77+
"Reactor shutdown confirmed: " <+> pretty msg
7378
LogServerExitWith (Right 0) ->
74-
"Server exited with succefully"
79+
"Server exited successfully"
7580
LogServerExitWith (Right code) ->
7681
"Server exited with failure code" <+> pretty code
7782
LogServerExitWith (Left _) ->
7883
"Server forcefully exited due to exception in reactor thread"
7984
LogShutDownTimeout seconds ->
80-
"Shutdown timeout, the server will exit now after waiting for" <+> pretty seconds <+> "milliseconds"
85+
"Shutdown timeout, the server will exit now after waiting for" <+> pretty seconds <+> "seconds"
8186
LogRegisteringIdeConfig ideConfig ->
8287
-- This log is also used to identify if HLS starts successfully in vscode-haskell,
8388
-- don't forget to update the corresponding test in vscode-haskell if the text in
@@ -91,13 +96,12 @@ instance Pretty Log where
9196
vcat
9297
[ "ReactorMessageActionException"
9398
, pretty $ displayException e ]
94-
LogReactorThreadStopped ->
95-
"Reactor thread stopped"
99+
LogReactorThreadStopped i ->
100+
"Reactor thread stopped" <+> pretty i
96101
LogCancelledRequest requestId ->
97102
"Cancelled request" <+> viaShow requestId
98103
LogSession msg -> pretty msg
99104
LogLspServer msg -> pretty msg
100-
LogServerShutdownMessage -> "Received shutdown message"
101105

102106
-- | Context for initializing the LSP language server.
103107
-- This record encapsulates all the configuration and callback functions
@@ -111,8 +115,10 @@ data InitializationContext config = InitializationContext
111115
-- ^ Function to determine the HIE database location for a given root path
112116
, ctxGetIdeState :: LSP.LanguageContextEnv config -> FilePath -> WithHieDb -> ThreadQueue -> IO IdeState
113117
-- ^ Function to create and initialize the IDE state with the given environment
114-
, ctxLifetime :: (MVar (), IO ())
115-
-- ^ Lifetime control: MVar to signal shutdown and confirmation action
118+
, ctxUntilReactorStopSignal :: IO () -> IO ()
119+
-- ^ Lifetime control: MVar to signal reactor shutdown
120+
, ctxconfirmReactorShutdown :: T.Text -> IO ()
121+
-- ^ Callback to log/confirm reactor shutdown with a reason
116122
, ctxForceShutdown :: IO ()
117123
-- ^ Action to forcefully exit the server when exception occurs
118124
, ctxClearReqId :: SomeLspId -> IO ()
@@ -196,18 +202,21 @@ setupLSP recorder defaultRoot getHieDbLoc userHandlers getIdeState clientMsgVar
196202

197203
-- An MVar to control the lifetime of the reactor loop.
198204
-- The loop will be stopped and resources freed when it's full
199-
reactorLifetime <- newEmptyMVar
200-
reactorLifetimeConfirmBarrier <- newBarrier
201-
let stopReactorLoopConfirm =
202-
signalBarrier reactorLifetimeConfirmBarrier ()
203-
let stopReactorLoop = do
204-
_ <- tryPutMVar reactorLifetime ()
205-
let timeOutSeconds = 3 * 1_000_000
206-
timeout timeOutSeconds (waitBarrier reactorLifetimeConfirmBarrier) >>= \case
207-
Just () -> pure ()
208-
-- If we don't get confirmation within 3 seconds, we log a warning and shutdown anyway.
209-
-- This is to avoid deadlocks in case the client does not respond to shutdown requests.
210-
Nothing -> logWith recorder Warning $ LogShutDownTimeout timeOutSeconds
205+
reactorStopSignal <- newEmptyMVar
206+
reactorConfirmBarrier <- newBarrier
207+
let
208+
untilReactorStopSignal = untilMVar reactorStopSignal
209+
confirmReactorShutdown reason = do
210+
logWith recorder Debug $ LogReactorShutdownConfirmed reason
211+
signalBarrier reactorConfirmBarrier ()
212+
requestReactorShutdown = do
213+
k <- tryPutMVar reactorStopSignal ()
214+
logWith recorder Info $ LogReactorShutdownRequested k
215+
let timeOutSeconds = 2
216+
timeout (timeOutSeconds * 1_000_000) (waitBarrier reactorConfirmBarrier) >>= \case
217+
Just () -> pure ()
218+
-- If we don't get confirmation within 2 seconds, we log a warning and shutdown anyway.
219+
Nothing -> logWith recorder Warning $ LogShutDownTimeout timeOutSeconds
211220

212221
-- Forcefully exit
213222
let exit = void $ tryPutMVar clientMsgVar ()
@@ -236,7 +245,7 @@ setupLSP recorder defaultRoot getHieDbLoc userHandlers getIdeState clientMsgVar
236245
let staticHandlers = mconcat
237246
[ userHandlers
238247
, cancelHandler cancelRequest
239-
, shutdownHandler recorder stopReactorLoop
248+
, shutdownHandler recorder requestReactorShutdown
240249
]
241250
-- Cancel requests are special since they need to be handled
242251
-- out of order to be useful. Existing handlers are run afterwards.
@@ -246,7 +255,8 @@ setupLSP recorder defaultRoot getHieDbLoc userHandlers getIdeState clientMsgVar
246255
, ctxDefaultRoot = defaultRoot
247256
, ctxGetHieDbLoc = getHieDbLoc
248257
, ctxGetIdeState = getIdeState
249-
, ctxLifetime = (reactorLifetime, stopReactorLoopConfirm)
258+
, ctxUntilReactorStopSignal = untilReactorStopSignal
259+
, ctxconfirmReactorShutdown = confirmReactorShutdown
250260
, ctxForceShutdown = exit
251261
, ctxClearReqId = clearReqId
252262
, ctxWaitForCancel = waitForCancel
@@ -256,8 +266,7 @@ setupLSP recorder defaultRoot getHieDbLoc userHandlers getIdeState clientMsgVar
256266
let doInitialize = handleInit initParams
257267

258268
let interpretHandler (env, st) = LSP.Iso (LSP.runLspT env . flip (runReaderT . unServerM) (clientMsgChan,st)) liftIO
259-
260-
let onExit = [stopReactorLoop, exit]
269+
let onExit = [void $ tryPutMVar reactorStopSignal ()]
261270

262271
pure MkSetup {doInitialize, staticHandlers, interpretHandler, onExit}
263272

@@ -267,30 +276,33 @@ handleInit
267276
-> LSP.LanguageContextEnv config -> TRequestMessage Method_Initialize -> IO (Either err (LSP.LanguageContextEnv config, IdeState))
268277
handleInit initParams env (TRequestMessage _ _ m params) = otTracedHandler "Initialize" (show m) $ \sp -> do
269278
traceWithSpan sp params
270-
-- only shift if lsp root is different from the rootDir
271-
-- see Note [Root Directory]
272-
let recorder = ctxRecorder initParams
273-
defaultRoot = ctxDefaultRoot initParams
274-
(lifetime, lifetimeConfirm) = ctxLifetime initParams
279+
-- only shift if lsp root is different from the rootDir
280+
-- see Note [Root Directory]
281+
let
282+
recorder = ctxRecorder initParams
283+
defaultRoot = ctxDefaultRoot initParams
284+
untilReactorStopSignal = ctxUntilReactorStopSignal initParams
285+
lifetimeConfirm = ctxconfirmReactorShutdown initParams
275286
root <- case LSP.resRootPath env of
276-
Just lspRoot | lspRoot /= defaultRoot -> setCurrentDirectory lspRoot >> return lspRoot
277-
_ -> pure defaultRoot
287+
Just lspRoot | lspRoot /= defaultRoot -> setCurrentDirectory lspRoot >> return lspRoot
288+
_ -> pure defaultRoot
278289
dbLoc <- ctxGetHieDbLoc initParams root
279290
let initConfig = parseConfiguration params
280291
logWith recorder Info $ LogRegisteringIdeConfig initConfig
281292
ideMVar <- newEmptyMVar
282293

283294
let handleServerExceptionOrShutDown me = do
284-
-- try to shutdown shake
285-
tryReadMVar ideMVar >>= \case
286-
Nothing -> return ()
287-
Just ide -> shutdown ide
288-
lifetimeConfirm
295+
-- shutdown shake
296+
readMVar ideMVar >>= \case
297+
ide -> shutdown ide
289298
case me of
290299
Left e -> do
300+
lifetimeConfirm "due to exception in reactor thread or shutdown message"
291301
logWith recorder Error $ LogReactorThreadException e
292302
ctxForceShutdown initParams
293-
_ -> return ()
303+
_ -> do
304+
lifetimeConfirm "due to shutdown message"
305+
return ()
294306

295307
exceptionInHandler e = do
296308
logWith recorder Error $ LogReactorMessageActionException e
@@ -314,17 +326,21 @@ handleInit initParams env (TRequestMessage _ _ m params) = otTracedHandler "Init
314326
exceptionInHandler e
315327
k $ TResponseError (InR ErrorCodes_InternalError) (T.pack $ show e) Nothing
316328
_ <- flip forkFinally handleServerExceptionOrShutDown $ do
317-
untilMVar lifetime $ runWithWorkerThreads (cmapWithPrio LogSession recorder) dbLoc $ \withHieDb' threadQueue' -> do
318-
ide <- ctxGetIdeState initParams env root withHieDb' threadQueue'
319-
putMVar ideMVar ide
320-
forever $ do
321-
msg <- readChan $ ctxClientMsgChan initParams
322-
-- We dispatch notifications synchronously and requests asynchronously
323-
-- This is to ensure that all file edits and config changes are applied before a request is handled
324-
case msg of
325-
ReactorNotification act -> handle exceptionInHandler act
326-
ReactorRequest _id act k -> void $ async $ checkCancelled _id act k
327-
logWith recorder Info LogReactorThreadStopped
329+
runWithWorkerThreads (cmapWithPrio LogSession recorder) dbLoc $ \withHieDb' threadQueue' ->
330+
do
331+
ide <- ctxGetIdeState initParams env root withHieDb' threadQueue'
332+
putMVar ideMVar ide
333+
-- We might be blocked indefinitly at initialization if reactorStop is signaled
334+
-- before we putMVar.
335+
untilReactorStopSignal $ forever $ do
336+
msg <- readChan $ ctxClientMsgChan initParams
337+
-- We dispatch notifications synchronously and requests asynchronously
338+
-- This is to ensure that all file edits and config changes are applied before a request is handled
339+
case msg of
340+
ReactorNotification act -> handle exceptionInHandler act
341+
ReactorRequest _id act k -> void $ async $ checkCancelled _id act k
342+
logWith recorder Info $ LogReactorThreadStopped 1
343+
logWith recorder Info $ LogReactorThreadStopped 2
328344

329345
ide <- readMVar ideMVar
330346
registerIdeConfiguration (shakeExtras ide) initConfig
@@ -360,10 +376,9 @@ cancelHandler cancelRequest = LSP.notificationHandler SMethod_CancelRequest $ \T
360376
toLspId (InR y) = IdString y
361377

362378
shutdownHandler :: Recorder (WithPriority Log) -> IO () -> LSP.Handlers (ServerM c)
363-
shutdownHandler recorder stopReactor = LSP.requestHandler SMethod_Shutdown $ \_ resp -> do
364-
liftIO $ logWith recorder Debug LogServerShutdownMessage
379+
shutdownHandler _recorder requestReactorShutdown = LSP.requestHandler SMethod_Shutdown $ \_ resp -> do
365380
-- stop the reactor to free up the hiedb connection and shut down shake
366-
liftIO stopReactor
381+
liftIO requestReactorShutdown
367382
resp $ Right Null
368383

369384
modifyOptions :: LSP.Options -> LSP.Options

0 commit comments

Comments
 (0)