redhat-developer / redhat-developer/lsp4ij
Server status shows "stopping" during `lastDocumentDisconnectedTimeout` grace period
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
- Register a language server with a
lastDocumentDisconnectedTimeout> 5 seconds, e.g.:<server id="myserver" name="MyServer" lastDocumentDisconnectedTimeout="180" factoryClass="..."/> - Open a file that triggers the language server to start.
- Close the file (or close all files associated with the server).
- 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:
LanguageServerProcessTreeNode.setServerStatus(stopping)— setsdisplayNameto"stopping pid:..."and starts the elapsed-time counter (startTime = System.currentTimeMillis()).LanguageServerTreeRenderer— detectsServerStatus.stoppingand 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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