[analyzer] Add "interactive" option to "getResolvedUnit"; "setSubscriptions" are not; non-interactive ones have less priority
The legacy_many_files_in_flutter_set_subscriptions benchmark shows how
"flutter.setSubscriptions" calls can make the analyzer slower to
respond.
What happens is this:
* The user opens a new file in the IDE.
* The IDE sends the `flutter.setSubscriptions` request which equates to
a call to `getResolvedUnit` for each file in the request. If this is,
say, 300 files it's 300 calls to `getResolvedUnit`.
* The IDE sends a `edit.getAssists` request for the newly opened file.
This request starts processing, reaches `getResolvedLibrary(file)`
which calls `getUnitElement` ultimately adding the path to
`_unitElementRequestedFiles` which in `performWork` is done _after_
`_requestedFiles`, meaning it has to do all the flutter requested
files first.
* The user might then request completion for instance, but because the
analyzer only processes one request at a time it has to wait for the
`edit.getAssists` request to finish first, which had to wait for the
files from the `flutter.setSubscriptions` request to process.
All in all it's a lot of waiting for the user.
This CL adds a `interactive` option to the `getResolvedUnit` call. It
defaults to true in which case files are still added to
`_requestedFiles` and processed the same. If it's false it will instead
be added to a newly introduced list instead and processed at a lower
priority. Subscription requests are changed to pass `false` to
`interactive`, avoiding the scenario above.
Comparing before this CL with this CL on the
"legacy_many_files_in_flutter_set_subscriptions" benchmark with 100
files / CodeType.ImportChain these are the statistics on the changes
based on 5 runs each:
```
Completion after open of new file: -81.6652% +/- 7.7564% (-3.70 +/- 0.35) (4.53 -> 0.83)
getAssists call: -96.6315% +/- 0.9307% (-3.61 +/- 0.03) (3.74 -> 0.13)
peak virtual memory size: -5.6786% +/- 3.2964% (-139.00 +/- 80.69) (2447.80 -> 2308.80)
total program size (virtual): -4.6387% +/- 3.8146% (-110.80 +/- 91.11) (2388.60 -> 2277.80)
```
Even when https://github.com/flutter/flutter-intellij/issues/7980 is
hopefully fixed I think it is a fair change to de-prioritize a
non-interactive request.
Change-Id: Icba2faebf12f9913cf24db7cb90fdc6f4c74164e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/418020
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Jens Johansen <jensj@google.com>
diff --git a/pkg/analysis_server/lib/src/analysis_server.dart b/pkg/analysis_server/lib/src/analysis_server.dart
index 1e210c5..1ec1ec5 100644
--- a/pkg/analysis_server/lib/src/analysis_server.dart
+++ b/pkg/analysis_server/lib/src/analysis_server.dart
@@ -793,6 +793,7 @@
Future<ResolvedUnitResult?>? getResolvedUnit(
String path, {
bool sendCachedToStream = false,
+ bool interactive = true,
}) {
if (!file_paths.isDart(resourceProvider.pathContext, path)) {
return null;
@@ -804,7 +805,11 @@
}
return driver
- .getResolvedUnit(path, sendCachedToStream: sendCachedToStream)
+ .getResolvedUnit(
+ path,
+ sendCachedToStream: sendCachedToStream,
+ interactive: interactive,
+ )
.then((value) => value is ResolvedUnitResult ? value : null)
.catchError((Object e, StackTrace st) {
instrumentationService.logException(e, st);
diff --git a/pkg/analysis_server/lib/src/legacy_analysis_server.dart b/pkg/analysis_server/lib/src/legacy_analysis_server.dart
index 0717609..d66349c 100644
--- a/pkg/analysis_server/lib/src/legacy_analysis_server.dart
+++ b/pkg/analysis_server/lib/src/legacy_analysis_server.dart
@@ -1190,7 +1190,7 @@
// the fully resolved unit, and processed with sending analysis
// notifications as it happens after content changes.
if (file_paths.isDart(resourceProvider.pathContext, file)) {
- getResolvedUnit(file, sendCachedToStream: true);
+ getResolvedUnit(file, sendCachedToStream: true, interactive: false);
}
}
}
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index e5f86fc..eee4eb2 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -183,6 +183,12 @@
final _requestedFiles = <String, List<Completer<SomeResolvedUnitResult>>>{};
/// The mapping from the files for which analysis was requested using
+ /// [getResolvedUnit] which are not interactive to the [Completer]s to report
+ /// the result.
+ final _requestedFilesNonInteractive =
+ <String, List<Completer<SomeResolvedUnitResult>>>{};
+
+ /// The mapping from the files for which analysis was requested using
/// [getResolvedLibrary] to the [Completer]s to report the result.
final _requestedLibraries =
<String, List<Completer<SomeResolvedLibraryResult>>>{};
@@ -494,6 +500,10 @@
}
}
}
+
+ if (_requestedFilesNonInteractive.isNotEmpty) {
+ return AnalysisDriverPriority.general;
+ }
if (_pendingFileChanges.isNotEmpty) {
return AnalysisDriverPriority.general;
}
@@ -525,6 +535,7 @@
_fileTracker.hasChangedFiles ||
_requestedLibraries.isNotEmpty ||
_requestedFiles.isNotEmpty ||
+ _requestedFilesNonInteractive.isNotEmpty ||
_errorsRequestedFiles.isNotEmpty ||
_definingClassMemberNameRequests.isNotEmpty ||
_referencingNameRequests.isNotEmpty ||
@@ -736,6 +747,13 @@
}
_requestedFiles.clear();
+ for (var completerList in _requestedFilesNonInteractive.values) {
+ for (var completer in completerList) {
+ completer.complete(DisposedAnalysisContextResult());
+ }
+ }
+ _requestedFilesNonInteractive.clear();
+
for (var completerList in _unitElementRequestedFiles.values) {
for (var completer in completerList) {
completer.complete(DisposedAnalysisContextResult());
@@ -1059,7 +1077,7 @@
/// of the files previously reported using [changeFile]), prior to the next
/// time the analysis state transitions to "idle".
Future<SomeResolvedUnitResult> getResolvedUnit(String path,
- {bool sendCachedToStream = false}) {
+ {bool sendCachedToStream = false, bool interactive = true}) {
if (!_isAbsolutePath(path)) {
return Future.value(
InvalidPathResult(),
@@ -1091,7 +1109,11 @@
// Schedule analysis.
var completer = Completer<SomeResolvedUnitResult>();
- _requestedFiles.add(path, completer);
+ if (interactive) {
+ _requestedFiles.add(path, completer);
+ } else {
+ _requestedFilesNonInteractive.add(path, completer);
+ }
_scheduler.notify();
return completer.future;
}
@@ -1245,6 +1267,12 @@
_produceErrors(path);
return;
}
+
+ // Analyze a requested - but not interactivly requested - file.
+ if (_requestedFilesNonInteractive.firstKey case var path?) {
+ _analyzeFile(path);
+ return;
+ }
}
/// Remove the file with the given [path] from the list of files to analyze.
@@ -1412,6 +1440,8 @@
// getResolvedUnit()
_requestedFiles.completeAll(unitFile.path, resolvedUnit);
+ _requestedFilesNonInteractive.completeAll(
+ unitFile.path, resolvedUnit);
// getErrors()
_errorsRequestedFiles.completeAll(
@@ -1517,6 +1547,9 @@
completeWithError(
_requestedFiles.remove(file.path),
);
+ completeWithError(
+ _requestedFilesNonInteractive.remove(file.path),
+ );
// getErrors()
completeWithError(
_errorsRequestedFiles.remove(file.path),