Gracefully cleanup name env on CU cache. NPE in JPQL tokens in Java

This commit is contained in:
aboyko
2024-04-17 14:51:04 -04:00
parent 8b4abbcfd2
commit 2543bdf1e0
4 changed files with 83 additions and 43 deletions

View File

@@ -42,6 +42,8 @@ import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.WorkspaceSymbol;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
@@ -63,7 +65,9 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
import com.google.common.collect.ImmutableMap;
public class CompositeLanguageServerComponents implements LanguageServerComponents {
private static final Logger log = LoggerFactory.getLogger(CompositeLanguageServerComponents.class);
public static class Builder {
private Map<LanguageId, List<LanguageServerComponents>> componentsByLanguageId = new HashMap<>();
@@ -258,10 +262,15 @@ public class CompositeLanguageServerComponents implements LanguageServerComponen
private Optional<SemanticTokensHandler> findHandler(TextDocumentIdentifier docId) {
TextDocument doc = server.getTextDocumentService().getLatestSnapshot(docId.getUri());
LanguageId language = doc.getLanguageId();
List<LanguageServerComponents> subComponents = componentsByLanguageId.get(language);
if (subComponents != null) {
return subComponents.stream().filter(sc -> sc.getSemanticTokensHandler().isPresent()).map(sc -> sc.getSemanticTokensHandler().get()).findFirst();
// Only opened docs ideally should get requests for semantic token to highlight
if (doc != null) {
LanguageId language = doc.getLanguageId();
List<LanguageServerComponents> subComponents = componentsByLanguageId.get(language);
if (subComponents != null) {
return subComponents.stream().filter(sc -> sc.getSemanticTokensHandler().isPresent()).map(sc -> sc.getSemanticTokensHandler().get()).findFirst();
}
} else {
log.error("Received Semantic Tokens request for a non opened document: %s".formatted(docId.getUri()));
}
return Optional.empty();
}

View File

@@ -73,6 +73,9 @@ public class JdtSemanticTokensHandler implements SemanticTokensHandler {
}
private SemanticTokens computeTokens(List<JdtSemanticTokensProvider> applicableTokenProviders, IJavaProject jp, CompilationUnit cu) {
if (cu == null) {
return new SemanticTokens();
}
List<SemanticTokenData> tokensData = applicableTokenProviders.stream().map(tp -> tp.computeTokens(jp, cu)).flatMap(t -> t.stream()).collect(Collectors.toList());
return new SemanticTokens(SemanticTokensUtils.mapTokensDataToLsp(tokensData, legend, offset -> cu.getLineNumber(offset) - 1, cu::getColumnNumber));
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017, 2023 Pivotal, Inc.
* Copyright (c) 2017, 2024 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
@@ -23,6 +23,9 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.function.Function;
import java.util.stream.Stream;
@@ -74,6 +77,9 @@ public final class CompilationUnitCache implements DocumentContentProvider {
private final Cache<URI, Set<URI>> projectToDocs;
private final Cache<URI, Tuple2<List<Classpath>, INameEnvironmentWithProgress>> lookupEnvCache;
private final ReentrantReadWriteLock environmentCacheLock = new ReentrantReadWriteLock(true);
private CompletableFuture<Void> debounceClassFileChanges = CompletableFuture.completedFuture(null);
private final Executor createCuExecutorThreadPool = Executors.newCachedThreadPool();
public CompilationUnitCache(JavaProjectFinder projectFinder, SimpleLanguageServer server, ProjectObserver projectObserver) {
@@ -153,7 +159,12 @@ public final class CompilationUnitCache implements DocumentContentProvider {
}
if (server != null) {
ServerUtils.listenToClassFileChanges(server.getWorkspaceService().getFileObserver(), projectFinder, this::invalidateProject);
ServerUtils.listenToClassFileCreateAndChange(server.getWorkspaceService().getFileObserver(), projectFinder, jp -> {
if (!debounceClassFileChanges.isDone()) {
debounceClassFileChanges.cancel(false);
}
debounceClassFileChanges = CompletableFuture.runAsync(() -> invalidateProject(jp), CompletableFuture.delayedExecutor(100, TimeUnit.MILLISECONDS));
});
}
}
@@ -200,45 +211,46 @@ public final class CompilationUnitCache implements DocumentContentProvider {
logger.info("CU Cache: work item submitted for doc {}", uri.toASCIIString());
if (project != null) {
CompilationUnit cu = null;
ReadLock lock = environmentCacheLock.readLock();
lock.lock();
try {
cu = requestCU(project, uri).get();
} catch (UncheckedExecutionException e1) {
// ignore errors from rewrite parser. There could be many parser exceptions due to
// user incrementally typing code's text
return null;
} catch (InvalidCacheLoadException | CancellationException e) {
// ignore
} catch (Exception e) {
logger.error("", e);
return requestor.apply(null);
}
if (cu != null) {
CompilationUnit cu = null;
try {
projectToDocs.get(project.getLocationUri(), () -> new HashSet<>()).add(uri);
logger.debug("CU Cache: start work on AST for {}", uri.toString());
return requestor.apply(cu);
}
catch (CancellationException e) {
throw e;
}
catch (Exception e) {
cu = requestCU(project, uri).get();
} catch (UncheckedExecutionException e1) {
// ignore errors from rewrite parser. There could be many parser exceptions due to
// user incrementally typing code's text
return null;
} catch (InvalidCacheLoadException | CancellationException e) {
// ignore
} catch (Exception e) {
logger.error("", e);
return requestor.apply(null);
}
finally {
logger.debug("CU Cache: end work on AST for {}", uri.toString());
if (cu != null) {
projectToDocs.get(project.getLocationUri(), () -> new HashSet<>()).add(uri);
logger.debug("CU Cache: start work on AST for {}", uri.toString());
return requestor.apply(cu);
}
}
catch (CancellationException e) {
throw e;
}
catch (Exception e) {
logger.error("", e);
}
finally {
logger.debug("CU Cache: end work on AST for {}", uri.toString());
lock.unlock();
}
}
return requestor.apply(null);
}
private synchronized CompletableFuture<CompilationUnit> requestCU(IJavaProject project, URI uri) throws ExecutionException {
private synchronized CompletableFuture<CompilationUnit> requestCU(IJavaProject project, URI uri) {
CompletableFuture<CompilationUnit> cuFuture = uriToCu.getIfPresent(uri);
if (cuFuture == null) {
cuFuture = CompletableFuture.supplyAsync(() -> {
@@ -352,7 +364,13 @@ public final class CompilationUnitCache implements DocumentContentProvider {
uriToCu.invalidateAll(docUris);
projectToDocs.invalidate(project.getLocationUri());
}
lookupEnvCache.invalidate(project.getLocationUri());
WriteLock lock = environmentCacheLock.writeLock();
lock.lock();
try {
lookupEnvCache.invalidate(project.getLocationUri());
} finally {
lock.unlock();
}
}
@Override

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2023 VMware, Inc.
* Copyright (c) 2023, 2024 VMware, 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
@@ -13,7 +13,9 @@ package org.springframework.ide.vscode.boot.java.utils;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import org.eclipse.lsp4j.TextDocumentIdentifier;
@@ -30,25 +32,33 @@ public class ServerUtils {
private static final List<String> CLASS_FILES_TO_WATCH_GLOB = List.of("**/*.class");
public static void listenToClassFileChanges(FileObserver fileObserver, JavaProjectFinder projectFinder, Consumer<IJavaProject> callback) {
public static void listenToAnyClassFileChanges(FileObserver fileObserver, JavaProjectFinder projectFinder, Consumer<IJavaProject> callback) {
fileObserver.onAnyChange(CLASS_FILES_TO_WATCH_GLOB, files -> handleFiles(projectFinder, files, callback));
}
public static void listenToClassFileCreateAndChange(FileObserver fileObserver, JavaProjectFinder projectFinder, Consumer<IJavaProject> callback) {
fileObserver.onCreatedOrChanged(CLASS_FILES_TO_WATCH_GLOB, files -> handleFiles(projectFinder, files, callback));
}
private static void handleFiles(JavaProjectFinder projectFinder, String[] files, Consumer<IJavaProject> callback) {
Set<IJavaProject> projects = new LinkedHashSet<>();
for (String f : files) {
URI uri = URI.create(f);
TextDocumentIdentifier docId = new TextDocumentIdentifier(uri.toASCIIString());
projectFinder.find(docId).ifPresent(project -> {
Path p = Paths.get(uri);
if (IClasspathUtil.getOutputFolders(project.getClasspath()).anyMatch(folder -> p.startsWith(folder.toPath()))) {
try {
callback.accept(project);
} catch (Throwable t) {
log.error("", t);
}
projects.add(project);
}
});
}
for (IJavaProject project : projects) {
try {
callback.accept(project);
} catch (Throwable t) {
log.error("", t);
}
}
}