Skip to content

Commit b625042

Browse files
committed
Add detailed comments for shutdown handling and sender thread synchronization
1 parent 0ae76eb commit b625042

File tree

2 files changed

+33
-15
lines changed

2 files changed

+33
-15
lines changed

lsp/src/Language/LSP/Server/Core.hs

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ data LanguageContextEnv config = LanguageContextEnv
138138
, resProgressUpdateDelay :: Int
139139
-- ^ The delay between sending progress updates, in microseconds
140140
, resWaitSender :: !(IO ())
141+
-- ^ An IO action that waits for the sender thread to finish sending all pending messages.
142+
-- This is used to ensure all responses are sent before the server exits. See Note [Shutdown]
141143
}
142144

143145
-- ---------------------------------------------------------------------
@@ -212,10 +214,9 @@ data LanguageContextState config = LanguageContextState
212214
, resRegistrationsReq :: !(TVar (RegistrationMap Request))
213215
, resLspId :: !(TVar Int32)
214216
, resShutdown :: !(C.Barrier ())
217+
-- ^ Barrier signaled when the server receives the 'shutdown' request. See Note [Shutdown]
215218
, resExit :: !(C.Barrier ())
216-
-- ^ Has the server received 'shutdown'? Can be used to conveniently trigger e.g. thread termination,
217-
-- but if you need a cleanup action to terminate before exiting, then you should install a full
218-
-- 'shutdown' handler
219+
-- ^ Barrier signaled when the server receives the 'exit' notification. See Note [Shutdown]
219220
}
220221

221222
type ResponseMap = IxMap LspId (Product SMethod ServerResponseCallback)
@@ -756,6 +757,8 @@ isShuttingDown = do
756757
Just _ -> True
757758
Nothing -> False
758759

760+
-- | Check if the server has received the 'exit' notification.
761+
-- See Note [Shutdown]
759762
isExiting :: (m ~ LspM config) => m Bool
760763
isExiting = do
761764
b <- resExit . resState <$> getLspEnv
@@ -853,18 +856,33 @@ The client can still always choose to cancel for another reason.
853856
-}
854857

855858
{- Note [Shutdown]
856-
The 'shutdown' request basically tells the server to clean up and stop doing things.
857-
In particular, it allows us to ignore or reject all further messages apart from 'exit'.
859+
The LSP protocol has a two-phase shutdown sequence:
858860
859-
We also provide a `Barrier` that indicates whether or not we are shutdown, this can
860-
be convenient, e.g. you can race a thread against `waitBarrier` to have it automatically
861-
be cancelled when we receive `shutdown`.
861+
1. `shutdown` request: ask the server to stop doing work and finish
862+
any in-flight operations.
863+
2. `exit` notification: tell the server to terminate the process.
862864
863-
Shutdown is a request, and the client won't send `exit` until a server responds, so if you
864-
want to be sure that some cleanup happens, you need to ensure we don't respond to `shutdown`
865-
until it's done. The best way to do this is just to install a specific `shutdown` handler.
865+
We expose two `Barrier`s to track this state:
866866
867-
After the `shutdown` request, we don't handle any more requests and notifications other than
868-
`exit`. We also don't handle any more responses to requests we have sent but just throw the
869-
responses away.
867+
- `resShutdown`: signalled when we receive the `shutdown` request.
868+
Use `isShuttingDown` to check this.
869+
- `resExit`: signalled when we receive the `exit` notification.
870+
Use `isExiting` to check this.
871+
872+
Shutdown is itself a request, and we assume the client will not send
873+
`exit` before `shutdown`. If you want to be sure that some cleanup has
874+
run before the server exits, make that cleanup part of your customize
875+
`shutdown` handler.
876+
877+
We use a dedicated sender thread to serialise all messages that go to
878+
the client. That thread is set up to stop sending messages after the
879+
`shutdown` response has been sent.
880+
881+
While handling the `shutdown` request we call `resWaitSender` to wait
882+
for the sender thread to flush and finish. Otherwise, we might get a
883+
"broken pipe" error from trying to send messages after the client has
884+
closed our output handle.
885+
886+
After the `shutdown` request has been processed, we do not handle any
887+
more requests or notifications except for `exit`.
870888
-}

lsp/src/Language/LSP/Server/Processing.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ initializeRequestHandler ::
118118
ServerDefinition config ->
119119
VFS ->
120120
(FromServerMessage -> IO ()) ->
121-
IO () ->
121+
IO () -> -- ^ Action that waits for the sender thread to finish. We use it to set LanguageContextEnv.resWaitSender, See Note [Shutdown].
122122
TMessage Method_Initialize ->
123123
IO (Maybe (LanguageContextEnv config))
124124
initializeRequestHandler logger ServerDefinition{..} vfs sendFunc waitSender req = do

0 commit comments

Comments
 (0)