From 3ec877e02332305dee78971e261f624601aeffef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kroi=C3=9F=2C=20Florian?= Date: Sun, 28 Jun 2026 14:02:38 +0200 Subject: [PATCH] fix: Properly wait for pending document setups The CompletableFuture that we want to wait for during testing is only added to `PENDING_CONNECTIONS` after a delay of one second. So it's possible, that `waitForAll` has already returned but there are still pending document setups --- ...umentToLanguageServerSetupParticipant.java | 40 +++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ConnectDocumentToLanguageServerSetupParticipant.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ConnectDocumentToLanguageServerSetupParticipant.java index c3785e892..bcf3bbdc3 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ConnectDocumentToLanguageServerSetupParticipant.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ConnectDocumentToLanguageServerSetupParticipant.java @@ -16,6 +16,8 @@ import java.util.WeakHashMap; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -36,8 +38,17 @@ */ public class ConnectDocumentToLanguageServerSetupParticipant implements IDocumentSetupParticipant, IDocumentSetupParticipantExtension { + /** + * Used to delay the actual document setup. + */ + private static ScheduledExecutorService DELAYED_EXECUTOR = createExecutor(); + private static final Set> PENDING_CONNECTIONS = Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>())); + private static ScheduledExecutorService createExecutor() { + return Executors.newSingleThreadScheduledExecutor(Thread.ofVirtual().name("LS-Document-Delayed-Setup").factory()); //$NON-NLS-1$ + } + @Override public void setup(IDocument document) { ITextFileBuffer buffer = ITextFileBufferManager.DEFAULT.getTextFileBuffer(document); @@ -49,10 +60,13 @@ public void setup(IDocument document) { @Override public void setup(final IDocument document, IPath location, LocationKind locationKind) { - // Force document connect - CompletableFuture.runAsync( - () -> PENDING_CONNECTIONS.add(LanguageServers.forDocument(document).collectAll(ls -> CompletableFuture.completedFuture(null))), - CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS)); // delay to ensure the document is initialized and can be resolved by LSPEclipseUtils.toUri + // Force document connect. + // Delay to ensure the document is initialized and can be resolved by + // LSPEclipseUtils.toUri + DELAYED_EXECUTOR.schedule(() -> { + PENDING_CONNECTIONS.add( + LanguageServers.forDocument(document).collectAll(ls -> CompletableFuture.completedFuture(null))); + }, 1, TimeUnit.SECONDS); } /** @@ -60,13 +74,25 @@ public void setup(final IDocument document, IPath location, LocationKind locatio * jobs trying to attach to them */ public static void waitForAll() { - PENDING_CONNECTIONS.forEach(cf -> { + // Don't accept any more document setups + DELAYED_EXECUTOR.shutdownNow(); + try { + DELAYED_EXECUTOR.awaitTermination(1, TimeUnit.SECONDS); + } catch (InterruptedException e) { + LanguageServerPlugin.logError("Failed to await termination of delayed document setup", e); //$NON-NLS-1$ + } + + // Now we can wait for the pending connection to finish. + PENDING_CONNECTIONS.forEach(future -> { try { - cf.get(1000, TimeUnit.MILLISECONDS); + future.get(1, TimeUnit.SECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { - LanguageServerPlugin.logInfo("Interrupted trying to cancel document setup"); //$NON-NLS-1$; + LanguageServerPlugin.logError("Interrupted trying to cancel document setup", e); //$NON-NLS-1$ ; } }); + + // Create new executor for next setup. + DELAYED_EXECUTOR = createExecutor(); } }