Skip to content

Clarification (wiki material) about connection state #227

Open
@ljw1004

Description

@ljw1004

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 |
        +------------------------------+

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions