major rework of the threading model behind the spring indexer, unified worker queue and initialization/refresh operations

This commit is contained in:
Martin Lippert
2018-01-11 12:27:13 +01:00
parent fd60557be2
commit f6d4971981

View File

@@ -16,6 +16,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -27,7 +28,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -68,39 +68,40 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
*/
public class SpringIndexer {
private BootJavaLanguageServer server;
private JavaProjectFinder projectFinder;
private AnnotationHierarchyAwareLookup<SymbolProvider> symbolProviders;
private final BootJavaLanguageServer server;
private final JavaProjectFinder projectFinder;
private final AnnotationHierarchyAwareLookup<SymbolProvider> symbolProviders;
private List<SymbolInformation> symbols;
private ConcurrentMap<String, List<SymbolInformation>> symbolsByDoc;
private final List<SymbolInformation> symbols;
private final ConcurrentMap<String, List<SymbolInformation>> symbolsByDoc;
private CompletableFuture<Void> initializeTask;
private Thread updateWorker;
private BlockingQueue<UpdateItem> updateQueue;
private AtomicBoolean initializing = new AtomicBoolean(false);
private final Thread updateWorker;
private final BlockingQueue<WorkerItem> updateQueue;
private final Listener projectListener = new Listener() {
@Override
public void created(IJavaProject project) {
Log.log("project created event: " + project.getElementName());
refresh();
}
@Override
public void changed(IJavaProject project) {
Log.log("project changed event: " + project.getElementName());
refresh();
}
@Override
public void deleted(IJavaProject project) {
Log.log("project deleted event: " + project.getElementName());
refresh();
}
};
private volatile InitializeItem lastInitializeItem;
public SpringIndexer(BootJavaLanguageServer server, JavaProjectFinder projectFinder, AnnotationHierarchyAwareLookup<SymbolProvider> specificProviders) {
this.server = server;
this.projectFinder = projectFinder;
@@ -109,87 +110,92 @@ public class SpringIndexer {
this.symbols = Collections.synchronizedList(new ArrayList<>());
this.symbolsByDoc = new ConcurrentHashMap<>();
this.updateQueue = new LinkedBlockingQueue<>();
this.updateWorker = new Thread(new Runnable() {
@Override
public void run() {
try {
while (true) {
WorkerItem workerItem = updateQueue.take();
workerItem.run();
}
}
catch (InterruptedException e) {
// ignore
}
catch (Exception e) {
e.printStackTrace();
}
}
}, "Spring Annotation Index Update Worker");
updateWorker.start();
server.getWorkspaceService().onDidChangeWorkspaceFolders(evt -> {
System.err.println("Workspace roots have changed!");
Log.log("workspace roots have changed event arrived - added: " + Arrays.toString(evt.getEvent().getAdded()) + " - removed: " + Arrays.toString(evt.getEvent().getRemoved()));
refresh();
});
if (server.getProjectObserver() != null) {
server.getProjectObserver().addListener(projectListener);
}
}
public CompletableFuture<Void> initialize(Collection<WorkspaceFolder> workspaceRoots) {
synchronized(this) {
if (this.initializeTask == null) {
initializing.set(true);
if (server.getProjectObserver() != null) {
server.getProjectObserver().addListener(projectListener);
try {
if (lastInitializeItem != null && !lastInitializeItem.getFuture().isDone()) {
lastInitializeItem.getFuture().cancel(false);
}
this.initializeTask = CompletableFuture.runAsync(new Runnable() {
@Override
public void run() {
for (WorkspaceFolder root : workspaceRoots) {
scanFiles(root);
}
SpringIndexer.this.updateQueue = new LinkedBlockingQueue<>();
SpringIndexer.this.updateWorker = new Thread(new Runnable() {
@Override
public void run() {
try {
while (true) {
UpdateItem updateItem = updateQueue.take();
scanFile(updateItem.getDocURI(), updateItem.getContent(), updateItem.getClasspathEntries());
updateItem.getFuture().complete(null);
}
}
catch (InterruptedException e) {
// ignore
}
catch (Exception e) {
e.printStackTrace();
}
}
}, "Spring Annotation Index Update Worker");
updateWorker.start();
Log.log("update worker STARTED: #" + updateWorker.getId() + " - " + updateWorker.toString());
initializing.set(false);
}
});
lastInitializeItem = new InitializeItem(workspaceRoots.toArray(new WorkspaceFolder[workspaceRoots.size()]));
updateQueue.put(lastInitializeItem);
return lastInitializeItem.getFuture();
}
catch (Exception e) {
Log.log(e);
}
return this.initializeTask;
}
return null;
}
public boolean isInitializing() {
return initializing.get();
return lastInitializeItem != null && !lastInitializeItem.getFuture().isDone();
}
private void waitForInitializeTask() {
synchronized (this) {
if (lastInitializeItem != null) {
try {
lastInitializeItem.getFuture().get();
} catch (InterruptedException | ExecutionException e) {
// ignore
}
}
}
}
private void refresh() {
synchronized (this) {
shutdown();
initializeTask = null;
symbols.clear();
symbolsByDoc.clear();
Log.info("Rebuilding SpringIndexer...");
initialize(server.getWorkspaceRoots());
Collection<WorkspaceFolder> roots = server.getWorkspaceRoots();
Log.log("refresh spring indexer for roots: " + roots.toString());
initialize(roots);
}
}
public void shutdown() {
try {
if (this.initializeTask != null) {
initializeTask.cancel(true);
}
synchronized(this) {
if (updateWorker != null && updateWorker.isAlive()) {
updateWorker.interrupt();
}
if (updateWorker != null && updateWorker.isAlive()) {
updateWorker.interrupt();
Log.log("update worker INTERRUPPTED: #" + updateWorker.getId() + " - " + updateWorker.toString());
}
if (server.getProjectObserver() != null) {
server.getProjectObserver().removeListener(projectListener);
if (server.getProjectObserver() != null) {
server.getProjectObserver().removeListener(projectListener);
}
}
} catch (Exception e) {
Log.log(e);
@@ -197,22 +203,21 @@ public class SpringIndexer {
}
public CompletableFuture<Void> updateDocument(String docURI, String content) {
if (docURI.endsWith(".java") && initializeTask != null) {
try {
initializeTask.get();
synchronized(this) {
if (docURI.endsWith(".java") && lastInitializeItem != null) {
try {
Optional<IJavaProject> maybeProject = projectFinder.find(new TextDocumentIdentifier(docURI));
if (maybeProject.isPresent()) {
String[] classpathEntries = getClasspathEntries(maybeProject.get());
Optional<IJavaProject> maybeProject = projectFinder.find(new TextDocumentIdentifier(docURI));
if (maybeProject.isPresent()) {
String[] classpathEntries = getClasspathEntries(maybeProject.get());
CompletableFuture<Void> future = new CompletableFuture<>();
UpdateItem updateItem = new UpdateItem(docURI, content, classpathEntries, future);
updateQueue.put(updateItem);
return future;
UpdateItem updateItem = new UpdateItem(docURI, content, classpathEntries);
updateQueue.put(updateItem);
return updateItem.getFuture();
}
}
catch (Exception e) {
e.printStackTrace();
}
}
catch (Exception e) {
e.printStackTrace();
}
}
@@ -220,46 +225,25 @@ public class SpringIndexer {
}
public List<SymbolInformation> getAllSymbols(String query) {
if (initializeTask != null) {
try {
initializeTask.get();
waitForInitializeTask();
if (query != null && query.length() > 0) {
return searchMatchingSymbols(this.symbols, query);
} else {
return this.symbols;
}
} catch (InterruptedException | ExecutionException e) {
// ignore
}
if (query != null && query.length() > 0) {
return searchMatchingSymbols(this.symbols, query);
} else {
return this.symbols;
}
return null;
}
public List<? extends SymbolInformation> getSymbols(String docURI) {
if (initializeTask != null) {
try {
initializeTask.get();
return this.symbolsByDoc.get(docURI);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
return null;
waitForInitializeTask();
return this.symbolsByDoc.get(docURI);
}
private List<SymbolInformation> searchMatchingSymbols(List<SymbolInformation> allsymbols, String query) {
if (initializeTask != null) {
try {
initializeTask.get();
return allsymbols.stream()
.filter(symbol -> StringUtil.containsCharactersCaseInsensitive(symbol.getName(), query))
.collect(Collectors.toList());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
return null;
waitForInitializeTask();
return allsymbols.stream()
.filter(symbol -> StringUtil.containsCharactersCaseInsensitive(symbol.getName(), query))
.collect(Collectors.toList());
}
private void scanFiles(WorkspaceFolder directory) {
@@ -490,36 +474,78 @@ public class SpringIndexer {
/**
* inner class to capture items for the update worker
*/
private static class UpdateItem {
private interface WorkerItem {
public void run();
public CompletableFuture<Void> getFuture();
}
private class UpdateItem implements WorkerItem {
private final String docURI;
private final String content;
private final String content;
private final String[] classpathEntries;
private final CompletableFuture<Void> future;
public UpdateItem(String docURI, String content, String[] classpathEntries, CompletableFuture<Void> future) {
public UpdateItem(String docURI, String content, String[] classpathEntries) {
this.docURI = docURI;
this.content = content;
this.classpathEntries = classpathEntries;
this.future = future;
}
public String getDocURI() {
return docURI;
}
public String getContent() {
return content;
}
public String[] getClasspathEntries() {
return classpathEntries;
this.future = new CompletableFuture<Void>();
}
@Override
public CompletableFuture<Void> getFuture() {
return future;
}
@Override
public void run() {
try {
SpringIndexer.this.scanFile(docURI, content, classpathEntries);
} catch (Exception e) {
Log.log(e);
}
future.complete(null);
}
}
private class InitializeItem implements WorkerItem {
private final WorkspaceFolder[] workspaceRoots;
private final CompletableFuture<Void> future;
public InitializeItem(WorkspaceFolder[] workspaceRoots) {
Log.log("initialze spring indexer task created for roots: " + Arrays.toString(workspaceRoots));
this.workspaceRoots = workspaceRoots;
this.future = new CompletableFuture<Void>();
}
@Override
public CompletableFuture<Void> getFuture() {
return future;
}
@Override
public void run() {
if (!future.isCancelled()) {
Log.log("initialze spring indexer task started for roots: " + Arrays.toString(workspaceRoots));
for (WorkspaceFolder root : workspaceRoots) {
SpringIndexer.this.scanFiles(root);
}
Log.log("initialze spring indexer task completed for roots: " + Arrays.toString(workspaceRoots));
future.complete(null);
}
else {
Log.log("initialze spring indexer task canceled for roots: " + Arrays.toString(workspaceRoots));
}
}
}
}