microsoft / microsoft/language-server-protocol

Clarification (wiki material) about connection state

Open
#227 4 comments 2 reactions 1 assignee View on GitHub

@dbaeumer is already working on this.

Since May 17, 2017.

clarification feature-request
Dominant language
TypeScript
Stars
13k
Forks
1k
Avg merge
6d 1h
Merged PRs (30d)
10

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

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.