diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteCompilationUnitCache.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteCompilationUnitCache.java index db28b4024..f60f59519 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteCompilationUnitCache.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteCompilationUnitCache.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.function.Function; import java.util.stream.Collectors; @@ -48,6 +49,9 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader.InvalidCacheLoadException; +import com.google.common.cache.RemovalListener; +import com.google.common.cache.RemovalNotification; import com.google.common.util.concurrent.UncheckedExecutionException; import reactor.core.Disposable; @@ -56,35 +60,53 @@ public class RewriteCompilationUnitCache implements DocumentContentProvider, Dis 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 JavaProjectFinder projectFinder; +// private static final long CU_ACCESS_EXPIRATION = 1; +// private JavaProjectFinder projectFinder; private ProjectObserver projectObserver; private final ProjectObserver.Listener projectListener; private final SimpleTextDocumentService documentService; - private final Cache uriToCu; + private final Cache> uriToCu; private final Cache> projectToDocs; private final Cache javaParsers; - + public RewriteCompilationUnitCache(JavaProjectFinder projectFinder, SimpleLanguageServer server, ProjectObserver projectObserver) { - this.projectFinder = projectFinder; +// this.projectFinder = projectFinder; this.projectObserver = projectObserver; // PT 154618835 - Avoid retaining the CU in the cache as it consumes memory if it hasn't been // accessed after some time this.uriToCu = CacheBuilder.newBuilder() -// .expireAfterWrite(CU_ACCESS_EXPIRATION, TimeUnit.MINUTES) -// .removalListener(new RemovalListener() { -// -// @Override -// public void onRemoval(RemovalNotification notification) { -// invalidateCuForJavaFile(notification.getKey().toString()); -// } -// }) +// .expireAfterAccess(CU_ACCESS_EXPIRATION, TimeUnit.MINUTES) + .removalListener(new RemovalListener>() { + + @Override + public void onRemoval(RemovalNotification> notification) { + URI uri = notification.getKey(); + CompletableFuture future = notification.getValue(); + + if (future != null) { + if (!future.isCancelled()) { + future.cancel(true); + } + Optional project = projectFinder.find(new TextDocumentIdentifier(uri.toString())); + if (project.isPresent()) { + + // TODO There seems to be an issue with java parser #reset() call. After + // resetting it + // still complains that it needs to be reset +// JavaParser parser = javaParsers.getIfPresent(project.get()); +// if (parser != null) { +// parser.reset(); +// } + javaParsers.invalidate(project.get()); + } + } + } + }) .build(); + this.projectToDocs = CacheBuilder.newBuilder().build(); this.javaParsers = CacheBuilder.newBuilder().build(); @@ -176,35 +198,18 @@ public class RewriteCompilationUnitCache implements DocumentContentProvider, Dis private void invalidateCuForJavaFile(String uriStr) { URI uri = URI.create(uriStr); - synchronized (URI_TO_CU_LOCK) { - if (uriToCu.getIfPresent(uri) != null) { - uriToCu.invalidate(uri); - Optional project = projectFinder.find(new TextDocumentIdentifier(uriStr)); - if (project.isPresent()) { - - //TODO There seems to be an issue with java parser #reset() call. After resetting it - // still complains that it needs to be reset -// JavaParser parser = javaParsers.getIfPresent(project.get()); -// if (parser != null) { -// parser.reset(); -// } - javaParsers.invalidate(project.get()); - } - } - } + uriToCu.invalidate(uri); } - + private void invalidateProject(IJavaProject project) { logger.info("CU Cache: invalidate project <{}>", project.getElementName()); - synchronized (URI_TO_CU_LOCK) { - Set docUris = projectToDocs.getIfPresent(project); - if (docUris != null) { - uriToCu.invalidateAll(docUris); - projectToDocs.invalidate(project); - } - javaParsers.invalidate(project); + Set docUris = projectToDocs.getIfPresent(project); + if (docUris != null) { + uriToCu.invalidateAll(docUris); + projectToDocs.invalidate(project); } + javaParsers.invalidate(project); } @Override @@ -221,59 +226,65 @@ public class RewriteCompilationUnitCache implements DocumentContentProvider, Dis public CompilationUnit getCU(IJavaProject project, URI uri) { try { if (project != null) { - synchronized (URI_TO_CU_LOCK) { try { return uriToCu.get(uri, () -> { - boolean newParser = javaParsers.getIfPresent(project) == null; - try { - 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 cus = ORAstUtils.parseInputs(javaParser, List.of(input)); - - CompilationUnit cu = cus.get(0); - - if (cu != null) { - projectToDocs.get(project, () -> new HashSet<>()).add(uri); - return cu; - } else { - throw new IllegalStateException("Failed to parse Java source"); + CompletableFuture future = CompletableFuture.supplyAsync(() -> { + try { + return doParse(project, uri); + } catch (Exception e) { + return null; } - } catch (Exception e) { - if (newParser) { - javaParsers.invalidate(project); - } - throw e; - } - - }); + }); + return future; + }).get(); } catch (UncheckedExecutionException e1) { - if (e1.getCause() instanceof IllegalStateException) { - logger.error("", e1); - } else { - // ignore errors from rewrite parser. There could be many parser exceptions due to - // user incrementally typing code's text - } + // 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 null; } } - } } catch (Exception e) { logger.error("", e); } return null; } + private CompilationUnit doParse(IJavaProject project, URI uri) throws Exception { + boolean newParser = javaParsers.getIfPresent(project) == null; + try { + 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 cus = ORAstUtils.parseInputs(javaParser, List.of(input)); + + CompilationUnit cu = cus.get(0); + + if (cu != null) { + projectToDocs.get(project, () -> new HashSet<>()).add(uri); + return cu; + } else { + throw new IllegalStateException("Failed to parse Java source"); + } + } catch (Exception e) { + if (newParser) { + javaParsers.invalidate(project); + } + throw e; + } + } + /** * Does not need to be via callback - kept the same in order to keep the same API to replace JDT with Rewrite in distant future */