Cap the amount of time spent waiting for plugin completions in LSP

Change-Id: Ic60d9936a252a1322becc233d696dfed41af834a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/150927
Commit-Queue: Danny Tuppeny <danny@tuppeny.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Danny Tuppeny
2020-06-12 14:11:24 +00:00
committed by commit-bot@chromium.org
parent 99bd9d7db5
commit 44f869d3a6
6 changed files with 81 additions and 9 deletions
@@ -111,11 +111,11 @@ class CompletionDomainHandler extends AbstractRequestHandler {
// false) then send empty results
//
// Add the fixes produced by plugins to the server-generated fixes.
// Add the completions produced by plugins to the server-generated list.
//
if (pluginFutures != null) {
var responses = await waitForResponses(pluginFutures,
requestParameters: requestParams);
requestParameters: requestParams, timeout: 100);
for (var response in responses) {
var result =
plugin.CompletionGetSuggestionsResult.fromResponse(response);
@@ -25,8 +25,8 @@ import 'package:analyzer/src/services/available_declarations.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
// If the client does not provide capabilities.completion.completionItemKind.valueSet
// then we must never send a kind that's not in this list.
/// If the client does not provide capabilities.completion.completionItemKind.valueSet
/// then we must never send a kind that's not in this list.
final defaultSupportedCompletionKinds = HashSet<CompletionItemKind>.of([
CompletionItemKind.Text,
CompletionItemKind.Method,
@@ -163,7 +163,8 @@ class CompletionHandler
int offset,
) async {
final requestParams = plugin.CompletionGetSuggestionsParams(path, offset);
final pluginResponses = await requestFromPlugins(path, requestParams);
final pluginResponses =
await requestFromPlugins(path, requestParams, timeout: 100);
final pluginResults = pluginResponses
.map((e) => plugin.CompletionGetSuggestionsResult.fromResponse(e))
@@ -82,12 +82,17 @@ mixin Handler<P, R> {
mixin LspPluginRequestHandlerMixin<T extends AbstractAnalysisServer>
on RequestHandlerMixin<T> {
Future<List<Response>> requestFromPlugins(String path, RequestParams params) {
Future<List<Response>> requestFromPlugins(
String path,
RequestParams params, {
int timeout = 500,
}) {
final driver = server.getAnalysisDriver(path);
final pluginFutures = server.pluginManager
.broadcastRequest(params, contextRoot: driver.contextRoot);
return waitForResponses(pluginFutures, requestParameters: params);
return waitForResponses(pluginFutures,
requestParameters: params, timeout: timeout);
}
}
@@ -192,6 +192,49 @@ class CompletionTest extends AbstractLspAnalysisServerTest {
expect(suggestion.label, equals('id'));
}
Future<void> test_fromPlugin_tooSlow() async {
final content = '''
void main() {
var x = '';
print(^);
}
''';
final pluginResult = plugin.CompletionGetSuggestionsResult(
content.indexOf('^'),
0,
[
plugin.CompletionSuggestion(
plugin.CompletionSuggestionKind.INVOCATION,
100,
'x.toUpperCase()',
-1,
-1,
false,
false,
),
],
);
configureTestPlugin(
respondWith: pluginResult,
// Don't respond within an acceptable time
respondAfter: Duration(seconds: 1),
);
await initialize();
await openFile(mainFileUri, withoutMarkers(content));
final res = await getCompletion(mainFileUri, positionFromMarker(content));
final fromServer = res.singleWhere((c) => c.label == 'x');
final fromPlugin = res.singleWhere((c) => c.label == 'x.toUpperCase()',
orElse: () => null);
// Server results should still be included.
expect(fromServer.kind, equals(CompletionItemKind.Variable));
// Plugin results are not because they didn't arrive in time.
expect(fromPlugin, isNull);
}
Future<void> test_gettersAndSetters() async {
final content = '''
class MyClass {
@@ -52,13 +52,15 @@ abstract class AbstractLspAnalysisServerTest
DiscoveredPluginInfo configureTestPlugin({
plugin.ResponseResult respondWith,
plugin.Notification notification,
Duration respondAfter = Duration.zero,
}) {
final info = DiscoveredPluginInfo('a', 'b', 'c', null, null);
pluginManager.plugins.add(info);
if (respondWith != null) {
pluginManager.broadcastResults = <PluginInfo, Future<plugin.Response>>{
info: Future.value(respondWith.toResponse('-', 1))
info: Future.delayed(respondAfter)
.then((_) => respondWith.toResponse('-', 1))
};
}
+22 -1
View File
@@ -39,6 +39,12 @@ class MockLspServerChannel implements LspServerCommunicationChannel {
/// Completer that will be signalled when the input stream is closed.
final Completer _closed = Completer();
/// Errors popups sent to the user.
final shownErrors = <lsp.ShowMessageParams>[];
/// Warning popups sent to the user.
final shownWarnings = <lsp.ShowMessageParams>[];
MockLspServerChannel(bool _printMessages) {
if (_printMessages) {
_serverToClient.stream
@@ -46,6 +52,20 @@ class MockLspServerChannel implements LspServerCommunicationChannel {
_clientToServer.stream
.listen((message) => print('==> ' + jsonEncode(message)));
}
// Keep track of any errors/warnings that are sent to the user with
// `window/showMessage`.
_serverToClient.stream.listen((message) {
if (message is lsp.NotificationMessage &&
message.method == Method.window_showMessage &&
message.params is lsp.ShowMessageParams) {
if (message.params?.type == MessageType.Error) {
shownErrors.add(message.params);
} else if (message.params?.type == MessageType.Warning) {
shownWarnings.add(message.params);
}
}
});
}
/// Future that will be completed when the input stream is closed.
@@ -167,7 +187,8 @@ class MockLspServerChannel implements LspServerCommunicationChannel {
(message is lsp.ResponseMessage && message.id == request.id) ||
(throwOnError &&
message is lsp.NotificationMessage &&
message.method == Method.window_showMessage));
message.method == Method.window_showMessage &&
message.params?.type == MessageType.Error));
if (response is lsp.ResponseMessage) {
return response;