@@ -59,25 +59,30 @@ data Log
59
59
= LogRegisteringIdeConfig ! IdeConfiguration
60
60
| LogReactorThreadException ! SomeException
61
61
| LogReactorMessageActionException ! SomeException
62
- | LogReactorThreadStopped
62
+ | LogReactorThreadStopped Int
63
63
| LogCancelledRequest ! SomeLspId
64
64
| LogSession Session. Log
65
65
| LogLspServer LspServerLog
66
- | LogServerShutdownMessage
66
+ | LogReactorShutdownRequested Bool
67
67
| LogShutDownTimeout Int
68
68
| LogServerExitWith (Either () Int )
69
+ | LogReactorShutdownConfirmed ! T. Text
69
70
deriving Show
70
71
71
72
instance Pretty Log where
72
73
pretty = \ case
74
+ LogReactorShutdownRequested b ->
75
+ " Requested reactor shutdown; stop signal posted: " <+> pretty b
76
+ LogReactorShutdownConfirmed msg ->
77
+ " Reactor shutdown confirmed: " <+> pretty msg
73
78
LogServerExitWith (Right 0 ) ->
74
- " Server exited with succefully "
79
+ " Server exited successfully "
75
80
LogServerExitWith (Right code) ->
76
81
" Server exited with failure code" <+> pretty code
77
82
LogServerExitWith (Left _) ->
78
83
" Server forcefully exited due to exception in reactor thread"
79
84
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 "
81
86
LogRegisteringIdeConfig ideConfig ->
82
87
-- This log is also used to identify if HLS starts successfully in vscode-haskell,
83
88
-- don't forget to update the corresponding test in vscode-haskell if the text in
@@ -91,13 +96,12 @@ instance Pretty Log where
91
96
vcat
92
97
[ " ReactorMessageActionException"
93
98
, pretty $ displayException e ]
94
- LogReactorThreadStopped ->
95
- " Reactor thread stopped"
99
+ LogReactorThreadStopped i ->
100
+ " Reactor thread stopped" <+> pretty i
96
101
LogCancelledRequest requestId ->
97
102
" Cancelled request" <+> viaShow requestId
98
103
LogSession msg -> pretty msg
99
104
LogLspServer msg -> pretty msg
100
- LogServerShutdownMessage -> " Received shutdown message"
101
105
102
106
-- | Context for initializing the LSP language server.
103
107
-- This record encapsulates all the configuration and callback functions
@@ -111,8 +115,10 @@ data InitializationContext config = InitializationContext
111
115
-- ^ Function to determine the HIE database location for a given root path
112
116
, ctxGetIdeState :: LSP. LanguageContextEnv config -> FilePath -> WithHieDb -> ThreadQueue -> IO IdeState
113
117
-- ^ 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
116
122
, ctxForceShutdown :: IO ()
117
123
-- ^ Action to forcefully exit the server when exception occurs
118
124
, ctxClearReqId :: SomeLspId -> IO ()
@@ -196,18 +202,21 @@ setupLSP recorder defaultRoot getHieDbLoc userHandlers getIdeState clientMsgVar
196
202
197
203
-- An MVar to control the lifetime of the reactor loop.
198
204
-- 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
211
220
212
221
-- Forcefully exit
213
222
let exit = void $ tryPutMVar clientMsgVar ()
@@ -236,7 +245,7 @@ setupLSP recorder defaultRoot getHieDbLoc userHandlers getIdeState clientMsgVar
236
245
let staticHandlers = mconcat
237
246
[ userHandlers
238
247
, cancelHandler cancelRequest
239
- , shutdownHandler recorder stopReactorLoop
248
+ , shutdownHandler recorder requestReactorShutdown
240
249
]
241
250
-- Cancel requests are special since they need to be handled
242
251
-- out of order to be useful. Existing handlers are run afterwards.
@@ -246,7 +255,8 @@ setupLSP recorder defaultRoot getHieDbLoc userHandlers getIdeState clientMsgVar
246
255
, ctxDefaultRoot = defaultRoot
247
256
, ctxGetHieDbLoc = getHieDbLoc
248
257
, ctxGetIdeState = getIdeState
249
- , ctxLifetime = (reactorLifetime, stopReactorLoopConfirm)
258
+ , ctxUntilReactorStopSignal = untilReactorStopSignal
259
+ , ctxconfirmReactorShutdown = confirmReactorShutdown
250
260
, ctxForceShutdown = exit
251
261
, ctxClearReqId = clearReqId
252
262
, ctxWaitForCancel = waitForCancel
@@ -256,8 +266,7 @@ setupLSP recorder defaultRoot getHieDbLoc userHandlers getIdeState clientMsgVar
256
266
let doInitialize = handleInit initParams
257
267
258
268
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 () ]
261
270
262
271
pure MkSetup {doInitialize, staticHandlers, interpretHandler, onExit}
263
272
@@ -267,30 +276,33 @@ handleInit
267
276
-> LSP. LanguageContextEnv config -> TRequestMessage Method_Initialize -> IO (Either err (LSP. LanguageContextEnv config , IdeState ))
268
277
handleInit initParams env (TRequestMessage _ _ m params) = otTracedHandler " Initialize" (show m) $ \ sp -> do
269
278
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
275
286
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
278
289
dbLoc <- ctxGetHieDbLoc initParams root
279
290
let initConfig = parseConfiguration params
280
291
logWith recorder Info $ LogRegisteringIdeConfig initConfig
281
292
ideMVar <- newEmptyMVar
282
293
283
294
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
289
298
case me of
290
299
Left e -> do
300
+ lifetimeConfirm " due to exception in reactor thread or shutdown message"
291
301
logWith recorder Error $ LogReactorThreadException e
292
302
ctxForceShutdown initParams
293
- _ -> return ()
303
+ _ -> do
304
+ lifetimeConfirm " due to shutdown message"
305
+ return ()
294
306
295
307
exceptionInHandler e = do
296
308
logWith recorder Error $ LogReactorMessageActionException e
@@ -314,17 +326,21 @@ handleInit initParams env (TRequestMessage _ _ m params) = otTracedHandler "Init
314
326
exceptionInHandler e
315
327
k $ TResponseError (InR ErrorCodes_InternalError ) (T. pack $ show e) Nothing
316
328
_ <- 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
328
344
329
345
ide <- readMVar ideMVar
330
346
registerIdeConfiguration (shakeExtras ide) initConfig
@@ -360,10 +376,9 @@ cancelHandler cancelRequest = LSP.notificationHandler SMethod_CancelRequest $ \T
360
376
toLspId (InR y) = IdString y
361
377
362
378
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
365
380
-- stop the reactor to free up the hiedb connection and shut down shake
366
- liftIO stopReactor
381
+ liftIO requestReactorShutdown
367
382
resp $ Right Null
368
383
369
384
modifyOptions :: LSP. Options -> LSP. Options
0 commit comments