redhat-developer / redhat-developer/lsp4ij

Server status shows "stopping" during `lastDocumentDisconnectedTimeout` grace period

Open Beginner friendly
#1,553 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Java
Stars
344
Forks
113
Avg merge
5h 22m
Merged PRs (30d)
15

Description

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

Contributor guide

Open the contributing guide

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.

Research direction

Start in src/main/java/com/redhat/devtools/lsp4ij/LanguageServerWrapper.java at startStopTimer(), then inspect how LanguageServerProcessTreeNode and LanguageServerTreeRenderer handle ServerStatus.stopping. Verify the grace-period behavior and the timeout callback; done means the UI does not present the live server as stopping during the grace period, while still showing shutdown after the timeout.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
tooling
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.