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.
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 onPsiFile)LSPCompletionSupport,LSPHoverSupport) are managed by subclasses ofAbstractLSPFeatureSupportwhich are held byLSPFileSupport(LSPFileSupport.java).LSPFileSupportis attached as user data to each openPsiFile(file.putUserData(LSP_FILE_SUPPORT_KEY, this)).2.
LanguageServerWrapperDoes Not Track or Cancel Feature FuturesunexpectedServerStopHandlertriggers thestop()sequence inLanguageServerWrapper(LanguageServerWrapper.java:L1603).stop()disposes thelanguageClient, disposes theDocumentContentSynchronizerfor all opened documents, and cancels the launcher listening thread.LanguageServerWrapperdoes not maintain references to the individual request futures (e.g., completion, document symbols) created by the variousAbstractLSPFeatureSupportcomponents. Consequently, it does not cancel or fail them.3. LSP4J Abrupt Stream Closure Pitfalls
RemoteEndpointtracks outstanding request futures.IOException(such as aBroken 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.4. Server Status Changes are Not Propagated to Active Feature Supports
LanguageServerWrapper.updateStatus()triggers status updates likeServerStatus.stoppingorServerStatus.stopped, thehandleServerStatusChanged()hooks inLanguageClientImplandLSPClientFeaturesare empty placeholders:AbstractLSPFeatureSupportinstances that the server has terminated, so they never callcancel()to clear and reject their pendingCancellationSupportandCompletableFuturestates.Summary of Impact
Because the wrapper has no tracking of these feature-specific futures and the
LSPFileSupportremains alive on thePsiFilewithout 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(), orawaitWithCheckCanceled()) will continue to block or leak memory.