Skip to content

Ongoing futures never complete when LSP server crashes #1565

Description

@SergejSalnikov

When the LSP server crashes or terminates unexpectedly, the ongoing futures for pending requests (such as Hover, Completion, Diagnostics, etc.) are never explicitly cancelled by LSP4IJ and can hang indefinitely (never completing).

Here is a detailed breakdown of the codebase research confirming this behavior and explaining why it happens:

1. Ongoing Futures are Owned by LSPFileSupport (Persisting on PsiFile)

  • Ongoing request futures (e.g., LSPCompletionSupport, LSPHoverSupport) are managed by subclasses of AbstractLSPFeatureSupport which are held by LSPFileSupport (LSPFileSupport.java).
  • LSPFileSupport is attached as user data to each open PsiFile (file.putUserData(LSP_FILE_SUPPORT_KEY, this)).
  • It is registered with the Project Disposer:
    Disposer.register(file.getProject(), this);
    This means the feature supports and their cached futures are only disposed when the entire IntelliJ project is closed, not when the LSP server stops or crashes.

2. LanguageServerWrapper Does Not Track or Cancel Feature Futures

  • When the LSP server terminates/crashes unexpectedly, the unexpectedServerStopHandler triggers the stop() sequence in LanguageServerWrapper (LanguageServerWrapper.java:L1603).
  • stop() disposes the languageClient, disposes the DocumentContentSynchronizer for all opened documents, and cancels the launcher listening thread.
  • However, LanguageServerWrapper does not maintain references to the individual request futures (e.g., completion, document symbols) created by the various AbstractLSPFeatureSupport components. Consequently, it does not cancel or fail them.

3. LSP4J Abrupt Stream Closure Pitfalls

  • Under normal circumstances, LSP4J's RemoteEndpoint tracks outstanding request futures.
  • However, if the LSP server process crashes or is killed abruptly, the stream throws a sudden IOException (such as a Broken Pipe). In these scenarios, LSP4J does not automatically cancel or complete outstanding request futures in all code paths unless the graceful shutdown/cleanup protocol completes.
  • Any future that does not get completed exceptionally by LSP4J will remain in an unresolved state permanently.

4. Server Status Changes are Not Propagated to Active Feature Supports

  • Although LanguageServerWrapper.updateStatus() triggers status updates like ServerStatus.stopping or ServerStatus.stopped, the handleServerStatusChanged() hooks in LanguageClientImpl and LSPClientFeatures are empty placeholders:
    public void handleServerStatusChanged(@NotNull ServerStatus serverStatus) {
        // Do nothing
    }
  • There is no listener or event propagation mechanism to inform active AbstractLSPFeatureSupport instances that the server has terminated, so they never call cancel() to clear and reject their pending CancellationSupport and CompletableFuture states.

Summary of Impact

Because the wrapper has no tracking of these feature-specific futures and the LSPFileSupport remains alive on the PsiFile without receiving status updates, any pending futures that LSP4J fails to resolve will hang indefinitely. Any part of the IDE awaiting these futures (using .get(), .join(), or awaitWithCheckCanceled()) will continue to block or leak memory.

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