Skip to content

Commit

Permalink
Add constructor for retaining path. (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
polina-c authored Jun 2, 2023
1 parent b70e538 commit f17da61
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 20 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# 7.0.4

* Fix path collection.
* Create constructor to collect path.

# 7.0.3

* Fix connection issue.
Expand Down
12 changes: 6 additions & 6 deletions lib/src/leak_tracking/_object_tracker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -186,14 +186,14 @@ class ObjectTracker implements LeakProvider {
}

Future<void> _addRetainingPath(List<int> objectsToGetPath) async {
final pathObtainers = objectsToGetPath.map((code) async {
final pathSetters = objectsToGetPath.map((code) async {
final record = _objects.notGCed[code]!;
record.setContext(
ContextKeys.retainingPath,
await obtainRetainingPath(record.type, record.code),
);
final path = await obtainRetainingPath(record.type, record.code);
if (path != null) {
record.setContext(ContextKeys.retainingPath, path);
}
});
await Future.wait(pathObtainers);
await Future.wait(pathSetters);
}

ObjectRecord _notGCed(int code) {
Expand Down
21 changes: 19 additions & 2 deletions lib/src/leak_tracking/leak_tracker_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ class LeakTrackingConfiguration {
/// Customized configuration is needed only for test debugging,
/// not for regular test runs.
class LeakTrackingTestConfig {
/// Creates a new instance of [LeakTrackingFlutterTestConfig].
/// Creates a new instance of [LeakTrackingTestConfig].
const LeakTrackingTestConfig({
this.leakDiagnosticConfig = const LeakDiagnosticConfig(),
this.onLeaks,
Expand All @@ -122,7 +122,10 @@ class LeakTrackingTestConfig {
this.notDisposedAllowList = const <String, int>{},
});

/// Creates a new instance of [LeakTrackingFlutterTestConfig] for debugging leaks.
/// Creates a new instance of [LeakTrackingTestConfig] for debugging leaks.
///
/// This configuration will collect stack traces on start and disposal,
/// and retaining path for notGCed objects.
LeakTrackingTestConfig.debug({
this.leakDiagnosticConfig = const LeakDiagnosticConfig(
collectStackTraceOnStart: true,
Expand All @@ -135,6 +138,20 @@ class LeakTrackingTestConfig {
this.notDisposedAllowList = const <String, int>{},
});

/// Creates a new instance of [LeakTrackingTestConfig] to collect retaining path.
///
/// This configuration will not collect stack traces,
/// and will collect retaining path for notGCed objects.
LeakTrackingTestConfig.retainingPath({
this.leakDiagnosticConfig = const LeakDiagnosticConfig(
collectRetainingPathForNonGCed: true,
),
this.onLeaks,
this.failTestOnLeaks = true,
this.notGCedAllowList = const <String, int>{},
this.notDisposedAllowList = const <String, int>{},
});

/// If true, a warning will be printed when leak tracking is
/// requested for a non-supported platform.
static bool warnForNonSupportedPlatforms = true;
Expand Down
14 changes: 8 additions & 6 deletions lib/src/leak_tracking/orchestration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class MemoryLeaksDetectedError extends StateError {
/// });
/// ```
///
/// If you use [withLeakTracking] inside [testWidget], pass [tester.runAsync]
/// If you use [withLeakTracking] inside `testWidget`, pass `tester.runAsync`
/// as [asyncCodeRunner] to run asynchronous leak detection after the
/// test code execution:
///
Expand Down Expand Up @@ -89,8 +89,8 @@ Future<Leaks> withLeakTracking(
await callback();
callback = null;

asyncCodeRunner ??= (action) => action();
late Leaks leaks;
asyncCodeRunner ??= (action) async => await action();
Leaks? leaks;

await asyncCodeRunner(
() async {
Expand All @@ -107,15 +107,17 @@ Future<Leaks> withLeakTracking(

leaks = await collectLeaks();

if (leaks.total > 0 && shouldThrowOnLeaks) {
if ((leaks?.total ?? 0) > 0 && shouldThrowOnLeaks) {
// `expect` should not be used here, because, when the method is used
// from Flutter, the packages `test` and `flutter_test` conflict.
throw MemoryLeaksDetectedError(leaks);
throw MemoryLeaksDetectedError(leaks!);
}
},
);

return leaks;
// `tester.runAsync` does not throw in case of errors, but collect them other way.
if (leaks == null) throw StateError('Leaks collection failed.');
return leaks!;
} finally {
disableLeakTracking();
}
Expand Down
6 changes: 2 additions & 4 deletions lib/src/leak_tracking/retaining_path/_retaining_path.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ import 'package:vm_service/vm_service.dart';

import '_connection.dart';

Future<RetainingPath> obtainRetainingPath(Type type, int code) async {
Future<RetainingPath?> obtainRetainingPath(Type type, int code) async {
final connection = await connect();

final fp = _ObjectFingerprint(type, code);
final theObject = await _objectInIsolate(connection, fp);
if (theObject == null) {
throw Exception('Could not find object in heap');
}
if (theObject == null) return null;

final result = await connection.service.getRetainingPath(
theObject.isolateId,
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: leak_tracker
version: 7.0.3
version: 7.0.4
description: A framework for memory leak tracking for Dart and Flutter applications.
repository: https://github.com/dart-lang/leak_tracker

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,24 @@ void main() {
final instance = MyClass();

final path = await obtainRetainingPath(MyClass, identityHashCode(instance));
expect(path.elements, isNotEmpty);
expect(path!.elements, isNotEmpty);
});

test('Connection is happening just once', () async {
final instance1 = MyClass();
final instance2 = MyClass();

final obtainers = [
obtainRetainingPath(MyClass, identityHashCode(instance1)),
obtainRetainingPath(MyClass, identityHashCode(instance2)),
];

await Future.wait(obtainers);

expect(
_logs.where((item) => item == 'Connecting to vm service protocol...'),
hasLength(1),
);
});

test('Connection is happening just once', () async {
Expand Down

0 comments on commit f17da61

Please sign in to comment.