Skip to content

Commit 44f869d

Browse files
DanTupcommit-bot@chromium.org
authored andcommitted
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 <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]>
1 parent 99bd9d7 commit 44f869d

File tree

6 files changed

+81
-9
lines changed

6 files changed

+81
-9
lines changed

pkg/analysis_server/lib/src/domain_completion.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,11 @@ class CompletionDomainHandler extends AbstractRequestHandler {
111111
// false) then send empty results
112112

113113
//
114-
// Add the fixes produced by plugins to the server-generated fixes.
114+
// Add the completions produced by plugins to the server-generated list.
115115
//
116116
if (pluginFutures != null) {
117117
var responses = await waitForResponses(pluginFutures,
118-
requestParameters: requestParams);
118+
requestParameters: requestParams, timeout: 100);
119119
for (var response in responses) {
120120
var result =
121121
plugin.CompletionGetSuggestionsResult.fromResponse(response);

pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ import 'package:analyzer/src/services/available_declarations.dart';
2525
import 'package:analyzer_plugin/protocol/protocol_common.dart';
2626
import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
2727

28-
// If the client does not provide capabilities.completion.completionItemKind.valueSet
29-
// then we must never send a kind that's not in this list.
28+
/// If the client does not provide capabilities.completion.completionItemKind.valueSet
29+
/// then we must never send a kind that's not in this list.
3030
final defaultSupportedCompletionKinds = HashSet<CompletionItemKind>.of([
3131
CompletionItemKind.Text,
3232
CompletionItemKind.Method,
@@ -163,7 +163,8 @@ class CompletionHandler
163163
int offset,
164164
) async {
165165
final requestParams = plugin.CompletionGetSuggestionsParams(path, offset);
166-
final pluginResponses = await requestFromPlugins(path, requestParams);
166+
final pluginResponses =
167+
await requestFromPlugins(path, requestParams, timeout: 100);
167168

168169
final pluginResults = pluginResponses
169170
.map((e) => plugin.CompletionGetSuggestionsResult.fromResponse(e))

pkg/analysis_server/lib/src/lsp/handlers/handlers.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,17 @@ mixin Handler<P, R> {
8282

8383
mixin LspPluginRequestHandlerMixin<T extends AbstractAnalysisServer>
8484
on RequestHandlerMixin<T> {
85-
Future<List<Response>> requestFromPlugins(String path, RequestParams params) {
85+
Future<List<Response>> requestFromPlugins(
86+
String path,
87+
RequestParams params, {
88+
int timeout = 500,
89+
}) {
8690
final driver = server.getAnalysisDriver(path);
8791
final pluginFutures = server.pluginManager
8892
.broadcastRequest(params, contextRoot: driver.contextRoot);
8993

90-
return waitForResponses(pluginFutures, requestParameters: params);
94+
return waitForResponses(pluginFutures,
95+
requestParameters: params, timeout: timeout);
9196
}
9297
}
9398

pkg/analysis_server/test/lsp/completion_test.dart

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,49 @@ class CompletionTest extends AbstractLspAnalysisServerTest {
192192
expect(suggestion.label, equals('id'));
193193
}
194194

195+
Future<void> test_fromPlugin_tooSlow() async {
196+
final content = '''
197+
void main() {
198+
var x = '';
199+
print(^);
200+
}
201+
''';
202+
203+
final pluginResult = plugin.CompletionGetSuggestionsResult(
204+
content.indexOf('^'),
205+
0,
206+
[
207+
plugin.CompletionSuggestion(
208+
plugin.CompletionSuggestionKind.INVOCATION,
209+
100,
210+
'x.toUpperCase()',
211+
-1,
212+
-1,
213+
false,
214+
false,
215+
),
216+
],
217+
);
218+
configureTestPlugin(
219+
respondWith: pluginResult,
220+
// Don't respond within an acceptable time
221+
respondAfter: Duration(seconds: 1),
222+
);
223+
224+
await initialize();
225+
await openFile(mainFileUri, withoutMarkers(content));
226+
227+
final res = await getCompletion(mainFileUri, positionFromMarker(content));
228+
final fromServer = res.singleWhere((c) => c.label == 'x');
229+
final fromPlugin = res.singleWhere((c) => c.label == 'x.toUpperCase()',
230+
orElse: () => null);
231+
232+
// Server results should still be included.
233+
expect(fromServer.kind, equals(CompletionItemKind.Variable));
234+
// Plugin results are not because they didn't arrive in time.
235+
expect(fromPlugin, isNull);
236+
}
237+
195238
Future<void> test_gettersAndSetters() async {
196239
final content = '''
197240
class MyClass {

pkg/analysis_server/test/lsp/server_abstract.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,15 @@ abstract class AbstractLspAnalysisServerTest
5252
DiscoveredPluginInfo configureTestPlugin({
5353
plugin.ResponseResult respondWith,
5454
plugin.Notification notification,
55+
Duration respondAfter = Duration.zero,
5556
}) {
5657
final info = DiscoveredPluginInfo('a', 'b', 'c', null, null);
5758
pluginManager.plugins.add(info);
5859

5960
if (respondWith != null) {
6061
pluginManager.broadcastResults = <PluginInfo, Future<plugin.Response>>{
61-
info: Future.value(respondWith.toResponse('-', 1))
62+
info: Future.delayed(respondAfter)
63+
.then((_) => respondWith.toResponse('-', 1))
6264
};
6365
}
6466

pkg/analysis_server/test/mocks.dart

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,33 @@ class MockLspServerChannel implements LspServerCommunicationChannel {
3939
/// Completer that will be signalled when the input stream is closed.
4040
final Completer _closed = Completer();
4141

42+
/// Errors popups sent to the user.
43+
final shownErrors = <lsp.ShowMessageParams>[];
44+
45+
/// Warning popups sent to the user.
46+
final shownWarnings = <lsp.ShowMessageParams>[];
47+
4248
MockLspServerChannel(bool _printMessages) {
4349
if (_printMessages) {
4450
_serverToClient.stream
4551
.listen((message) => print('<== ' + jsonEncode(message)));
4652
_clientToServer.stream
4753
.listen((message) => print('==> ' + jsonEncode(message)));
4854
}
55+
56+
// Keep track of any errors/warnings that are sent to the user with
57+
// `window/showMessage`.
58+
_serverToClient.stream.listen((message) {
59+
if (message is lsp.NotificationMessage &&
60+
message.method == Method.window_showMessage &&
61+
message.params is lsp.ShowMessageParams) {
62+
if (message.params?.type == MessageType.Error) {
63+
shownErrors.add(message.params);
64+
} else if (message.params?.type == MessageType.Warning) {
65+
shownWarnings.add(message.params);
66+
}
67+
}
68+
});
4969
}
5070

5171
/// Future that will be completed when the input stream is closed.
@@ -167,7 +187,8 @@ class MockLspServerChannel implements LspServerCommunicationChannel {
167187
(message is lsp.ResponseMessage && message.id == request.id) ||
168188
(throwOnError &&
169189
message is lsp.NotificationMessage &&
170-
message.method == Method.window_showMessage));
190+
message.method == Method.window_showMessage &&
191+
message.params?.type == MessageType.Error));
171192

172193
if (response is lsp.ResponseMessage) {
173194
return response;

0 commit comments

Comments
 (0)