Skip to content

Commit

Permalink
-
Browse files Browse the repository at this point in the history
  • Loading branch information
polina-c committed Jul 9, 2023
1 parent c9562b8 commit ac68c5c
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,17 @@ final _log = Logger('_connection.dart');
class Connection {
Connection(this.service, this.isolates);

final List<String> isolates;
final List<IsolateInfo> isolates;
final VmService service;
}

class IsolateInfo {
IsolateInfo({required this.id, required this.name});

final String id;
final String name;
}

Completer<Connection>? _completer;

void disconnect() => _completer = null;
Expand Down Expand Up @@ -45,7 +52,7 @@ Future<Connection> connect() async {
throw error ?? Exception('Error connecting to service protocol');
});
await service.getVersion(); // Warming up and validating the connection.
final isolates = await _getIdForTwoIsolates(service);
final isolates = await _getTwoIsolates(service);

final result = Connection(service, isolates);
completer.complete(result);
Expand All @@ -57,10 +64,10 @@ Future<Connection> connect() async {
/// Depending on environment (command line / IDE, Flutter / Dart), isolates may have different names,
/// and there can be one or two. Sometimes the second one appears with latency.
/// And sometimes there are two isolates with name 'main'.
Future<List<String>> _getIdForTwoIsolates(VmService service) async {
Future<List<IsolateInfo>> _getTwoIsolates(VmService service) async {
_log.info('Started loading isolates...');

final result = <String>[];
final result = <IsolateInfo>[];

const isolatesToGet = 2;
const watingTime = Duration(seconds: 2);
Expand All @@ -69,7 +76,7 @@ Future<List<String>> _getIdForTwoIsolates(VmService service) async {
result.clear();
await _forEachIsolate(
service,
(IsolateRef r) async => result.add(r.id!),
(IsolateRef r) async => result.add(IsolateInfo(id: r.id!, name: r.name!)),
);
if (result.length < isolatesToGet) {
await Future.delayed(const Duration(milliseconds: 100));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Future<RetainingPath?> obtainRetainingPath(Type type, int code) async {
if (theObject == null) return null;

final result = await connection.service.getRetainingPath(
theObject.isolateId,
theObject.isolateInfo.id,
theObject.itemId,
100000,
);
Expand Down Expand Up @@ -47,7 +47,7 @@ Future<_ItemInIsolate?> _objectInIsolate(
print('looking for ${theClass.name}');

final instances = (await connection.service.getInstances(
theClass.isolateId,
theClass.isolateInfo.id,
theClass.itemId,
pathLengthLimit,
))
Expand All @@ -59,7 +59,8 @@ Future<_ItemInIsolate?> _objectInIsolate(
if (result != null) {
throw ('found!!!! for ${theClass.name}');

return _ItemInIsolate(isolateId: theClass.isolateId, itemId: result.id!);
return _ItemInIsolate(
isolateInfo: theClass.isolateInfo, itemId: result.id!);
}
}
throw ('not found!!!!');
Expand All @@ -70,10 +71,10 @@ Future<_ItemInIsolate?> _objectInIsolate(
///
/// It can be class or object.
class _ItemInIsolate {
_ItemInIsolate({required this.isolateId, required this.itemId, this.name});
_ItemInIsolate({required this.isolateInfo, required this.itemId, this.name});

/// Id of the isolate.
final String isolateId;
/// The isolate.
final IsolateInfo isolateInfo;

/// Id of the item in the isolate.
final String itemId;
Expand All @@ -88,16 +89,16 @@ Future<List<_ItemInIsolate>> _findClasses(
) async {
final result = <_ItemInIsolate>[];

for (final isolateId in connection.isolates) {
var classes = await connection.service.getClassList(isolateId);
for (final isolateInfo in connection.isolates) {
var classes = await connection.service.getClassList(isolateInfo.id);

const watingTime = Duration(seconds: 2);
final stopwatch = Stopwatch()..start();

// In the beginning list of classes may be empty.
while (classes.classes?.isEmpty ?? true && stopwatch.elapsed < watingTime) {
await Future.delayed(const Duration(milliseconds: 100));
classes = await connection.service.getClassList(isolateId);
classes = await connection.service.getClassList(isolateInfo.id);
}
if (classes.classes?.isEmpty ?? true) {
throw StateError('Could not get list of classes.');
Expand All @@ -113,7 +114,7 @@ Future<List<_ItemInIsolate>> _findClasses(
filtered.map(
(classRef) => _ItemInIsolate(
itemId: classRef.id!,
isolateId: isolateId,
isolateInfo: isolateInfo,
name: classRef.name,
),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

import 'dart:async';

import 'package:collection/collection.dart';
import 'package:leak_tracker/src/leak_tracking/retaining_path/_connection.dart';
import 'package:leak_tracker/src/leak_tracking/retaining_path/_retaining_path.dart';
import 'package:logging/logging.dart';
import 'package:test/test.dart';
import 'package:vm_service/vm_service.dart' hide LogRecord;

class MyClass {
MyClass();
Expand Down Expand Up @@ -44,37 +46,67 @@ void main() {
expect(path!.elements, isNotEmpty);
});

ObjRef? _find(List<ObjRef> instances, int code) {
return instances.firstWhereOrNull(
(ObjRef objRef) =>
objRef is InstanceRef && objRef.identityHashCode == code,
);
}

test(
'Instance of array is found.',
() async {
final myClass = MyClass();
final instance = <int>[1, 2, 3, 4, 5];
ObjRef? myClassRef;

final myList = <int>[1, 2, 3, 4, 5];
ObjRef? myListRef;

final connection = await connect();
print(connection.isolates.length);
final isolateId = connection.isolates[0];
var classList = await connection.service.getClassList(isolateId);

// In the beginning list of classes may be empty.
while (classList.classes?.isEmpty ?? true) {
await Future.delayed(const Duration(milliseconds: 100));
classList = await connection.service.getClassList(isolateId);
print(connection.isolates.map((i) => '${i.name}-${i.id}'));

for (final isolate in connection.isolates) {
var classList = await connection.service.getClassList(isolate.id);
// In the beginning list of classes may be empty.
while (classList.classes?.isEmpty ?? true) {
await Future.delayed(const Duration(milliseconds: 100));
classList = await connection.service.getClassList(isolate.id);
}
if (classList.classes?.isEmpty ?? true) {
throw StateError('Could not get list of classes.');
}

final classes = classList.classes!;

for (final theClass in classes) {
const pathLengthLimit = 10000000;

// TODO(polina-c): remove when issue is fixed
// https://github.com/dart-lang/sdk/issues/52893
if (theClass.name == 'TypeParameters') continue;

final instances = (await connection.service.getInstances(
isolate.id,
theClass.id!,
pathLengthLimit,
))
.instances ??
<ObjRef>[];

myClassRef ??= _find(instances, identityHashCode(myClass));
myListRef ??= _find(instances, identityHashCode(myList));

if (myClassRef != null && myListRef != null) {
throw 'Found both instances!!!';
}
}
}
if (classList.classes?.isEmpty ?? true) {
throw StateError('Could not get list of classes.');
}

final classes = classList.classes!;

final path = await obtainRetainingPath(
instance.runtimeType, identityHashCode(instance));
print(instance);
instance.add(7);
expect(path!.elements, isNotEmpty);
print('myClassRef: $myClassRef, myListRef: $myListRef');

// To make sure instance is not const.
instance.add(6);
instance.add(7);
// To make sure [myList] is not const.
myList.add(6);
myList.add(7);
},
timeout: const Timeout(Duration(minutes: 20)),
);
Expand Down

0 comments on commit ac68c5c

Please sign in to comment.