diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md index ebb8dad9043..f018f749165 100644 --- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md +++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md @@ -35,7 +35,7 @@ TODO: Remove this section if there are not any updates. ## Debugger updates -TODO: Remove this section if there are not any updates. +* Prevent values from being garbage-collected, while being evaluated. ## Network profiler updates diff --git a/packages/devtools_app_shared/CHANGELOG.md b/packages/devtools_app_shared/CHANGELOG.md index b2529769d62..7dadf83d6ea 100644 --- a/packages/devtools_app_shared/CHANGELOG.md +++ b/packages/devtools_app_shared/CHANGELOG.md @@ -6,6 +6,7 @@ found in the LICENSE file or at https://developers.google.com/open-source/licens ## 0.5.2-wip * Fix a `RangeError` thrown by `SplitPane` when the number of children changes between rebuilds. +* Fix garbage collection issues with the result list in `asyncEval` on both native VM and web. * The minimum Dart SDK version is bumped to 3.11.0. * The minimum Flutter SDK version is bumped to 3.41.0. diff --git a/packages/devtools_app_shared/lib/src/service/eval_on_dart_library.dart b/packages/devtools_app_shared/lib/src/service/eval_on_dart_library.dart index 3de4f8608ec..8df2f921353 100644 --- a/packages/devtools_app_shared/lib/src/service/eval_on_dart_library.dart +++ b/packages/devtools_app_shared/lib/src/service/eval_on_dart_library.dart @@ -5,15 +5,15 @@ // This code is directly based on src/io/flutter/inspector/EvalOnDartLibrary.java // If you add a method to this class you should also add it to EvalOnDartLibrary.java import 'dart:async'; -import 'dart:core' hide Error; import 'dart:core' as core; +import 'dart:core' hide Error; import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; -import 'package:vm_service/vm_service.dart' hide Error; import 'package:vm_service/vm_service.dart' as vm_service; +import 'package:vm_service/vm_service.dart' hide Error; import '../utils/auto_dispose.dart'; import 'service_manager.dart'; @@ -429,6 +429,22 @@ class EvalOnDartLibrary extends DisposableController await safeEval( '() async {' ' final reader = widgetInspectorService.toObject("$readerId", "$readerGroup") as List;' + ' /* Keep a strong reference to `reader` in the target app to prevent it' + ' from being garbage collected by Chrome/VM before the future resolves.' + ' Without this, the reader is only weakly referenced by the inspector' + ' service and is aggressively GCed, causing a TypeError/TimeoutException' + ' or failing the assertion that the retrieved result length is 1 or 2.' + ' We use Future.delayed in a loop instead of Timer because Future is in' + ' dart:core and guaranteed to be resolved without requiring dart:async. */' + ' bool isDone = false;' + ' () async {' + ' int bufferTicks = 0;' + ' /* Stop pinning after a 1-second buffer when the future has completed. */' + ' while (!isDone && ++bufferTicks <=20) {' + ' final _ = reader;' + ' await Future.delayed(const Duration(milliseconds: 50));' + ' }' + ' }();' ' try {' // Cast as dynamic so that it is possible to await Future ' dynamic result = ($expression) as dynamic;' @@ -437,6 +453,7 @@ class EvalOnDartLibrary extends DisposableController ' reader.add(err);' ' reader.add(stack);' ' } finally {' + ' isDone = true;' ' postEvent("future_completed", {"future_id": $futureId, "client_id": $_clientId});' ' }' '}()',