Attempted fix of suspected deadlock
See: https://github.com/spring-projects/sts4/issues/136
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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))));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user