Skip to content

Commit ca31648

Browse files
committed
Support 'new' analyzer plugins in dart analyze.
This change allows the LegacyAnalysisServer to understand when the plugin isolate (if there is one) is analyzing or not. There are a few primary concepts: * The plugin isolate (PluginServer) notifies the analysis server, when analyzing all files in a context collection, and analyzing changed files, that it is analyzing, and later that it isn't. * The NotificationManager tracks whether the plugin isolate is analyzing or not, based on the last status. * The PluginManager tracks whether new plugins are initialized or not. This is determined by the work done by the PluginWatcher. If no plugins are configured, then plugins are declared to be "initialized". Otherwise, the AnalysisServer sets their status to be "initialized" after receiving the first status notification from the plugin isolate. * The LegacyAnalysisServer now uses the additional "are plugins analyzing" signal, held in NotificationManager, and the "are plugins initializing" signal, held in PluginManager, to determine whether to notify the client that analysis is complete. Change-Id: Ie2b6a6048f074d7a26d7d5d07622a17c30fcab96 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/405444 Reviewed-by: Brian Wilkerson <[email protected]>
1 parent f5d01eb commit ca31648

File tree

11 files changed

+266
-122
lines changed

11 files changed

+266
-122
lines changed

pkg/analysis_server/lib/src/legacy_analysis_server.dart

Lines changed: 90 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
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+
/// @docImport 'package:analysis_server_plugin/src/plugin_server.dart';
6+
library;
7+
58
import 'dart:async';
69
import 'dart:io' as io;
710
import 'dart:math' show max;
@@ -267,10 +270,15 @@ class LegacyAnalysisServer extends AnalysisServer {
267270
late final FutureOr<InitializedStateMessageHandler> lspInitialized =
268271
InitializedStateMessageHandler(this);
269272

270-
/// A flag indicating the value of the 'analyzing' parameter sent in the last
271-
/// status message to the client.
273+
/// Whether either the last status message sent to the client or the last
274+
/// status message sent from any [PluginServer] indicated `isWorking: true`.
275+
@visibleForTesting
272276
bool statusAnalyzing = false;
273277

278+
/// Whether the analysis server is currently analyzing (not including any
279+
/// plugins).
280+
bool serverStatusAnalyzing = false;
281+
274282
/// A set of the [ServerService]s to send notifications for.
275283
Set<ServerService> serverServices = {};
276284

@@ -321,6 +329,9 @@ class LegacyAnalysisServer extends AnalysisServer {
321329
int nextSearchId = 0;
322330

323331
/// The [Completer] that completes when analysis is complete.
332+
///
333+
/// This Completer is not used for communicating to the client whether we are
334+
/// analyzing; it is only used by some 'search_find' handlers, and in some tests.
324335
Completer<void>? _onAnalysisCompleteCompleter;
325336

326337
/// The controller that is notified when analysis is started.
@@ -423,6 +434,30 @@ class LegacyAnalysisServer extends AnalysisServer {
423434
discardedRequests,
424435
).listen(handleRequestOrResponse, onDone: done, onError: error);
425436
_newRefactoringManager();
437+
438+
pluginManager.initializedCompleter.future.then((_) {
439+
// Perform "on idle" tasks in case the `pluginManger` determines that no
440+
// plugins should be run, _after_ the analysis server has reported its
441+
// final `isAnalyzing: false` status.
442+
_performOnIdleActions(
443+
// Use the existing plugin analyzing status.
444+
isPluginAnalyzing: notificationManager.pluginStatusAnalyzing,
445+
);
446+
});
447+
448+
notificationManager.pluginAnalysisStatusChanges.listen((
449+
pluginStatusAnalyzing,
450+
) {
451+
if (!pluginManager.initializedCompleter.isCompleted) {
452+
// Without `this.`, some portion of the analyzer believes we are accessing
453+
// the super parameter, instead of the field in the super class.
454+
// See https://github.com/dart-lang/sdk/issues/59996.
455+
// ignore: unnecessary_this
456+
this.pluginManager.initializedCompleter.complete();
457+
} else {
458+
_performOnIdleActions(isPluginAnalyzing: pluginStatusAnalyzing);
459+
}
460+
});
426461
}
427462

428463
/// The most recently registered set of client capabilities. The default is to
@@ -468,6 +503,10 @@ class LegacyAnalysisServer extends AnalysisServer {
468503
_editorClientCapabilities;
469504

470505
/// The [Future] that completes when analysis is complete.
506+
///
507+
/// This Future is not used for communicating to the client whether we are
508+
/// analyzing; it is only used by 'search_find' handlers, tests, and for
509+
/// performance calculations.
471510
Future<void> get onAnalysisComplete {
472511
if (_isAnalysisComplete) {
473512
return Future.value();
@@ -526,6 +565,7 @@ class LegacyAnalysisServer extends AnalysisServer {
526565
bool get supportsShowMessageRequest =>
527566
clientCapabilities.requests.contains('showMessageRequest');
528567

568+
// TODO(srawlins): Do we need to alter this to account for plugin status?
529569
bool get _isAnalysisComplete => !analysisDriverScheduler.isWorking;
530570

531571
void cancelRequest(String id) {
@@ -817,43 +857,20 @@ class LegacyAnalysisServer extends AnalysisServer {
817857
/// Send status notification to the client. The state of analysis is given by
818858
/// the [status] information.
819859
void sendStatusNotificationNew(analysis.AnalysisStatus status) {
820-
var isAnalyzing = status.isWorking;
821-
if (isAnalyzing) {
860+
var isServerAnalyzing = status.isWorking;
861+
if (isServerAnalyzing) {
822862
_onAnalysisStartedController.add(true);
823863
}
824864
var onAnalysisCompleteCompleter = _onAnalysisCompleteCompleter;
825-
if (onAnalysisCompleteCompleter != null && !isAnalyzing) {
865+
if (onAnalysisCompleteCompleter != null && !isServerAnalyzing) {
826866
onAnalysisCompleteCompleter.complete();
827867
_onAnalysisCompleteCompleter = null;
828868
}
829-
// Perform on-idle actions.
830-
if (!isAnalyzing) {
831-
if (generalAnalysisServices.contains(
832-
GeneralAnalysisService.ANALYZED_FILES,
833-
)) {
834-
sendAnalysisNotificationAnalyzedFiles(this);
835-
}
836-
_scheduleAnalysisImplementedNotification();
837-
filesResolvedSinceLastIdle.clear();
838-
}
839-
// Only send status when subscribed.
840-
if (!serverServices.contains(ServerService.STATUS)) {
841-
return;
842-
}
843-
// Only send status when it changes
844-
if (statusAnalyzing == isAnalyzing) {
845-
return;
846-
}
847-
statusAnalyzing = isAnalyzing;
848-
if (!isAnalyzing) {
849-
// Only send analysis analytics after analysis is complete.
850-
reportAnalysisAnalytics();
851-
}
852-
var analysis = AnalysisStatus(isAnalyzing);
853-
channel.sendNotification(
854-
ServerStatusParams(
855-
analysis: analysis,
856-
).toNotification(clientUriConverter: uriConverter),
869+
serverStatusAnalyzing = isServerAnalyzing;
870+
871+
_performOnIdleActions(
872+
// Use the existing plugin analyzing status.
873+
isPluginAnalyzing: notificationManager.pluginStatusAnalyzing,
857874
);
858875
}
859876

@@ -1099,6 +1116,46 @@ class LegacyAnalysisServer extends AnalysisServer {
10991116
_refactoringManager = RefactoringManager(this, refactoringWorkspace);
11001117
}
11011118

1119+
/// Performs "on idle" actions, given either a new status for whether the
1120+
/// server is analyzing, or a new status for whether the plugin isolate is
1121+
/// analyzing.
1122+
void _performOnIdleActions({required bool isPluginAnalyzing}) {
1123+
// Perform on-idle actions.
1124+
var isAnalyzing =
1125+
serverStatusAnalyzing ||
1126+
isPluginAnalyzing ||
1127+
!pluginManager.initializedCompleter.isCompleted;
1128+
if (!serverStatusAnalyzing) {
1129+
if (generalAnalysisServices.contains(
1130+
GeneralAnalysisService.ANALYZED_FILES,
1131+
)) {
1132+
sendAnalysisNotificationAnalyzedFiles(this);
1133+
}
1134+
_scheduleAnalysisImplementedNotification();
1135+
filesResolvedSinceLastIdle.clear();
1136+
}
1137+
// Only send status when subscribed.
1138+
if (!serverServices.contains(ServerService.STATUS)) {
1139+
return;
1140+
}
1141+
1142+
// Only send status when it changes.
1143+
if (statusAnalyzing == isAnalyzing) {
1144+
return;
1145+
}
1146+
statusAnalyzing = isAnalyzing;
1147+
if (!serverStatusAnalyzing) {
1148+
// Only send analysis analytics after analysis is complete.
1149+
reportAnalysisAnalytics();
1150+
}
1151+
var analysis = AnalysisStatus(isAnalyzing);
1152+
channel.sendNotification(
1153+
ServerStatusParams(
1154+
analysis: analysis,
1155+
).toNotification(clientUriConverter: uriConverter),
1156+
);
1157+
}
1158+
11021159
void _scheduleAnalysisImplementedNotification() {
11031160
var subscribed = analysisServices[AnalysisService.IMPLEMENTED];
11041161
if (subscribed == null) {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,10 @@ class LspAnalysisServer extends AnalysisServer {
201201
_pluginChangeSubscription = pluginManager.pluginsChanged.listen(
202202
(_) => _onPluginsChanged(),
203203
);
204+
205+
// TODO(srawlins): Listen to
206+
// `notificationManager.pluginAnalysisStatusChanges` and perform "on idle"
207+
// tasks.
204208
}
205209
}
206210

pkg/analysis_server/lib/src/plugin/notification_manager.dart

Lines changed: 35 additions & 0 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:collection';
67

78
import 'package:analysis_server/protocol/protocol_generated.dart' as server;
@@ -71,6 +72,14 @@ abstract class AbstractNotificationManager {
7172
/// The object used to merge results.
7273
final ResultMerger merger = ResultMerger();
7374

75+
/// Whether the plugin isolate is currently analyzing, as per its last status
76+
/// notification.
77+
bool pluginStatusAnalyzing = false;
78+
79+
/// The controller that is notified when analysis status changes.
80+
final StreamController<bool> _analysisStatusChangesController =
81+
StreamController.broadcast();
82+
7483
/// Initialize a newly created notification manager.
7584
AbstractNotificationManager(this._pathContext)
7685
: folding = ResultCollector<List<FoldingRegion>>(serverId),
@@ -79,6 +88,13 @@ abstract class AbstractNotificationManager {
7988
_occurrences = ResultCollector<List<Occurrences>>(serverId),
8089
_outlines = ResultCollector<List<Outline>>(serverId);
8190

91+
/// The Stream of analysis statuses from the plugin isolate.
92+
///
93+
/// Each value emitted represents whether the plugin isolate is analyzing or
94+
/// not, as per each status notification.
95+
Stream<bool> get pluginAnalysisStatusChanges =>
96+
_analysisStatusChangesController.stream;
97+
8298
/// Handle the given [notification] from the plugin with the given [pluginId].
8399
void handlePluginNotification(
84100
String pluginId,
@@ -120,6 +136,8 @@ abstract class AbstractNotificationManager {
120136
recordOutlines(pluginId, params.file, params.outline);
121137
case plugin.PLUGIN_NOTIFICATION_ERROR:
122138
sendPluginErrorNotification(notification);
139+
case plugin.PLUGIN_NOTIFICATION_STATUS:
140+
_setPluginStatus(notification);
123141
}
124142
}
125143

@@ -335,6 +353,23 @@ abstract class AbstractNotificationManager {
335353
// disabled.
336354
return isIncluded() && !isExcluded();
337355
}
356+
357+
/// Records a status notification from the analyzer plugin.
358+
void _setPluginStatus(plugin.Notification notification) {
359+
var params = plugin.PluginStatusParams.fromNotification(notification);
360+
var analysis = params.analysis;
361+
if (analysis == null) {
362+
return;
363+
}
364+
var isAnalyzing = analysis.isAnalyzing;
365+
_analysisStatusChangesController.add(isAnalyzing);
366+
367+
// Only send status when it changes.
368+
if (pluginStatusAnalyzing == isAnalyzing) {
369+
return;
370+
}
371+
pluginStatusAnalyzing = isAnalyzing;
372+
}
338373
}
339374

340375
class NotificationManager extends AbstractNotificationManager {

pkg/analysis_server/lib/src/plugin/plugin_manager.dart

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
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+
/// @docImport 'package:analysis_server_plugin/src/plugin_server.dart';
6+
/// @docImport 'package:analysis_server/src/plugin/plugin_watcher.dart';
7+
library;
8+
59
import 'dart:async';
610
import 'dart:collection';
711
import 'dart:convert';
@@ -320,6 +324,14 @@ class PluginManager {
320324

321325
final StreamController<void> _pluginsChanged = StreamController.broadcast();
322326

327+
/// Whether plugins are "initialized."
328+
///
329+
/// Plugins are declared to be initialized either (a) when the [PluginWatcher]
330+
/// has determined no plugins are configured to be run, or (b) when the
331+
/// plugins are configured and the first status notification is received by
332+
/// the analysis server.
333+
Completer<void> initializedCompleter = Completer();
334+
323335
/// Initialize a newly created plugin manager. The notifications from the
324336
/// running plugins will be handled by the given [notificationManager].
325337
PluginManager(
@@ -376,6 +388,7 @@ class PluginManager {
376388
);
377389
_pluginMap[path] = plugin;
378390
try {
391+
instrumentationService.logInfo('Starting plugin "$plugin"');
379392
var session = await plugin.start(byteStorePath, sdkPath);
380393
unawaited(
381394
session?.onDone.then((_) {
@@ -948,7 +961,7 @@ class PluginSession {
948961
/// Return a future that will complete when the plugin has stopped.
949962
Future<void> get onDone => pluginStoppedCompleter.future;
950963

951-
/// Handle the given [notification].
964+
/// Handle the given [notification] from [PluginServer].
952965
void handleNotification(Notification notification) {
953966
if (notification.event == PLUGIN_NOTIFICATION_ERROR) {
954967
var params = PluginErrorParams.fromNotification(notification);

0 commit comments

Comments
 (0)