Skip to content

Server status shows "stopping" during lastDocumentDisconnectedTimeout grace period #1553

Description

@SergejSalnikov

When lastDocumentDisconnectedTimeout is set to a non-trivial value (e.g., 180 seconds), the LSP console UI displays "stopping pid:12345" with an animated spinner and an elapsed-time counter for the entire duration of the grace period. The server is still fully functional during this time — it accepts new connections and processes requests — but the UI incorrectly suggests it is in the process of shutting down.

Steps to Reproduce

  1. Register a language server with a lastDocumentDisconnectedTimeout > 5 seconds, e.g.:
    <server id="myserver" name="MyServer"
            lastDocumentDisconnectedTimeout="180"
            factoryClass="..."/>
  2. Open a file that triggers the language server to start.
  3. Close the file (or close all files associated with the server).
  4. Observe the Language Servers tool window (LSP console tree).

Expected: The server shows as "started pid:12345" (or a new "idle" state) during the grace period, since it's still alive and accepting connections.

Actual: The server immediately shows "stopping pid:12345" with a spinning animation and an elapsed-time counter that ticks for the full 180 seconds.

Root Cause

In LanguageServerWrapper.startStopTimer() (line ~606):

private void startStopTimer() {
    updateStatus(ServerStatus.stopping);  // ← BUG: status set immediately
    int delayMs = (int) TimeUnit.SECONDS.toMillis(
        serverDefinition.getLastDocumentDisconnectedTimeout());
    getStopAlarm().addRequest(() -> {
        try {
            stop();
        } catch (Throwable t) {
            LOGGER.error("...", t);
        }
    }, delayMs);
}

updateStatus(ServerStatus.stopping) is called immediately when the timer starts, not when the server actually begins its shutdown sequence. This triggers:

  1. LanguageServerProcessTreeNode.setServerStatus(stopping) — sets displayName to "stopping pid:..." and starts the elapsed-time counter (startTime = System.currentTimeMillis()).
  2. LanguageServerTreeRenderer — detects ServerStatus.stopping and renders the animated spinner icon + elapsed time text.

When a new file connects during the grace period, removeStopTimer(false) correctly cancels the alarm and restores ServerStatus.started:

private void removeStopTimer(boolean stopping) {
    Alarm stopAlarm = this.stopAlarm;
    if (stopAlarm != null) {
        if (stopAlarm.getActiveRequestCount() > 0) {
            stopAlarm.cancelAllRequests();
            if (!stopping) {
                updateStatus(ServerStatus.started);  // ← restored here
            }
        }
    }
}

But during the idle period without a reconnection, the server appears as "stopping" the whole time.

Impact

  • With the default 5-second timeout, this is barely noticeable.
  • With longer timeouts (30s, 60s, 180s — common for expensive-to-start servers like CiderLSP), the UI is misleading. Users may think the server is broken or unresponsive.
  • The elapsed time counter keeps ticking, reinforcing the false impression that a slow shutdown is in progress.
  • The animated spinner icon (AnimatedIcon.Default) continuously animates, adding visual noise.

Suggested Fixes

Option A: Defer status change (minimal change)

Move updateStatus(ServerStatus.stopping) inside the alarm callback, just before stop():

private void startStopTimer() {
    // Don't change status yet — the server is still alive and functional
    int delayMs = (int) TimeUnit.SECONDS.toMillis(
        serverDefinition.getLastDocumentDisconnectedTimeout());
    getStopAlarm().addRequest(() -> {
        try {
            updateStatus(ServerStatus.stopping);  // ← moved here
            stop();
        } catch (Throwable t) {
            LOGGER.error("...", t);
        }
    }, delayMs);
}

Trade-off: During the grace period the status stays started, which is accurate (server is running) but doesn't communicate that it will stop soon.

Option B: New idle / waiting_to_stop status (more informative)

Introduce a new ServerStatus value (e.g., idle or waiting_to_stop) that the tree renderer displays differently — e.g., "idle pid:12345" with no spinner and no elapsed time counter.

This gives users accurate feedback: the server is alive but has no open documents, and will auto-stop if no files are opened soon.

Environment

  • lsp4ij version: (current HEAD)
  • IntelliJ IDEA version: 2025.1+
  • OS: Linux

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions