Attempted fix of suspected deadlock

See: https://github.com/spring-projects/sts4/issues/136
This commit is contained in:
Kris De Volder
2018-11-21 13:49:30 -08:00
parent 8f39484733
commit 7ffbe07008
7 changed files with 87 additions and 87 deletions

View File

@@ -30,6 +30,7 @@ import com.google.gson.Gson;
import com.google.gson.JsonElement;
import reactor.core.Disposable;
import reactor.core.publisher.Mono;
public class ClasspathListenerManager {
@@ -46,7 +47,7 @@ public class ClasspathListenerManager {
this.async = server.getAsync();
}
public Disposable addClasspathListener(ClasspathListener classpathListener) {
public Mono<Disposable> addClasspathListener(ClasspathListener classpathListener) {
String callbackCommandId = "sts4.classpath." + RandomStringUtils.randomAlphabetic(8);
// 1. register callback command handler in SimpleLanguageServer
@@ -78,29 +79,31 @@ public class ClasspathListenerManager {
ImmutableMap.of("commands", ImmutableList.of(callbackCommandId))
)
));
server.getClient().registerCapability(params).join();
// 3. call the client to ask it to call that callback
server.getClient().addClasspathListener(
new ClasspathListenerParams(callbackCommandId)
).join();
Mono<Void> registerCallbackCommand = Mono.defer(() -> Mono.fromFuture(
server.getClient().registerCapability(params)
));
Mono<Object> registerClasspathListener = Mono.defer(() -> Mono.fromFuture(
server.getClient().addClasspathListener(new ClasspathListenerParams(callbackCommandId))
));
// Cleanups:
return () -> {
try {
log.info("Unregistering classpath callback "+callbackCommandId +" ...");
this.server.getClient().removeClasspathListener(
new ClasspathListenerParams(callbackCommandId)
).join();
log.info("Unregistering classpath callback "+callbackCommandId +" OK");
Disposable cleanups = () -> {
log.info("Unregistering classpath callback "+callbackCommandId +" ...");
AsyncRunner.thenLog(log,
this.server.getClient().removeClasspathListener(new ClasspathListenerParams(callbackCommandId))
);
log.info("Unregistering classpath callback "+callbackCommandId +" OK");
AsyncRunner.thenLog(log,
this.server.getClient().unregisterCapability(new UnregistrationParams(ImmutableList.of(
new Unregistration(registrationId, WORKSPACE_EXECUTE_COMMAND)
))).join();
unregisterCommand.dispose();
} catch (Exception e) {
log.error("", e);
}
)))
);
unregisterCommand.dispose();
};
return
registerCallbackCommand
.then(registerClasspathListener)
.thenReturn(cleanups);
}
}

View File

@@ -657,8 +657,14 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, LanguageC
this.initializeHandler = handler;
}
public synchronized void onInitialized(Runnable handler) {
thenLog(log, this.initialized.thenAccept((whocares) -> handler.run()));
public <T> Mono<T> onInitialized(Mono<T> handler) {
return Mono.fromFuture(this.initialized).then(handler)
.doOnError(error -> log.error("", error));
}
public void doOnInitialized(Runnable action) {
onInitialized(Mono.fromRunnable(action)).toFuture();
}
public synchronized void onShutdown(Runnable handler) {
@@ -677,7 +683,7 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, LanguageC
return this.async;
}
public synchronized Disposable addClasspathListener(ClasspathListener classpathListener) throws Exception {
public synchronized Mono<Disposable> addClasspathListener(ClasspathListener classpathListener) {
if (classpathListenerManager == null) {
classpathListenerManager = new ClasspathListenerManager(this);
}

View File

@@ -62,7 +62,7 @@ public class SimpleServerFileObserver extends BasicFileObserver {
}
private void subscribe(String subscriptionId, List<String> globPattern, int kind) {
server.onInitialized(() -> {
server.doOnInitialized(() -> {
if (server.canRegisterFileWatchersDynamically()) {
List<FileSystemWatcher> watchers = globPattern.stream().map(pattern -> new FileSystemWatcher(pattern, kind)).collect(Collectors.toList());
Registration registration = new Registration(subscriptionId, WORKSPACE_DID_CHANGE_WATCHED_FILES, new DidChangeWatchedFilesRegistrationOptions(watchers));
@@ -73,7 +73,7 @@ public class SimpleServerFileObserver extends BasicFileObserver {
@Override
public boolean unsubscribe(String subscriptionId) {
server.onInitialized(() -> {
server.doOnInitialized(() -> {
if (server.canRegisterFileWatchersDynamically()) {
server.getClient().unregisterCapability(new UnregistrationParams(Arrays.asList(new Unregistration(subscriptionId, WORKSPACE_DID_CHANGE_WATCHED_FILES))));
}

View File

@@ -217,7 +217,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
});
server.onInitialize(this::initialize);
server.onInitialized(this::initialized);
server.doOnInitialized(this::initialized);
server.onShutdown(this::shutdown);
}

View File

@@ -11,9 +11,10 @@
package org.springframework.ide.vscode.boot.jdt.ls;
import reactor.core.Disposable;
import reactor.core.publisher.Mono;
public interface InitializableJavaProjectsService extends JavaProjectsService {
Disposable initialize() throws Exception;
Mono<Disposable> initialize();
}

View File

@@ -28,10 +28,10 @@ import com.google.common.base.Suppliers;
import reactor.core.Disposable;
public class JavaProjectsServiceWithFallback implements JavaProjectsService {
private Logger log = LoggerFactory.getLogger(JavaProjectsServiceWithFallback.class);
final private CompletableFuture<Void> initialized = new CompletableFuture<Void>();
final private CompletableFuture<Disposable> mainServiceInitialized;
final private SimpleLanguageServer server;
private Supplier<JavaProjectsService> fallback;
@@ -42,30 +42,22 @@ public class JavaProjectsServiceWithFallback implements JavaProjectsService {
this.main = main;
this.fallback = Suppliers.memoize(fallback);
this.server = server;
CompletableFuture<Disposable> disposable = new CompletableFuture<Disposable>();
this.server.onInitialized(() -> {
try {
disposable.complete(main.initialize());
initialized.complete(null);
} catch (Throwable e) {
log.info("Fallback classpath provider will be enabled");
disposable.complete(()-> {});
initialized.completeExceptionally(e);
}
});
this.server.onShutdown(() ->
disposable.thenAccept(Disposable::dispose).join()
this.mainServiceInitialized = this.server
.onInitialized(main.initialize())
.toFuture();
this.server.onShutdown(() ->
mainServiceInitialized.thenAccept(Disposable::dispose).join()
);
}
@Override
public Optional<IJavaProject> find(TextDocumentIdentifier doc) {
if (initialized.isDone()) {
if (initialized.isCompletedExceptionally()) {
if (mainServiceInitialized.isDone()) {
if (mainServiceInitialized.isCompletedExceptionally()) {
return fallback.get().find(doc);
} else {
return main.find(doc);
}
}
} else {
log.debug("find => NOT INITIALIZED YET");
}
@@ -75,7 +67,7 @@ public class JavaProjectsServiceWithFallback implements JavaProjectsService {
@Override
public void addListener(Listener listener) {
initialized.handle((success, failed) -> {
mainServiceInitialized.handle((success, failed) -> {
if (failed!=null) {
fallback.get().addListener(listener);
} else {
@@ -87,7 +79,7 @@ public class JavaProjectsServiceWithFallback implements JavaProjectsService {
@Override
public void removeListener(Listener listener) {
initialized.handle((success, failed) -> {
mainServiceInitialized.handle((success, failed) -> {
if (failed!=null) {
fallback.get().removeListener(listener);
} else {
@@ -99,12 +91,12 @@ public class JavaProjectsServiceWithFallback implements JavaProjectsService {
@Override
public IJavadocProvider javadocProvider(String projectUri, CPE classpathEntry) {
if (initialized.isDone()) {
if (initialized.isCompletedExceptionally()) {
if (mainServiceInitialized.isDone()) {
if (mainServiceInitialized.isCompletedExceptionally()) {
return fallback.get().javadocProvider(projectUri, classpathEntry);
} else {
return main.javadocProvider(projectUri, classpathEntry);
}
}
} else {
log.debug("javadoc => NOT INITIALIZED YET");
}

View File

@@ -38,6 +38,7 @@ import org.springframework.ide.vscode.commons.util.FileObserver;
import org.springframework.ide.vscode.commons.util.UriUtil;
import reactor.core.Disposable;
import reactor.core.publisher.Mono;
public class JdtLsProjectCache implements InitializableJavaProjectsService {
@@ -163,45 +164,43 @@ public class JdtLsProjectCache implements InitializableJavaProjectsService {
}
@Override
public Disposable initialize() throws Exception {
try {
return server.addClasspathListener(new ClasspathListener() {
@Override
public void changed(Event event) {
log.debug("claspath event received {}", event);
server.onInitialized(() -> {
//log.info("initialized.thenRun block entered");
try {
synchronized (table) {
String uri = UriUtil.normalize(event.projectUri);
log.debug("uri = {}", uri);
if (event.deleted) {
log.debug("event.deleted = true");
JavaProject deleted = table.remove(uri);
if (deleted!=null) {
log.debug("removed from table = true");
notifyDelete(deleted);
} else {
log.warn("Deleted project not removed because uri {} not found in {}", uri, table.keySet());
}
public Mono<Disposable> initialize() {
return server.addClasspathListener(new ClasspathListener() {
@Override
public void changed(Event event) {
log.debug("claspath event received {}", event);
server.doOnInitialized(() -> {
//log.info("initialized.thenRun block entered");
try {
synchronized (table) {
String uri = UriUtil.normalize(event.projectUri);
log.debug("uri = {}", uri);
if (event.deleted) {
log.debug("event.deleted = true");
JavaProject deleted = table.remove(uri);
if (deleted!=null) {
log.debug("removed from table = true");
notifyDelete(deleted);
} else {
log.debug("deleted = false");
JavaProject newProject = new JavaProject(getFileObserver(), new URI(uri), new ClasspathData(event.name, event.classpath.getEntries()), JdtLsProjectCache.this);
JavaProject oldProject = table.put(uri, newProject);
if (oldProject != null) {
notifyChanged(newProject);
} else {
notifyCreated(newProject);
}
log.warn("Deleted project not removed because uri {} not found in {}", uri, table.keySet());
}
} else {
log.debug("deleted = false");
JavaProject newProject = new JavaProject(getFileObserver(), new URI(uri), new ClasspathData(event.name, event.classpath.getEntries()), JdtLsProjectCache.this);
JavaProject oldProject = table.put(uri, newProject);
if (oldProject != null) {
notifyChanged(newProject);
} else {
notifyCreated(newProject);
}
}
} catch (Exception e) {
log.error("", e);
}
});
}
});
} catch (Throwable t) {
} catch (Exception e) {
log.error("", e);
}
});
}
}).doOnError(t -> {
if (isNoJdtError(t)) {
log.info("JDT Language Server not available. Fallback classpath provider will be used instead.");
} else if (isOldJdt(t)) {
@@ -209,7 +208,6 @@ public class JdtLsProjectCache implements InitializableJavaProjectsService {
} else {
log.error("Unexpected error registering classpath listener with JDT. Fallback classpath provider will be used instead.", t);
}
throw t;
}
});
}
}