Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 7edcd83

Browse files
DanTupcommit-bot@chromium.org
authored andcommitted
[Analyzer] Re-create progress tokens after each use + fix race
Change-Id: Ibc6dd59c71d40c5f0127a2881dade13cde8cfd87 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/173271 Commit-Queue: Danny Tuppeny <danny@tuppeny.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
1 parent 3380168 commit 7edcd83

File tree

4 files changed

+68
-34
lines changed

4 files changed

+68
-34
lines changed

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

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,6 @@ class LspAnalysisServer extends AbstractAnalysisServer {
155155
channel.listen(handleMessage, onDone: done, onError: socketError);
156156
_pluginChangeSubscription =
157157
pluginManager.pluginsChanged.listen((_) => _onPluginsChanged());
158-
159-
analyzingProgressReporter =
160-
ProgressReporter.serverCreated(this, analyzingProgressToken);
161158
}
162159

163160
/// The capabilities of the LSP client. Will be null prior to initialization.
@@ -549,7 +546,7 @@ class LspAnalysisServer extends AbstractAnalysisServer {
549546

550547
/// Send status notification to the client. The state of analysis is given by
551548
/// the [status] information.
552-
void sendStatusNotification(nd.AnalysisStatus status) {
549+
Future<void> sendStatusNotification(nd.AnalysisStatus status) async {
553550
// Send old custom notifications to clients that do not support $/progress.
554551
// TODO(dantup): Remove this custom notification (and related classes) when
555552
// it's unlikely to be in use by any clients.
@@ -563,9 +560,16 @@ class LspAnalysisServer extends AbstractAnalysisServer {
563560
}
564561

565562
if (status.isAnalyzing) {
566-
analyzingProgressReporter.begin('Analyzing…');
563+
analyzingProgressReporter ??=
564+
ProgressReporter.serverCreated(this, analyzingProgressToken)
565+
..begin('Analyzing…');
567566
} else {
568-
analyzingProgressReporter.end();
567+
if (analyzingProgressReporter != null) {
568+
// Do not null this out until after end completes, otherwise we may try
569+
// to create a new token before it's really completed.
570+
await analyzingProgressReporter.end();
571+
analyzingProgressReporter = null;
572+
}
569573
}
570574
}
571575

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

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'dart:async';
56
import 'dart:math';
67

78
import 'package:analysis_server/lsp_protocol/protocol_generated.dart';
@@ -30,9 +31,9 @@ abstract class ProgressReporter {
3031
ProgressReporter._();
3132

3233
// TODO(dantup): Add support for cancellable progress notifications.
33-
void begin(String title, {String message});
34+
FutureOr<void> begin(String title, {String message});
3435

35-
void end([String message]);
36+
FutureOr<void> end([String message]);
3637
}
3738

3839
class _NoopProgressReporter extends ProgressReporter {
@@ -45,7 +46,7 @@ class _NoopProgressReporter extends ProgressReporter {
4546

4647
class _ServerCreatedProgressReporter extends _TokenProgressReporter {
4748
static final _random = Random();
48-
Future<ResponseMessage> _tokenCreateRequest;
49+
Future<bool> _tokenBeginRequest;
4950

5051
_ServerCreatedProgressReporter(
5152
LspAnalysisServer server,
@@ -56,28 +57,39 @@ class _ServerCreatedProgressReporter extends _TokenProgressReporter {
5657
);
5758

5859
@override
59-
void begin(String title, {String message}) {
60-
// Create the token lazily so we don't create it if it's not required.
61-
_tokenCreateRequest ??= _server.sendRequest(
62-
Method.window_workDoneProgress_create,
63-
WorkDoneProgressCreateParams(token: _token));
64-
65-
// Chain onto the end of tokenCreateRequest so we do not try to use
66-
// the token without the client accepting it.
67-
_tokenCreateRequest.then((response) {
68-
if (response.error != null) return;
60+
Future<void> begin(String title, {String message}) async {
61+
assert(_tokenBeginRequest == null,
62+
'Begin should not be called more than once');
63+
64+
// Put the create/begin into a future so if end() is called before the
65+
// begin is sent (which could happen because create is async), end will
66+
// not be sent/return too early.
67+
_tokenBeginRequest = _server
68+
.sendRequest(Method.window_workDoneProgress_create,
69+
WorkDoneProgressCreateParams(token: _token))
70+
.then((response) {
71+
// If the client did not create a token, do not send begin (and signal
72+
// that we should also not send end).
73+
if (response.error != null) return false;
6974
super.begin(title, message: message);
75+
return true;
7076
});
77+
78+
await _tokenBeginRequest;
7179
}
7280

7381
@override
74-
void end([String message]) {
75-
// Chain onto the end of tokenCreateRequest so we do not try to use
76-
// the token without the client accepting it.
77-
_tokenCreateRequest.then((response) {
78-
if (response.error != null) return;
79-
super.end(message);
80-
});
82+
Future<void> end([String message]) async {
83+
// Only end the token after both create/begin have completed, and return
84+
// a Future to indicate that has happened to callers know when it's safe
85+
// to re-use the token identifier.
86+
if (_tokenBeginRequest != null) {
87+
final didBegin = await _tokenBeginRequest;
88+
if (didBegin) {
89+
super.end(message);
90+
}
91+
_tokenBeginRequest = null;
92+
}
8193
}
8294

8395
static String _randomTokenIdentifier() {

pkg/analysis_server/test/lsp/code_actions_refactor_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ void newMethod() {
205205
findCommand(codeActions, Commands.performRefactor, extractMethodTitle);
206206
await verifyCodeActionEdits(
207207
codeAction, withoutMarkers(content), expectedContent,
208-
workDoneToken: testWorkDoneToken);
208+
workDoneToken: clientProvidedTestWorkDoneToken);
209209
}
210210

211211
Future<void> test_progress_notSupported() async {

pkg/analysis_server/test/lsp/server_abstract.dart

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,6 @@ abstract class AbstractLspAnalysisServerTest
5151
TestPluginManager pluginManager;
5252
LspAnalysisServer server;
5353

54-
final testWorkDoneToken = Either2<num, String>.t2('test');
55-
5654
AnalysisServerOptions get serverOptions => AnalysisServerOptions();
5755

5856
@override
@@ -474,6 +472,11 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
474472
static final allMarkersPattern =
475473
RegExp(allMarkers.map(RegExp.escape).join('|'));
476474

475+
/// A progress token used in tests where the client-provides the token, which
476+
/// should not be validated as being created by the server first.
477+
final clientProvidedTestWorkDoneToken =
478+
Either2<num, String>.t2('client-test');
479+
477480
int _id = 0;
478481
String projectFolderPath, mainFilePath, pubspecFilePath, analysisOptionsPath;
479482
Uri projectFolderUri, mainFileUri, pubspecFileUri, analysisOptionsUri;
@@ -1105,6 +1108,12 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
11051108
}
11061109
});
11071110

1111+
notificationsFromServer.listen((notification) async {
1112+
if (notification.method == Method.progress) {
1113+
await _handleProgress(notification);
1114+
}
1115+
});
1116+
11081117
// Assume if none of the project options were set, that we want to default to
11091118
// opening the test project folder.
11101119
if (rootPath == null &&
@@ -1436,10 +1445,6 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
14361445
}
14371446

14381447
final params = ProgressParams.fromJson(message.params);
1439-
if (!validProgressTokens.contains(params.token)) {
1440-
throw Exception('Server sent a progress notification for a token '
1441-
'that has not been created: ${params.token}');
1442-
}
14431448

14441449
// Skip unrelated progress notifications.
14451450
if (params.token != analyzingProgressToken) {
@@ -1539,14 +1544,27 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
15391544
T Function(Map<String, dynamic>) fromJson) =>
15401545
(input) => input.cast<Map<String, dynamic>>().map(fromJson).toList();
15411546

1547+
Future<void> _handleProgress(NotificationMessage request) async {
1548+
final params = ProgressParams.fromJson(request.params);
1549+
if (params.token != clientProvidedTestWorkDoneToken &&
1550+
!validProgressTokens.contains(params.token)) {
1551+
throw Exception('Server sent a progress notification for a token '
1552+
'that has not been created: ${params.token}');
1553+
}
1554+
1555+
if (WorkDoneProgressEnd.canParse(params.value, nullLspJsonReporter)) {
1556+
validProgressTokens.remove(params.token);
1557+
}
1558+
}
1559+
15421560
Future<void> _handleWorkDoneProgressCreate(RequestMessage request) async {
15431561
if (_clientCapabilities.window?.workDoneProgress != true) {
15441562
throw Exception('Server sent ${Method.window_workDoneProgress_create} '
15451563
'but client capabilities do not allow');
15461564
}
15471565
final params = WorkDoneProgressCreateParams.fromJson(request.params);
15481566
if (validProgressTokens.contains(params.token)) {
1549-
throw Exception('Server tried to create the same progress token twice');
1567+
throw Exception('Server tried to create already-active progress token');
15501568
}
15511569
validProgressTokens.add(params.token);
15521570
}

0 commit comments

Comments
 (0)