@@ -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
221222type 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]
759762isExiting :: (m ~ LspM config ) => m Bool
760763isExiting = 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-}
0 commit comments