Skip to content

Commit c30f51d

Browse files
authored
Prevent GC of value during async eval (#9885)
1 parent ee63060 commit c30f51d

3 files changed

Lines changed: 22 additions & 3 deletions

File tree

packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ TODO: Remove this section if there are not any updates.
3535

3636
## Debugger updates
3737

38-
TODO: Remove this section if there are not any updates.
38+
* Prevent values from being garbage-collected, while being evaluated.
39+
[#9885](https://github.com/flutter/devtools/pull/9885)
3940

4041
## Network profiler updates
4142

packages/devtools_app_shared/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ found in the LICENSE file or at https://developers.google.com/open-source/licens
66
## 0.5.2-wip
77
* Fix a `RangeError` thrown by `SplitPane` when the number of children
88
changes between rebuilds.
9+
* Fix garbage collection issues with the result list in `asyncEval` on both native VM and web.
910
* The minimum Dart SDK version is bumped to 3.11.0.
1011
* The minimum Flutter SDK version is bumped to 3.41.0.
1112

packages/devtools_app_shared/lib/src/service/eval_on_dart_library.dart

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
// This code is directly based on src/io/flutter/inspector/EvalOnDartLibrary.java
66
// If you add a method to this class you should also add it to EvalOnDartLibrary.java
77
import 'dart:async';
8-
import 'dart:core' hide Error;
98
import 'dart:core' as core;
9+
import 'dart:core' hide Error;
1010
import 'dart:math';
1111

1212
import 'package:flutter/foundation.dart';
1313
import 'package:logging/logging.dart';
1414
import 'package:meta/meta.dart';
15-
import 'package:vm_service/vm_service.dart' hide Error;
1615
import 'package:vm_service/vm_service.dart' as vm_service;
16+
import 'package:vm_service/vm_service.dart' hide Error;
1717

1818
import '../utils/auto_dispose.dart';
1919
import 'service_manager.dart';
@@ -429,6 +429,22 @@ class EvalOnDartLibrary extends DisposableController
429429
await safeEval(
430430
'() async {'
431431
' final reader = widgetInspectorService.toObject("$readerId", "$readerGroup") as List;'
432+
' /* Keep a strong reference to `reader` in the target app to prevent it'
433+
' from being garbage collected by Chrome/VM before the future resolves.'
434+
' Without this, the reader is only weakly referenced by the inspector'
435+
' service and is aggressively GCed, causing a TypeError/TimeoutException'
436+
' or failing the assertion that the retrieved result length is 1 or 2.'
437+
' We use Future.delayed in a loop instead of Timer because Future is in'
438+
' dart:core and guaranteed to be resolved without requiring dart:async. */'
439+
' bool isDone = false;'
440+
' () async {'
441+
' int bufferTicks = 0;'
442+
' /* Stop pinning after a 1-second buffer when the future has completed. */'
443+
' while (!isDone && ++bufferTicks <=20) {'
444+
' final _ = reader;'
445+
' await Future.delayed(const Duration(milliseconds: 50));'
446+
' }'
447+
' }();'
432448
' try {'
433449
// Cast as dynamic so that it is possible to await Future<void>
434450
' dynamic result = ($expression) as dynamic;'
@@ -437,6 +453,7 @@ class EvalOnDartLibrary extends DisposableController
437453
' reader.add(err);'
438454
' reader.add(stack);'
439455
' } finally {'
456+
' isDone = true;'
440457
' postEvent("future_completed", {"future_id": $futureId, "client_id": $_clientId});'
441458
' }'
442459
'}()',

0 commit comments

Comments
 (0)