Delay reconcile
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.{
|
||||
|
||||
Reference in New Issue
Block a user