Multi-threading in Rewrite CU Cache
This commit is contained in:
@@ -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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user