Delay reconcile

This commit is contained in:
aboyko
2022-07-14 19:01:45 -04:00
parent 722a8cf6d5
commit 3b678ce9ab
5 changed files with 83 additions and 68 deletions

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2018 Pivotal, Inc.
* Copyright (c) 2018, 2022 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -46,6 +46,11 @@ public class LanguageServerProperties {
* Hover request handler timeout in milliseconds
*/
private long hoverTimeout = -1;
/**
* Reconciling delay time interval. Maximum reconcile rate.
*/
private long reconcileDelay = 0;
public boolean isStandalone() {
return standalone;
@@ -87,4 +92,12 @@ public class LanguageServerProperties {
this.hoverTimeout = hoverTimeout;
}
public long getReconcileDelay() {
return reconcileDelay;
}
public void setReconcileDelay(long reconcileDelay) {
this.reconcileDelay = reconcileDelay;
}
}

View File

@@ -13,16 +13,15 @@ package org.springframework.ide.vscode.commons.languageserver.util;
import java.lang.management.ManagementFactory;
import java.net.URI;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
@@ -52,7 +51,6 @@ import org.eclipse.lsp4j.RegistrationParams;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
import org.eclipse.lsp4j.WorkDoneProgressBegin;
import org.eclipse.lsp4j.WorkDoneProgressCreateParams;
import org.eclipse.lsp4j.WorkDoneProgressEnd;
@@ -594,7 +592,7 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, LanguageC
* in a burst. Rather than execute the same request repeatedly we can avoid queuing
* up more requests if the previous request has not yet been started.
*/
private Set<VersionedTextDocumentIdentifier> queuedReconcileRequests = Collections.synchronizedSet(new HashSet<>());
private Map<String, CompletableFuture<Void>> reconcileRequests = new ConcurrentHashMap<>();
private DiagnosticSeverityProvider severityProvider = DiagnosticSeverityProvider.DEFAULT;
@@ -605,40 +603,36 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, LanguageC
public void validateWith(TextDocumentIdentifier docId, IReconcileEngine engine) {
SimpleTextDocumentService documents = getTextDocumentService();
TextDocument doc = documents.getLatestSnapshot(docId.getUri());
if (doc == null) {
if (documents.getLatestSnapshot(docId.getUri()) == null) {
log.debug("Reconcile skipped due to document doesn't exist anymore {}", docId.getUri());
return;
}
int requestedVersion = doc.getVersion();
VersionedTextDocumentIdentifier request = new VersionedTextDocumentIdentifier(docId.getUri(), requestedVersion);
log.debug("Reconcile requested {} - {}", request.getUri(), request.getVersion());
if (!queuedReconcileRequests.add(request)) {
log.debug("Reconcile skipped {} - {}", request.getUri(), request.getVersion());
String uri = docId.getUri();
CompletableFuture<Void> newFuture = new CompletableFuture<Void>();
CompletableFuture<Void> oldFuture = reconcileRequests.putIfAbsent(uri, newFuture);
if (oldFuture != null && oldFuture != newFuture) {
log.debug("Reconcile skipped {}", uri);
return;
}
CompletableFuture<Void> reconcileSession = this.busyReconcile = new CompletableFuture<Void>();
CompletableFuture<Void> reconcileSession = this.busyReconcile = oldFuture == null ? newFuture : oldFuture;
// Log.debug("Reconciling BUSY");
// Avoid running in the same thread as lsp4j as it can result
// in long "hangs" for slow reconcile providers
Mono.fromRunnable(() -> {
queuedReconcileRequests.remove(request);
log.debug("Reconcile starting {} - {}", request.getUri(), request.getVersion());
Mono<?> mono = props.getReconcileDelay() > 0
? Mono.delay(Duration.ofMillis(props.getReconcileDelay())).publishOn(RECONCILER_SCHEDULER)
: Mono.empty().publishOn(RECONCILER_SCHEDULER);
mono.then(Mono.fromRunnable(() -> {
reconcileRequests.remove(uri);
log.debug("Reconcile starting {}", uri);
// TextDocument doc = documents.getDocument(docId.getUri()).copy();
// if (doc == null) {
// log.debug("Reconcile aborted due to document doesn't exist {} - {}", request.getUri(), request.getVersion());
// //Do not bother reconciling if document doesn't exist anymore (got closed in the meantime)
// return;
// }
if (requestedVersion != doc.getVersion()) {
log.debug("Reconcile aborted due to document being already stale {} - {}", request.getUri(), request.getVersion());
//Do not bother reconciling if document contents is already stale.
TextDocument doc = documents.getLatestSnapshot(docId.getUri());
if (doc == null) {
//Do not bother reconciling if document doesn't exist anymore (got closed in the meantime)
return;
}
@@ -698,7 +692,7 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, LanguageC
};
engine.reconcile(doc, problems);
})
}))
.onErrorResume(error -> {
log.error("", error);
return Mono.empty();
@@ -707,7 +701,6 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, LanguageC
reconcileSession.complete(null);
// Log.debug("Reconciler DONE : "+this.busyReconcile.isDone());
})
.subscribeOn(RECONCILER_SCHEDULER)
.subscribe();
}

View File

@@ -78,7 +78,7 @@ public class BootLanguageServerInitializer implements InitializingBean {
private static ProjectObserver.Listener reconcileOpenDocumentsForProjectChange(SimpleLanguageServer s, CompositeLanguageServerComponents c, JavaProjectFinder projectFinder) {
return ProjectObserver.onAny(project -> {
c.getReconcileEngine().ifPresent(reconciler -> {
log.info("A project changed {}, triggering reconcile on all project's open documents", project.getElementName());
log.debug("A project changed {}, triggering reconcile on all project's open documents", project.getElementName());
for (TextDocument doc : s.getTextDocumentService().getAll()) {
if (projectFinder.find(doc.getId()).orElse(null) == project) {
s.validateWith(doc.getId(), reconciler);

View File

@@ -35,7 +35,6 @@ import org.openrewrite.java.JavaParser;
import org.openrewrite.java.tree.J.CompilationUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
import org.springframework.ide.vscode.boot.java.utils.DocumentContentProvider;
import org.springframework.ide.vscode.commons.java.IClasspath;
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
@@ -54,9 +53,11 @@ import reactor.core.Disposable;
public class RewriteCompilationUnitCache implements DocumentContentProvider, Disposable {
private static final Logger logger = LoggerFactory.getLogger(CompilationUnitCache.class);
private static final Logger logger = LoggerFactory.getLogger(RewriteCompilationUnitCache.class);
private final Object URI_TO_CU_LOCK = new Object();
private static final long CU_ACCESS_EXPIRATION = 5;
// private static final long CU_ACCESS_EXPIRATION = 5;
private JavaProjectFinder projectFinder;
private ProjectObserver projectObserver;
@@ -106,20 +107,20 @@ public class RewriteCompilationUnitCache implements DocumentContentProvider, Dis
@Override
public void deleted(IJavaProject project) {
logger.info("CU Cache: deleted project {}", project.getElementName());
logger.debug("CU Cache: deleted project {}", project.getElementName());
invalidateProject(project);
}
@Override
public void created(IJavaProject project) {
logger.info("CU Cache: created project {}", project.getElementName());
logger.debug("CU Cache: created project {}", project.getElementName());
invalidateProject(project);
// loadJavaParser(project);
}
@Override
public void changed(IJavaProject project) {
logger.info("CU Cache: changed project {}", project.getElementName());
logger.debug("CU Cache: changed project {}", project.getElementName());
invalidateProject(project);
// Load the new cache the value right away
// loadJavaParser(project);
@@ -173,15 +174,17 @@ public class RewriteCompilationUnitCache implements DocumentContentProvider, Dis
}
private void invalidateCuForJavaFile(String uriStr) {
logger.info("CU Cache: invalidate AST for {}", uriStr);
URI uri = URI.create(uriStr);
uriToCu.invalidate(uri);
Optional<IJavaProject> project = projectFinder.find(new TextDocumentIdentifier(uriStr));
if (project.isPresent()) {
JavaParser parser = javaParsers.getIfPresent(project.get());
if (parser != null) {
parser.reset();
synchronized (URI_TO_CU_LOCK) {
if (uriToCu.getIfPresent(uri) != null) {
uriToCu.invalidate(uri);
Optional<IJavaProject> project = projectFinder.find(new TextDocumentIdentifier(uriStr));
if (project.isPresent()) {
JavaParser parser = javaParsers.getIfPresent(project.get());
if (parser != null) {
parser.reset();
}
}
}
}
}
@@ -189,12 +192,14 @@ public class RewriteCompilationUnitCache implements DocumentContentProvider, Dis
private void invalidateProject(IJavaProject project) {
logger.info("CU Cache: invalidate project <{}>", project.getElementName());
Set<URI> docUris = projectToDocs.getIfPresent(project);
if (docUris != null) {
uriToCu.invalidateAll(docUris);
projectToDocs.invalidate(project);
synchronized (URI_TO_CU_LOCK) {
Set<URI> docUris = projectToDocs.getIfPresent(project);
if (docUris != null) {
uriToCu.invalidateAll(docUris);
projectToDocs.invalidate(project);
}
javaParsers.invalidate(project);
}
javaParsers.invalidate(project);
}
@Override
@@ -211,27 +216,30 @@ public class RewriteCompilationUnitCache implements DocumentContentProvider, Dis
public CompilationUnit getCU(IJavaProject project, URI uri) {
try {
if (project != null) {
return uriToCu.get(uri, () -> {
JavaParser javaParser = /*loadJavaParser(project)*/createJavaParser(project);
Input input = new Input(Paths.get(uri), () -> {
try {
return new ByteArrayInputStream(fetchContent(uri).getBytes());
} catch (Exception e) {
throw new IllegalStateException("Unexpected error fetching document content");
synchronized (URI_TO_CU_LOCK) {
return uriToCu.get(uri, () -> {
logger.debug("Parsing CU {}", uri);
JavaParser javaParser = loadJavaParser(project);
Input input = new Input(Paths.get(uri), () -> {
try {
return new ByteArrayInputStream(fetchContent(uri).getBytes());
} catch (Exception e) {
throw new IllegalStateException("Unexpected error fetching document content");
}
});
List<CompilationUnit> cus = ORAstUtils.parseInputs(javaParser, List.of(input));
CompilationUnit cu = cus.get(0);
if (cu != null) {
projectToDocs.get(project, () -> new HashSet<>()).add(uri);
}
});
List<CompilationUnit> cus = ORAstUtils.parseInputs(javaParser, List.of(input));
CompilationUnit cu = cus.get(0);
if (cu != null) {
projectToDocs.get(project, () -> new HashSet<>()).add(uri);
}
return cu;
});
return cu;
});
}
}
} catch (Exception e) {
logger.error("", e);

View File

@@ -1,4 +1,5 @@
languageserver.extension-id: vscode-spring-boot
languageserver.reconcile-delay=100
languageserver.completion-trigger-characters.java:
languageserver.completion-trigger-characters.xml:
languageserver.completion-trigger-characters.spring-boot-properties-yaml: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.{