Multi-threading in Rewrite CU Cache

This commit is contained in:
aboyko
2022-08-05 17:12:30 -04:00
parent 697d1473d3
commit 6df31e621d

View File

@@ -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<URI, CompilationUnit> uriToCu;
private final Cache<URI, CompletableFuture<CompilationUnit>> uriToCu;
private final Cache<IJavaProject, Set<URI>> projectToDocs;
private final Cache<IJavaProject, JavaParser> 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<URI, CompilationUnit>() {
//
// @Override
// public void onRemoval(RemovalNotification<URI, CompilationUnit> notification) {
// invalidateCuForJavaFile(notification.getKey().toString());
// }
// })
// .expireAfterAccess(CU_ACCESS_EXPIRATION, TimeUnit.MINUTES)
.removalListener(new RemovalListener<URI, CompletableFuture<CompilationUnit>>() {
@Override
public void onRemoval(RemovalNotification<URI, CompletableFuture<CompilationUnit>> notification) {
URI uri = notification.getKey();
CompletableFuture<CompilationUnit> future = notification.getValue();
if (future != null) {
if (!future.isCancelled()) {
future.cancel(true);
}
Optional<IJavaProject> 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<IJavaProject> 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<URI> docUris = projectToDocs.getIfPresent(project);
if (docUris != null) {
uriToCu.invalidateAll(docUris);
projectToDocs.invalidate(project);
}
javaParsers.invalidate(project);
Set<URI> 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<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;
} else {
throw new IllegalStateException("Failed to parse Java source");
CompletableFuture<CompilationUnit> 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<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;
} 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
*/