Skip to content

Commit

Permalink
feat: Support crash of language server starting (#1001)
Browse files Browse the repository at this point in the history
Fixes #1001

Signed-off-by: azerr <[email protected]>
  • Loading branch information
angelozerr committed Jul 27, 2023
1 parent 357eee5 commit 60cd710
Show file tree
Hide file tree
Showing 27 changed files with 767 additions and 401 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,16 @@ public abstract static class LanguageServerDefinition {

private static final int DEFAULT_LAST_DOCUMENTED_DISCONNECTED_TIMEOUT = 5;

public final @Nonnull String id;
public final @Nonnull String label;
public final @Nonnull
String id;
public final @Nonnull
String label;
public final boolean isSingleton;
public final @Nonnull Map<Language, String> languageIdMappings;
public final @Nonnull
Map<Language, String> languageIdMappings;
public final String description;
public final int lastDocumentDisconnectedTimeout;
private boolean enabled;

public LanguageServerDefinition(@Nonnull String id, @Nonnull String label, String description, boolean isSingleton, Integer lastDocumentDisconnectedTimeout) {
this.id = id;
Expand All @@ -53,6 +57,26 @@ public LanguageServerDefinition(@Nonnull String id, @Nonnull String label, Strin
this.isSingleton = isSingleton;
this.lastDocumentDisconnectedTimeout = lastDocumentDisconnectedTimeout != null && lastDocumentDisconnectedTimeout > 0 ? lastDocumentDisconnectedTimeout : DEFAULT_LAST_DOCUMENTED_DISCONNECTED_TIMEOUT;
this.languageIdMappings = new ConcurrentHashMap<>();
setEnabled(true);
}


/**
* Returns true if the language server definition is enabled and false otherwise.
*
* @return true if the language server definition is enabled and false otherwise.
*/
public boolean isEnabled() {
return enabled;
}

/**
* Set enabled the language server definition.
*
* @param enabled enabled the language server definition.
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public void registerAssociation(@Nonnull Language language, @Nonnull String languageId) {
Expand Down Expand Up @@ -224,9 +248,12 @@ LanguageServerDefinition getDefinition(@NonNull String languageServerId) {
*/
private static class LanguageMapping {

@Nonnull public final String id;
@Nonnull public final Language language;
@Nullable public final String languageId;
@Nonnull
public final String id;
@Nonnull
public final Language language;
@Nullable
public final String languageId;

public LanguageMapping(@Nonnull Language language, @Nonnull String id, @Nullable String languageId) {
this.language = language;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
/*******************************************************************************
* Copyright (c) 2019 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at https://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package com.redhat.devtools.intellij.lsp4ij;

import com.intellij.lang.Language;
Expand All @@ -21,31 +31,23 @@
import javax.annotation.Nullable;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
* Language server accessor.
*/
public class LanguageServiceAccessor {
private static final Logger LOGGER = LoggerFactory.getLogger(LanguageServiceAccessor.class);
private final Project project;

public static LanguageServiceAccessor getInstance(Project project) {
return ServiceManager.getService(project, LanguageServiceAccessor.class);
}

private LanguageServiceAccessor(Project project) {
this.project = project;
}
Expand All @@ -64,6 +66,15 @@ public void clearStartedServers() {
}
}

/**
* Return the started servers.
*
* @return the started servers.
*/
public Set<LanguageServerWrapper> getStartedServers() {
return startedServers;
}

void shutdownAllDispatchers() {
startedServers.forEach(LanguageServerWrapper::stopDispatcher);
}
Expand Down Expand Up @@ -201,7 +212,7 @@ public void enableLanguageServerContentType(
*/
@Deprecated
public LanguageServer getLanguageServer(@Nonnull VirtualFile file, @Nonnull LanguageServersRegistry.LanguageServerDefinition lsDefinition,
Predicate<ServerCapabilities> capabilitiesPredicate)
Predicate<ServerCapabilities> capabilitiesPredicate)
throws IOException {
LanguageServerWrapper wrapper = getLSWrapperForConnection(LSPIJUtils.getProject(file), lsDefinition, LSPIJUtils.toUri(file));
if (capabilitiesPredicate == null
Expand All @@ -222,8 +233,8 @@ public LanguageServer getLanguageServer(@Nonnull VirtualFile file, @Nonnull Lang
* @return a LanguageServer for the given file, which is defined with provided server ID and conforms to specified request
*/
public CompletableFuture<LanguageServer> getInitializedLanguageServer(@Nonnull VirtualFile file,
@Nonnull LanguageServersRegistry.LanguageServerDefinition lsDefinition,
Predicate<ServerCapabilities> capabilitiesPredicate)
@Nonnull LanguageServersRegistry.LanguageServerDefinition lsDefinition,
Predicate<ServerCapabilities> capabilitiesPredicate)
throws IOException {
LanguageServerWrapper wrapper = getLSWrapperForConnection(LSPIJUtils.getProject(file), lsDefinition, LSPIJUtils.toUri(file));
if (capabilitiesPredicate == null
Expand All @@ -239,17 +250,16 @@ public CompletableFuture<LanguageServer> getInitializedLanguageServer(@Nonnull V
* Get the requested language server instance for the given document. Starts the
* language server if not already started.
*
* @param document the document for which the initialized LanguageServer shall be returned
* @param serverId the ID of the LanguageServer to be returned
* @param capabilitesPredicate
* a predicate to check capabilities
* @param document the document for which the initialized LanguageServer shall be returned
* @param serverId the ID of the LanguageServer to be returned
* @param capabilitesPredicate a predicate to check capabilities
* @return a LanguageServer for the given file, which is defined with provided
* server ID and conforms to specified request. If
* {@code capabilitesPredicate} does not test positive for the server's
* capabilities, {@code null} is returned.
* server ID and conforms to specified request. If
* {@code capabilitesPredicate} does not test positive for the server's
* capabilities, {@code null} is returned.
*/
public CompletableFuture<LanguageServer> getInitializedLanguageServer(Document document,
LanguageServersRegistry.LanguageServerDefinition lsDefinition, Predicate<ServerCapabilities> capabilitiesPredicate)
LanguageServersRegistry.LanguageServerDefinition lsDefinition, Predicate<ServerCapabilities> capabilitiesPredicate)
throws IOException {
URI initialPath = LSPIJUtils.toUri(document);
LanguageServerWrapper wrapper = getLSWrapperForConnection(document, lsDefinition, initialPath);
Expand All @@ -264,15 +274,13 @@ public CompletableFuture<LanguageServer> getInitializedLanguageServer(Document d
* Checks if the given {@code wrapper}'s capabilities comply with the given
* {@code capabilitiesPredicate}.
*
* @param wrapper
* the server that's capabilities are tested with
* {@code capabilitiesPredicate}
* @param capabilitiesPredicate
* predicate testing the capabilities of {@code wrapper}.
* @param wrapper the server that's capabilities are tested with
* {@code capabilitiesPredicate}
* @param capabilitiesPredicate predicate testing the capabilities of {@code wrapper}.
* @return The result of applying the capabilities of {@code wrapper} to
* {@code capabilitiesPredicate}, or {@code false} if
* {@code capabilitiesPredicate == null} or
* {@code wrapper.getServerCapabilities() == null}
* {@code capabilitiesPredicate}, or {@code false} if
* {@code capabilitiesPredicate == null} or
* {@code wrapper.getServerCapabilities() == null}
*/
private static boolean capabilitiesComply(LanguageServerWrapper wrapper,
Predicate<ServerCapabilities> capabilitiesPredicate) {
Expand All @@ -282,8 +290,6 @@ private static boolean capabilitiesComply(LanguageServerWrapper wrapper,
}




/**
* TODO we need a similar method for generic IDocument (enabling non-IFiles)
*
Expand All @@ -296,7 +302,7 @@ private static boolean capabilitiesComply(LanguageServerWrapper wrapper,
*/
@Nonnull
public Collection<LanguageServerWrapper> getLSWrappers(@Nonnull VirtualFile file,
@Nullable Predicate<ServerCapabilities> request) throws IOException {
@Nullable Predicate<ServerCapabilities> request) throws IOException {
LinkedHashSet<LanguageServerWrapper> res = new LinkedHashSet<>();
Module project = LSPIJUtils.getProject(file);
if (project == null) {
Expand Down Expand Up @@ -350,7 +356,7 @@ private Collection<LanguageServerWrapper> getLSWrappers(@Nonnull Document docume
res.addAll(startedServers.stream()
.filter(wrapper -> {
try {
return wrapper.isConnectedTo(path) || LanguageServersRegistry.getInstance().matches(document, wrapper.serverDefinition, project);
return wrapper.isEnabled() && (wrapper.isConnectedTo(path) || LanguageServersRegistry.getInstance().matches(document, wrapper.serverDefinition, project));
} catch (ProcessCanceledException cancellation) {
throw cancellation;
} catch (Exception e) {
Expand Down Expand Up @@ -408,13 +414,17 @@ private Collection<LanguageServerWrapper> getLSWrappers(@Nonnull Document docume
*/
@Deprecated
public LanguageServerWrapper getLSWrapperForConnection(@Nonnull Module project,
@Nonnull LanguageServersRegistry.LanguageServerDefinition serverDefinition) throws IOException {
@Nonnull LanguageServersRegistry.LanguageServerDefinition serverDefinition) throws IOException {
return getLSWrapperForConnection(project, serverDefinition, null);
}

@Deprecated
private LanguageServerWrapper getLSWrapperForConnection(@Nonnull Module project,
@Nonnull LanguageServersRegistry.LanguageServerDefinition serverDefinition, @Nullable URI initialPath) throws IOException {
@Nonnull LanguageServersRegistry.LanguageServerDefinition serverDefinition, @Nullable URI initialPath) throws IOException {
if (!serverDefinition.isEnabled()) {
// don't return a language server wrapper for the given server definition
return null;
}
LanguageServerWrapper wrapper = null;

synchronized (startedServers) {
Expand All @@ -436,7 +446,11 @@ private LanguageServerWrapper getLSWrapperForConnection(@Nonnull Module project,
}

private LanguageServerWrapper getLSWrapperForConnection(Document document,
LanguageServersRegistry.LanguageServerDefinition serverDefinition, URI initialPath) throws IOException {
LanguageServersRegistry.LanguageServerDefinition serverDefinition, URI initialPath) throws IOException {
if (!serverDefinition.isEnabled()) {
// don't return a language server wrapper for the given server definition
return null;
}
LanguageServerWrapper wrapper = null;

synchronized (startedServers) {
Expand Down Expand Up @@ -476,9 +490,8 @@ private List<LanguageServerWrapper> getStartedLSWrappers(Predicate<LanguageServe
}



private Collection<LanguageServerWrapper> getMatchingStartedWrappers(@Nonnull VirtualFile file,
@Nullable Predicate<ServerCapabilities> request) {
@Nullable Predicate<ServerCapabilities> request) {
synchronized (startedServers) {
return startedServers.stream().filter(wrapper -> wrapper.isConnectedTo(LSPIJUtils.toUri(file))
|| (LanguageServersRegistry.getInstance().matches(file, wrapper.serverDefinition, project)
Expand Down Expand Up @@ -509,7 +522,7 @@ public List<LanguageServer> getActiveLanguageServers(Predicate<ServerCapabilitie
*/
@Nonnull
public List<LanguageServer> getLanguageServers(@Nonnull Module project,
Predicate<ServerCapabilities> request) {
Predicate<ServerCapabilities> request) {
return getLanguageServers(project, request, false);
}

Expand All @@ -523,7 +536,7 @@ public List<LanguageServer> getLanguageServers(@Nonnull Module project,
*/
@Nonnull
public List<LanguageServer> getLanguageServers(@Nullable Module project,
Predicate<ServerCapabilities> request, boolean onlyActiveLS) {
Predicate<ServerCapabilities> request, boolean onlyActiveLS) {
List<LanguageServer> serverInfos = new ArrayList<>();
for (LanguageServerWrapper wrapper : startedServers) {
if ((!onlyActiveLS || wrapper.isActive()) && (project == null || wrapper.canOperate(project))) {
Expand Down Expand Up @@ -576,16 +589,17 @@ public CompletableFuture<List<Pair<LanguageServerWrapper, LanguageServer>>> getL
final List<Pair<LanguageServerWrapper, LanguageServer>> res = Collections.synchronizedList(new ArrayList<>());
try {
return CompletableFuture.allOf(getLSWrappers(document).stream().map(wrapper ->
wrapper.getInitializedServer().thenComposeAsync(server -> {
if (server != null && (filter == null || filter.test(wrapper.getServerCapabilities()))) {
try {
return wrapper.connect(document);
} catch (IOException ex) {
LOGGER.warn(ex.getLocalizedMessage(), ex);
}
}
return CompletableFuture.completedFuture(null);
}).thenAccept(server -> {
wrapper.getInitializedServer()
.thenComposeAsync(server -> {
if (server != null && wrapper.isEnabled() && (filter == null || filter.test(wrapper.getServerCapabilities()))) {
try {
return wrapper.connect(document);
} catch (IOException ex) {
LOGGER.warn(ex.getLocalizedMessage(), ex);
}
}
return CompletableFuture.completedFuture(null);
}).thenAccept(server -> {
if (server != null) {
res.add(new Pair(wrapper, server));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package com.redhat.devtools.intellij.lsp4ij.console.explorer;
package com.redhat.devtools.intellij.lsp4ij;

/**
* Language server status.
*/
public enum ServerStatus {

startingProcess,
startedProcess,
starting,
started,
stopping,
stopped;
none, // initial status
starting, // The language server process is starting
started, // The language server is started without error
stopping, // The language server is stopping
stopped // The language server is stopped with or without error;

}
Loading

0 comments on commit 60cd710

Please sign in to comment.