Version 2.13.0-154.0.dev

Merge commit 'dd9b00f7b93d72f8e174bfc630f02617726d9307' into 'dev'
diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json
index beda8cf..1b1635e 100644
--- a/.dart_tool/package_config.json
+++ b/.dart_tool/package_config.json
@@ -11,7 +11,7 @@
     "constraint, update this by running tools/generate_package_config.dart."
   ],
   "configVersion": 2,
-  "generated": "2021-03-18T10:46:33.455990",
+  "generated": "2021-03-19T12:24:33.467679",
   "generator": "tools/generate_package_config.dart",
   "packages": [
     {
@@ -244,7 +244,7 @@
       "name": "dds",
       "rootUri": "../pkg/dds",
       "packageUri": "lib/",
-      "languageVersion": "2.6"
+      "languageVersion": "2.12"
     },
     {
       "name": "dev_compiler",
@@ -581,7 +581,7 @@
       "name": "shelf_proxy",
       "rootUri": "../third_party/pkg/shelf_proxy",
       "packageUri": "lib/",
-      "languageVersion": "2.0"
+      "languageVersion": "2.12"
     },
     {
       "name": "shelf_static",
@@ -791,7 +791,7 @@
       "name": "webdriver",
       "rootUri": "../third_party/pkg/webdriver",
       "packageUri": "lib/",
-      "languageVersion": "2.0"
+      "languageVersion": "2.12"
     },
     {
       "name": "webkit_inspection_protocol",
diff --git a/DEPS b/DEPS
index f148696..225447d 100644
--- a/DEPS
+++ b/DEPS
@@ -112,7 +112,7 @@
   "http_multi_server_rev" : "6bf4b6e5d4d890e6d54559b858ff229d79711171",
   "http_parser_rev": "5dd4d16693242049dfb43b5efa429fedbf932e98",
   "http_retry_rev": "845771af7bb5ab38ab740ce4a31f3b0c7680302b",
-  "http_rev": "d5c678cd63c3e9c1d779a09acfa95b7e3af84665",
+  "http_rev": "615380db687d0649057ed8dba9d933d7d5f5051b",
   "http_throttle_tag" : "1.0.2",
   "icu_rev" : "81d656878ec611cb0b42d52c82e9dae93920d9ba",
   "idl_parser_rev": "5fb1ebf49d235b5a70c9f49047e83b0654031eb7",
@@ -141,11 +141,11 @@
   "resource_rev": "6b79867d0becf5395e5819a75720963b8298e9a7",
   "root_certificates_rev": "7e5ec82c99677a2e5b95ce296c4d68b0d3378ed8",
   "rust_revision": "b7856f695d65a8ebc846754f97d15814bcb1c244",
-  "shelf_static_rev": "bafde9eaddb5d02040a614e41deddd971b4d67e6",
+  "shelf_static_rev": "fa30419055279a00c9e428439b1abe362d18f25d",
   "shelf_packages_handler_rev": "78302e67c035047e6348e692b0c1182131f0fe35",
-  "shelf_proxy_tag": "0.1.0+7",
-  "shelf_rev": "e9294125f0c1fc2049c958577cd586ca2395168f",
-  "shelf_web_socket_rev": "aa312d3cdeef96fb64bc3e602bc354a05a995624",
+  "shelf_proxy_tag": "v1.0.0",
+  "shelf_rev": "00e50adfb776602c25942a99d89f8704cc20db9c",
+  "shelf_web_socket_rev": "24fb8a04befa75a94ac63a27047b231d1a22aab4",
   "source_map_stack_trace_rev": "1c3026f69d9771acf2f8c176a1ab750463309cce",
   "source_maps-0.9.4_rev": "38524",
   "source_maps_rev": "53eb92ccfe6e64924054f83038a534b959b12b3e",
@@ -165,9 +165,9 @@
   "usage_rev": "6c64d9e7b6b3758d06d030efcb5afe20bfc04dde",
   "vector_math_rev": "0c9f5d68c047813a6dcdeb88ba7a42daddf25025",
   "watcher_rev": "3924194385fb215cef483193ed2879a618a3d69c",
-  "webdriver_rev": "5a8d6805d9cf8a3cbb4fcd64849b538b7491e50e",
+  "webdriver_rev": "ff5ccb1522edf4bed578ead4d65e0cbc1f2c4f02",
   "web_components_rev": "8f57dac273412a7172c8ade6f361b407e2e4ed02",
-  "web_socket_channel_rev": "76931ea1b81ba71e8319330c35285d3e88566315",
+  "web_socket_channel_rev": "d3e100de8feb0283a04732366bb591ebd2282d7d",
   "WebCore_rev": "fb11e887f77919450e497344da570d780e078bc8",
   "webkit_inspection_protocol_rev": "6b15729292d030f2e5c5861022da4c5a4c11961c",
   "yaml_rev": "b4c4411631bda556ce9a45af1ab0eecaf9f3ac53",
diff --git a/WATCHLISTS b/WATCHLISTS
index 23100a3..e4b5b89 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -18,6 +18,9 @@
         '^tools/buildtools/'
       )
     },
+    'experimental_features': {
+      'filepath': 'tools/experimental_features\\.yaml',
+    },
     'front_end': {
       'filepath': '^pkg/front_end',
     },
@@ -75,6 +78,7 @@
   'WATCHLISTS': {
     'dart2js': [ 'dart2js-team+reviews@google.com' ],
     'dartdevc': [ 'dart-dc-team+reviews@google.com' ],
+    'experimental_features': [ 'scheglov@google.com' ],
     'front_end': [ 'dart-fe-team+reviews@google.com' ],
     'kernel': [ 'jensj@google.com', 'alexmarkov@google.com' ],
     'messages_review': [ 'dart-uxr+reviews@google.com' ],
diff --git a/pkg/dds/CHANGELOG.md b/pkg/dds/CHANGELOG.md
index 62904a1..e374a78 100644
--- a/pkg/dds/CHANGELOG.md
+++ b/pkg/dds/CHANGELOG.md
@@ -1,3 +1,6 @@
+# 1.7.6
+- Update dependencies.
+
 # 1.7.5
 - Add 30 second keep alive period for SSE connections.
 
diff --git a/pkg/dds/bin/dds.dart b/pkg/dds/bin/dds.dart
index 3ac9a09..9937d26 100644
--- a/pkg/dds/bin/dds.dart
+++ b/pkg/dds/bin/dds.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.
 
+// @dart=2.10
+
 import 'dart:io';
 
 import 'package:dds/dds.dart';
diff --git a/pkg/dds/example/example.dart b/pkg/dds/example/example.dart
index 883c678..561dca9 100644
--- a/pkg/dds/example/example.dart
+++ b/pkg/dds/example/example.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.
 
+// @dart=2.10
+
 import 'package:dds/dds.dart';
 import 'package:vm_service/vm_service_io.dart';
 
diff --git a/pkg/dds/lib/dds.dart b/pkg/dds/lib/dds.dart
index a9024dd..f7c7a05 100644
--- a/pkg/dds/lib/dds.dart
+++ b/pkg/dds/lib/dds.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.
 
+// @dart=2.10
+
 /// A library used to spawn the Dart Developer Service, used to communicate
 /// with a Dart VM Service instance.
 library dds;
diff --git a/pkg/dds/lib/src/binary_compatible_peer.dart b/pkg/dds/lib/src/binary_compatible_peer.dart
index b701f5e..2e1af84 100644
--- a/pkg/dds/lib/src/binary_compatible_peer.dart
+++ b/pkg/dds/lib/src/binary_compatible_peer.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.
 
+// @dart=2.10
+
 import 'dart:async';
 import 'dart:convert';
 import 'dart:typed_data';
diff --git a/pkg/dds/lib/src/client.dart b/pkg/dds/lib/src/client.dart
index 0c854c3..f81bd25 100644
--- a/pkg/dds/lib/src/client.dart
+++ b/pkg/dds/lib/src/client.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.
 
+// @dart=2.10
+
 import 'dart:async';
 
 import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
diff --git a/pkg/dds/lib/src/client_manager.dart b/pkg/dds/lib/src/client_manager.dart
index b168978..c911c51 100644
--- a/pkg/dds/lib/src/client_manager.dart
+++ b/pkg/dds/lib/src/client_manager.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.
 
+// @dart=2.10
+
 import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
 
 import 'client.dart';
diff --git a/pkg/dds/lib/src/constants.dart b/pkg/dds/lib/src/constants.dart
index c6450d5..2466390 100644
--- a/pkg/dds/lib/src/constants.dart
+++ b/pkg/dds/lib/src/constants.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.
 
+// @dart=2.10
+
 abstract class RPCResponses {
   static const success = <String, dynamic>{
     'type': 'Success',
diff --git a/pkg/dds/lib/src/dds_impl.dart b/pkg/dds/lib/src/dds_impl.dart
index 6daaedd..db355fb 100644
--- a/pkg/dds/lib/src/dds_impl.dart
+++ b/pkg/dds/lib/src/dds_impl.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.
 
+// @dart=2.10
+
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
diff --git a/pkg/dds/lib/src/expression_evaluator.dart b/pkg/dds/lib/src/expression_evaluator.dart
index bb69bb5..59bbe36 100644
--- a/pkg/dds/lib/src/expression_evaluator.dart
+++ b/pkg/dds/lib/src/expression_evaluator.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.
 
+// @dart=2.10
+
 import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
 
 import 'client.dart';
diff --git a/pkg/dds/lib/src/isolate_manager.dart b/pkg/dds/lib/src/isolate_manager.dart
index 2714021..a5139d5 100644
--- a/pkg/dds/lib/src/isolate_manager.dart
+++ b/pkg/dds/lib/src/isolate_manager.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.
 
+// @dart=2.10
+
 import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
 
 import 'client.dart';
diff --git a/pkg/dds/lib/src/logging_repository.dart b/pkg/dds/lib/src/logging_repository.dart
index f87fdaa..0b31ca6 100644
--- a/pkg/dds/lib/src/logging_repository.dart
+++ b/pkg/dds/lib/src/logging_repository.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.
 
+// @dart=2.10
+
 import 'dart:math';
 
 import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
diff --git a/pkg/dds/lib/src/named_lookup.dart b/pkg/dds/lib/src/named_lookup.dart
index 603bbc0..8515411 100644
--- a/pkg/dds/lib/src/named_lookup.dart
+++ b/pkg/dds/lib/src/named_lookup.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.
 
+// @dart=2.10
+
 // Originally pulled from dart:_vmservice.
 
 import 'dart:collection';
diff --git a/pkg/dds/lib/src/rpc_error_codes.dart b/pkg/dds/lib/src/rpc_error_codes.dart
index 93c514e..895c07c 100644
--- a/pkg/dds/lib/src/rpc_error_codes.dart
+++ b/pkg/dds/lib/src/rpc_error_codes.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.
 
+// @dart=2.10
+
 import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
 
 abstract class RpcErrorCodes {
diff --git a/pkg/dds/lib/src/stream_manager.dart b/pkg/dds/lib/src/stream_manager.dart
index 28c7f28..26bc6d7 100644
--- a/pkg/dds/lib/src/stream_manager.dart
+++ b/pkg/dds/lib/src/stream_manager.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.
 
+// @dart=2.10
+
 import 'dart:typed_data';
 
 import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
diff --git a/pkg/dds/lib/vm_service_extensions.dart b/pkg/dds/lib/vm_service_extensions.dart
index 8373b87..0b6aaf4 100644
--- a/pkg/dds/lib/vm_service_extensions.dart
+++ b/pkg/dds/lib/vm_service_extensions.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.
 
+// @dart=2.10
+
 import 'dart:async';
 import 'dart:collection';
 
diff --git a/pkg/dds/pubspec.yaml b/pkg/dds/pubspec.yaml
index 469fc0e..221e3df 100644
--- a/pkg/dds/pubspec.yaml
+++ b/pkg/dds/pubspec.yaml
@@ -3,27 +3,27 @@
   A library used to spawn the Dart Developer Service, used to communicate with
   a Dart VM Service instance.
 
-version: 1.7.5
+version: 1.7.6
 
 homepage: https://github.com/dart-lang/sdk/tree/master/pkg/dds
 
 environment:
-  sdk: '>=2.6.0 <3.0.0'
+  sdk: '>=2.12.0 <3.0.0'
 
 dependencies:
   async: ^2.4.1
   json_rpc_2: ^2.2.0
   meta: ^1.1.8
   pedantic: ^1.7.0
-  shelf: ^0.7.5
-  shelf_proxy: ^0.1.0+7
-  shelf_web_socket: ^0.2.3
+  shelf: ^1.0.0
+  shelf_proxy: ^1.0.0
+  shelf_web_socket: ^1.0.0
   sse: ^3.7.0
   stream_channel: ^2.0.0
   vm_service: ^6.0.1-nullsafety.0
-  web_socket_channel: ^1.1.0
+  web_socket_channel: ^2.0.0
 
 dev_dependencies:
-  shelf_static: ^0.2.8
+  shelf_static: ^1.0.0
   test: ^1.0.0
-  webdriver: ^2.1.2
+  webdriver: ^3.0.0
diff --git a/pkg/dds/test/auth_codes_test.dart b/pkg/dds/test/auth_codes_test.dart
index 653ff41..fa8cd44 100644
--- a/pkg/dds/test/auth_codes_test.dart
+++ b/pkg/dds/test/auth_codes_test.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.
 
+// @dart=2.10
+
 import 'dart:convert';
 import 'dart:io';
 
diff --git a/pkg/dds/test/common/fakes.dart b/pkg/dds/test/common/fakes.dart
index 7e11f9d..663cdd0 100644
--- a/pkg/dds/test/common/fakes.dart
+++ b/pkg/dds/test/common/fakes.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.
 
+// @dart=2.10
+
 import 'dart:async';
 
 import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
diff --git a/pkg/dds/test/common/test_helper.dart b/pkg/dds/test/common/test_helper.dart
index cda9ce2..7cdb204 100644
--- a/pkg/dds/test/common/test_helper.dart
+++ b/pkg/dds/test/common/test_helper.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.
 
+// @dart=2.10
+
 import 'dart:convert';
 import 'dart:io';
 
diff --git a/pkg/dds/test/external_compilation_service_script.dart b/pkg/dds/test/external_compilation_service_script.dart
index b01ed60..00c9208 100644
--- a/pkg/dds/test/external_compilation_service_script.dart
+++ b/pkg/dds/test/external_compilation_service_script.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.
 
+// @dart=2.10
+
 import 'dart:developer';
 
 main() {
diff --git a/pkg/dds/test/external_compilation_service_test.dart b/pkg/dds/test/external_compilation_service_test.dart
index 92e5ac8..f31b9c0 100644
--- a/pkg/dds/test/external_compilation_service_test.dart
+++ b/pkg/dds/test/external_compilation_service_test.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.
 
+// @dart=2.10
+
 import 'dart:io';
 
 import 'package:dds/dds.dart';
diff --git a/pkg/dds/test/get_stream_history_script.dart b/pkg/dds/test/get_stream_history_script.dart
index 68dafec..1f8b820 100644
--- a/pkg/dds/test/get_stream_history_script.dart
+++ b/pkg/dds/test/get_stream_history_script.dart
@@ -1,3 +1,5 @@
+// @dart = 2.10
+
 import 'dart:developer';
 
 void main() {
diff --git a/pkg/dds/test/get_stream_history_test.dart b/pkg/dds/test/get_stream_history_test.dart
index c2ae8f9..6631d24 100644
--- a/pkg/dds/test/get_stream_history_test.dart
+++ b/pkg/dds/test/get_stream_history_test.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.
 
+// @dart=2.10
+
 import 'dart:io';
 
 import 'package:dds/dds.dart';
diff --git a/pkg/dds/test/handles_client_disconnect_state_error_test.dart b/pkg/dds/test/handles_client_disconnect_state_error_test.dart
index 87d027c..974814a 100644
--- a/pkg/dds/test/handles_client_disconnect_state_error_test.dart
+++ b/pkg/dds/test/handles_client_disconnect_state_error_test.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.
 
+// @dart=2.10
+
 import 'dart:async';
 
 import 'package:dds/dds.dart';
diff --git a/pkg/dds/test/handles_connection_closed_before_full_header.dart b/pkg/dds/test/handles_connection_closed_before_full_header.dart
index 80f1744..7be1043 100644
--- a/pkg/dds/test/handles_connection_closed_before_full_header.dart
+++ b/pkg/dds/test/handles_connection_closed_before_full_header.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.
 
+// @dart=2.10
+
 import 'dart:async';
 import 'dart:io';
 
diff --git a/pkg/dds/test/on_event_with_history_script.dart b/pkg/dds/test/on_event_with_history_script.dart
index 3f27df0..e35decc 100644
--- a/pkg/dds/test/on_event_with_history_script.dart
+++ b/pkg/dds/test/on_event_with_history_script.dart
@@ -1,3 +1,5 @@
+// @dart=2.10
+
 import 'dart:developer';
 
 void main() {
diff --git a/pkg/dds/test/on_event_with_history_test.dart b/pkg/dds/test/on_event_with_history_test.dart
index 55e04b9..42f25a1 100644
--- a/pkg/dds/test/on_event_with_history_test.dart
+++ b/pkg/dds/test/on_event_with_history_test.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.
 
+// @dart=2.10
+
 import 'dart:async';
 import 'dart:io';
 
diff --git a/pkg/dds/test/smoke.dart b/pkg/dds/test/smoke.dart
index f4c4774..f1f92a2 100644
--- a/pkg/dds/test/smoke.dart
+++ b/pkg/dds/test/smoke.dart
@@ -1,3 +1,5 @@
+// @dart=2.10
+
 void main() {
   print('Hello world!');
 }
diff --git a/pkg/dds/test/smoke_test.dart b/pkg/dds/test/smoke_test.dart
index 983a2da..2f59ef0 100644
--- a/pkg/dds/test/smoke_test.dart
+++ b/pkg/dds/test/smoke_test.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.
 
+// @dart=2.10
+
 import 'dart:convert';
 import 'dart:io';
 
diff --git a/pkg/dds/test/sse_smoke_test.dart b/pkg/dds/test/sse_smoke_test.dart
index 9d4a9a6..2b5e696 100644
--- a/pkg/dds/test/sse_smoke_test.dart
+++ b/pkg/dds/test/sse_smoke_test.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.
 
+// @dart=2.10
+
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
diff --git a/pkg/dds/test/uri_test.dart b/pkg/dds/test/uri_test.dart
index 8c7e64e..dad20f3 100644
--- a/pkg/dds/test/uri_test.dart
+++ b/pkg/dds/test/uri_test.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.
 
+// @dart=2.10
+
 import 'dart:io';
 
 import 'package:dds/dds.dart';
diff --git a/pkg/dds/test/web/sse_smoke_driver.dart b/pkg/dds/test/web/sse_smoke_driver.dart
index 4f0ab25..f8769f5 100644
--- a/pkg/dds/test/web/sse_smoke_driver.dart
+++ b/pkg/dds/test/web/sse_smoke_driver.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.
 
+// @dart=2.10
+
 // This file must be compiled for changes to be picked up.
 //
 // Run the following command from the root of this package if this file is
diff --git a/pkg/front_end/lib/src/fasta/builder/type_variable_builder.dart b/pkg/front_end/lib/src/fasta/builder/type_variable_builder.dart
index b884f68..ba03221 100644
--- a/pkg/front_end/lib/src/fasta/builder/type_variable_builder.dart
+++ b/pkg/front_end/lib/src/fasta/builder/type_variable_builder.dart
@@ -12,11 +12,10 @@
 
 import '../fasta_codes.dart'
     show
-        templateCycleInTypeVariables,
         templateInternalProblemUnfinishedTypeVariable,
         templateTypeArgumentsOnTypeVariable;
 
-import '../source/source_library_builder.dart' show SourceLibraryBuilder;
+import '../source/source_library_builder.dart';
 import '../util/helpers.dart';
 
 import 'class_builder.dart';
@@ -40,8 +39,8 @@
 
   final bool isExtensionTypeParameter;
 
-  TypeVariableBuilder(
-      String name, SourceLibraryBuilder compilationUnit, int charOffset,
+  TypeVariableBuilder(String name, SourceLibraryBuilder compilationUnit,
+      int charOffset, Uri fileUri,
       {this.bound,
       this.isExtensionTypeParameter: false,
       int variableVariance,
@@ -49,7 +48,7 @@
       : actualParameter = new TypeParameter(name, null)
           ..fileOffset = charOffset
           ..variance = variableVariance,
-        super(metadata, 0, name, compilationUnit, charOffset);
+        super(metadata, 0, name, compilationUnit, charOffset, fileUri);
 
   TypeVariableBuilder.fromKernel(
       TypeParameter parameter, LibraryBuilder compilationUnit)
@@ -116,12 +115,12 @@
     DartType type = buildTypesWithBuiltArguments(library, nullability, null);
     if (needsPostUpdate) {
       if (library is SourceLibraryBuilder) {
-        library.pendingNullabilities.add(type);
+        library.registerPendingNullability(fileUri, charOffset, type);
       } else {
         library.addProblem(
             templateInternalProblemUnfinishedTypeVariable.withArguments(
                 name, library?.importUri),
-            parameter.fileOffset,
+            charOffset,
             name.length,
             fileUri);
       }
@@ -166,74 +165,6 @@
             : dynamicType.build(library));
   }
 
-  /// Assigns nullabilities to types in [pendingNullabilities].
-  ///
-  /// It's a helper function to assign the nullabilities to type-parameter types
-  /// after the corresponding type parameters have their bounds set or changed.
-  /// The function takes into account that some of the types in the input list
-  /// may be bounds to some of the type parameters of other types from the input
-  /// list.
-  static void finishNullabilities(LibraryBuilder libraryBuilder,
-      List<TypeParameterType> pendingNullabilities) {
-    // The bounds of type parameters may be type-parameter types of other
-    // parameters from the same declaration.  In this case we need to set the
-    // nullability for them first.  To preserve the ordering, we implement a
-    // depth-first search over the types.  We use the fact that a nullability
-    // of a type parameter type can't ever be 'nullable' if computed from the
-    // bound. It allows us to use 'nullable' nullability as the marker in the
-    // DFS implementation.
-    Nullability marker = Nullability.nullable;
-    List<TypeParameterType> stack =
-        new List<TypeParameterType>.filled(pendingNullabilities.length, null);
-    int stackTop = 0;
-    for (TypeParameterType type in pendingNullabilities) {
-      type.declaredNullability = null;
-    }
-    for (TypeParameterType type in pendingNullabilities) {
-      if (type.declaredNullability != null) {
-        // Nullability for [type] was already computed on one of the branches
-        // of the depth-first search.  Continue to the next one.
-        continue;
-      }
-      if (type.parameter.bound is TypeParameterType) {
-        TypeParameterType current = type;
-        TypeParameterType next = current.parameter.bound;
-        while (next != null && next.declaredNullability == null) {
-          stack[stackTop++] = current;
-          current.declaredNullability = marker;
-
-          current = next;
-          if (current.parameter.bound is TypeParameterType) {
-            next = current.parameter.bound;
-            if (next.declaredNullability == marker) {
-              next.declaredNullability = Nullability.undetermined;
-              libraryBuilder.addProblem(
-                  templateCycleInTypeVariables.withArguments(
-                      next.parameter.name, current.parameter.name),
-                  next.parameter.fileOffset,
-                  next.parameter.name.length,
-                  next.parameter.location.file);
-              next = null;
-            }
-          } else {
-            next = null;
-          }
-        }
-        current.declaredNullability =
-            TypeParameterType.computeNullabilityFromBound(current.parameter);
-        while (stackTop != 0) {
-          --stackTop;
-          current = stack[stackTop];
-          current.declaredNullability =
-              TypeParameterType.computeNullabilityFromBound(current.parameter);
-        }
-      } else {
-        type.declaredNullability =
-            TypeParameterType.computeNullabilityFromBound(type.parameter);
-      }
-    }
-  }
-
   void applyPatch(covariant TypeVariableBuilder patch) {
     patch.actualOrigin = this;
   }
@@ -242,7 +173,7 @@
     // TODO(dmitryas): Figure out if using [charOffset] here is a good idea.
     // An alternative is to use the offset of the node the cloned type variable
     // is declared on.
-    return new TypeVariableBuilder(name, parent, charOffset,
+    return new TypeVariableBuilder(name, parent, charOffset, fileUri,
         bound: bound.clone(newTypes), variableVariance: variance);
   }
 
diff --git a/pkg/front_end/lib/src/fasta/kernel/body_builder.dart b/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
index 4386b20..c697bac 100644
--- a/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
@@ -5881,8 +5881,8 @@
     debugEvent("beginTypeVariable");
     Identifier name = pop();
     List<Expression> annotations = pop();
-    TypeVariableBuilder variable =
-        new TypeVariableBuilder(name.name, libraryBuilder, name.charOffset);
+    TypeVariableBuilder variable = new TypeVariableBuilder(
+        name.name, libraryBuilder, name.charOffset, uri);
     if (annotations != null) {
       inferAnnotations(variable.parameter, annotations);
       for (Expression annotation in annotations) {
@@ -5952,9 +5952,7 @@
           libraryBuilder.loader.target.objectClassBuilder,
           libraryBuilder.loader.target.dynamicType);
     }
-    TypeVariableBuilder.finishNullabilities(
-        libraryBuilder, libraryBuilder.pendingNullabilities);
-    libraryBuilder.pendingNullabilities.clear();
+    libraryBuilder.processPendingNullabilities();
   }
 
   @override
diff --git a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
index 1a39e6a..ff30dfe 100644
--- a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
@@ -1169,7 +1169,10 @@
     } else {
       bool sentinelInserted = false;
       if (nodeCache.containsKey(node)) {
-        if (nodeCache[node] == null) {
+        bool isRecursiveFunctionCall =
+            node is MethodInvocation || node is StaticInvocation;
+        if (nodeCache[node] == null &&
+            !(enableConstFunctions && isRecursiveFunctionCall)) {
           // recursive call
           return createErrorConstant(node, messageConstEvalCircularity);
         }
diff --git a/pkg/front_end/lib/src/fasta/kernel/type_algorithms.dart b/pkg/front_end/lib/src/fasta/kernel/type_algorithms.dart
index 57f8b57..7aebe35 100644
--- a/pkg/front_end/lib/src/fasta/kernel/type_algorithms.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/type_algorithms.dart
@@ -299,8 +299,8 @@
             variance: Variance.invariant);
         if (bound != variable.bound) {
           TypeVariableBuilder newTypeVariableBuilder = variables[i] =
-              new TypeVariableBuilder(
-                  variable.name, variable.parent, variable.charOffset,
+              new TypeVariableBuilder(variable.name, variable.parent,
+                  variable.charOffset, variable.fileUri,
                   bound: bound);
           unboundTypeVariables.add(newTypeVariableBuilder);
           if (functionTypeUpperSubstitution == null) {
diff --git a/pkg/front_end/lib/src/fasta/source/outline_builder.dart b/pkg/front_end/lib/src/fasta/source/outline_builder.dart
index 4fd5278..e9004a2 100644
--- a/pkg/front_end/lib/src/fasta/source/outline_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/outline_builder.dart
@@ -1843,7 +1843,8 @@
     if (name is ParserRecovery) {
       push(name);
     } else {
-      push(libraryBuilder.addTypeVariable(metadata, name, null, charOffset));
+      push(libraryBuilder.addTypeVariable(
+          metadata, name, null, charOffset, uri));
     }
   }
 
diff --git a/pkg/front_end/lib/src/fasta/source/source_library_builder.dart b/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
index ce5fd71..6a3e269 100644
--- a/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
@@ -203,7 +203,7 @@
   // default nullability of the corresponding type-parameter types.  This list
   // is used to collect such type-parameter types in order to set the
   // nullability after the bounds are built.
-  final List<TypeParameterType> pendingNullabilities = <TypeParameterType>[];
+  final List<PendingNullability> _pendingNullabilities = <PendingNullability>[];
 
   // A library to use for Names generated when compiling code in this library.
   // This allows code generated in one library to use the private namespace of
@@ -2567,9 +2567,9 @@
   }
 
   TypeVariableBuilder addTypeVariable(List<MetadataBuilder> metadata,
-      String name, TypeBuilder bound, int charOffset) {
+      String name, TypeBuilder bound, int charOffset, Uri fileUri) {
     TypeVariableBuilder builder = new TypeVariableBuilder(
-        name, this, charOffset,
+        name, this, charOffset, fileUri,
         bound: bound, metadata: metadata);
     boundlessTypeVariables.add(builder);
     return builder;
@@ -2878,7 +2878,7 @@
     List<TypeVariableBuilder> copy = <TypeVariableBuilder>[];
     for (TypeVariableBuilder variable in original) {
       TypeVariableBuilder newVariable = new TypeVariableBuilder(
-          variable.name, this, variable.charOffset,
+          variable.name, this, variable.charOffset, variable.fileUri,
           bound: variable.bound?.clone(newTypes),
           isExtensionTypeParameter: isExtensionTypeParameter,
           variableVariance:
@@ -2902,12 +2902,82 @@
     }
     boundlessTypeVariables.clear();
 
-    TypeVariableBuilder.finishNullabilities(this, pendingNullabilities);
-    pendingNullabilities.clear();
+    processPendingNullabilities();
 
     return count;
   }
 
+  /// Assigns nullabilities to types in [_pendingNullabilities].
+  ///
+  /// It's a helper function to assign the nullabilities to type-parameter types
+  /// after the corresponding type parameters have their bounds set or changed.
+  /// The function takes into account that some of the types in the input list
+  /// may be bounds to some of the type parameters of other types from the input
+  /// list.
+  void processPendingNullabilities() {
+    // The bounds of type parameters may be type-parameter types of other
+    // parameters from the same declaration.  In this case we need to set the
+    // nullability for them first.  To preserve the ordering, we implement a
+    // depth-first search over the types.  We use the fact that a nullability
+    // of a type parameter type can't ever be 'nullable' if computed from the
+    // bound. It allows us to use 'nullable' nullability as the marker in the
+    // DFS implementation.
+    Nullability marker = Nullability.nullable;
+    List<TypeParameterType> stack =
+        new List<TypeParameterType>.filled(_pendingNullabilities.length, null);
+    int stackTop = 0;
+    for (PendingNullability pendingNullability in _pendingNullabilities) {
+      pendingNullability.type.declaredNullability = null;
+    }
+    for (PendingNullability pendingNullability in _pendingNullabilities) {
+      TypeParameterType type = pendingNullability.type;
+      if (type.declaredNullability != null) {
+        // Nullability for [type] was already computed on one of the branches
+        // of the depth-first search.  Continue to the next one.
+        continue;
+      }
+      if (type.parameter.bound is TypeParameterType) {
+        TypeParameterType current = type;
+        TypeParameterType next = current.parameter.bound;
+        while (next != null && next.declaredNullability == null) {
+          stack[stackTop++] = current;
+          current.declaredNullability = marker;
+
+          current = next;
+          if (current.parameter.bound is TypeParameterType) {
+            next = current.parameter.bound;
+            if (next.declaredNullability == marker) {
+              next.declaredNullability = Nullability.undetermined;
+              current.parameter.bound = const InvalidType();
+              current.parameter.defaultType = const InvalidType();
+              addProblem(
+                  templateCycleInTypeVariables.withArguments(
+                      next.parameter.name, current.parameter.name),
+                  pendingNullability.charOffset,
+                  next.parameter.name.length,
+                  pendingNullability.fileUri);
+              next = null;
+            }
+          } else {
+            next = null;
+          }
+        }
+        current.declaredNullability =
+            TypeParameterType.computeNullabilityFromBound(current.parameter);
+        while (stackTop != 0) {
+          --stackTop;
+          current = stack[stackTop];
+          current.declaredNullability =
+              TypeParameterType.computeNullabilityFromBound(current.parameter);
+        }
+      } else {
+        type.declaredNullability =
+            TypeParameterType.computeNullabilityFromBound(type.parameter);
+      }
+    }
+    _pendingNullabilities.clear();
+  }
+
   int computeVariances() {
     int count = 0;
     for (Builder declaration in libraryDeclaration.members.values) {
@@ -3750,6 +3820,12 @@
           referencesFromIndexed.lookupIndexedClass(name);
     }
   }
+
+  void registerPendingNullability(
+      Uri fileUri, int charOffset, TypeParameterType type) {
+    _pendingNullabilities
+        .add(new PendingNullability(fileUri, charOffset, type));
+  }
 }
 
 // The kind of type parameter scope built by a [TypeParameterScopeBuilder]
@@ -4208,3 +4284,11 @@
     }
   }
 }
+
+class PendingNullability {
+  final Uri fileUri;
+  final int charOffset;
+  final TypeParameterType type;
+
+  PendingNullability(this.fileUri, this.charOffset, this.type);
+}
diff --git a/pkg/front_end/lib/src/fasta/source/source_type_alias_builder.dart b/pkg/front_end/lib/src/fasta/source/source_type_alias_builder.dart
index 6fb5965..1d8e254 100644
--- a/pkg/front_end/lib/src/fasta/source/source_type_alias_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_type_alias_builder.dart
@@ -195,7 +195,8 @@
         // otherwise it's a compiled library loaded from a dill file, and the
         // bounds should have been assigned.
         SourceLibraryBuilder parentLibrary = parent;
-        parentLibrary.pendingNullabilities.add(asTypeArguments[i]);
+        parentLibrary.registerPendingNullability(_typeVariables[i].fileUri,
+            _typeVariables[i].charOffset, asTypeArguments[i]);
       }
     }
     return result;
diff --git a/pkg/front_end/testcases/const_functions/const_functions_recursion.dart b/pkg/front_end/testcases/const_functions/const_functions_recursion.dart
new file mode 100644
index 0000000..73f16fa
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_recursion.dart
@@ -0,0 +1,28 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+// Tests recursive function calls for const functions.
+
+import "package:expect/expect.dart";
+
+const b = fn(4);
+int fn(int a) {
+  if (a == 1) return 1;
+  return a * fn(a - 1);
+}
+
+int localTest() {
+  int fnLocal(int a) {
+    if (a == 1) return 1;
+    return a * fnLocal(a - 1);
+  }
+
+  const c = fnLocal(4);
+  return c;
+}
+
+void main() {
+  Expect.equals(b, 24);
+  Expect.equals(localTest(), 24);
+}
diff --git a/pkg/front_end/testcases/const_functions/const_functions_recursion.dart.strong.expect b/pkg/front_end/testcases/const_functions/const_functions_recursion.dart.strong.expect
new file mode 100644
index 0000000..c53d3dc
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_recursion.dart.strong.expect
@@ -0,0 +1,29 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "package:expect/expect.dart" as exp;
+
+import "package:expect/expect.dart";
+
+static const field core::int b = #C1;
+static method fn(core::int a) → core::int {
+  if(a.{core::num::==}(1))
+    return 1;
+  return a.{core::num::*}(self::fn(a.{core::num::-}(1)));
+}
+static method localTest() → core::int {
+  function fnLocal(core::int a) → core::int {
+    if(a.{core::num::==}(1))
+      return 1;
+    return a.{core::num::*}(fnLocal.call(a.{core::num::-}(1)));
+  }
+  return #C1;
+}
+static method main() → void {
+  exp::Expect::equals(#C1, 24);
+  exp::Expect::equals(self::localTest(), 24);
+}
+
+constants  {
+  #C1 = 24
+}
diff --git a/pkg/front_end/testcases/const_functions/const_functions_recursion.dart.strong.transformed.expect b/pkg/front_end/testcases/const_functions/const_functions_recursion.dart.strong.transformed.expect
new file mode 100644
index 0000000..c53d3dc
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_recursion.dart.strong.transformed.expect
@@ -0,0 +1,29 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "package:expect/expect.dart" as exp;
+
+import "package:expect/expect.dart";
+
+static const field core::int b = #C1;
+static method fn(core::int a) → core::int {
+  if(a.{core::num::==}(1))
+    return 1;
+  return a.{core::num::*}(self::fn(a.{core::num::-}(1)));
+}
+static method localTest() → core::int {
+  function fnLocal(core::int a) → core::int {
+    if(a.{core::num::==}(1))
+      return 1;
+    return a.{core::num::*}(fnLocal.call(a.{core::num::-}(1)));
+  }
+  return #C1;
+}
+static method main() → void {
+  exp::Expect::equals(#C1, 24);
+  exp::Expect::equals(self::localTest(), 24);
+}
+
+constants  {
+  #C1 = 24
+}
diff --git a/pkg/front_end/testcases/const_functions/const_functions_recursion.dart.textual_outline.expect b/pkg/front_end/testcases/const_functions/const_functions_recursion.dart.textual_outline.expect
new file mode 100644
index 0000000..e73f951
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_recursion.dart.textual_outline.expect
@@ -0,0 +1,6 @@
+import "package:expect/expect.dart";
+
+const b = fn(4);
+int fn(int a) {}
+int localTest() {}
+void main() {}
diff --git a/pkg/front_end/testcases/const_functions/const_functions_recursion.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/const_functions/const_functions_recursion.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..e73f951
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_recursion.dart.textual_outline_modelled.expect
@@ -0,0 +1,6 @@
+import "package:expect/expect.dart";
+
+const b = fn(4);
+int fn(int a) {}
+int localTest() {}
+void main() {}
diff --git a/pkg/front_end/testcases/const_functions/const_functions_recursion.dart.weak.expect b/pkg/front_end/testcases/const_functions/const_functions_recursion.dart.weak.expect
new file mode 100644
index 0000000..c53d3dc
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_recursion.dart.weak.expect
@@ -0,0 +1,29 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "package:expect/expect.dart" as exp;
+
+import "package:expect/expect.dart";
+
+static const field core::int b = #C1;
+static method fn(core::int a) → core::int {
+  if(a.{core::num::==}(1))
+    return 1;
+  return a.{core::num::*}(self::fn(a.{core::num::-}(1)));
+}
+static method localTest() → core::int {
+  function fnLocal(core::int a) → core::int {
+    if(a.{core::num::==}(1))
+      return 1;
+    return a.{core::num::*}(fnLocal.call(a.{core::num::-}(1)));
+  }
+  return #C1;
+}
+static method main() → void {
+  exp::Expect::equals(#C1, 24);
+  exp::Expect::equals(self::localTest(), 24);
+}
+
+constants  {
+  #C1 = 24
+}
diff --git a/pkg/front_end/testcases/const_functions/const_functions_recursion.dart.weak.outline.expect b/pkg/front_end/testcases/const_functions/const_functions_recursion.dart.weak.outline.expect
new file mode 100644
index 0000000..e25beae
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_recursion.dart.weak.outline.expect
@@ -0,0 +1,13 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+import "package:expect/expect.dart";
+
+static const field core::int b = self::fn(4);
+static method fn(core::int a) → core::int
+  ;
+static method localTest() → core::int
+  ;
+static method main() → void
+  ;
diff --git a/pkg/front_end/testcases/const_functions/const_functions_recursion.dart.weak.transformed.expect b/pkg/front_end/testcases/const_functions/const_functions_recursion.dart.weak.transformed.expect
new file mode 100644
index 0000000..c53d3dc
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_recursion.dart.weak.transformed.expect
@@ -0,0 +1,29 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "package:expect/expect.dart" as exp;
+
+import "package:expect/expect.dart";
+
+static const field core::int b = #C1;
+static method fn(core::int a) → core::int {
+  if(a.{core::num::==}(1))
+    return 1;
+  return a.{core::num::*}(self::fn(a.{core::num::-}(1)));
+}
+static method localTest() → core::int {
+  function fnLocal(core::int a) → core::int {
+    if(a.{core::num::==}(1))
+      return 1;
+    return a.{core::num::*}(fnLocal.call(a.{core::num::-}(1)));
+  }
+  return #C1;
+}
+static method main() → void {
+  exp::Expect::equals(#C1, 24);
+  exp::Expect::equals(self::localTest(), 24);
+}
+
+constants  {
+  #C1 = 24
+}
diff --git a/pkg/front_end/testcases/general/issue45330.dart b/pkg/front_end/testcases/general/issue45330.dart
new file mode 100644
index 0000000..95ebfd0
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue45330.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+part 'issue45330_lib.dart';
+
+void genericMethod<T>() {}
+
+testInMain() {
+  genericMethod<void Function<T extends T>()>();
+}
+
+main() {}
diff --git a/pkg/front_end/testcases/general/issue45330.dart.textual_outline.expect b/pkg/front_end/testcases/general/issue45330.dart.textual_outline.expect
new file mode 100644
index 0000000..23719be
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue45330.dart.textual_outline.expect
@@ -0,0 +1,5 @@
+part 'issue45330_lib.dart';
+
+void genericMethod<T>() {}
+testInMain() {}
+main() {}
diff --git a/pkg/front_end/testcases/general/issue45330.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/issue45330.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..eb6bf12
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue45330.dart.textual_outline_modelled.expect
@@ -0,0 +1,5 @@
+part 'issue45330_lib.dart';
+
+main() {}
+testInMain() {}
+void genericMethod<T>() {}
diff --git a/pkg/front_end/testcases/general/issue45330.dart.weak.expect b/pkg/front_end/testcases/general/issue45330.dart.weak.expect
new file mode 100644
index 0000000..c315320
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue45330.dart.weak.expect
@@ -0,0 +1,36 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/issue45330.dart:10:31: Error: Type 'T' is a bound of itself via 'T'.
+// Try breaking the cycle by removing at least on of the 'extends' clauses in the cycle.
+//   genericMethod<void Function<T extends T>()>();
+//                               ^
+//
+// pkg/front_end/testcases/general/issue45330.dart:10:3: Error: A generic function type can't be used as a type argument.
+// Try using a non-generic function type.
+//   genericMethod<void Function<T extends T>()>();
+//   ^
+//
+// pkg/front_end/testcases/general/issue45330_lib.dart:35:31: Error: Type 'T' is a bound of itself via 'T'.
+// Try breaking the cycle by removing at least on of the 'extends' clauses in the cycle.
+//   genericMethod<void Function<T extends T>()>();
+//                               ^
+//
+// pkg/front_end/testcases/general/issue45330_lib.dart:35:3: Error: A generic function type can't be used as a type argument.
+// Try using a non-generic function type.
+//   genericMethod<void Function<T extends T>()>();
+//   ^
+//
+import self as self;
+import "dart:core" as core;
+
+part issue45330_lib.dart;
+static method genericMethod<T extends core::Object? = dynamic>() → void {}
+static method testInMain() → dynamic {
+  self::genericMethod<<T extends invalid-type = invalid-type>() → void>();
+}
+static method main() → dynamic {}
+static method /* from org-dartlang-testcase:///issue45330_lib.dart */ testInPart() → dynamic {
+  self::genericMethod<<T extends invalid-type = invalid-type>() → void>();
+}
diff --git a/pkg/front_end/testcases/general/issue45330.dart.weak.outline.expect b/pkg/front_end/testcases/general/issue45330.dart.weak.outline.expect
new file mode 100644
index 0000000..fc3746d
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue45330.dart.weak.outline.expect
@@ -0,0 +1,13 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+part issue45330_lib.dart;
+static method genericMethod<T extends core::Object? = dynamic>() → void
+  ;
+static method testInMain() → dynamic
+  ;
+static method main() → dynamic
+  ;
+static method /* from org-dartlang-testcase:///issue45330_lib.dart */ testInPart() → dynamic
+  ;
diff --git a/pkg/front_end/testcases/general/issue45330.dart.weak.transformed.expect b/pkg/front_end/testcases/general/issue45330.dart.weak.transformed.expect
new file mode 100644
index 0000000..c315320
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue45330.dart.weak.transformed.expect
@@ -0,0 +1,36 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/issue45330.dart:10:31: Error: Type 'T' is a bound of itself via 'T'.
+// Try breaking the cycle by removing at least on of the 'extends' clauses in the cycle.
+//   genericMethod<void Function<T extends T>()>();
+//                               ^
+//
+// pkg/front_end/testcases/general/issue45330.dart:10:3: Error: A generic function type can't be used as a type argument.
+// Try using a non-generic function type.
+//   genericMethod<void Function<T extends T>()>();
+//   ^
+//
+// pkg/front_end/testcases/general/issue45330_lib.dart:35:31: Error: Type 'T' is a bound of itself via 'T'.
+// Try breaking the cycle by removing at least on of the 'extends' clauses in the cycle.
+//   genericMethod<void Function<T extends T>()>();
+//                               ^
+//
+// pkg/front_end/testcases/general/issue45330_lib.dart:35:3: Error: A generic function type can't be used as a type argument.
+// Try using a non-generic function type.
+//   genericMethod<void Function<T extends T>()>();
+//   ^
+//
+import self as self;
+import "dart:core" as core;
+
+part issue45330_lib.dart;
+static method genericMethod<T extends core::Object? = dynamic>() → void {}
+static method testInMain() → dynamic {
+  self::genericMethod<<T extends invalid-type = invalid-type>() → void>();
+}
+static method main() → dynamic {}
+static method /* from org-dartlang-testcase:///issue45330_lib.dart */ testInPart() → dynamic {
+  self::genericMethod<<T extends invalid-type = invalid-type>() → void>();
+}
diff --git a/pkg/front_end/testcases/general/issue45330_lib.dart b/pkg/front_end/testcases/general/issue45330_lib.dart
new file mode 100644
index 0000000..1772937
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue45330_lib.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+
+part of 'issue45330.dart';
+
+testInPart() {
+  genericMethod<void Function<T extends T>()>();
+}
diff --git a/pkg/front_end/testcases/generic_metadata/issue45330.dart b/pkg/front_end/testcases/generic_metadata/issue45330.dart
new file mode 100644
index 0000000..95ebfd0
--- /dev/null
+++ b/pkg/front_end/testcases/generic_metadata/issue45330.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+part 'issue45330_lib.dart';
+
+void genericMethod<T>() {}
+
+testInMain() {
+  genericMethod<void Function<T extends T>()>();
+}
+
+main() {}
diff --git a/pkg/front_end/testcases/generic_metadata/issue45330.dart.strong.expect b/pkg/front_end/testcases/generic_metadata/issue45330.dart.strong.expect
new file mode 100644
index 0000000..bf8ebf6
--- /dev/null
+++ b/pkg/front_end/testcases/generic_metadata/issue45330.dart.strong.expect
@@ -0,0 +1,26 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/generic_metadata/issue45330.dart:10:31: Error: Type 'T' is a bound of itself via 'T'.
+// Try breaking the cycle by removing at least on of the 'extends' clauses in the cycle.
+//   genericMethod<void Function<T extends T>()>();
+//                               ^
+//
+// pkg/front_end/testcases/generic_metadata/issue45330_lib.dart:35:31: Error: Type 'T' is a bound of itself via 'T'.
+// Try breaking the cycle by removing at least on of the 'extends' clauses in the cycle.
+//   genericMethod<void Function<T extends T>()>();
+//                               ^
+//
+import self as self;
+import "dart:core" as core;
+
+part issue45330_lib.dart;
+static method genericMethod<T extends core::Object? = dynamic>() → void {}
+static method testInMain() → dynamic {
+  self::genericMethod<<T extends invalid-type = invalid-type>() → void>();
+}
+static method main() → dynamic {}
+static method /* from org-dartlang-testcase:///issue45330_lib.dart */ testInPart() → dynamic {
+  self::genericMethod<<T extends invalid-type = invalid-type>() → void>();
+}
diff --git a/pkg/front_end/testcases/generic_metadata/issue45330.dart.strong.transformed.expect b/pkg/front_end/testcases/generic_metadata/issue45330.dart.strong.transformed.expect
new file mode 100644
index 0000000..bf8ebf6
--- /dev/null
+++ b/pkg/front_end/testcases/generic_metadata/issue45330.dart.strong.transformed.expect
@@ -0,0 +1,26 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/generic_metadata/issue45330.dart:10:31: Error: Type 'T' is a bound of itself via 'T'.
+// Try breaking the cycle by removing at least on of the 'extends' clauses in the cycle.
+//   genericMethod<void Function<T extends T>()>();
+//                               ^
+//
+// pkg/front_end/testcases/generic_metadata/issue45330_lib.dart:35:31: Error: Type 'T' is a bound of itself via 'T'.
+// Try breaking the cycle by removing at least on of the 'extends' clauses in the cycle.
+//   genericMethod<void Function<T extends T>()>();
+//                               ^
+//
+import self as self;
+import "dart:core" as core;
+
+part issue45330_lib.dart;
+static method genericMethod<T extends core::Object? = dynamic>() → void {}
+static method testInMain() → dynamic {
+  self::genericMethod<<T extends invalid-type = invalid-type>() → void>();
+}
+static method main() → dynamic {}
+static method /* from org-dartlang-testcase:///issue45330_lib.dart */ testInPart() → dynamic {
+  self::genericMethod<<T extends invalid-type = invalid-type>() → void>();
+}
diff --git a/pkg/front_end/testcases/generic_metadata/issue45330.dart.textual_outline.expect b/pkg/front_end/testcases/generic_metadata/issue45330.dart.textual_outline.expect
new file mode 100644
index 0000000..23719be
--- /dev/null
+++ b/pkg/front_end/testcases/generic_metadata/issue45330.dart.textual_outline.expect
@@ -0,0 +1,5 @@
+part 'issue45330_lib.dart';
+
+void genericMethod<T>() {}
+testInMain() {}
+main() {}
diff --git a/pkg/front_end/testcases/generic_metadata/issue45330.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/generic_metadata/issue45330.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..eb6bf12
--- /dev/null
+++ b/pkg/front_end/testcases/generic_metadata/issue45330.dart.textual_outline_modelled.expect
@@ -0,0 +1,5 @@
+part 'issue45330_lib.dart';
+
+main() {}
+testInMain() {}
+void genericMethod<T>() {}
diff --git a/pkg/front_end/testcases/generic_metadata/issue45330.dart.weak.expect b/pkg/front_end/testcases/generic_metadata/issue45330.dart.weak.expect
new file mode 100644
index 0000000..bf8ebf6
--- /dev/null
+++ b/pkg/front_end/testcases/generic_metadata/issue45330.dart.weak.expect
@@ -0,0 +1,26 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/generic_metadata/issue45330.dart:10:31: Error: Type 'T' is a bound of itself via 'T'.
+// Try breaking the cycle by removing at least on of the 'extends' clauses in the cycle.
+//   genericMethod<void Function<T extends T>()>();
+//                               ^
+//
+// pkg/front_end/testcases/generic_metadata/issue45330_lib.dart:35:31: Error: Type 'T' is a bound of itself via 'T'.
+// Try breaking the cycle by removing at least on of the 'extends' clauses in the cycle.
+//   genericMethod<void Function<T extends T>()>();
+//                               ^
+//
+import self as self;
+import "dart:core" as core;
+
+part issue45330_lib.dart;
+static method genericMethod<T extends core::Object? = dynamic>() → void {}
+static method testInMain() → dynamic {
+  self::genericMethod<<T extends invalid-type = invalid-type>() → void>();
+}
+static method main() → dynamic {}
+static method /* from org-dartlang-testcase:///issue45330_lib.dart */ testInPart() → dynamic {
+  self::genericMethod<<T extends invalid-type = invalid-type>() → void>();
+}
diff --git a/pkg/front_end/testcases/generic_metadata/issue45330.dart.weak.outline.expect b/pkg/front_end/testcases/generic_metadata/issue45330.dart.weak.outline.expect
new file mode 100644
index 0000000..fc3746d
--- /dev/null
+++ b/pkg/front_end/testcases/generic_metadata/issue45330.dart.weak.outline.expect
@@ -0,0 +1,13 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+part issue45330_lib.dart;
+static method genericMethod<T extends core::Object? = dynamic>() → void
+  ;
+static method testInMain() → dynamic
+  ;
+static method main() → dynamic
+  ;
+static method /* from org-dartlang-testcase:///issue45330_lib.dart */ testInPart() → dynamic
+  ;
diff --git a/pkg/front_end/testcases/generic_metadata/issue45330.dart.weak.transformed.expect b/pkg/front_end/testcases/generic_metadata/issue45330.dart.weak.transformed.expect
new file mode 100644
index 0000000..bf8ebf6
--- /dev/null
+++ b/pkg/front_end/testcases/generic_metadata/issue45330.dart.weak.transformed.expect
@@ -0,0 +1,26 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/generic_metadata/issue45330.dart:10:31: Error: Type 'T' is a bound of itself via 'T'.
+// Try breaking the cycle by removing at least on of the 'extends' clauses in the cycle.
+//   genericMethod<void Function<T extends T>()>();
+//                               ^
+//
+// pkg/front_end/testcases/generic_metadata/issue45330_lib.dart:35:31: Error: Type 'T' is a bound of itself via 'T'.
+// Try breaking the cycle by removing at least on of the 'extends' clauses in the cycle.
+//   genericMethod<void Function<T extends T>()>();
+//                               ^
+//
+import self as self;
+import "dart:core" as core;
+
+part issue45330_lib.dart;
+static method genericMethod<T extends core::Object? = dynamic>() → void {}
+static method testInMain() → dynamic {
+  self::genericMethod<<T extends invalid-type = invalid-type>() → void>();
+}
+static method main() → dynamic {}
+static method /* from org-dartlang-testcase:///issue45330_lib.dart */ testInPart() → dynamic {
+  self::genericMethod<<T extends invalid-type = invalid-type>() → void>();
+}
diff --git a/pkg/front_end/testcases/generic_metadata/issue45330_lib.dart b/pkg/front_end/testcases/generic_metadata/issue45330_lib.dart
new file mode 100644
index 0000000..1772937
--- /dev/null
+++ b/pkg/front_end/testcases/generic_metadata/issue45330_lib.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+// Long comment to increase error offset.
+
+part of 'issue45330.dart';
+
+testInPart() {
+  genericMethod<void Function<T extends T>()>();
+}
diff --git a/runtime/observatory/tests/service/break_on_function_child_isolate_test.dart b/runtime/observatory/tests/service/break_on_function_child_isolate_test.dart
index d30310a..bd471ac 100644
--- a/runtime/observatory/tests/service/break_on_function_child_isolate_test.dart
+++ b/runtime/observatory/tests/service/break_on_function_child_isolate_test.dart
@@ -2,83 +2,9 @@
 // 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.
 // VMOptions=--verbose_debug --enable-isolate-groups --experimental-enable-isolate-groups-jit
-import 'dart:async';
-import 'dart:isolate' as dart_isolate;
 
-import 'package:observatory/service_io.dart';
-import 'package:test/test.dart';
-import 'service_test_common.dart';
-import 'test_helper.dart';
-import 'dart:developer';
-
-const int LINE_A = 18;
-const int LINE_B = 25;
-const int LINE_C = 29;
-
-foo(args) { // LINE_A
-  final dart_isolate.SendPort sendPort = args[0] as dart_isolate.SendPort;
-  sendPort.send('reply from foo');
-}
-
-testMain() async {
-  final rpResponse = dart_isolate.ReceivePort();
-  debugger(); // LINE_B
-  await dart_isolate.Isolate.spawn(foo, [rpResponse.sendPort]);
-  await rpResponse.first;
-  rpResponse.close();
-  debugger(); // LINE_C
-}
-
-final completerAtFoo = Completer();
-
-final tests = <IsolateTest>[
-  hasPausedAtStart,
-  resumeIsolate,
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_B + 1),
-  (Isolate isolate) async {
-    // Set up a listener to wait for child isolate launch and breakpoint events.
-    final stream = await isolate.vm.getEventStream(VM.kDebugStream);
-    var childIsolate;
-    var subscription;
-    subscription = stream.listen((ServiceEvent event) async {
-      switch (event.kind) {
-        case ServiceEvent.kPauseStart:
-          childIsolate = event.isolate!;
-          await childIsolate.reload();
-
-          Library rootLib = await childIsolate.rootLibrary.load() as Library;
-          final foo = rootLib.functions.singleWhere((f) => f.name == 'foo');
-          final bpt = await childIsolate.addBreakpointAtEntry(foo);
-
-          expect(bpt is Breakpoint, isTrue);
-          childIsolate.resume();
-          break;
-        case ServiceEvent.kPauseBreakpoint:
-          if (childIsolate == event.isolate) {
-            ServiceMap stack = await childIsolate.getStack();
-            Frame top = stack['frames'][0];
-            Script script = await top.location!.script.load() as Script;
-            expect(script.tokenToLine(top.location!.tokenPos), equals(LINE_A));
-
-            childIsolate.resume();
-            subscription.cancel();
-            completerAtFoo.complete();
-          }
-          break;
-      }
-    });
-  },
-  resumeIsolate,
-  (Isolate isolate) async {
-    await completerAtFoo.future;
-  },
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_C + 1),
-  resumeIsolate,
-];
+import 'break_on_function_many_child_isolates_test.dart';
 
 main(args) async {
-  runIsolateTests(args, tests,
-      testeeConcurrent: testMain, pause_on_start: true);
+  await runIsolateBreakpointPauseTest(args, /*nIsolates=*/ 1);
 }
diff --git a/runtime/observatory/tests/service/break_on_function_many_child_isolates_test.dart b/runtime/observatory/tests/service/break_on_function_many_child_isolates_test.dart
new file mode 100644
index 0000000..916e6f9
--- /dev/null
+++ b/runtime/observatory/tests/service/break_on_function_many_child_isolates_test.dart
@@ -0,0 +1,113 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// 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.
+// VMOptions=--verbose_debug --enable-isolate-groups --experimental-enable-isolate-groups-jit
+//
+// Tests breakpoint pausing and resuming with many isolates running and pausing
+// simultaneously.
+
+import 'dart:async';
+import 'dart:developer';
+import 'dart:isolate' as dart_isolate;
+
+import 'package:observatory/service_io.dart';
+import 'package:test/test.dart';
+
+import 'service_test_common.dart';
+import 'test_helper.dart';
+
+const int LINE_A = 23;
+const int LINE_B = 35;
+const int LINE_C = 41;
+
+foo(args) { // LINE_A
+  print('${dart_isolate.Isolate.current.debugName}: $args');
+  final sendPort = args[0] as dart_isolate.SendPort;
+  final int i = args[1] as int;
+  sendPort.send('reply from foo: $i');
+}
+
+int nIsolates = -1;
+
+testMain() async {
+  final rps = List<dart_isolate.ReceivePort>.generate(
+      nIsolates, (i) => dart_isolate.ReceivePort());
+  debugger(); // LINE_B
+  for (int i = 0; i < nIsolates; i++) {
+    await dart_isolate.Isolate.spawn(foo, [rps[i].sendPort, i],
+        debugName: "foo$i");
+  }
+  print(await Future.wait(rps.map((rp) => rp.first)));
+  debugger(); // LINE_C
+}
+
+final completerAtFoo = List<Completer>.generate(nIsolates, (_) => Completer());
+int completerCount = 0;
+
+final tests = <IsolateTest>[
+  hasPausedAtStart,
+  resumeIsolate,
+  hasStoppedAtBreakpoint,
+  stoppedAtLine(LINE_B + 1),
+  (Isolate isolate) async {
+    // Set up a listener to wait for child isolate launch and breakpoint events.
+    final stream = await isolate.vm.getEventStream(VM.kDebugStream);
+    var subscription;
+    subscription = stream.listen((ServiceEvent event) async {
+      switch (event.kind) {
+        case ServiceEvent.kPauseStart:
+          final childIsolate = event.isolate!;
+          await childIsolate.reload();
+
+          for (Library lib in childIsolate.libraries) {
+            await lib.load();
+            if (lib.uri!
+                .endsWith('break_on_function_many_child_isolates_test.dart')) {
+              final foo = lib.functions.singleWhere((f) => f.name == 'foo');
+              final bpt = await childIsolate.addBreakpointAtEntry(foo);
+
+              expect(bpt is Breakpoint, isTrue);
+              break;
+            }
+          }
+          childIsolate.resume();
+          break;
+        case ServiceEvent.kPauseBreakpoint:
+          final name = event.isolate!.name!;
+          if (!name.startsWith('foo')) {
+            break;
+          }
+          final childIsolate = event.isolate;
+          final ndx = int.parse(name.substring('foo'.length));
+          final stack = await childIsolate!.getStack();
+          final top = stack['frames'][0];
+          final script = await top.location.script.load() as Script;
+          expect(script.tokenToLine(top.location.tokenPos), equals(LINE_A));
+
+          childIsolate.resume();
+          if ((++completerCount) == nIsolates) {
+            subscription.cancel();
+          }
+          completerAtFoo[ndx].complete();
+          break;
+      }
+    });
+  },
+  resumeIsolate,
+  (Isolate isolate) async {
+    await Future.wait(completerAtFoo.map((c) => c.future));
+  },
+  hasStoppedAtBreakpoint,
+  stoppedAtLine(LINE_C + 1),
+  resumeIsolate,
+];
+
+Future runIsolateBreakpointPauseTest(args, nIsolates_) {
+  nIsolates = nIsolates_;
+  return runIsolateTests(args, tests,
+      testeeConcurrent: testMain, pause_on_start: true);
+}
+
+main(args) async {
+  await runIsolateBreakpointPauseTest(args, /*nIsolates=*/ 30);
+}
diff --git a/runtime/observatory/tests/service/service_kernel.status b/runtime/observatory/tests/service/service_kernel.status
index 600d2df..1a1d71d 100644
--- a/runtime/observatory/tests/service/service_kernel.status
+++ b/runtime/observatory/tests/service/service_kernel.status
@@ -59,6 +59,7 @@
 break_on_async_function_test: SkipByDesign # Debugger is disabled in AOT mode.
 break_on_default_constructor_test: SkipByDesign # Debugger is disabled in AOT mode.
 break_on_function_child_isolate_test: SkipByDesign # Debugger is disabled in AOT mode.
+break_on_function_many_child_isolates_test: SkipByDesign # Debugger is disabled in AOT mode.
 break_on_function_test: SkipByDesign # Debugger is disabled in AOT mode.
 breakpoint_async_break_test: SkipByDesign # Debugger is disabled in AOT mode.
 breakpoint_in_package_parts_class_file_uri_test: SkipByDesign # Debugger is disabled in AOT mode.
diff --git a/runtime/observatory_2/tests/service_2/break_on_function_child_isolate_test.dart b/runtime/observatory_2/tests/service_2/break_on_function_child_isolate_test.dart
new file mode 100644
index 0000000..bd471ac
--- /dev/null
+++ b/runtime/observatory_2/tests/service_2/break_on_function_child_isolate_test.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// 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.
+// VMOptions=--verbose_debug --enable-isolate-groups --experimental-enable-isolate-groups-jit
+
+import 'break_on_function_many_child_isolates_test.dart';
+
+main(args) async {
+  await runIsolateBreakpointPauseTest(args, /*nIsolates=*/ 1);
+}
diff --git a/runtime/observatory_2/tests/service_2/break_on_function_many_child_isolates_test.dart b/runtime/observatory_2/tests/service_2/break_on_function_many_child_isolates_test.dart
new file mode 100644
index 0000000..b527cb54
--- /dev/null
+++ b/runtime/observatory_2/tests/service_2/break_on_function_many_child_isolates_test.dart
@@ -0,0 +1,113 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// 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.
+// VMOptions=--verbose_debug --enable-isolate-groups --experimental-enable-isolate-groups-jit
+//
+// Tests breakpoint pausing and resuming with many isolates running and pausing
+// simultaneously.
+
+import 'dart:async';
+import 'dart:developer';
+import 'dart:isolate' as dart_isolate;
+
+import 'package:observatory_2/service_io.dart';
+import 'package:test/test.dart';
+
+import 'service_test_common.dart';
+import 'test_helper.dart';
+
+const int LINE_A = 23;
+const int LINE_B = 35;
+const int LINE_C = 41;
+
+foo(args) { // LINE_A
+  print('${dart_isolate.Isolate.current.debugName}: $args');
+  final sendPort = args[0] as dart_isolate.SendPort;
+  final int i = args[1] as int;
+  sendPort.send('reply from foo: $i');
+}
+
+int nIsolates = -1;
+
+testMain() async {
+  final rps = List<dart_isolate.ReceivePort>.generate(
+      nIsolates, (i) => dart_isolate.ReceivePort());
+  debugger(); // LINE_B
+  for (int i = 0; i < nIsolates; i++) {
+    await dart_isolate.Isolate.spawn(foo, [rps[i].sendPort, i],
+        debugName: "foo$i");
+  }
+  print(await Future.wait(rps.map((rp) => rp.first)));
+  debugger(); // LINE_C
+}
+
+final completerAtFoo = List<Completer>.generate(nIsolates, (_) => Completer());
+int completerCount = 0;
+
+final tests = <IsolateTest>[
+  hasPausedAtStart,
+  resumeIsolate,
+  hasStoppedAtBreakpoint,
+  stoppedAtLine(LINE_B + 1),
+  (Isolate isolate) async {
+    // Set up a listener to wait for child isolate launch and breakpoint events.
+    final stream = await isolate.vm.getEventStream(VM.kDebugStream);
+    var subscription;
+    subscription = stream.listen((ServiceEvent event) async {
+      switch (event.kind) {
+        case ServiceEvent.kPauseStart:
+          final childIsolate = event.isolate;
+          await childIsolate.reload();
+
+          for (Library lib in childIsolate.libraries) {
+            await lib.load();
+            if (lib.uri
+                .endsWith('break_on_function_many_child_isolates_test.dart')) {
+              final foo = lib.functions.singleWhere((f) => f.name == 'foo');
+              final bpt = await childIsolate.addBreakpointAtEntry(foo);
+
+              expect(bpt is Breakpoint, isTrue);
+              break;
+            }
+          }
+          childIsolate.resume();
+          break;
+        case ServiceEvent.kPauseBreakpoint:
+          final name = event.isolate.name;
+          if (!name.startsWith('foo')) {
+            break;
+          }
+          final childIsolate = event.isolate;
+          final ndx = int.parse(name.substring('foo'.length));
+          final stack = await childIsolate.getStack();
+          final top = stack['frames'][0];
+          final script = await top.location.script.load() as Script;
+          expect(script.tokenToLine(top.location.tokenPos), equals(LINE_A));
+
+          childIsolate.resume();
+          if ((++completerCount) == nIsolates) {
+            subscription.cancel();
+          }
+          completerAtFoo[ndx].complete();
+          break;
+      }
+    });
+  },
+  resumeIsolate,
+  (Isolate isolate) async {
+    await Future.wait(completerAtFoo.map((c) => c.future));
+  },
+  hasStoppedAtBreakpoint,
+  stoppedAtLine(LINE_C + 1),
+  resumeIsolate,
+];
+
+Future runIsolateBreakpointPauseTest(args, nIsolates_) {
+  nIsolates = nIsolates_;
+  return runIsolateTests(args, tests,
+      testeeConcurrent: testMain, pause_on_start: true);
+}
+
+main(args) async {
+  await runIsolateBreakpointPauseTest(args, /*nIsolates=*/ 30);
+}
diff --git a/runtime/observatory_2/tests/service_2/service_2_kernel.status b/runtime/observatory_2/tests/service_2/service_2_kernel.status
index a04fc9c..0c4696e 100644
--- a/runtime/observatory_2/tests/service_2/service_2_kernel.status
+++ b/runtime/observatory_2/tests/service_2/service_2_kernel.status
@@ -59,6 +59,7 @@
 break_on_async_function_test: SkipByDesign # Debugger is disabled in AOT mode.
 break_on_default_constructor_test: SkipByDesign # Debugger is disabled in AOT mode.
 break_on_function_child_isolate_test: SkipByDesign # Debugger is disabled in AOT mode.
+break_on_function_many_child_isolates_test: SkipByDesign # Debugger is disabled in AOT mode.
 break_on_function_test: SkipByDesign # Debugger is disabled in AOT mode.
 breakpoint_async_break_test: SkipByDesign # Debugger is disabled in AOT mode.
 breakpoint_in_package_parts_class_file_uri_test: SkipByDesign # Debugger is disabled in AOT mode.
diff --git a/runtime/vm/compiler/jit/compiler.cc b/runtime/vm/compiler/jit/compiler.cc
index 4ca1604..9b5c55e 100644
--- a/runtime/vm/compiler/jit/compiler.cc
+++ b/runtime/vm/compiler/jit/compiler.cc
@@ -603,6 +603,23 @@
         auto install_code_fun = [&]() {
           *result =
               FinalizeCompilation(&assembler, &graph_compiler, flow_graph);
+#if !defined(PRODUCT)
+          // Isolate debuggers need to be notified of compiled function right
+          // away as code is installed because there might be latent breakpoints
+          // in compiled function, which have to be activated before functions
+          // code is executed. Otherwise concurrently running isolates might
+          // execute code before its patched and miss a need to pause at a
+          // breakpoint.
+          if (!result->IsNull()) {
+            if (!function.HasOptimizedCode()) {
+              thread()->isolate_group()->ForEachIsolate(
+                  [&](Isolate* isolate) {
+                    isolate->debugger()->NotifyCompilation(function);
+                  },
+                  /*at_safepoint=*/true);
+            }
+          }
+#endif
         };
 
         // Grab write program_lock outside of potential safepoint, that lock
@@ -632,15 +649,6 @@
         // Must be called outside of safepoint.
         Code::NotifyCodeObservers(function, *result, optimized());
 
-#if !defined(PRODUCT)
-        if (!function.HasOptimizedCode()) {
-          // TODO(dartbug.com/36097): We might need to adjust this once we start
-          // adding debugging support to --enable-isolate-groups.
-          thread()->isolate_group()->ForEachIsolate([&](Isolate* isolate) {
-            isolate->debugger()->NotifyCompilation(function);
-          });
-        }
-#endif
         if (FLAG_disassemble && FlowGraphPrinter::ShouldPrint(function)) {
           Disassembler::DisassembleCode(function, *result, optimized());
         } else if (FLAG_disassemble_optimized && optimized() &&
diff --git a/runtime/vm/debugger.cc b/runtime/vm/debugger.cc
index 643e324..47dc4cd 100644
--- a/runtime/vm/debugger.cc
+++ b/runtime/vm/debugger.cc
@@ -1455,7 +1455,7 @@
       token_pos_(token_pos),
       pc_(pc),
       line_number_(-1),
-      is_enabled_(false),
+      enabled_count_(0),
       next_(NULL),
       breakpoint_kind_(kind),
       saved_value_(Code::null()) {
@@ -1501,17 +1501,17 @@
 }
 
 void CodeBreakpoint::Enable() {
-  if (!is_enabled_) {
+  if (enabled_count_ == 0) {
     PatchCode();
   }
-  ASSERT(is_enabled_);
+  ++enabled_count_;
 }
 
 void CodeBreakpoint::Disable() {
-  if (is_enabled_) {
+  if (enabled_count_ == 1) {
     RestoreCode();
   }
-  ASSERT(!is_enabled_);
+  --enabled_count_;
 }
 
 bool CodeBreakpoint::HasBreakpointLocation(
@@ -1599,13 +1599,13 @@
   }
   while (breakpoint_locations_ != nullptr) {
     BreakpointLocation* loc = breakpoint_locations_;
-    group_debugger()->DisableCodeBreakpointsFor(loc);
+    group_debugger()->UnlinkCodeBreakpoints(loc);
     breakpoint_locations_ = breakpoint_locations_->next();
     delete loc;
   }
   while (latent_locations_ != nullptr) {
     BreakpointLocation* loc = latent_locations_;
-    group_debugger()->DisableCodeBreakpointsFor(loc);
+    group_debugger()->UnlinkCodeBreakpoints(loc);
     latent_locations_ = latent_locations_->next();
     delete loc;
   }
@@ -2510,29 +2510,26 @@
   }
   if (lowest_pc_offset != kUwordMax) {
     uword lowest_pc = code.PayloadStart() + lowest_pc_offset;
+    SafepointWriteRwLocker sl(Thread::Current(), code_breakpoints_lock());
     CodeBreakpoint* code_bpt = GetCodeBreakpoint(lowest_pc);
     if (code_bpt == nullptr) {
-      SafepointWriteRwLocker sl(Thread::Current(), code_breakpoints_lock());
-      code_bpt = GetCodeBreakpoint(lowest_pc);
-      if (code_bpt == nullptr) {
-        // No code breakpoint for this code exists; create one.
-        code_bpt =
-            new CodeBreakpoint(code, loc->token_pos_, lowest_pc, lowest_kind);
-        if (FLAG_verbose_debug) {
-          OS::PrintErr("Setting code breakpoint at pos %s pc %#" Px
-                       " offset %#" Px "\n",
-                       loc->token_pos_.ToCString(), lowest_pc,
-                       lowest_pc - code.PayloadStart());
-        }
-        RegisterCodeBreakpoint(code_bpt);
-      } else {
-        if (FLAG_verbose_debug) {
-          OS::PrintErr(
-              "Adding location to existing code breakpoint at pos %s pc %#" Px
-              " offset %#" Px "\n",
-              loc->token_pos_.ToCString(), lowest_pc,
-              lowest_pc - code.PayloadStart());
-        }
+      // No code breakpoint for this code exists; create one.
+      code_bpt =
+          new CodeBreakpoint(code, loc->token_pos_, lowest_pc, lowest_kind);
+      if (FLAG_verbose_debug) {
+        OS::PrintErr("Setting code breakpoint at pos %s pc %#" Px
+                     " offset %#" Px "\n",
+                     loc->token_pos_.ToCString(), lowest_pc,
+                     lowest_pc - code.PayloadStart());
+      }
+      RegisterCodeBreakpoint(code_bpt);
+    } else {
+      if (FLAG_verbose_debug) {
+        OS::PrintErr(
+            "Adding location to existing code breakpoint at pos %s pc %#" Px
+            " offset %#" Px "\n",
+            loc->token_pos_.ToCString(), lowest_pc,
+            lowest_pc - code.PayloadStart());
       }
     }
     code_bpt->AddBreakpointLocation(loc);
@@ -2913,7 +2910,7 @@
 void GroupDebugger::SyncBreakpointLocation(BreakpointLocation* loc) {
   bool any_enabled = loc->AnyEnabled();
 
-  SafepointReadRwLocker sl(Thread::Current(), code_breakpoints_lock());
+  SafepointWriteRwLocker sl(Thread::Current(), code_breakpoints_lock());
   CodeBreakpoint* cbpt = code_breakpoints_;
   while (cbpt != NULL) {
     if (cbpt->HasBreakpointLocation(loc)) {
@@ -2927,17 +2924,6 @@
   }
 }
 
-void GroupDebugger::DisableCodeBreakpointsFor(BreakpointLocation* location) {
-  SafepointReadRwLocker sl(Thread::Current(), code_breakpoints_lock());
-  CodeBreakpoint* cbpt = code_breakpoints_;
-  while (cbpt != nullptr) {
-    if (cbpt->HasBreakpointLocation(location)) {
-      cbpt->Disable();
-    }
-    cbpt = cbpt->next();
-  }
-}
-
 Breakpoint* Debugger::SetBreakpointAtEntry(const Function& target_function,
                                            bool single_shot) {
   ASSERT(!target_function.IsNull());
@@ -4174,6 +4160,7 @@
 // TODO(hausner): Could potentially make this faster by checking
 // whether the call target at pc is a debugger stub.
 bool GroupDebugger::HasActiveBreakpoint(uword pc) {
+  SafepointReadRwLocker sl(Thread::Current(), code_breakpoints_lock());
   CodeBreakpoint* cbpt = GetCodeBreakpoint(pc);
   return (cbpt != nullptr) && (cbpt->IsEnabled());
 }
@@ -4213,6 +4200,7 @@
 }
 
 CodePtr GroupDebugger::GetPatchedStubAddress(uword breakpoint_address) {
+  SafepointReadRwLocker sl(Thread::Current(), code_breakpoints_lock());
   CodeBreakpoint* cbpt = GetCodeBreakpoint(breakpoint_address);
   if (cbpt != NULL) {
     return cbpt->OrigStubAddress();
@@ -4305,14 +4293,12 @@
 // should be hit before it gets deleted.
 void GroupDebugger::UnlinkCodeBreakpoints(BreakpointLocation* bpt_location) {
   ASSERT(bpt_location != nullptr);
-  SafepointReadRwLocker sl(Thread::Current(), code_breakpoints_lock());
+  SafepointWriteRwLocker sl(Thread::Current(), code_breakpoints_lock());
   CodeBreakpoint* curr_bpt = code_breakpoints_;
   while (curr_bpt != nullptr) {
     if (curr_bpt->FindAndDeleteBreakpointLocation(bpt_location)) {
-      if (curr_bpt->HasNoBreakpointLocations()) {
-        curr_bpt->Disable();
-        needs_breakpoint_cleanup_ = true;
-      }
+      curr_bpt->Disable();
+      needs_breakpoint_cleanup_ = true;
     }
     curr_bpt = curr_bpt->next();
   }
diff --git a/runtime/vm/debugger.h b/runtime/vm/debugger.h
index c833e5d..ff044ed 100644
--- a/runtime/vm/debugger.h
+++ b/runtime/vm/debugger.h
@@ -203,6 +203,12 @@
 // There may be more than one BreakpointLocation associated with CodeBreakpoint,
 // one for for every isolate in a group that sets a breakpoint at particular
 // code location represented by the CodeBreakpoint.
+// Each BreakpointLocation might be enabled/disabled based on whether it has
+// any actual breakpoints associated with it.
+// The CodeBreakpoint is enabled if it has any such BreakpointLocations
+// associated with it.
+// The class is not thread-safe - users of this class need to ensure the access
+// is synchronized, guarded by mutexes.
 class CodeBreakpoint {
  public:
   CodeBreakpoint(const Code& code,
@@ -226,7 +232,7 @@
 
   void Enable();
   void Disable();
-  bool IsEnabled() const { return is_enabled_; }
+  bool IsEnabled() const { return enabled_count_ > 0; }
 
   CodePtr OrigStubAddress() const;
 
@@ -248,7 +254,7 @@
   TokenPosition token_pos_;
   uword pc_;
   intptr_t line_number_;
-  bool is_enabled_;
+  int enabled_count_;  // incremented for every enabled breakpoint location
 
   // Breakpoint locations from different debuggers/isolates that
   // point to this code breakpoint.
@@ -521,7 +527,6 @@
   bool HasBreakpointInCode(const Code& code);
 
   void SyncBreakpointLocation(BreakpointLocation* loc);
-  void DisableCodeBreakpointsFor(BreakpointLocation* loc);
 
   void Pause();
 
diff --git a/runtime/vm/debugger_arm.cc b/runtime/vm/debugger_arm.cc
index 7fe5605..675c98e 100644
--- a/runtime/vm/debugger_arm.cc
+++ b/runtime/vm/debugger_arm.cc
@@ -20,7 +20,7 @@
 }
 
 void CodeBreakpoint::PatchCode() {
-  ASSERT(!is_enabled_);
+  ASSERT(!IsEnabled());
   Code& stub_target = Code::Handle();
   switch (breakpoint_kind_) {
     case UntaggedPcDescriptors::kIcCall:
@@ -38,11 +38,10 @@
   const Code& code = Code::Handle(code_);
   saved_value_ = CodePatcher::GetStaticCallTargetAt(pc_, code);
   CodePatcher::PatchStaticCallAt(pc_, code, stub_target);
-  is_enabled_ = true;
 }
 
 void CodeBreakpoint::RestoreCode() {
-  ASSERT(is_enabled_);
+  ASSERT(IsEnabled());
   const Code& code = Code::Handle(code_);
   switch (breakpoint_kind_) {
     case UntaggedPcDescriptors::kIcCall:
@@ -54,7 +53,6 @@
     default:
       UNREACHABLE();
   }
-  is_enabled_ = false;
 }
 
 #endif  // !PRODUCT
diff --git a/runtime/vm/debugger_arm64.cc b/runtime/vm/debugger_arm64.cc
index d3f251b..17cb472 100644
--- a/runtime/vm/debugger_arm64.cc
+++ b/runtime/vm/debugger_arm64.cc
@@ -20,7 +20,7 @@
 }
 
 void CodeBreakpoint::PatchCode() {
-  ASSERT(!is_enabled_);
+  ASSERT(!IsEnabled());
   const Code& code = Code::Handle(code_);
   switch (breakpoint_kind_) {
     case UntaggedPcDescriptors::kIcCall: {
@@ -45,11 +45,10 @@
     default:
       UNREACHABLE();
   }
-  is_enabled_ = true;
 }
 
 void CodeBreakpoint::RestoreCode() {
-  ASSERT(is_enabled_);
+  ASSERT(IsEnabled());
   const Code& code = Code::Handle(code_);
   switch (breakpoint_kind_) {
     case UntaggedPcDescriptors::kIcCall: {
@@ -68,7 +67,6 @@
     default:
       UNREACHABLE();
   }
-  is_enabled_ = false;
 }
 
 #endif  // !PRODUCT
diff --git a/runtime/vm/debugger_ia32.cc b/runtime/vm/debugger_ia32.cc
index f9de84f..f244a34 100644
--- a/runtime/vm/debugger_ia32.cc
+++ b/runtime/vm/debugger_ia32.cc
@@ -24,7 +24,7 @@
 }
 
 void CodeBreakpoint::PatchCode() {
-  ASSERT(!is_enabled_);
+  ASSERT(!IsEnabled());
   auto thread = Thread::Current();
   auto zone = thread->zone();
   const Code& code = Code::Handle(zone, code_);
@@ -52,11 +52,10 @@
     saved_value_ = CodePatcher::GetStaticCallTargetAt(pc_, code);
     CodePatcher::PatchStaticCallAt(pc_, code, stub_target);
   });
-  is_enabled_ = true;
 }
 
 void CodeBreakpoint::RestoreCode() {
-  ASSERT(is_enabled_);
+  ASSERT(IsEnabled());
   auto thread = Thread::Current();
   auto zone = thread->zone();
   const Code& code = Code::Handle(zone, code_);
@@ -74,7 +73,6 @@
         UNREACHABLE();
     }
   });
-  is_enabled_ = false;
 }
 
 #endif  // !PRODUCT
diff --git a/runtime/vm/debugger_x64.cc b/runtime/vm/debugger_x64.cc
index 7a0e046..a6161a0 100644
--- a/runtime/vm/debugger_x64.cc
+++ b/runtime/vm/debugger_x64.cc
@@ -21,7 +21,7 @@
 }
 
 void CodeBreakpoint::PatchCode() {
-  ASSERT(!is_enabled_);
+  ASSERT(!IsEnabled());
   Code& stub_target = Code::Handle();
   switch (breakpoint_kind_) {
     case UntaggedPcDescriptors::kIcCall:
@@ -39,11 +39,10 @@
   const Code& code = Code::Handle(code_);
   saved_value_ = CodePatcher::GetStaticCallTargetAt(pc_, code);
   CodePatcher::PatchPoolPointerCallAt(pc_, code, stub_target);
-  is_enabled_ = true;
 }
 
 void CodeBreakpoint::RestoreCode() {
-  ASSERT(is_enabled_);
+  ASSERT(IsEnabled());
   const Code& code = Code::Handle(code_);
   switch (breakpoint_kind_) {
     case UntaggedPcDescriptors::kIcCall:
@@ -56,7 +55,6 @@
     default:
       UNREACHABLE();
   }
-  is_enabled_ = false;
 }
 
 #endif  // !PRODUCT
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index 95d3875..d82c602 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -3011,10 +3011,7 @@
   ASSERT(!unoptimized_code.IsNull());
   // The switch to unoptimized code may have already occurred.
   if (function.HasOptimizedCode()) {
-    SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
-    if (function.HasOptimizedCode()) {
-      function.SwitchToUnoptimizedCode();
-    }
+    function.SwitchToUnoptimizedCode();
   }
 
   if (frame->IsMarkedForLazyDeopt()) {
@@ -3048,23 +3045,29 @@
 // Currently checks only that all optimized frames have kDeoptIndex
 // and unoptimized code has the kDeoptAfter.
 void DeoptimizeFunctionsOnStack() {
-  auto isolate_group = IsolateGroup::Current();
+  auto thread = Thread::Current();
+  // Have to grab program_lock before stopping everybody else.
+  SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
+
+  auto isolate_group = thread->isolate_group();
   isolate_group->RunWithStoppedMutators([&]() {
     Code& optimized_code = Code::Handle();
-    isolate_group->ForEachIsolate([&](Isolate* isolate) {
-      auto mutator_thread = isolate->mutator_thread();
-      DartFrameIterator iterator(
-          mutator_thread, StackFrameIterator::kAllowCrossThreadIteration);
-      StackFrame* frame = iterator.NextFrame();
-      while (frame != nullptr) {
-        optimized_code = frame->LookupDartCode();
-        if (optimized_code.is_optimized() &&
-            !optimized_code.is_force_optimized()) {
-          DeoptimizeAt(mutator_thread, optimized_code, frame);
-        }
-        frame = iterator.NextFrame();
-      }
-    });
+    isolate_group->ForEachIsolate(
+        [&](Isolate* isolate) {
+          auto mutator_thread = isolate->mutator_thread();
+          DartFrameIterator iterator(
+              mutator_thread, StackFrameIterator::kAllowCrossThreadIteration);
+          StackFrame* frame = iterator.NextFrame();
+          while (frame != nullptr) {
+            optimized_code = frame->LookupDartCode();
+            if (optimized_code.is_optimized() &&
+                !optimized_code.is_force_optimized()) {
+              DeoptimizeAt(mutator_thread, optimized_code, frame);
+            }
+            frame = iterator.NextFrame();
+          }
+        },
+        /*at_safepoint=*/true);
   });
 }
 
diff --git a/runtime/vm/timeline.cc b/runtime/vm/timeline.cc
index 609f66e..0a48da5 100644
--- a/runtime/vm/timeline.cc
+++ b/runtime/vm/timeline.cc
@@ -86,6 +86,8 @@
 //
 // Locking notes:
 // The following locks are used by the timeline system:
+// - |Timeline::recorder_lock_| This lock is held whenever Timeline::recorder()
+// is accessed or modified.
 // - |TimelineEventRecorder::lock_| This lock is held whenever a
 // |TimelineEventBlock| is being requested or reclaimed.
 // - |Thread::timeline_block_lock_| This lock is held whenever a |Thread|'s
@@ -94,9 +96,10 @@
 // |Thread|s.
 //
 // Locks must always be taken in the following order:
-//   |Thread::thread_list_lock_|
-//     |Thread::timeline_block_lock_|
-//       |TimelineEventRecorder::lock_|
+//   |Timeline::recorder_lock_|
+//     |Thread::thread_list_lock_|
+//       |Thread::timeline_block_lock_|
+//         |TimelineEventRecorder::lock_|
 //
 
 static TimelineEventRecorder* CreateTimelineRecorder() {
@@ -205,6 +208,10 @@
 }
 
 void Timeline::Init() {
+  if (recorder_lock_ == nullptr) {
+    recorder_lock_ = new Mutex();
+  }
+  MutexLocker ml(recorder_lock_);
   ASSERT(recorder_ == NULL);
   recorder_ = CreateTimelineRecorder();
   ASSERT(recorder_ != NULL);
@@ -217,6 +224,8 @@
 }
 
 void Timeline::Cleanup() {
+  ASSERT(recorder_lock_ != nullptr);
+  MutexLocker ml(recorder_lock_);
   ASSERT(recorder_ != NULL);
 
 #ifndef PRODUCT
@@ -243,6 +252,12 @@
 }
 
 void Timeline::ReclaimCachedBlocksFromThreads() {
+  ASSERT(recorder_lock_ != nullptr);
+  MutexLocker ml(recorder_lock_);
+  ReclaimCachedBlocksFromThreadsLocked();
+}
+
+void Timeline::ReclaimCachedBlocksFromThreadsLocked() {
   TimelineEventRecorder* recorder = Timeline::recorder();
   if (recorder == NULL) {
     return;
@@ -274,6 +289,8 @@
 }
 
 void Timeline::PrintFlagsToJSON(JSONStream* js) {
+  ASSERT(recorder_lock_ != nullptr);
+  MutexLocker ml(recorder_lock_);
   JSONObject obj(js);
   obj.AddProperty("type", "TimelineFlags");
   TimelineEventRecorder* recorder = Timeline::recorder();
@@ -301,11 +318,13 @@
 #endif
 
 void Timeline::Clear() {
+  ASSERT(recorder_lock_ != nullptr);
+  MutexLocker ml(recorder_lock_);
   TimelineEventRecorder* recorder = Timeline::recorder();
   if (recorder == NULL) {
     return;
   }
-  ReclaimCachedBlocksFromThreads();
+  ReclaimCachedBlocksFromThreadsLocked();
   recorder->Clear();
 }
 
@@ -390,6 +409,7 @@
 
 TimelineEventRecorder* Timeline::recorder_ = NULL;
 MallocGrowableArray<char*>* Timeline::enabled_streams_ = NULL;
+Mutex* Timeline::recorder_lock_ = nullptr;
 
 #define TIMELINE_STREAM_DEFINE(name, fuchsia_name)                             \
   TimelineStream Timeline::stream_##name##_(#name, fuchsia_name, false);
@@ -556,6 +576,8 @@
 }
 
 void TimelineEvent::Complete() {
+  ASSERT(Timeline::recorder_lock() != nullptr);
+  MutexLocker ml(Timeline::recorder_lock());
   TimelineEventRecorder* recorder = Timeline::recorder();
   if (recorder != NULL) {
     recorder->CompleteEvent(this);
@@ -775,6 +797,10 @@
 }
 
 TimelineEvent* TimelineStream::StartEvent() {
+  if (Timeline::recorder_lock() == nullptr) {
+    return nullptr;
+  }
+  MutexLocker ml(Timeline::recorder_lock());
   TimelineEventRecorder* recorder = Timeline::recorder();
   if (!enabled() || (recorder == NULL)) {
     return NULL;
@@ -1137,6 +1163,7 @@
 }
 
 TimelineEventFixedBufferRecorder::~TimelineEventFixedBufferRecorder() {
+  MutexLocker ml(&lock_);
   // Delete all blocks.
   for (intptr_t i = 0; i < num_blocks_; i++) {
     blocks_[i].Reset();
@@ -1334,6 +1361,7 @@
     : head_(nullptr), tail_(nullptr), block_index_(0) {}
 
 TimelineEventEndlessRecorder::~TimelineEventEndlessRecorder() {
+  MutexLocker ml(&lock_);
   TimelineEventBlock* current = head_;
   head_ = tail_ = nullptr;
 
@@ -1424,6 +1452,9 @@
 #endif
 
 void TimelineEventEndlessRecorder::Clear() {
+  OSThread* thread = OSThread::Current();
+  MutexLocker ml(thread->timeline_block_lock());
+  MutexLocker ml2(&lock_);
   TimelineEventBlock* current = head_;
   while (current != NULL) {
     TimelineEventBlock* next = current->next();
@@ -1431,8 +1462,8 @@
     current = next;
   }
   head_ = NULL;
+  tail_ = NULL;
   block_index_ = 0;
-  OSThread* thread = OSThread::Current();
   thread->set_timeline_block(NULL);
 }
 
diff --git a/runtime/vm/timeline.h b/runtime/vm/timeline.h
index 38e739a..a1eb5d6 100644
--- a/runtime/vm/timeline.h
+++ b/runtime/vm/timeline.h
@@ -23,7 +23,7 @@
 #if defined(__MAC_10_14) || defined (__IPHONE_12_0)
 #define HOST_OS_SUPPORTS_SIGNPOST 1
 #endif
-//signpost.h exists in macOS 10.14, iOS 12 or above
+// signpost.h exists in macOS 10.14, iOS 12 or above
 #if defined(HOST_OS_SUPPORTS_SIGNPOST)
 #include <os/signpost.h>
 #else
@@ -123,15 +123,18 @@
 
 class Timeline : public AllStatic {
  public:
-  // Initialize timeline system. Not thread safe.
+  // Initialize timeline system.
   static void Init();
 
-  // Cleanup timeline system. Not thread safe.
+  // Cleanup timeline system.
   static void Cleanup();
 
-  // Access the global recorder. Not thread safe.
+  // Access the global recorder. recorder_lock() must be held when accessing
+  // the returned |TimelineEventRecorder|.
   static TimelineEventRecorder* recorder();
 
+  static Mutex* recorder_lock() { return recorder_lock_; }
+
   // Reclaim all |TimelineEventBlocks|s that are cached by threads.
   static void ReclaimCachedBlocksFromThreads();
 
@@ -158,8 +161,13 @@
 #undef TIMELINE_STREAM_FLAGS
 
  private:
+  // Reclaims all |TimelineEventBlocks|s that are cached by threads, assuming
+  // that lock_ is held.
+  static void ReclaimCachedBlocksFromThreadsLocked();
+
   static TimelineEventRecorder* recorder_;
   static MallocGrowableArray<char*>* enabled_streams_;
+  static Mutex* recorder_lock_;
 
 #define TIMELINE_STREAM_DECLARE(name, fuchsia_name)                            \
   static TimelineStream stream_##name##_;
diff --git a/tests/language/const_functions/const_functions_recursion_error_test.dart b/tests/language/const_functions/const_functions_recursion_error_test.dart
new file mode 100644
index 0000000..f4332a9
--- /dev/null
+++ b/tests/language/const_functions/const_functions_recursion_error_test.dart
@@ -0,0 +1,21 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+// Tests recursive function calls for const functions which have a cycle in the
+// dependencies.
+
+// SharedOptions=--enable-experiment=const-functions
+
+import "package:expect/expect.dart";
+
+const dependsOnB = b;
+//                 ^
+// [cfe] Constant evaluation error:
+const b = fn(4);
+//        ^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+int fn(int a) {
+  if (a == 1) return dependsOnB;
+  return dependsOnB * fn(a - 1);
+}
diff --git a/tests/language/const_functions/const_functions_recursion_test.dart b/tests/language/const_functions/const_functions_recursion_test.dart
new file mode 100644
index 0000000..c1343b1
--- /dev/null
+++ b/tests/language/const_functions/const_functions_recursion_test.dart
@@ -0,0 +1,34 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+// Tests recursive function calls for const functions.
+
+// SharedOptions=--enable-experiment=const-functions
+
+import "package:expect/expect.dart";
+
+const b = fn(4);
+//        ^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+int fn(int a) {
+  if (a == 1) return 1;
+  return a * fn(a - 1);
+}
+
+int localTest() {
+  int fnLocal(int a) {
+    if (a == 1) return 1;
+    return a * fnLocal(a - 1);
+  }
+
+  const c = fnLocal(4);
+  //        ^^^^^^^^^^
+  // [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+  return c;
+}
+
+void main() {
+  Expect.equals(b, 24);
+  Expect.equals(localTest(), 24);
+}
diff --git a/tools/VERSION b/tools/VERSION
index 78dca80..5ed7e50 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 13
 PATCH 0
-PRERELEASE 153
+PRERELEASE 154
 PRERELEASE_PATCH 0
\ No newline at end of file