Skip to content

Commit

Permalink
When LSP Server fails to start too many times, don't try start it again.
Browse files Browse the repository at this point in the history
  • Loading branch information
jlahoda committed Apr 5, 2021
1 parent 5a9c949 commit 281781d
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.netbeans.modules.cpplite.editor.spi.CProjectConfigurationProvider.ProjectConfiguration;
import org.openide.filesystems.FileUtil;
import org.openide.modules.Places;
import org.openide.util.Pair;

/**
*
Expand All @@ -66,7 +67,7 @@ public class LanguageServerImpl implements LanguageServerProvider {

private static final Logger LOG = Logger.getLogger(LanguageServerImpl.class.getName());

private static final Map<Project, LanguageServerDescription> prj2Server = new HashMap<>();
private static final Map<Project, Pair<Process, LanguageServerDescription>> prj2Server = new HashMap<>();

@Override
public LanguageServerDescription startServer(Lookup lookup) {
Expand All @@ -88,7 +89,10 @@ public void preferenceChange(PreferenceChangeEvent evt) {
String ccls = Utils.getCCLSPath();
String clangd = Utils.getCLANGDPath();
if (ccls != null || clangd != null) {
return prj2Server.computeIfAbsent(prj, (Project p) -> {
return prj2Server.compute(prj, (p, pair) -> {
if (pair != null && pair.first().isAlive()) {
return pair;
}
try {
List<String> command = new ArrayList<>();

Expand Down Expand Up @@ -128,14 +132,14 @@ public void stateChanged(ChangeEvent e) {
in = new CopyInput(in, System.err);
out = new CopyOutput(out, System.err);
}
return LanguageServerDescription.create(in, out, process);
return Pair.of(process, LanguageServerDescription.create(in, out, process));
}
return null;
} catch (IOException ex) {
LOG.log(Level.FINE, null, ex);
return null;
}
});
}).second();
}
return null;
}
Expand Down
71 changes: 57 additions & 14 deletions ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.net.InetAddress;
Expand Down Expand Up @@ -74,11 +75,13 @@
import org.netbeans.modules.lsp.client.spi.ServerRestarter;
import org.netbeans.modules.lsp.client.spi.LanguageServerProvider;
import org.netbeans.modules.lsp.client.spi.LanguageServerProvider.LanguageServerDescription;
import org.openide.awt.NotificationDisplayer;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.modules.OnStop;
import org.openide.util.ChangeSupport;
import org.openide.util.Exceptions;
import org.openide.util.ImageUtilities;
import org.openide.util.Lookup;
import org.openide.util.NbBundle.Messages;
import org.openide.util.RequestProcessor;
Expand All @@ -95,10 +98,12 @@ public class LSPBindings {

private static final int DELAY = 500;
private static final int LSP_KEEP_ALIVE_MINUTES = 10;
private static final int INVALID_START_TIME = 1 * 60 * 1000;
private static final int INVALID_START_MAX_COUNT = 5;
private static final RequestProcessor WORKER = new RequestProcessor(LanguageClientImpl.class.getName(), 1, false, false);
private static final ChangeSupport cs = new ChangeSupport(LSPBindings.class);
private static final Map<LSPBindings,Long> lspKeepAlive = new IdentityHashMap<>();
private static final Map<URI, Map<String, WeakReference<LSPBindings>>> project2MimeType2Server = new HashMap<>();
private static final Map<URI, Map<String, ServerDescription>> project2MimeType2Server = new HashMap<>();
private static final Map<FileObject, Map<String, LSPBindings>> workspace2Extension2Server = new HashMap<>();

static {
Expand Down Expand Up @@ -168,23 +173,28 @@ public static synchronized LSPBindings getBindingsImpl(Project prj, FileObject f
URI uri = dir.toURI();

LSPBindings bindings = null;
WeakReference<LSPBindings> bindingsReference =
ServerDescription description =
project2MimeType2Server.computeIfAbsent(uri, p -> new HashMap<>())
.get(mimeType);
.computeIfAbsent(mimeType, m -> new ServerDescription());

if(bindingsReference != null) {
bindings = bindingsReference.get();
if (description.bindings != null) {
bindings = description.bindings.get();
}

if (bindings != null && bindings.process != null && !bindings.process.isAlive()) {
startFailed(description, mimeType);
bindings = null;
}

if (description.failedCount >= INVALID_START_MAX_COUNT) {
return null;
}

if (bindings == null) {
bindings = buildBindings(prj, mimeType, dir, uri);
bindings = buildBindings(description, prj, mimeType, dir, uri);
if (bindings != null) {
project2MimeType2Server.computeIfAbsent(uri, p -> new HashMap<>())
.put(mimeType, new WeakReference<>(bindings));
description.bindings = new WeakReference<>(bindings);
description.lastStartTimeStamp = System.currentTimeMillis();
WORKER.post(() -> cs.fireChange());
}
}
Expand All @@ -196,12 +206,34 @@ public static synchronized LSPBindings getBindingsImpl(Project prj, FileObject f
return bindings != null ? bindings : null;
}

@Messages({
"# {0} - the mime type for which the LSP server failed to start",
"TITLE_FailedToStart=LSP Server for {0} failed to start too many times.",
"DETAIL_FailedToStart=The LSP Server failed to start too many times in a short time, and will not be restarted anymore."
})
private static void startFailed(ServerDescription description, String mimeType) {
long timeStamp = System.currentTimeMillis();
if (timeStamp - description.lastStartTimeStamp < INVALID_START_TIME) {
description.failedCount++;
if (description.failedCount == INVALID_START_MAX_COUNT) {
NotificationDisplayer.getDefault().notify(Bundle.TITLE_FailedToStart(mimeType),
ImageUtilities.loadImageIcon("/org/netbeans/modules/lsp/client/resources/error_16.png", false),
Bundle.DETAIL_FailedToStart(),
null);
}
} else {
description.failedCount = 0;
}
description.lastStartTimeStamp = timeStamp;
}

@SuppressWarnings({"AccessingNonPublicFieldOfAnotherObject", "ResultOfObjectAllocationIgnored"})
private static LSPBindings buildBindings(Project prj, String mt, FileObject dir, URI baseUri) {
private static LSPBindings buildBindings(ServerDescription inDescription, Project prj, String mt, FileObject dir, URI baseUri) {
MimeTypeInfo mimeTypeInfo = new MimeTypeInfo(mt);
ServerRestarter restarter = () -> {
synchronized (LSPBindings.class) {
WeakReference<LSPBindings> bRef = project2MimeType2Server.getOrDefault(baseUri, Collections.emptyMap()).remove(mt);
ServerDescription description = project2MimeType2Server.getOrDefault(baseUri, Collections.emptyMap()).remove(mt);
Reference<LSPBindings> bRef = description != null ? description.bindings : null;
LSPBindings b = bRef != null ? bRef.get() : null;

if (b != null) {
Expand All @@ -219,6 +251,8 @@ private static LSPBindings buildBindings(Project prj, String mt, FileObject dir,
}
};

boolean foundServer = false;

for (LanguageServerProvider provider : MimeLookup.getLookup(mt).lookupAll(LanguageServerProvider.class)) {
final Lookup lkp = prj != null ? Lookups.fixed(prj, mimeTypeInfo, restarter) : Lookups.fixed(mimeTypeInfo, restarter);
LanguageServerDescription desc = provider.startServer(lkp);
Expand All @@ -228,6 +262,7 @@ private static LSPBindings buildBindings(Project prj, String mt, FileObject dir,
if (b != null) {
return b;
}
foundServer = true;
try {
LanguageClientImpl lci = new LanguageClientImpl();
InputStream in = LanguageServerProviderAccessor.getINSTANCE().getInputStream(desc);
Expand All @@ -249,6 +284,9 @@ private static LSPBindings buildBindings(Project prj, String mt, FileObject dir,
}
}
}
if (foundServer) {
startFailed(inDescription, mt);
}
return null;
}

Expand Down Expand Up @@ -328,7 +366,7 @@ public static synchronized Set<LSPBindings> getAllBindings() {
project2MimeType2Server.values()
.stream()
.flatMap(n -> n.values().stream())
.map(bindingRef -> bindingRef.get())
.map(description -> description.bindings.get())
.filter(binding -> binding != null)
.forEach(allBindings::add);
workspace2Extension2Server.values()
Expand Down Expand Up @@ -427,9 +465,9 @@ public static class Cleanup implements Runnable {
@Override
@SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
public void run() {
for (Map<String, WeakReference<LSPBindings>> mime2Bindings : project2MimeType2Server.values()) {
for (WeakReference<LSPBindings> bRef : mime2Bindings.values()) {
LSPBindings b = bRef != null ? bRef.get() : null;
for (Map<String, ServerDescription> mime2Bindings : project2MimeType2Server.values()) {
for (ServerDescription description : mime2Bindings.values()) {
LSPBindings b = description.bindings != null ? description.bindings.get() : null;
if (b != null && b.process != null) {
b.process.destroy();
}
Expand Down Expand Up @@ -488,4 +526,9 @@ public void run() {

}
}
private static class ServerDescription {
public long lastStartTimeStamp;
public int failedCount;
public Reference<LSPBindings> bindings;
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 281781d

Please sign in to comment.