Version 2.16.0-15.0.dev

Merge commit 'ffcc3e667eaf0272c703435bd7796a6d6aded531' into 'dev'
diff --git a/DEPS b/DEPS
index a79507d..a4e2e0f 100644
--- a/DEPS
+++ b/DEPS
@@ -39,7 +39,7 @@
 
   # Checked-in SDK version. The checked-in SDK is a Dart SDK distribution in a
   # cipd package used to run Dart scripts in the build and test infrastructure.
-  "sdk_tag": "version:2.15.0-82.0.dev",
+  "sdk_tag": "version:2.15.0-268.8.beta",
 
   # co19 is a cipd package. Use update.sh in tests/co19[_2] to update these
   # hashes. It requires access to the dart-build-access group, which EngProd
diff --git a/pkg/analysis_server/lib/protocol/protocol_constants.dart b/pkg/analysis_server/lib/protocol/protocol_constants.dart
index 2689506..85a4474 100644
--- a/pkg/analysis_server/lib/protocol/protocol_constants.dart
+++ b/pkg/analysis_server/lib/protocol/protocol_constants.dart
@@ -141,6 +141,7 @@
 const String COMPLETION_REQUEST_GET_SUGGESTIONS2_FILE = 'file';
 const String COMPLETION_REQUEST_GET_SUGGESTIONS2_MAX_RESULTS = 'maxResults';
 const String COMPLETION_REQUEST_GET_SUGGESTIONS2_OFFSET = 'offset';
+const String COMPLETION_REQUEST_GET_SUGGESTIONS2_TIMEOUT = 'timeout';
 const String COMPLETION_REQUEST_GET_SUGGESTIONS_FILE = 'file';
 const String COMPLETION_REQUEST_GET_SUGGESTIONS_OFFSET = 'offset';
 const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS =
diff --git a/pkg/analysis_server/lib/protocol/protocol_generated.dart b/pkg/analysis_server/lib/protocol/protocol_generated.dart
index 8cd904d..082036f 100644
--- a/pkg/analysis_server/lib/protocol/protocol_generated.dart
+++ b/pkg/analysis_server/lib/protocol/protocol_generated.dart
@@ -4698,7 +4698,14 @@
   /// to true.
   int maxResults;
 
-  CompletionGetSuggestions2Params(this.file, this.offset, this.maxResults);
+  /// The approximate time in milliseconds that the server should spend. The
+  /// server will perform some steps anyway, even if it takes longer than the
+  /// specified timeout. This field is intended to be used for benchmarking,
+  /// and usually should not be provided, so that the default timeout is used.
+  int? timeout;
+
+  CompletionGetSuggestions2Params(this.file, this.offset, this.maxResults,
+      {this.timeout});
 
   factory CompletionGetSuggestions2Params.fromJson(
       JsonDecoder jsonDecoder, String jsonPath, Object? json) {
@@ -4723,7 +4730,12 @@
       } else {
         throw jsonDecoder.mismatch(jsonPath, 'maxResults');
       }
-      return CompletionGetSuggestions2Params(file, offset, maxResults);
+      int? timeout;
+      if (json.containsKey('timeout')) {
+        timeout = jsonDecoder.decodeInt(jsonPath + '.timeout', json['timeout']);
+      }
+      return CompletionGetSuggestions2Params(file, offset, maxResults,
+          timeout: timeout);
     } else {
       throw jsonDecoder.mismatch(
           jsonPath, 'completion.getSuggestions2 params', json);
@@ -4741,6 +4753,10 @@
     result['file'] = file;
     result['offset'] = offset;
     result['maxResults'] = maxResults;
+    var timeout = this.timeout;
+    if (timeout != null) {
+      result['timeout'] = timeout;
+    }
     return result;
   }
 
@@ -4757,7 +4773,8 @@
     if (other is CompletionGetSuggestions2Params) {
       return file == other.file &&
           offset == other.offset &&
-          maxResults == other.maxResults;
+          maxResults == other.maxResults &&
+          timeout == other.timeout;
     }
     return false;
   }
@@ -4767,6 +4784,7 @@
         file,
         offset,
         maxResults,
+        timeout,
       );
 }
 
diff --git a/pkg/analysis_server/lib/src/domain_completion.dart b/pkg/analysis_server/lib/src/domain_completion.dart
index cfc49eb..60c81b6 100644
--- a/pkg/analysis_server/lib/src/domain_completion.dart
+++ b/pkg/analysis_server/lib/src/domain_completion.dart
@@ -267,12 +267,17 @@
 
   /// Implement the 'completion.getSuggestions2' request.
   void getSuggestions2(Request request) async {
-    var budget = CompletionBudget(budgetDuration);
-
     var params = CompletionGetSuggestions2Params.fromRequest(request);
     var file = params.file;
     var offset = params.offset;
 
+    var timeoutMilliseconds = params.timeout;
+    var budget = CompletionBudget(
+      timeoutMilliseconds != null
+          ? Duration(milliseconds: timeoutMilliseconds)
+          : budgetDuration,
+    );
+
     var provider = server.resourceProvider;
     var pathContext = provider.pathContext;
 
diff --git a/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart b/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
index a09c3b7..ede2ade 100644
--- a/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
+++ b/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
@@ -7,10 +7,13 @@
 import 'package:analysis_server/src/services/correction/change_workspace.dart';
 import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
 import 'package:analysis_server/src/services/correction/dart/data_driven.dart';
+import 'package:analysis_server/src/services/correction/dart/organize_imports.dart';
+import 'package:analysis_server/src/services/correction/dart/remove_unused_import.dart';
 import 'package:analysis_server/src/services/correction/fix.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/transform_override_set.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/transform_override_set_parser.dart';
 import 'package:analysis_server/src/services/correction/fix_internal.dart';
+import 'package:analysis_server/src/services/linter/lint_names.dart';
 import 'package:analyzer/dart/analysis/analysis_context.dart';
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/error/error.dart';
@@ -226,28 +229,107 @@
     return builder;
   }
 
+  Future<void> _applyProducer(
+      CorrectionProducerContext context, CorrectionProducer producer) async {
+    producer.configure(context);
+    try {
+      var localBuilder = builder.copy();
+      await producer.compute(localBuilder);
+      builder = localBuilder;
+    } on ConflictingEditException {
+      // If a conflicting edit was added in [compute], then the [localBuilder]
+      // is discarded and we revert to the previous state of the builder.
+    }
+  }
+
   /// Use the change [builder] to create fixes for the diagnostics in the
   /// library associated with the analysis [result].
   Future<void> _fixErrorsInLibrary(ResolvedLibraryResult result) async {
     var analysisOptions = result.session.analysisContext.analysisOptions;
-    for (var unitResult in result.units) {
-      var overrideSet = _readOverrideSet(unitResult);
 
-      var errors = List.from(unitResult.errors, growable: false);
+    Iterable<AnalysisError> filteredErrors(ResolvedUnitResult result) sync* {
+      var errors = result.errors.toList();
       errors.sort((a, b) => a.offset.compareTo(b.offset));
-
+      // Only fix errors not filtered out in analysis options.
       for (var error in errors) {
         var processor = ErrorProcessor.getProcessor(analysisOptions, error);
-        // Only fix errors not filtered out in analysis options.
         if (processor == null || processor.severity != null) {
-          final fixContext = DartFixContextImpl(
-            instrumentationService,
-            workspace,
-            unitResult,
-            error,
-            (name) => [],
-          );
-          await _fixSingleError(fixContext, unitResult, error, overrideSet);
+          yield error;
+        }
+      }
+    }
+
+    DartFixContextImpl fixContext(
+        ResolvedUnitResult result, AnalysisError diagnostic) {
+      return DartFixContextImpl(
+        instrumentationService,
+        workspace,
+        result,
+        diagnostic,
+        (name) => [],
+      );
+    }
+
+    CorrectionProducerContext? correctionContext(
+        ResolvedUnitResult result, AnalysisError diagnostic) {
+      var overrideSet = _readOverrideSet(result);
+      return CorrectionProducerContext.create(
+        applyingBulkFixes: true,
+        dartFixContext: fixContext(result, diagnostic),
+        diagnostic: diagnostic,
+        overrideSet: overrideSet,
+        resolvedResult: result,
+        selectionOffset: diagnostic.offset,
+        selectionLength: diagnostic.length,
+        workspace: workspace,
+      );
+    }
+
+    //
+    // Attempt to apply the fixes that aren't related to directives.
+    //
+    for (var unitResult in result.units) {
+      var overrideSet = _readOverrideSet(unitResult);
+      for (var error in filteredErrors(unitResult)) {
+        await _fixSingleError(
+            fixContext(unitResult, error), unitResult, error, overrideSet);
+      }
+    }
+    //
+    // If there are no such fixes in the defining compilation unit, then apply
+    // the fixes related to directives.
+    //
+    var definingUnit = result.units[0];
+    AnalysisError? directivesOrderingError;
+    var unusedImportErrors = <AnalysisError>[];
+    if (!builder.hasEditsFor(definingUnit.path)) {
+      for (var error in filteredErrors(definingUnit)) {
+        var errorCode = error.errorCode;
+        if (errorCode is LintCode) {
+          var lintName = errorCode.name;
+          if (lintName == LintNames.directives_ordering) {
+            directivesOrderingError = error;
+            break;
+          }
+        } else if (errorCode == HintCode.DUPLICATE_IMPORT ||
+            errorCode == HintCode.UNNECESSARY_IMPORT ||
+            errorCode == HintCode.UNUSED_IMPORT) {
+          unusedImportErrors.add(error);
+        }
+      }
+      if (directivesOrderingError != null) {
+        // `OrganizeImports` will also remove some of the unused imports, so we
+        // apply it first.
+        var context = correctionContext(definingUnit, directivesOrderingError);
+        if (context != null) {
+          await _applyProducer(context, OrganizeImports.newInstance());
+        }
+      } else {
+        for (var error in unusedImportErrors) {
+          var context = correctionContext(definingUnit, error);
+          if (context != null) {
+            await _applyProducer(context, RemoveUnusedImport.newInstance());
+          }
         }
       }
     }
@@ -275,23 +357,11 @@
       return;
     }
 
-    Future<void> compute(CorrectionProducer producer) async {
-      producer.configure(context);
-      try {
-        var localBuilder = builder.copy();
-        await producer.compute(localBuilder);
-        builder = localBuilder;
-      } on ConflictingEditException {
-        // If a conflicting edit was added in [compute], then the [localBuilder]
-        // is discarded and we revert to the previous state of the builder.
-      }
-    }
-
     int computeChangeHash() => (builder as ChangeBuilderImpl).changeHash;
 
     Future<void> generate(CorrectionProducer producer, String code) async {
       var oldHash = computeChangeHash();
-      await compute(producer);
+      await _applyProducer(context, producer);
       var newHash = computeChangeHash();
       if (newHash != oldHash) {
         changeMap.add(result.path, code);
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/organize_imports.dart b/pkg/analysis_server/lib/src/services/correction/dart/organize_imports.dart
index d5cf7c0..d655cc3 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/organize_imports.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/organize_imports.dart
@@ -11,7 +11,8 @@
 
 class OrganizeImports extends CorrectionProducer {
   @override
-  bool get canBeAppliedInBulk => true;
+  // Bulk application is supported by a distinct import cleanup fix phase.
+  bool get canBeAppliedInBulk => false;
 
   @override
   // The fix is to sort all the directives, which will already fix all of the
diff --git a/pkg/analysis_server/test/integration/support/integration_test_methods.dart b/pkg/analysis_server/test/integration/support/integration_test_methods.dart
index 9a707cf..aa0d470 100644
--- a/pkg/analysis_server/test/integration/support/integration_test_methods.dart
+++ b/pkg/analysis_server/test/integration/support/integration_test_methods.dart
@@ -1020,9 +1020,11 @@
   ///   True if the number of suggestions after filtering was greater than the
   ///   requested maxResults.
   Future<CompletionGetSuggestions2Result> sendCompletionGetSuggestions2(
-      String file, int offset, int maxResults) async {
-    var params =
-        CompletionGetSuggestions2Params(file, offset, maxResults).toJson();
+      String file, int offset, int maxResults,
+      {int? timeout}) async {
+    var params = CompletionGetSuggestions2Params(file, offset, maxResults,
+            timeout: timeout)
+        .toJson();
     var result = await server.send('completion.getSuggestions2', params);
     var decoder = ResponseDecoder(null);
     return CompletionGetSuggestions2Result.fromJson(decoder, 'result', result);
diff --git a/pkg/analysis_server/test/integration/support/protocol_matchers.dart b/pkg/analysis_server/test/integration/support/protocol_matchers.dart
index 5fd8c3c..70848d4 100644
--- a/pkg/analysis_server/test/integration/support/protocol_matchers.dart
+++ b/pkg/analysis_server/test/integration/support/protocol_matchers.dart
@@ -2139,7 +2139,8 @@
 /// }
 final Matcher isCompletionGetSuggestions2Params = LazyMatcher(() =>
     MatchesJsonObject('completion.getSuggestions2 params',
-        {'file': isFilePath, 'offset': isInt, 'maxResults': isInt}));
+        {'file': isFilePath, 'offset': isInt, 'maxResults': isInt},
+        optionalFields: {'timeout': isInt}));
 
 /// completion.getSuggestions2 result
 ///
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test.dart
index cb280c7..6fc6671 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test.dart
@@ -1169,7 +1169,9 @@
 import '$importUri';
 void f(Old p) {}
 ''');
-    await assertNoFix();
+    await assertHasFix('''
+void f(Old p) {}
+''');
   }
 }
 
diff --git a/pkg/analysis_server/test/src/services/correction/fix/remove_unused_import_test.dart b/pkg/analysis_server/test/src/services/correction/fix/remove_unused_import_test.dart
index 6fbc9d8..c486e53 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/remove_unused_import_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/remove_unused_import_test.dart
@@ -11,34 +11,85 @@
 
 void main() {
   defineReflectiveSuite(() {
-    defineReflectiveTests(RemoveUnusedImportTest);
+    defineReflectiveTests(RemoveUnusedImportBulkTest);
     defineReflectiveTests(RemoveUnusedImportMultiTest);
+    defineReflectiveTests(RemoveUnusedImportTest);
   });
 }
 
 @reflectiveTest
+class RemoveUnusedImportBulkTest extends BulkFixProcessorTest {
+  @FailingTest(reason: 'multiple deletions conflict')
+  Future<void> test_multipleOnSingleLine() async {
+    // TODO(brianwilkerson) Remove test_multipleOnSingleLine_temporary when this
+    //  test starts to pass.
+    await resolveTestCode('''
+import 'dart:collection'; import 'dart:math'; import 'dart:async';
+void f() {}
+''');
+    await assertHasFix('''
+
+void f() {}
+''');
+  }
+
+  Future<void> test_multipleOnSingleLine_temporary() async {
+    await resolveTestCode('''
+import 'dart:collection'; import 'dart:math'; import 'dart:async';
+void f() {}
+''');
+    await assertHasFix('''
+import 'dart:math';
+void f() {}
+''');
+  }
+
+  Future<void> test_multipleUnused() async {
+    await resolveTestCode('''
+import 'dart:collection';
+import 'dart:math';
+import 'dart:async';
+void f() {}
+''');
+    await assertHasFix('''
+void f() {}
+''');
+  }
+
+  Future<void> test_usedAndUnused() async {
+    await resolveTestCode('''
+import 'dart:async';
+import 'dart:math' as math;
+import 'dart:async';
+
+var tau = math.pi * 2;
+
+void f() {}
+''');
+    await assertHasFix('''
+import 'dart:math' as math;
+
+var tau = math.pi * 2;
+
+void f() {}
+''');
+  }
+}
+
+@reflectiveTest
 class RemoveUnusedImportMultiTest extends FixProcessorTest {
   @override
   FixKind get kind => DartFixKind.REMOVE_UNUSED_IMPORT_MULTI;
 
-  @override
-  void setUp() {
-    super.setUp();
-    // TODO(dantup): Get these tests passing with either line ending.
-    useLineEndingsForPlatform = false;
-  }
-
   Future<void> test_all_diverseImports() async {
     await resolveTestCode('''
 import 'dart:math';
 import 'dart:math';
 import 'dart:async';
-main() {
-}
+void f() {}
 ''');
     await assertHasFixAllFix(HintCode.UNUSED_IMPORT, '''
-main() {
-}
+void f() {}
 ''');
   }
 
@@ -50,29 +101,39 @@
 
 var tau = math.pi * 2;
 
-main() {
-}
+void f() {}
 ''');
     await assertHasFixAllFix(HintCode.UNUSED_IMPORT, '''
 import 'dart:math' as math;
 
 var tau = math.pi * 2;
 
-main() {
-}
+void f() {}
 ''');
   }
 
-  @FailingTest(reason: 'one unused import remains unremoved')
+  @FailingTest(reason: 'multiple deletions conflict')
   Future<void> test_all_singleLine() async {
+    // TODO(brianwilkerson) Remove test_multipleOnSingleLine_temporary when this
+    //  test starts to pass.
     await resolveTestCode('''
 import 'dart:math'; import 'dart:math'; import 'dart:math';
-main() {
-}
+void f() {}
 ''');
     await assertHasFixAllFix(HintCode.UNUSED_IMPORT, '''
-main() {
-}
+
+void f() {}
+''');
+  }
+
+  Future<void> test_all_singleLine_temporary() async {
+    await resolveTestCode('''
+import 'dart:math'; import 'dart:math'; import 'dart:math';
+void f() {}
+''');
+    await assertHasFixAllFix(HintCode.UNUSED_IMPORT, '''
+import 'dart:math';
+void f() {}
 ''');
   }
 
@@ -81,12 +142,10 @@
 import 'dart:math';
 import 'dart:math';
 import 'dart:math';
-main() {
-}
+void f() {}
 ''');
     await assertHasFixAllFix(HintCode.UNUSED_IMPORT, '''
-main() {
-}
+void f() {}
 ''');
   }
 }
@@ -96,13 +155,6 @@
   @override
   FixKind get kind => DartFixKind.REMOVE_UNUSED_IMPORT;
 
-  @override
-  void setUp() {
-    super.setUp();
-    // TODO(dantup): Get these tests passing with either line ending.
-    useLineEndingsForPlatform = false;
-  }
-
   Future<void> test_anotherImportOnLine() async {
     await resolveTestCode('''
 import 'dart:math'; import 'dart:async';
@@ -125,14 +177,14 @@
 import 'dart:math';
 import 'dart:math';
 
-main() {
+void f() {
   print(min(0, 1));
 }
 ''');
     await assertHasFix('''
 import 'dart:math';
 
-main() {
+void f() {
   print(min(0, 1));
 }
 ''');
@@ -142,11 +194,11 @@
     await resolveTestCode('''
 import
   'dart:math';
-main() {
+void f() {
 }
 ''');
     await assertHasFix('''
-main() {
+void f() {
 }
 ''');
   }
@@ -154,11 +206,11 @@
   Future<void> test_single() async {
     await resolveTestCode('''
 import 'dart:math';
-main() {
+void f() {
 }
 ''');
     await assertHasFix('''
-main() {
+void f() {
 }
 ''');
   }
diff --git a/pkg/analysis_server/test/src/services/correction/fix/replace_with_tear_off_test.dart b/pkg/analysis_server/test/src/services/correction/fix/replace_with_tear_off_test.dart
index 49f9041..e3245f3 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/replace_with_tear_off_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/replace_with_tear_off_test.dart
@@ -82,7 +82,6 @@
 ''');
   }
 
-  // @soloTest
   Future<void> test_constructorTearOff_nameUnnamed() async {
     await resolveTestCode('''
 class C {
diff --git a/pkg/analysis_server/tool/spec/generated/java/AnalysisServer.java b/pkg/analysis_server/tool/spec/generated/java/AnalysisServer.java
index 9f7e268..2108516 100644
--- a/pkg/analysis_server/tool/spec/generated/java/AnalysisServer.java
+++ b/pkg/analysis_server/tool/spec/generated/java/AnalysisServer.java
@@ -441,8 +441,12 @@
    * @param offset The offset within the file at which suggestions are to be made.
    * @param maxResults The maximum number of suggestions to return. If the number of suggestions
    *         after filtering is greater than the maxResults, then isIncomplete is set to true.
+   * @param timeout The approximate time in milliseconds that the server should spend. The server
+   *         will perform some steps anyway, even if it takes longer than the specified timeout. This
+   *         field is intended to be used for benchmarking, and usually should not be provided, so
+   *         that the default timeout is used.
    */
-  public void completion_getSuggestions2(String file, int offset, int maxResults, GetSuggestions2Consumer consumer);
+  public void completion_getSuggestions2(String file, int offset, int maxResults, int timeout, GetSuggestions2Consumer consumer);
 
   /**
    * {@code completion.registerLibraryPaths}
diff --git a/pkg/analysis_server/tool/spec/spec_input.html b/pkg/analysis_server/tool/spec/spec_input.html
index aa08d74..d89a44b 100644
--- a/pkg/analysis_server/tool/spec/spec_input.html
+++ b/pkg/analysis_server/tool/spec/spec_input.html
@@ -1482,6 +1482,16 @@
           then <tt>isIncomplete</tt> is set to <tt>true</tt>.
         </p>
       </field>
+      <field name="timeout" experimental="true" optional="true">
+        <ref>int</ref>
+        <p>
+          The approximate time in milliseconds that the server should spend.
+          The server will perform some steps anyway, even if it takes longer
+          than the specified timeout. This field is intended to be used for
+          benchmarking, and usually should not be  provided, so that the
+          default timeout is used.
+        </p>
+      </field>
     </params>
     <result>
       <field name="replacementOffset">
diff --git a/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart b/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart
index 2689506..85a4474 100644
--- a/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart
+++ b/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart
@@ -141,6 +141,7 @@
 const String COMPLETION_REQUEST_GET_SUGGESTIONS2_FILE = 'file';
 const String COMPLETION_REQUEST_GET_SUGGESTIONS2_MAX_RESULTS = 'maxResults';
 const String COMPLETION_REQUEST_GET_SUGGESTIONS2_OFFSET = 'offset';
+const String COMPLETION_REQUEST_GET_SUGGESTIONS2_TIMEOUT = 'timeout';
 const String COMPLETION_REQUEST_GET_SUGGESTIONS_FILE = 'file';
 const String COMPLETION_REQUEST_GET_SUGGESTIONS_OFFSET = 'offset';
 const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS =
diff --git a/pkg/analysis_server_client/lib/src/protocol/protocol_generated.dart b/pkg/analysis_server_client/lib/src/protocol/protocol_generated.dart
index 2dcbd32..2ebd67f 100644
--- a/pkg/analysis_server_client/lib/src/protocol/protocol_generated.dart
+++ b/pkg/analysis_server_client/lib/src/protocol/protocol_generated.dart
@@ -4698,7 +4698,14 @@
   /// to true.
   int maxResults;
 
-  CompletionGetSuggestions2Params(this.file, this.offset, this.maxResults);
+  /// The approximate time in milliseconds that the server should spend. The
+  /// server will perform some steps anyway, even if it takes longer than the
+  /// specified timeout. This field is intended to be used for benchmarking,
+  /// and usually should not be provided, so that the default timeout is used.
+  int? timeout;
+
+  CompletionGetSuggestions2Params(this.file, this.offset, this.maxResults,
+      {this.timeout});
 
   factory CompletionGetSuggestions2Params.fromJson(
       JsonDecoder jsonDecoder, String jsonPath, Object? json) {
@@ -4723,7 +4730,12 @@
       } else {
         throw jsonDecoder.mismatch(jsonPath, 'maxResults');
       }
-      return CompletionGetSuggestions2Params(file, offset, maxResults);
+      int? timeout;
+      if (json.containsKey('timeout')) {
+        timeout = jsonDecoder.decodeInt(jsonPath + '.timeout', json['timeout']);
+      }
+      return CompletionGetSuggestions2Params(file, offset, maxResults,
+          timeout: timeout);
     } else {
       throw jsonDecoder.mismatch(
           jsonPath, 'completion.getSuggestions2 params', json);
@@ -4741,6 +4753,10 @@
     result['file'] = file;
     result['offset'] = offset;
     result['maxResults'] = maxResults;
+    var timeout = this.timeout;
+    if (timeout != null) {
+      result['timeout'] = timeout;
+    }
     return result;
   }
 
@@ -4757,7 +4773,8 @@
     if (other is CompletionGetSuggestions2Params) {
       return file == other.file &&
           offset == other.offset &&
-          maxResults == other.maxResults;
+          maxResults == other.maxResults &&
+          timeout == other.timeout;
     }
     return false;
   }
@@ -4767,6 +4784,7 @@
         file,
         offset,
         maxResults,
+        timeout,
       );
 }
 
diff --git a/pkg/analyzer/lib/src/dart/micro/library_graph.dart b/pkg/analyzer/lib/src/dart/micro/library_graph.dart
index 7bd4630..581a44e 100644
--- a/pkg/analyzer/lib/src/dart/micro/library_graph.dart
+++ b/pkg/analyzer/lib/src/dart/micro/library_graph.dart
@@ -428,11 +428,7 @@
   }
 
   String? getPathForUri(Uri uri) {
-    var source = _sourceFactory.forUri2(uri);
-    if (source == null) {
-      return null;
-    }
-    return source.fullName;
+    return _sourceFactory.forUri2(uri)?.fullName;
   }
 
   /// Computes the set of [FileState]'s used/not used to analyze the given
@@ -712,19 +708,28 @@
     return _fsState._resourceProvider.getFile(path);
   }
 
+  Uri? resolveRelativeUriStr(String relativeUriStr) {
+    if (relativeUriStr.isEmpty) {
+      return null;
+    }
+
+    Uri relativeUri;
+    try {
+      relativeUri = Uri.parse(relativeUriStr);
+    } on FormatException {
+      return null;
+    }
+
+    return resolveRelativeUri(uri, relativeUri);
+  }
+
   FileState? _fileForRelativeUri({
     FileState? containingLibrary,
     required String relativeUri,
     required OperationPerformanceImpl performance,
   }) {
-    if (relativeUri.isEmpty) {
-      return null;
-    }
-
-    Uri absoluteUri;
-    try {
-      absoluteUri = resolveRelativeUri(uri, Uri.parse(relativeUri));
-    } on FormatException {
+    var absoluteUri = resolveRelativeUriStr(relativeUri);
+    if (absoluteUri == null) {
       return null;
     }
 
@@ -900,41 +905,35 @@
   }
 
   void _prefetchDirectReferences() {
-    if (location._fsState.prefetchFiles == null) {
+    var prefetchFiles = location._fsState.prefetchFiles;
+    if (prefetchFiles == null) {
       return;
     }
 
     var paths = <String>{};
 
-    /// TODO(scheglov) This is duplicate.
-    void findPathForUri(String relativeUri) {
-      if (relativeUri.isEmpty) {
-        return;
-      }
-      Uri absoluteUri;
-      try {
-        absoluteUri = resolveRelativeUri(location.uri, Uri.parse(relativeUri));
-      } on FormatException {
-        return;
-      }
-      var p = location._fsState.getPathForUri(absoluteUri);
-      if (p != null) {
-        paths.add(p);
+    void addRelativeUri(String relativeUri) {
+      var absoluteUri = location.resolveRelativeUriStr(relativeUri);
+      if (absoluteUri != null) {
+        var path = location._fsState.getPathForUri(absoluteUri);
+        if (path != null) {
+          paths.add(path);
+        }
       }
     }
 
     var unlinkedUnit = unlinked.unit;
     for (var directive in unlinkedUnit.imports) {
-      findPathForUri(directive.uri);
+      addRelativeUri(directive.uri);
     }
     for (var directive in unlinkedUnit.exports) {
-      findPathForUri(directive.uri);
+      addRelativeUri(directive.uri);
     }
     for (var uri in unlinkedUnit.parts) {
-      findPathForUri(uri);
+      addRelativeUri(uri);
     }
 
-    location._fsState.prefetchFiles!(paths.toList());
+    prefetchFiles(paths.toList());
   }
 
   static CompilationUnitImpl parse(AnalysisErrorListener errorListener,
diff --git a/pkg/analyzer/lib/src/lint/pub.dart b/pkg/analyzer/lib/src/lint/pub.dart
index 86d7c17..be237ff 100644
--- a/pkg/analyzer/lib/src/lint/pub.dart
+++ b/pkg/analyzer/lib/src/lint/pub.dart
@@ -75,17 +75,6 @@
   return null;
 }
 
-PSNodeList? _processList(
-    YamlScalar key, YamlNode v, ResourceProvider? resourceProvider) {
-  if (v is! YamlList) {
-    return null;
-  }
-  YamlList nodeList = v;
-
-  return _PSNodeList(_PSNode(key, resourceProvider),
-      nodeList.nodes.map((n) => _PSNode(n, resourceProvider)));
-}
-
 PSEntry? _processScalar(
     YamlScalar key, YamlNode value, ResourceProvider? resourceProvider) {
   if (value is! YamlScalar) {
@@ -96,6 +85,20 @@
       _PSNode(key, resourceProvider), _PSNode(value, resourceProvider));
 }
 
+PSNodeList? _processScalarList(
+    YamlScalar key, YamlNode v, ResourceProvider? resourceProvider) {
+  if (v is! YamlList) {
+    return null;
+  }
+  YamlList nodeList = v;
+
+  return _PSNodeList(
+      _PSNode(key, resourceProvider),
+      nodeList.nodes
+          .whereType<YamlScalar>()
+          .map((n) => _PSNode(n, resourceProvider)));
+}
+
 /// Representation of a key/value pair a map from package name to
 /// _package description_.
 ///
@@ -166,9 +169,22 @@
   PSEntry? get url;
 }
 
+/// Representation of a leaf-node from `pubspec.yaml`.
 abstract class PSNode {
   Source get source;
   SourceSpan get span;
+
+  /// String value of the node, or `null` if value in pubspec.yaml is `null` or
+  /// omitted.
+  ///
+  /// **Example**
+  /// ```
+  /// name: foo
+  /// version:
+  /// ```
+  /// In the example above the [PSNode] for `foo` will have [text] "foo", and
+  /// the [PSNode] for `version` will have not have [text] as `null`, as empty
+  /// value or `"null"` is the same in YAML.
   String? get text;
 }
 
@@ -353,7 +369,7 @@
 
   final ResourceProvider? resourceProvider;
 
-  _PSNode(YamlNode node, this.resourceProvider)
+  _PSNode(YamlScalar node, this.resourceProvider)
       : text = node.value?.toString(),
         span = node.span;
 
@@ -481,7 +497,7 @@
           author = _processScalar(key, v, resourceProvider);
           break;
         case 'authors':
-          authors = _processList(key, v, resourceProvider);
+          authors = _processScalarList(key, v, resourceProvider);
           break;
         case 'homepage':
           homepage = _processScalar(key, v, resourceProvider);
diff --git a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart
index a9597f7..d2d24b8 100644
--- a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart
+++ b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart
@@ -242,6 +242,13 @@
   }
 
   @override
+  bool hasEditsFor(String path) {
+    return _dartFileEditBuilders.containsKey(path) ||
+        _genericFileEditBuilders.containsKey(path) ||
+        _yamlFileEditBuilders.containsKey(path);
+  }
+
+  @override
   void setSelection(Position position) {
     _selection = position;
   }
diff --git a/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_core.dart b/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_core.dart
index e407543..804c3c4c 100644
--- a/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_core.dart
+++ b/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_core.dart
@@ -63,6 +63,10 @@
   /// that changes to the copy will not effect this change builder.
   ChangeBuilder copy();
 
+  /// Return `true` if this builder already has edits for the file with the
+  /// given [path].
+  bool hasEditsFor(String path);
+
   /// Set the selection for the change being built to the given [position].
   void setSelection(Position position);
 }
diff --git a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
index 91c66d3..cd26c6b 100644
--- a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
+++ b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
@@ -189,11 +189,12 @@
   };
 }
 
-// Creates a lazy final field that uses non-nullable initialization semantics.
+// Creates a lazy final static field that uses non-nullable initialization
+// semantics.
 //
-// A lazy field has a storage entry, [name], which holds the value, and a
-// getter ([getterName]) to access the field. If the field wasn't set before
-// the first access, it is initialized with the [initializer].
+// A lazy final field has a storage entry, [name], which holds the value, and a
+// getter ([getterName]) to access the field. The field is initialized on first
+// access with the [initializer].
 function lazyFinal(holder, name, getterName, initializer) {
   var uninitializedSentinel = holder;
   holder[name] = uninitializedSentinel;
@@ -201,12 +202,21 @@
     if (holder[name] === uninitializedSentinel) {
       var value = initializer();
       if (holder[name] !== uninitializedSentinel) {
+        // Since there is no setter, the only way to get here is via bounded
+        // recursion, where `initializer` calls the lazy final getter.
         #throwLateFieldADI(name);
       }
       holder[name] = value;
     }
-    holder[getterName] = function() { return this[name]; };
-    return holder[name];
+    // TODO(sra): Does the value need to be stored in the holder at all?
+    // Potentially a call to the getter could be replaced with an access to
+    // holder slot if dominated by a previous call to the getter. Does the
+    // optimizer do this? If so, within a single function, the dominance
+    // relations should allow a local copy via GVN, so it is not that valuable
+    // an optimization for a `final` static variable.
+    var finalValue = holder[name];
+    holder[getterName] = function() { return finalValue; };
+    return finalValue;
   };
 }
 
diff --git a/pkg/dds/lib/src/dap/adapters/dart.dart b/pkg/dds/lib/src/dap/adapters/dart.dart
index 655d646..5f70e3b 100644
--- a/pkg/dds/lib/src/dap/adapters/dart.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart.dart
@@ -53,6 +53,13 @@
 /// Typedef for handlers of VM Service stream events.
 typedef _StreamEventHandler<T> = FutureOr<void> Function(T data);
 
+/// A null result passed to `sendResponse` functions when there is no result.
+///
+/// Because the signature of `sendResponse` is generic, an argument must be
+/// provided even when the generic type is `void`. This value is used to make
+/// it clearer in calling code that the result is unused.
+const _noResult = null;
+
 /// Pattern for extracting useful error messages from an evaluation exception.
 final _evalErrorMessagePattern = RegExp('Error: (.*)');
 
@@ -661,26 +668,26 @@
   ) async {
     switch (request.command) {
 
-      /// Used by tests to validate available protocols (e.g. DDS). There may be
-      /// value in making this available to clients in future, but for now it's
-      /// internal.
+      // Used by tests to validate available protocols (e.g. DDS). There may be
+      // value in making this available to clients in future, but for now it's
+      // internal.
       case '_getSupportedProtocols':
         final protocols = await vmService?.getSupportedProtocols();
         sendResponse(protocols?.toJson());
         break;
 
-      /// Used to toggle debug settings such as whether SDK/Packages are
-      /// debuggable while the session is in progress.
+      // Used to toggle debug settings such as whether SDK/Packages are
+      // debuggable while the session is in progress.
       case 'updateDebugOptions':
         if (args != null) {
           await _updateDebugOptions(args.args);
         }
-        sendResponse(null);
+        sendResponse(_noResult);
         break;
 
-      /// Allows an editor to call a service/service extension that it was told
-      /// about via a custom 'dart.serviceRegistered' or
-      /// 'dart.serviceExtensionAdded' event.
+      // Allows an editor to call a service/service extension that it was told
+      // about via a custom 'dart.serviceRegistered' or
+      // 'dart.serviceExtensionAdded' event.
       case 'callService':
         final method = args?.args['method'] as String?;
         if (method == null) {
@@ -696,6 +703,15 @@
         sendResponse(response?.json);
         break;
 
+      // Used to reload sources for all isolates. This supports Hot Reload for
+      // Dart apps. Flutter's DAP handles this command itself (and sends it
+      // through the run daemon) as it needs to perform additional work to
+      // rebuild widgets afterwards.
+      case 'hotReload':
+        await _isolateManager.reloadSources();
+        sendResponse(_noResult);
+        break;
+
       default:
         await super.customRequest(request, args, sendResponse);
     }
diff --git a/pkg/dds/lib/src/dap/base_debug_adapter.dart b/pkg/dds/lib/src/dap/base_debug_adapter.dart
index 2d82bbd..215a975 100644
--- a/pkg/dds/lib/src/dap/base_debug_adapter.dart
+++ b/pkg/dds/lib/src/dap/base_debug_adapter.dart
@@ -376,7 +376,7 @@
   /// passes an unused arg so that `Function()` can be passed to a function
   /// accepting `Function<T>(T x)` where `T` happens to be `void`.
   ///
-  /// This allows handlers to simple call sendResponse() where they have no
+  /// This allows handlers to simply call sendResponse() where they have no
   /// return value but need to send a valid response.
   _VoidArgRequestHandler<TArg> _withVoidResponse<TArg>(
     _VoidNoArgRequestHandler<TArg> handler,
diff --git a/pkg/dds/lib/src/dap/isolate_manager.dart b/pkg/dds/lib/src/dap/isolate_manager.dart
index fd2d342..47a8dde 100644
--- a/pkg/dds/lib/src/dap/isolate_manager.dart
+++ b/pkg/dds/lib/src/dap/isolate_manager.dart
@@ -136,10 +136,7 @@
     vm.Event event, {
     bool resumeIfStarting = true,
   }) async {
-    final isolateId = event.isolate?.id;
-    if (isolateId == null) {
-      return;
-    }
+    final isolateId = event.isolate?.id!;
 
     final eventKind = event.kind;
     if (eventKind == vm.EventKind.kIsolateStart ||
@@ -203,12 +200,16 @@
     return info;
   }
 
+  /// Calls reloadSources for all isolates.
+  Future<void> reloadSources() async {
+    await Future.wait(_threadsByThreadId.values.map(
+      (isolate) => _reloadSources(isolate.isolate),
+    ));
+  }
+
   Future<void> resumeIsolate(vm.IsolateRef isolateRef,
       [String? resumeType]) async {
-    final isolateId = isolateRef.id;
-    if (isolateId == null) {
-      return;
-    }
+    final isolateId = isolateRef.id!;
 
     final thread = _threadsByIsolateId[isolateId];
     if (thread == null) {
@@ -527,6 +528,18 @@
     }
   }
 
+  /// Calls reloadSources for the given isolate.
+  Future<void> _reloadSources(vm.IsolateRef isolateRef) async {
+    final service = _adapter.vmService;
+    if (!debug || service == null) {
+      return;
+    }
+
+    final isolateId = isolateRef.id!;
+
+    await service.reloadSources(isolateId);
+  }
+
   /// Sets breakpoints for an individual isolate.
   ///
   /// If [uri] is provided, only breakpoints for that URI will be sent (used
@@ -592,10 +605,7 @@
       return;
     }
 
-    final isolateId = isolateRef.id;
-    if (isolateId == null) {
-      return;
-    }
+    final isolateId = isolateRef.id!;
 
     final isolate = await service.getIsolate(isolateId);
     final libraries = isolate.libraries;
diff --git a/pkg/dds/test/dap/integration/debug_test.dart b/pkg/dds/test/dap/integration/debug_test.dart
index 12f17b4..d6df8e6 100644
--- a/pkg/dds/test/dap/integration/debug_test.dart
+++ b/pkg/dds/test/dap/integration/debug_test.dart
@@ -173,6 +173,34 @@
       unawaited(dap.client.event('thread').then((_) => dap.client.terminate()));
       await dap.client.start(file: testFile);
     });
+
+    test('can hot reload', () async {
+      const originalText = 'ORIGINAL TEXT';
+      const newText = 'NEW TEXT';
+
+      // Create a script that prints 'ORIGINAL TEXT'.
+      final testFile = dap.createTestFile(stringPrintingProgram(originalText));
+
+      // Start the program and wait for 'ORIGINAL TEXT' to be printed.
+      await Future.wait([
+        dap.client.initialize(),
+        dap.client.launch(testFile.path),
+      ], eagerError: true);
+
+      // Expect the original text.
+      await dap.client.outputEvents
+          .firstWhere((event) => event.output.trim() == originalText);
+
+      // Update the file and hot reload.
+      testFile.writeAsStringSync(stringPrintingProgram(newText));
+      await dap.client.hotReload();
+
+      // Expect the new text.
+      await dap.client.outputEvents
+          .firstWhere((event) => event.output.trim() == newText);
+
+      await dap.client.terminate();
+    });
     // These tests can be slow due to starting up the external server process.
   }, timeout: Timeout.none);
 
diff --git a/pkg/dds/test/dap/integration/test_client.dart b/pkg/dds/test/dap/integration/test_client.dart
index af40d1c..55f21c0 100644
--- a/pkg/dds/test/dap/integration/test_client.dart
+++ b/pkg/dds/test/dap/integration/test_client.dart
@@ -148,7 +148,7 @@
       sendRequest(ContinueArguments(threadId: threadId));
 
   /// Sends a custom request to the server and waits for a response.
-  Future<Response> custom(String name, Object? args) async {
+  Future<Response> custom(String name, [Object? args]) async {
     return sendRequest(args, overrideCommand: name);
   }
 
@@ -191,6 +191,11 @@
     _serverRequestHandlers[request] = handler;
   }
 
+  /// Send a custom 'hotReload' request to the server.
+  Future<Response> hotReload() async {
+    return custom('hotReload');
+  }
+
   /// Send an initialize request to the server.
   ///
   /// This occurs before the request to start running/debugging a script and is
diff --git a/pkg/dds/test/dap/integration/test_scripts.dart b/pkg/dds/test/dap/integration/test_scripts.dart
index 2b24727..59bc718 100644
--- a/pkg/dds/test/dap/integration/test_scripts.dart
+++ b/pkg/dds/test/dap/integration/test_scripts.dart
@@ -2,6 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'dart:convert';
+
 import 'package:test/test.dart';
 
 /// A marker used in some test scripts/tests for where to set breakpoints.
@@ -53,6 +55,24 @@
   }
 ''';
 
+/// Returns a simple Dart script that prints the provided string repeatedly.
+String stringPrintingProgram(String text) {
+  // jsonEncode the string to get it into a quoted/escaped form that can be
+  // embedded in the string.
+  final encodedTextString = jsonEncode(text);
+  return '''
+  import 'dart:async';
+
+  main() async {
+    Timer.periodic(Duration(milliseconds: 10), (_) => printSomething());
+  }
+
+  void printSomething() {
+    print($encodedTextString);
+  }
+''';
+}
+
 /// A simple async Dart script that when stopped at the line of '// BREAKPOINT'
 /// will contain multiple stack frames across some async boundaries.
 const simpleAsyncProgram = '''
diff --git a/pkg/dds/tool/dap/README.md b/pkg/dds/tool/dap/README.md
index a65f707..ac86324 100644
--- a/pkg/dds/tool/dap/README.md
+++ b/pkg/dds/tool/dap/README.md
@@ -74,6 +74,17 @@
 }
 ```
 
+### `hotReload`
+
+`hotReload` calls the VM's `reloadSources` service for each active isolate, reloading all modified source files.
+
+```
+{
+	"method": "hotReload",
+	"params": null
+}
+```
+
 ## Custom Events
 
 The debug adapter may emit several custom events that are useful to clients.
diff --git a/pkg/front_end/testcases/strong.status b/pkg/front_end/testcases/strong.status
index 3619d46..e35f2a1 100644
--- a/pkg/front_end/testcases/strong.status
+++ b/pkg/front_end/testcases/strong.status
@@ -6,6 +6,11 @@
 # Kernel ASTs directly, that is, code in pkg/fasta/lib/src/kernel/ with
 # strong-mode enabled.
 
+dart2js/late_fields: SemiFuzzFailure
+dart2js/late_from_dill/main: SemiFuzzFailure
+dart2js/late_statics: SemiFuzzFailure
+static_field_lowering/opt_in: SemiFuzzFailure
+
 constructor_tearoffs/call_instantiation: TypeCheckError
 constructor_tearoffs/lowering/invalid_redirect: VerificationError
 extension_types/access_setter_as_getter: ExpectationFileMismatchSerialized # Expected.
diff --git a/pkg/front_end/testcases/weak.status b/pkg/front_end/testcases/weak.status
index 3b2aaee..a4258c6 100644
--- a/pkg/front_end/testcases/weak.status
+++ b/pkg/front_end/testcases/weak.status
@@ -4,11 +4,20 @@
 
 # Status file for the weak_suite.dart test suite.
 
-general/platform_invalid_uris/main: SemiFuzzFailure
+dart2js/late_fields: SemiFuzzFailure
+dart2js/late_from_dill/main: SemiFuzzFailure
+dart2js/late_statics: SemiFuzzFailure
+dart2js/mixin_from_opt_in/main: SemiFuzzFailure
+dart2js/mixin_from_opt_in/main.no_link: SemiFuzzFailure
 general/error_recovery/issue_39058_prime.crash: SemiFuzzFailure
 general/error_recovery/issue_39202.crash: SemiFuzzCrash
 general/error_recovery/issue_39058.crash: SemiFuzzFailure
+general/issue47339: SemiFuzzCrash
+general/platform_invalid_uris/main: SemiFuzzFailure
+nnbd_mixed/mixin_from_opt_in/main: SemiFuzzFailure
+nnbd_mixed/mixin_from_opt_in/main.no_link: SemiFuzzFailure
 regress/utf_16_le_content.crash: SemiFuzzCrash
+static_field_lowering/opt_in: SemiFuzzFailure
 
 constructor_tearoffs/call_instantiation: TypeCheckError
 constructor_tearoffs/lowering/invalid_redirect: VerificationError
diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc
index 34521bc..515d1bd 100644
--- a/runtime/vm/isolate.cc
+++ b/runtime/vm/isolate.cc
@@ -2449,7 +2449,18 @@
 
 // static
 void Isolate::NotifyLowMemory() {
-  Isolate::KillAllIsolates(Isolate::kLowMemoryMsg);
+  IsolateGroup::ForEach([](IsolateGroup* group) { group->NotifyLowMemory(); });
+}
+
+void IsolateGroup::NotifyLowMemory() {
+  SafepointReadRwLocker ml(Thread::Current(), isolates_lock_.get());
+  MonitorLocker ml2(Isolate::isolate_creation_monitor_);
+  for (Isolate* isolate : isolates_) {
+    if (isolate->AcceptsMessagesLocked()) {
+      isolate->KillLocked(Isolate::kLowMemoryMsg);
+      return;  // Only wake up one member of the group.
+    }
+  }
 }
 
 void Isolate::LowLevelShutdown() {
diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h
index f9616f3..2c531f0 100644
--- a/runtime/vm/isolate.h
+++ b/runtime/vm/isolate.h
@@ -597,6 +597,8 @@
     return deferred_marking_stack_;
   }
 
+  void NotifyLowMemory();
+
   // Runs the given [function] on every isolate in the isolate group.
   //
   // During the duration of this function, no new isolates can be added or
diff --git a/tools/VERSION b/tools/VERSION
index 746a27a..0fd0143 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 16
 PATCH 0
-PRERELEASE 14
+PRERELEASE 15
 PRERELEASE_PATCH 0
\ No newline at end of file