Description
I'd like to contribute some "implementor's notes". It feels like the wiki would be a natural place for these, but Github doesn't support PRs for wiki contributions, so I'm writing it out as an issue.
(@dbaeumer, it's your call whether you think that implementor's notes are useful, and if so, then where they should go).
For the main wiki page:
Implementator's notes:
- [[Connection State]] How the canonical LanguageClient handles connecting to the server, crashes, retries, Initialize request, Shutdown request, and Exit notification
For a new wiki page called Connection-State.md:
When writing a client you'd like to know how to launch an LSP server, and how to respond to various events, e.g.
- If the server responds to Initialize request with a ResponseError, then the client pops up a message dialog, and the
retry
flag indicates whether the dialog should offer the user a button to press to retry initialization. - If the server closes its connection to the client, then the client should restart.
It's useful to see how the canonical implementation of a LSP language client handles those and other events, as implemented in the class LanguageClient.
The relevant state of a LanguageClient object is characterized by three variables:
private _state: Initial | Starting | StartFailed | Running | Stopping | Stopped;
private _connectionPromise: Thenable<IConnection> | undefined;
private _resolvedConnection: IConnection | undefined;
Here's the flow chart:
(INITIAL)--------------------------+
| _state == ClientState.Initial |
| _connectionPromise == undefined |
| _resolvedConnection == undefined |
+----------------------------------+
|
| [external] extension author invokes .start()
\|/
(STARTING)-------------------------+
| _state == ClientState.Starting |
| _connectionPromise = underway |
+----------------------------------+
|
| We called resolveConnection(), which calls createConnection(),
| which launches the LSP server and connects to its stdin/stdout.
|
| [external] either the process launches correctly and we have stdin/stdout, or not!
|
<>-----------------------------------+
| |
|success: |failure:
| \|/
+------------------------------+ (STARTFAILED)-----------------------+
| _connectionPromise = success | | _state == ClientState.StartFailed |
+------------------------------+ | _connectionPromise = failed |
| +-----------------------------------+
| |
| | message to user
| call initialize() to send an | "Couldn't start server"
| Initialize request to LSP server \|/
| X
|[external] get back response
|[external] *** or connectionClosed/Error
|
<>-----------------------------------+
| |
|ok: |error:
| |
(RUNNING)--------------------------+ | message to user "Couldn't start server"
| _state == ClientState.Running | | The message has a Retry button if .retry
| _resolvedConnection = connection | | flag was set on the reply from the server
+----------------------------------+ |
| | [external] user clicks button
| everything's okay! | [external] *** or connectionClosed/Error
| |
| [external] *** or |
| connectionClosed/Error <>----------------+
\|/ | |
X |cancel: |retry:
| |
| +-> jump to the initialize()
| call a few lines up
|
\|/
(STOPPING)----------------------+
| _state = ClientState.Stopping |
+-------------------------------+
|
| send Shutdown request to LSP server
|
| [external] get back response
| [external] *** or connectionError
|
<>----------------+
| |
|ok: |error:
| |
| +-> X. Nothing further.
|
| send Exit notification
| to LSP server
\|/
(STOPPED)----------------------+
| _state = ClientState.Stopped |
| _connectionPromise = null |
| _resolvedConnection = null |
+------------------------------+
|
| Wait 2000ms, then do
| "kill -0 $LSP_PID"
| which checks whether the process still exists.
| If it does, then do
| "kill -9 $LSP_PID" or similar.
\|/
X
***
The vscode-jsonrpc library might signal two other events, which get extra
handling at the locations marked ***
in the diagram above:
<>---...---+
| |
| |connectionError:
. |
. | If the error came from attempting to write to LSP's stdin,
. | and we'd had no more than two successive errors, then ignore it.
| Otherwise:
|
+--> jump straight to the (STOPPED) state.
<>---...--+
| |
| |connectionClosed:
. |
. \|/
. +----------------------------+
| _connectionPromise = null |
| _resolvedConnection = null |
+----------------------------+
|
| Log the time at which this occurred.
| Have there 5 or more closures in the past three minutes?
|
<>---------------------------------+
| |
| >=5 closures | <5 closures
\|/ |
+------------------------------+ +-> jump to the (STARTING) state.
| _state = ClientState.Stopped |
+------------------------------+