Version 2.18.0-240.0.dev

Merge commit '1c461e06c1d53cf60e4458bb89b2a1543266bd95' into 'dev'
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index ba502a6..d19cc50 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -1363,7 +1363,7 @@
         return result;
       } catch (exception, stackTrace) {
         String? contextKey =
-            _storeExceptionContext(path, library.file, exception, stackTrace);
+            _storeExceptionContext(path, library, exception, stackTrace);
         throw _ExceptionState(exception, stackTrace, contextKey);
       }
     });
@@ -1500,7 +1500,6 @@
   /// When we look at a part that has a `part of name;` directive, we
   /// usually don't know the library (in contrast to `part of uri;`).
   /// So, we have no choice than to resolve this part as its own library.
-  /// TODO(scheglov) Maybe just return an error result instead?
   ///
   /// But parts of `dart:xyz` libraries are special. The reason is that
   /// `dart:core` is always implicitly imported. So, when we start building
@@ -1519,7 +1518,8 @@
 
     _fsState.getFileForUri(Uri.parse('dart:core')).map(
       (file) {
-        file?.transitiveFiles;
+        final kind = file?.kind as LibraryFileStateKind;
+        kind.discoverReferencedFiles();
       },
       (externalLibrary) {},
     );
@@ -1810,7 +1810,7 @@
         .toBuffer();
   }
 
-  String? _storeExceptionContext(String path, FileState libraryFile,
+  String? _storeExceptionContext(String path, LibraryFileStateKind library,
       Object exception, StackTrace stackTrace) {
     if (allowedNumberOfContextsToWrite <= 0) {
       return null;
@@ -1818,8 +1818,7 @@
       allowedNumberOfContextsToWrite--;
     }
     try {
-      List<AnalysisDriverExceptionFileBuilder> contextFiles = libraryFile
-          .transitiveFiles
+      final contextFiles = library.files
           .map((file) => AnalysisDriverExceptionFileBuilder(
               path: file.path, content: file.content))
           .toList();
diff --git a/pkg/analyzer/lib/src/dart/analysis/file_state.dart b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
index ffd31fe..8e11c437 100644
--- a/pkg/analyzer/lib/src/dart/analysis/file_state.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
@@ -124,8 +124,15 @@
   bool isAugmentationOf(LibraryOrAugmentationFileKind container) => false;
 }
 
+/// Information about a directive that "includes" a file - `import`, `export`,
+/// or `part`. But not `part of` or `library augment` - these are modelled as
+/// kinds.
+class DirectiveState {
+  void dispose() {}
+}
+
 /// Information about a single `import` directive.
-class ExportDirectiveState {
+class ExportDirectiveState extends DirectiveState {
   final UnlinkedNamespaceDirective directive;
 
   ExportDirectiveState({
@@ -145,12 +152,16 @@
 
 /// [ExportDirectiveState] that has a valid URI that references a file.
 class ExportDirectiveWithFile extends ExportDirectiveState {
+  final LibraryOrAugmentationFileKind container;
   final FileState exportedFile;
 
   ExportDirectiveWithFile({
     required super.directive,
+    required this.container,
     required this.exportedFile,
-  });
+  }) {
+    exportedFile.referencingFiles.add(container.file);
+  }
 
   /// Returns [exportedFile] if it is a library.
   LibraryFileStateKind? get exportedLibrary {
@@ -171,6 +182,11 @@
 
   @override
   Source get exportedSource => exportedFile.source;
+
+  @override
+  void dispose() {
+    exportedFile.referencingFiles.remove(container.file);
+  }
 }
 
 /// [ExportDirectiveState] with a URI that resolves to [InSummarySource].
@@ -420,21 +436,6 @@
   @visibleForTesting
   FileStateTestView get test => FileStateTestView(this);
 
-  /// Return the set of transitive files - the file itself and all of the
-  /// directly or indirectly referenced files.
-  Set<FileState> get transitiveFiles {
-    var transitiveFiles = <FileState>{};
-
-    void appendReferenced(FileState file) {
-      if (transitiveFiles.add(file)) {
-        file.directReferencedFiles.forEach(appendReferenced);
-      }
-    }
-
-    appendReferenced(this);
-    return transitiveFiles;
-  }
-
   /// The [UnlinkedUnit] of the file.
   UnlinkedUnit get unlinked2 => _unlinked2!;
 
@@ -551,10 +552,6 @@
         !_equalByteLists(_apiSignature, newApiSignature);
     _apiSignature = newApiSignature;
 
-    // It is possible that this file does not reference these files.
-    // TODO(scheglov) This also could be moved, almost.
-    _stopReferencingByThisFile();
-
     // Read imports/exports on demand.
     _importedFiles = null;
     _exportedFiles = null;
@@ -706,19 +703,6 @@
     return directive.uri;
   }
 
-  void _stopReferencingByThisFile() {
-    void removeForOne(List<FileState?>? referencedFiles) {
-      if (referencedFiles != null) {
-        for (var referenced in referencedFiles) {
-          referenced?.referencingFiles.remove(this);
-        }
-      }
-    }
-
-    removeForOne(_exportedFiles);
-    removeForOne(_importedFiles);
-  }
-
   void _updateKind() {
     _kind?.dispose();
 
@@ -1460,12 +1444,10 @@
 }
 
 /// Information about a single `import augment` directive.
-class ImportAugmentationDirectiveState {
-  final LibraryOrAugmentationFileKind container;
+class ImportAugmentationDirectiveState extends DirectiveState {
   final UnlinkedImportAugmentationDirective directive;
 
   ImportAugmentationDirectiveState({
-    required this.container,
     required this.directive,
   });
 
@@ -1478,13 +1460,16 @@
 /// [PartDirectiveState] that has a valid URI that references a file.
 class ImportAugmentationDirectiveWithFile
     extends ImportAugmentationDirectiveState {
+  final LibraryOrAugmentationFileKind container;
   final FileState importedFile;
 
   ImportAugmentationDirectiveWithFile({
-    required super.container,
+    required this.container,
     required super.directive,
     required this.importedFile,
-  });
+  }) {
+    importedFile.referencingFiles.add(container.file);
+  }
 
   /// If [importedFile] is a [AugmentationFileStateKind], and it confirms that
   /// it is an augmentation of the [container], returns the [importedFile].
@@ -1498,10 +1483,15 @@
 
   @override
   Source? get importedSource => importedFile.source;
+
+  @override
+  void dispose() {
+    importedFile.referencingFiles.remove(container.file);
+  }
 }
 
 /// Information about a single `import` directive.
-class ImportDirectiveState {
+class ImportDirectiveState extends DirectiveState {
   final UnlinkedNamespaceDirective directive;
 
   ImportDirectiveState({
@@ -1523,12 +1513,16 @@
 
 /// [ImportDirectiveState] that has a valid URI that references a file.
 class ImportDirectiveWithFile extends ImportDirectiveState {
+  final LibraryOrAugmentationFileKind container;
   final FileState importedFile;
 
   ImportDirectiveWithFile({
+    required this.container,
     required super.directive,
     required this.importedFile,
-  });
+  }) {
+    importedFile.referencingFiles.add(container.file);
+  }
 
   /// Returns [importedFile] if it is a library.
   LibraryFileStateKind? get importedLibrary {
@@ -1549,6 +1543,11 @@
 
   @override
   Source get importedSource => importedFile.source;
+
+  @override
+  void dispose() {
+    importedFile.referencingFiles.remove(container.file);
+  }
 }
 
 /// [ImportDirectiveState] with a URI that resolves to [InSummarySource].
@@ -1630,7 +1629,6 @@
       return file._fileForRelativeUri(directive.uri).map(
         (refFile) {
           if (refFile != null) {
-            refFile.referencingFiles.add(file);
             return PartDirectiveWithFile(
               library: this,
               directive: directive,
@@ -1663,16 +1661,7 @@
   void dispose() {
     invalidateLibraryCycle();
     file._fsState._libraryNameToFiles.remove(this);
-
-    final parts = _parts;
-    if (parts != null) {
-      for (final part in parts) {
-        if (part is PartDirectiveWithFile) {
-          part.includedFile.referencingFiles.remove(file);
-        }
-      }
-    }
-
+    _parts?.disposeAll();
     super.dispose();
   }
 
@@ -1711,7 +1700,6 @@
       return file._fileForRelativeUri(directive.uri).map(
         (refFile) {
           if (refFile != null) {
-            refFile.referencingFiles.add(file);
             return ImportAugmentationDirectiveWithFile(
               container: this,
               directive: directive,
@@ -1719,14 +1707,12 @@
             );
           } else {
             return ImportAugmentationDirectiveState(
-              container: this,
               directive: directive,
             );
           }
         },
         (externalLibrary) {
           return ImportAugmentationDirectiveState(
-            container: this,
             directive: directive,
           );
         },
@@ -1740,8 +1726,8 @@
       return file._fileForRelativeUri(uriStr).map(
         (refFile) {
           if (refFile != null) {
-            refFile.referencingFiles.add(file);
             return ExportDirectiveWithFile(
+              container: this,
               directive: directive,
               exportedFile: refFile,
             );
@@ -1767,8 +1753,8 @@
       return file._fileForRelativeUri(uriStr).map(
         (refFile) {
           if (refFile != null) {
-            refFile.referencingFiles.add(file);
             return ImportDirectiveWithFile(
+              container: this,
               directive: directive,
               importedFile: refFile,
             );
@@ -1806,33 +1792,9 @@
 
   @override
   void dispose() {
-    final augmentations = _augmentations;
-    if (augmentations != null) {
-      for (final import in augmentations) {
-        if (import is ImportAugmentationDirectiveWithFile) {
-          import.importedFile.referencingFiles.remove(file);
-        }
-      }
-    }
-
-    final exports = _exports;
-    if (exports != null) {
-      for (final export in exports) {
-        if (export is ExportDirectiveWithFile) {
-          export.exportedFile.referencingFiles.remove(file);
-        }
-      }
-    }
-
-    final imports = _imports;
-    if (imports != null) {
-      for (final import in imports) {
-        if (import is ImportDirectiveWithFile) {
-          import.importedFile.referencingFiles.remove(file);
-        }
-      }
-    }
-
+    _augmentations?.disposeAll();
+    _exports?.disposeAll();
+    _imports?.disposeAll();
     super.dispose();
   }
 
@@ -1849,7 +1811,7 @@
 }
 
 /// Information about a single `part` directive.
-class PartDirectiveState {
+class PartDirectiveState extends DirectiveState {
   final LibraryFileStateKind library;
   final UnlinkedPartDirective directive;
 
@@ -1872,7 +1834,9 @@
     required super.library,
     required super.directive,
     required this.includedFile,
-  });
+  }) {
+    includedFile.referencingFiles.add(library.file);
+  }
 
   /// If [includedFile] is a [PartFileStateKind], and it confirms that it
   /// is a part of the [library], returns the [includedFile].
@@ -1886,6 +1850,11 @@
 
   @override
   Source? get includedSource => includedFile.source;
+
+  @override
+  void dispose() {
+    includedFile.referencingFiles.remove(library.file);
+  }
 }
 
 /// The file has `part of` directive.
@@ -2103,3 +2072,11 @@
     }
   }
 }
+
+extension on List<DirectiveState> {
+  void disposeAll() {
+    for (final directive in this) {
+      directive.dispose();
+    }
+  }
+}
diff --git a/pkg/analyzer/test/src/dart/analysis/analyzer_state_printer.dart b/pkg/analyzer/test/src/dart/analysis/analyzer_state_printer.dart
index 242801d..1c43336 100644
--- a/pkg/analyzer/test/src/dart/analysis/analyzer_state_printer.dart
+++ b/pkg/analyzer/test/src/dart/analysis/analyzer_state_printer.dart
@@ -96,13 +96,13 @@
     _writeElements<ImportAugmentationDirectiveState>(
       'augmentations',
       container.augmentations,
-      (augmentation) {
-        expect(augmentation.container, same(container));
-        if (augmentation is ImportAugmentationDirectiveWithFile) {
-          final file = augmentation.importedFile;
+      (import) {
+        if (import is ImportAugmentationDirectiveWithFile) {
+          expect(import.container, same(container));
+          final file = import.importedFile;
           sink.write(_indent);
 
-          final importedAugmentation = augmentation.importedAugmentation;
+          final importedAugmentation = import.importedAugmentation;
           if (importedAugmentation != null) {
             expect(importedAugmentation.file, file);
             sink.write(idProvider.fileStateKind(importedAugmentation));
@@ -112,7 +112,7 @@
           sink.writeln();
         } else {
           sink.write(_indent);
-          sink.write('uri: ${_stringOfUriStr(augmentation.directive.uri)}');
+          sink.write('uri: ${_stringOfUriStr(import.directive.uri)}');
           sink.writeln();
         }
       },
@@ -169,89 +169,99 @@
     });
   }
 
-  void _writeFileExports(LibraryOrAugmentationFileKind file) {
-    _writeElements<ExportDirectiveState>('exports', file.exports, (export) {
-      if (export is ExportDirectiveWithFile) {
-        final file = export.exportedFile;
-        sink.write(_indent);
+  void _writeFileExports(LibraryOrAugmentationFileKind container) {
+    _writeElements<ExportDirectiveState>(
+      'exports',
+      container.exports,
+      (export) {
+        if (export is ExportDirectiveWithFile) {
+          expect(export.container, same(container));
+          final file = export.exportedFile;
+          sink.write(_indent);
 
-        final exportedLibrary = export.exportedLibrary;
-        if (exportedLibrary != null) {
-          expect(exportedLibrary.file, file);
-          sink.write(idProvider.fileStateKind(exportedLibrary));
+          final exportedLibrary = export.exportedLibrary;
+          if (exportedLibrary != null) {
+            expect(exportedLibrary.file, file);
+            sink.write(idProvider.fileStateKind(exportedLibrary));
+          } else {
+            sink.write('notLibrary ${idProvider.fileState(file)}');
+          }
+
+          if (omitSdkFiles && file.uri.isScheme('dart')) {
+            sink.write(' ${file.uri}');
+          }
+          sink.writeln();
+        } else if (export is ExportDirectiveWithInSummarySource) {
+          sink.write(_indent);
+          sink.write('inSummary ${export.exportedSource.uri}');
+
+          final librarySource = export.exportedLibrarySource;
+          if (librarySource != null) {
+            expect(librarySource, same(export.exportedSource));
+          } else {
+            sink.write(' notLibrary');
+          }
+          sink.writeln();
         } else {
-          sink.write('notLibrary ${idProvider.fileState(file)}');
+          sink.write(_indent);
+          sink.write('uri: ${_stringOfUriStr(export.directive.uri)}');
+          sink.writeln();
         }
-
-        if (omitSdkFiles && file.uri.isScheme('dart')) {
-          sink.write(' ${file.uri}');
-        }
-        sink.writeln();
-      } else if (export is ExportDirectiveWithInSummarySource) {
-        sink.write(_indent);
-        sink.write('inSummary ${export.exportedSource.uri}');
-
-        final librarySource = export.exportedLibrarySource;
-        if (librarySource != null) {
-          expect(librarySource, same(export.exportedSource));
-        } else {
-          sink.write(' notLibrary');
-        }
-        sink.writeln();
-      } else {
-        sink.write(_indent);
-        sink.write('uri: ${_stringOfUriStr(export.directive.uri)}');
-        sink.writeln();
-      }
-    });
+      },
+    );
   }
 
-  void _writeFileImports(LibraryOrAugmentationFileKind file) {
-    _writeElements<ImportDirectiveState>('imports', file.imports, (import) {
-      if (import is ImportDirectiveWithFile) {
-        final file = import.importedFile;
-        sink.write(_indent);
+  void _writeFileImports(LibraryOrAugmentationFileKind container) {
+    _writeElements<ImportDirectiveState>(
+      'imports',
+      container.imports,
+      (import) {
+        if (import is ImportDirectiveWithFile) {
+          expect(import.container, same(container));
+          final file = import.importedFile;
+          sink.write(_indent);
 
-        final importedLibrary = import.importedLibrary;
-        if (importedLibrary != null) {
-          expect(importedLibrary.file, file);
-          sink.write(idProvider.fileStateKind(importedLibrary));
+          final importedLibrary = import.importedLibrary;
+          if (importedLibrary != null) {
+            expect(importedLibrary.file, file);
+            sink.write(idProvider.fileStateKind(importedLibrary));
+          } else {
+            sink.write('notLibrary ${idProvider.fileState(file)}');
+          }
+
+          if (omitSdkFiles && file.uri.isScheme('dart')) {
+            sink.write(' ${file.uri}');
+          }
+
+          if (import.isSyntheticDartCoreImport) {
+            sink.write(' synthetic');
+          }
+          sink.writeln();
+        } else if (import is ImportDirectiveWithInSummarySource) {
+          sink.write(_indent);
+          sink.write('inSummary ${import.importedSource.uri}');
+
+          final librarySource = import.importedLibrarySource;
+          if (librarySource != null) {
+            expect(librarySource, same(import.importedSource));
+          } else {
+            sink.write(' notLibrary');
+          }
+
+          if (import.isSyntheticDartCoreImport) {
+            sink.write(' synthetic');
+          }
+          sink.writeln();
         } else {
-          sink.write('notLibrary ${idProvider.fileState(file)}');
+          sink.write(_indent);
+          sink.write('uri: ${_stringOfUriStr(import.directive.uri)}');
+          if (import.isSyntheticDartCoreImport) {
+            sink.write(' synthetic');
+          }
+          sink.writeln();
         }
-
-        if (omitSdkFiles && file.uri.isScheme('dart')) {
-          sink.write(' ${file.uri}');
-        }
-
-        if (import.isSyntheticDartCoreImport) {
-          sink.write(' synthetic');
-        }
-        sink.writeln();
-      } else if (import is ImportDirectiveWithInSummarySource) {
-        sink.write(_indent);
-        sink.write('inSummary ${import.importedSource.uri}');
-
-        final librarySource = import.importedLibrarySource;
-        if (librarySource != null) {
-          expect(librarySource, same(import.importedSource));
-        } else {
-          sink.write(' notLibrary');
-        }
-
-        if (import.isSyntheticDartCoreImport) {
-          sink.write(' synthetic');
-        }
-        sink.writeln();
-      } else {
-        sink.write(_indent);
-        sink.write('uri: ${_stringOfUriStr(import.directive.uri)}');
-        if (import.isSyntheticDartCoreImport) {
-          sink.write(' synthetic');
-        }
-        sink.writeln();
-      }
-    });
+      },
+    );
   }
 
   void _writeFileKind(FileState file) {
diff --git a/runtime/vm/dart.cc b/runtime/vm/dart.cc
index 65aea3a..9d66e4d 100644
--- a/runtime/vm/dart.cc
+++ b/runtime/vm/dart.cc
@@ -1059,9 +1059,6 @@
     return error.ptr();
   }
 
-  if (!was_child_cloned_into_existing_isolate) {
-    IG->heap()->InitGrowthControl();
-  }
   I->set_init_callback_data(isolate_data);
   if (FLAG_print_class_table) {
     IG->class_table()->Print();
diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc
index eb33322..adac74b 100644
--- a/runtime/vm/dart_api_impl.cc
+++ b/runtime/vm/dart_api_impl.cc
@@ -1313,9 +1313,6 @@
   }
 
   if (success) {
-    if (is_new_group) {
-      group->heap()->InitGrowthControl();
-    }
     // A Thread structure has been associated to the thread, we do the
     // safepoint transition explicitly here instead of using the
     // TransitionXXX scope objects as the reverse transition happens
diff --git a/runtime/vm/dart_api_impl_test.cc b/runtime/vm/dart_api_impl_test.cc
index 4d7f959..0e7442a 100644
--- a/runtime/vm/dart_api_impl_test.cc
+++ b/runtime/vm/dart_api_impl_test.cc
@@ -3280,7 +3280,7 @@
     {
       // GCs due to allocations or weak handle creation can cause early
       // promotion and interfere with the scenario this test is verifying.
-      NoHeapGrowthControlScope force_growth;
+      ForceGrowthScope force_growth(thread);
 
       // Create an object in new space.
       new_ref = AllocateNewString("new string");
@@ -3425,7 +3425,7 @@
     {
       // GCs due to allocations or weak handle creation can cause early
       // promotion and interfere with the scenario this test is verifying.
-      NoHeapGrowthControlScope force_growth;
+      ForceGrowthScope force_growth(thread);
 
       // Create an object in new space.
       new_ref = AllocateNewString("new string");
diff --git a/runtime/vm/heap/heap.cc b/runtime/vm/heap/heap.cc
index d8b0c3f..c218c50 100644
--- a/runtime/vm/heap/heap.cc
+++ b/runtime/vm/heap/heap.cc
@@ -74,7 +74,7 @@
   if (LIKELY(addr != 0)) {
     return addr;
   }
-  if (!assume_scavenge_will_fail_ && new_space_.GrowthControlState()) {
+  if (!assume_scavenge_will_fail_ && !thread->force_growth()) {
     // This call to CollectGarbage might end up "reusing" a collection spawned
     // from a different thread and will be racing to allocate the requested
     // memory with other threads being released after the collection.
@@ -93,7 +93,7 @@
 
 uword Heap::AllocateOld(Thread* thread, intptr_t size, OldPage::PageType type) {
   ASSERT(thread->no_safepoint_scope_depth() == 0);
-  if (old_space_.GrowthControlState()) {
+  if (!thread->force_growth()) {
     CollectForDebugging(thread);
     uword addr = old_space_.TryAllocate(size, type);
     if (addr != 0) {
@@ -132,7 +132,7 @@
     return addr;
   }
 
-  if (old_space_.GrowthControlState()) {
+  if (!thread->force_growth()) {
     WaitForSweeperTasks(thread);
     old_space_.TryReleaseReservation();
   } else {
@@ -155,10 +155,10 @@
   }
 
   Thread* thread = Thread::Current();
-  if (thread->no_callback_scope_depth() == 0) {
+  if ((thread->no_callback_scope_depth() == 0) && !thread->force_growth()) {
     CheckExternalGC(thread);
   } else {
-    // Check delayed until Dart_TypedDataRelease.
+    // Check delayed until Dart_TypedDataRelease/~ForceGrowthScope.
   }
 }
 
@@ -179,6 +179,7 @@
 void Heap::CheckExternalGC(Thread* thread) {
   ASSERT(thread->no_safepoint_scope_depth() == 0);
   ASSERT(thread->no_callback_scope_depth() == 0);
+  ASSERT(!thread->force_growth());
   if (new_space_.ExternalInWords() >= (4 * new_space_.CapacityInWords())) {
     // Attempt to free some external allocation by a scavenge. (If the total
     // remains above the limit, next external alloc will trigger another.)
@@ -566,6 +567,8 @@
 void Heap::CheckConcurrentMarking(Thread* thread,
                                   GCReason reason,
                                   intptr_t size) {
+  ASSERT(!thread->force_growth());
+
   PageSpace::Phase phase;
   {
     MonitorLocker ml(old_space_.tasks_lock());
@@ -662,21 +665,6 @@
       (UsedInWords(Heap::kOld) * kWordSize));
 }
 
-void Heap::InitGrowthControl() {
-  new_space_.InitGrowthControl();
-  old_space_.InitGrowthControl();
-}
-
-void Heap::SetGrowthControlState(bool state) {
-  new_space_.SetGrowthControlState(state);
-  old_space_.SetGrowthControlState(state);
-}
-
-bool Heap::GrowthControlState() {
-  ASSERT(new_space_.GrowthControlState() == old_space_.GrowthControlState());
-  return old_space_.GrowthControlState();
-}
-
 void Heap::WriteProtect(bool read_only) {
   read_only_ = read_only;
   new_space_.WriteProtect(read_only);
@@ -1187,16 +1175,13 @@
   }
 }
 
-NoHeapGrowthControlScope::NoHeapGrowthControlScope()
-    : ThreadStackResource(Thread::Current()) {
-  Heap* heap = isolate_group()->heap();
-  current_growth_controller_state_ = heap->GrowthControlState();
-  heap->DisableGrowthControl();
+ForceGrowthScope::ForceGrowthScope(Thread* thread)
+    : ThreadStackResource(thread) {
+  thread->IncrementForceGrowthScopeDepth();
 }
 
-NoHeapGrowthControlScope::~NoHeapGrowthControlScope() {
-  Heap* heap = isolate_group()->heap();
-  heap->SetGrowthControlState(current_growth_controller_state_);
+ForceGrowthScope::~ForceGrowthScope() {
+  thread()->DecrementForceGrowthScopeDepth();
 }
 
 WritableVMIsolateScope::WritableVMIsolateScope(Thread* thread)
diff --git a/runtime/vm/heap/heap.h b/runtime/vm/heap/heap.h
index 3ceab79..3408cb2 100644
--- a/runtime/vm/heap/heap.h
+++ b/runtime/vm/heap/heap.h
@@ -131,13 +131,6 @@
   void WaitForSweeperTasks(Thread* thread);
   void WaitForSweeperTasksAtSafepoint(Thread* thread);
 
-  // Enables growth control on the page space heaps.  This should be
-  // called before any user code is executed.
-  void InitGrowthControl();
-  void DisableGrowthControl() { SetGrowthControlState(false); }
-  void SetGrowthControlState(bool state);
-  bool GrowthControlState();
-
   // Protect access to the heap. Note: Code pages are made
   // executable/non-executable when 'read_only' is true/false, respectively.
   void WriteProtect(bool read_only);
@@ -424,14 +417,13 @@
   DISALLOW_COPY_AND_ASSIGN(HeapIterationScope);
 };
 
-class NoHeapGrowthControlScope : public ThreadStackResource {
+class ForceGrowthScope : public ThreadStackResource {
  public:
-  NoHeapGrowthControlScope();
-  ~NoHeapGrowthControlScope();
+  explicit ForceGrowthScope(Thread* thread);
+  ~ForceGrowthScope();
 
  private:
-  bool current_growth_controller_state_;
-  DISALLOW_COPY_AND_ASSIGN(NoHeapGrowthControlScope);
+  DISALLOW_COPY_AND_ASSIGN(ForceGrowthScope);
 };
 
 // Note: During this scope all pages are writable and the code pages are
diff --git a/runtime/vm/heap/heap_test.cc b/runtime/vm/heap/heap_test.cc
index d2e2c41..51a139b 100644
--- a/runtime/vm/heap/heap_test.cc
+++ b/runtime/vm/heap/heap_test.cc
@@ -721,4 +721,65 @@
   }
 }
 
+class ConcurrentForceGrowthScopeTask : public ThreadPool::Task {
+ public:
+  ConcurrentForceGrowthScopeTask(Isolate* isolate,
+                                 Monitor* monitor,
+                                 intptr_t* done_count)
+      : isolate_(isolate), monitor_(monitor), done_count_(done_count) {}
+
+  virtual void Run() {
+    Thread::EnterIsolateAsHelper(isolate_, Thread::kUnknownTask);
+    {
+      Thread* thread = Thread::Current();
+      StackZone stack_zone(thread);
+
+      GrowableObjectArray& accumulate =
+          GrowableObjectArray::Handle(GrowableObjectArray::New());
+      Object& element = Object::Handle();
+      for (intptr_t i = 0; i < 1000; i++) {
+        // Lots of entering and leaving ForceGrowth scopes. Previously, this
+        // would have been data races on the per-Heap force-growth flag.
+        {
+          ForceGrowthScope force_growth(thread);
+          GrowableObjectArrayPtr unsafe_accumulate = accumulate.ptr();
+          element = Array::New(0);
+          accumulate = unsafe_accumulate;
+        }
+        accumulate.Add(element);
+      }
+    }
+    Thread::ExitIsolateAsHelper();
+    // Notify the main thread that this thread has exited.
+    {
+      MonitorLocker ml(monitor_);
+      *done_count_ += 1;
+      ml.Notify();
+    }
+  }
+
+ private:
+  Isolate* isolate_;
+  Monitor* monitor_;
+  intptr_t* done_count_;
+};
+
+ISOLATE_UNIT_TEST_CASE(ConcurrentForceGrowthScope) {
+  intptr_t task_count = 8;
+  Monitor monitor;
+  intptr_t done_count = 0;
+
+  for (intptr_t i = 0; i < task_count; i++) {
+    Dart::thread_pool()->Run<ConcurrentForceGrowthScopeTask>(
+        thread->isolate(), &monitor, &done_count);
+  }
+
+  {
+    MonitorLocker ml(&monitor);
+    while (done_count < task_count) {
+      ml.WaitWithSafepointCheck(thread);
+    }
+  }
+}
+
 }  // namespace dart
diff --git a/runtime/vm/heap/pages.cc b/runtime/vm/heap/pages.cc
index 1d66e97..e207185 100644
--- a/runtime/vm/heap/pages.cc
+++ b/runtime/vm/heap/pages.cc
@@ -471,7 +471,7 @@
   ASSERT(Heap::IsAllocatableViaFreeLists(size));
 
   if (growth_policy != kForceGrowth) {
-    ASSERT(GrowthControlState());
+    ASSERT(!Thread::Current()->force_growth());
     if (heap_ != nullptr) {  // Some unit tests.
       heap_->CheckConcurrentMarking(Thread::Current(), GCReason::kOldSpace,
                                     kOldPageSize);
@@ -513,7 +513,7 @@
   ASSERT(!Heap::IsAllocatableViaFreeLists(size));
 
   if (growth_policy != kForceGrowth) {
-    ASSERT(GrowthControlState());
+    ASSERT(!Thread::Current()->force_growth());
     if (heap_ != nullptr) {  // Some unit tests.
       heap_->CheckConcurrentMarking(Thread::Current(), GCReason::kOldSpace,
                                     size);
@@ -1110,7 +1110,7 @@
 }
 
 void PageSpace::CollectGarbage(Thread* thread, bool compact, bool finalize) {
-  ASSERT(GrowthControlState());
+  ASSERT(!Thread::Current()->force_growth());
 
   if (!finalize) {
 #if defined(TARGET_ARCH_IA32)
@@ -1509,7 +1509,6 @@
                                          int heap_growth_max,
                                          int garbage_collection_time_ratio)
     : heap_(heap),
-      is_enabled_(false),
       heap_growth_ratio_(heap_growth_ratio),
       desired_utilization_((100.0 - heap_growth_ratio) / 100.0),
       heap_growth_max_(heap_growth_max),
@@ -1522,9 +1521,6 @@
 PageSpaceController::~PageSpaceController() {}
 
 bool PageSpaceController::ReachedHardThreshold(SpaceUsage after) const {
-  if (!is_enabled_) {
-    return false;
-  }
   if (heap_growth_ratio_ == 100) {
     return false;
   }
@@ -1532,9 +1528,6 @@
 }
 
 bool PageSpaceController::ReachedSoftThreshold(SpaceUsage after) const {
-  if (!is_enabled_) {
-    return false;
-  }
   if (heap_growth_ratio_ == 100) {
     return false;
   }
@@ -1542,9 +1535,6 @@
 }
 
 bool PageSpaceController::ReachedIdleThreshold(SpaceUsage current) const {
-  if (!is_enabled_) {
-    return false;
-  }
   if (heap_growth_ratio_ == 100) {
     return false;
   }
diff --git a/runtime/vm/heap/pages.h b/runtime/vm/heap/pages.h
index aff20f3..36e8ab2 100644
--- a/runtime/vm/heap/pages.h
+++ b/runtime/vm/heap/pages.h
@@ -270,10 +270,6 @@
 
   void set_last_usage(SpaceUsage current) { last_usage_ = current; }
 
-  void Enable() { is_enabled_ = true; }
-  void Disable() { is_enabled_ = false; }
-  bool is_enabled() { return is_enabled_; }
-
  private:
   friend class PageSpace;  // For MergeOtherPageSpaceController
 
@@ -285,8 +281,6 @@
 
   Heap* heap_;
 
-  bool is_enabled_;
-
   // Usage after last evaluated GC or last enabled.
   SpaceUsage last_usage_;
 
@@ -415,21 +409,6 @@
 
   void AddRegionsToObjectSet(ObjectSet* set) const;
 
-  void InitGrowthControl() {
-    page_space_controller_.set_last_usage(usage_);
-    page_space_controller_.Enable();
-  }
-
-  void SetGrowthControlState(bool state) {
-    if (state) {
-      page_space_controller_.Enable();
-    } else {
-      page_space_controller_.Disable();
-    }
-  }
-
-  bool GrowthControlState() { return page_space_controller_.is_enabled(); }
-
   // Note: Code pages are made executable/non-executable when 'read_only' is
   // true/false, respectively.
   void WriteProtect(bool read_only);
diff --git a/runtime/vm/heap/pages_test.cc b/runtime/vm/heap/pages_test.cc
index fed0a88..1db6113 100644
--- a/runtime/vm/heap/pages_test.cc
+++ b/runtime/vm/heap/pages_test.cc
@@ -10,7 +10,6 @@
 
 TEST_CASE(Pages) {
   PageSpace* space = new PageSpace(NULL, 4 * MBInWords);
-  space->InitGrowthControl();
   EXPECT(!space->Contains(reinterpret_cast<uword>(&space)));
   uword block = space->TryAllocate(8 * kWordSize);
   EXPECT(block != 0);
diff --git a/runtime/vm/heap/safepoint.cc b/runtime/vm/heap/safepoint.cc
index d75d06a..3853e9c 100644
--- a/runtime/vm/heap/safepoint.cc
+++ b/runtime/vm/heap/safepoint.cc
@@ -37,13 +37,10 @@
   IsolateGroup* IG = T->isolate_group();
   ASSERT(IG != NULL);
 
+  T->IncrementForceGrowthScopeDepth();
+
   auto handler = IG->safepoint_handler();
   handler->SafepointThreads(T, level_);
-
-  // N.B.: Change growth policy inside the safepoint to prevent racy access.
-  Heap* heap = IG->heap();
-  current_growth_controller_state_ = heap->GrowthControlState();
-  heap->DisableGrowthControl();
 }
 
 ForceGrowthSafepointOperationScope::~ForceGrowthSafepointOperationScope() {
@@ -52,16 +49,14 @@
   IsolateGroup* IG = T->isolate_group();
   ASSERT(IG != NULL);
 
-  // N.B.: Change growth policy inside the safepoint to prevent racy access.
-  Heap* heap = IG->heap();
-  heap->SetGrowthControlState(current_growth_controller_state_);
-
   auto handler = IG->safepoint_handler();
   handler->ResumeThreads(T, level_);
 
-  if (current_growth_controller_state_) {
+  T->DecrementForceGrowthScopeDepth();
+  if (!T->force_growth()) {
     ASSERT(T->CanCollectGarbage());
     // Check if we passed the growth limit during the scope.
+    Heap* heap = T->heap();
     if (heap->old_space()->ReachedHardThreshold()) {
       heap->CollectGarbage(T, GCType::kMarkSweep, GCReason::kOldSpace);
     } else {
diff --git a/runtime/vm/heap/scavenger.cc b/runtime/vm/heap/scavenger.cc
index 5bce434..8dc0cfd 100644
--- a/runtime/vm/heap/scavenger.cc
+++ b/runtime/vm/heap/scavenger.cc
@@ -1678,7 +1678,7 @@
 
   AbandonRemainingTLAB(thread);
 
-  if (can_safepoint) {
+  if (can_safepoint && !thread->force_growth()) {
     ASSERT(thread->no_safepoint_scope_depth() == 0);
     heap_->CheckConcurrentMarking(thread, GCReason::kNewSpace, kNewPageSize);
   }
diff --git a/runtime/vm/heap/scavenger.h b/runtime/vm/heap/scavenger.h
index 3c1051c..43eac30 100644
--- a/runtime/vm/heap/scavenger.h
+++ b/runtime/vm/heap/scavenger.h
@@ -349,16 +349,6 @@
   void MakeNewSpaceIterable();
   int64_t FreeSpaceInWords(Isolate* isolate) const;
 
-  void InitGrowthControl() {
-    growth_control_ = true;
-  }
-
-  void SetGrowthControlState(bool state) {
-    growth_control_ = state;
-  }
-
-  bool GrowthControlState() { return growth_control_; }
-
   bool scavenging() const { return scavenging_; }
 
   // The maximum number of Dart mutator threads we allow to execute at the same
@@ -457,10 +447,6 @@
   RelaxedAtomic<bool> failed_to_promote_;
   RelaxedAtomic<bool> abort_;
 
-  // When the isolate group is ready it will enable growth control via
-  // InitGrowthControl.
-  bool growth_control_ = false;
-
   // Protects new space during the allocation of new TLABs
   mutable Mutex space_lock_;
 
diff --git a/runtime/vm/isolate_reload.cc b/runtime/vm/isolate_reload.cc
index f182dce..f40c12b 100644
--- a/runtime/vm/isolate_reload.cc
+++ b/runtime/vm/isolate_reload.cc
@@ -753,7 +753,7 @@
           // "become" operation below replaces all the instances of the old
           // size with forwarding corpses. Force heap growth to prevent size
           // confusion during this period.
-          NoHeapGrowthControlScope scope;
+          ForceGrowthScope force_growth(thread);
           // The HeapIterationScope above ensures no other GC tasks can be
           // active.
           ASSERT(HasNoTasks(heap));
diff --git a/runtime/vm/thread.h b/runtime/vm/thread.h
index 906efbf..5f9660d 100644
--- a/runtime/vm/thread.h
+++ b/runtime/vm/thread.h
@@ -586,17 +586,25 @@
   }
 
   int32_t no_callback_scope_depth() const { return no_callback_scope_depth_; }
-
   void IncrementNoCallbackScopeDepth() {
     ASSERT(no_callback_scope_depth_ < INT_MAX);
     no_callback_scope_depth_ += 1;
   }
-
   void DecrementNoCallbackScopeDepth() {
     ASSERT(no_callback_scope_depth_ > 0);
     no_callback_scope_depth_ -= 1;
   }
 
+  bool force_growth() const { return force_growth_scope_depth_ != 0; }
+  void IncrementForceGrowthScopeDepth() {
+    ASSERT(force_growth_scope_depth_ < INT_MAX);
+    force_growth_scope_depth_ += 1;
+  }
+  void DecrementForceGrowthScopeDepth() {
+    ASSERT(force_growth_scope_depth_ > 0);
+    force_growth_scope_depth_ -= 1;
+  }
+
   bool is_unwind_in_progress() const { return is_unwind_in_progress_; }
 
   void StartUnwindError() {
@@ -1219,6 +1227,7 @@
   mutable Monitor thread_lock_;
   ApiLocalScope* api_reusable_scope_;
   int32_t no_callback_scope_depth_;
+  int32_t force_growth_scope_depth_ = 0;
   intptr_t no_reload_scope_depth_ = 0;
   intptr_t stopped_mutators_scope_depth_ = 0;
 #if defined(DEBUG)
diff --git a/runtime/vm/thread_test.cc b/runtime/vm/thread_test.cc
index 5faa11b..796faa2 100644
--- a/runtime/vm/thread_test.cc
+++ b/runtime/vm/thread_test.cc
@@ -303,9 +303,9 @@
   intptr_t done_count = 0;
   bool wait = true;
 
-  EXPECT(isolate->group()->heap()->GrowthControlState());
+  EXPECT(!thread->force_growth());
 
-  NoHeapGrowthControlScope no_heap_growth_scope;
+  ForceGrowthScope no_heap_growth_scope(thread);
 
   for (intptr_t i = 0; i < kTaskCount; i++) {
     Dart::thread_pool()->Run<SimpleTaskWithZoneAllocation>(
diff --git a/tools/VERSION b/tools/VERSION
index 702aecd..706b141 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 18
 PATCH 0
-PRERELEASE 239
+PRERELEASE 240
 PRERELEASE_PATCH 0
\ No newline at end of file