import 'dart:async';
import 'dart:collection';
import 'dart:ffi';
import 'package:flutter/widgets.dart';
import 'leak_detector_task.dart';
import 'leak_data.dart';
import 'leak_record_handler.dart';
import 'vm_service_utils.dart';
typedef LeakEventListener = void Function(DetectorEvent event);
class LeakDetector {
static LeakDetector? _instance;
static int? maxRetainingPath;
Map<String, Expando> _watchGroup = {};
Queue<DetectorTask> _checkTaskQueue = Queue();
StreamController<LeakedInfo> _onLeakedStreamController =
StreamController.broadcast();
StreamController<DetectorEvent> _onEventStreamController =
StreamController.broadcast();
DetectorTask? _currentTask;
Stream<LeakedInfo> get onLeakedStream => _onLeakedStreamController.stream;
Stream<DetectorEvent> get onEventStream => _onEventStreamController.stream;
factory LeakDetector() {
_instance ??= LeakDetector._();
return _instance!;
}
void init({int maxRetainingPath = 300}) {
LeakDetector.maxRetainingPath = maxRetainingPath;
}
LeakDetector._() {
assert(() {
VmServerUtils().getVmService();
onLeakedStream
.listen(saveLeakedRecord);
return true;
}());
}
ensureReleaseAsync(String? group, {int delay = 0}) async {
Expando? expando = _watchGroup[group];
_watchGroup.remove(group);
if (expando != null) {
Timer(Duration(milliseconds: delay), () async {
_checkTaskQueue.add(
DetectorTask(
expando,
sink: _onEventStreamController.sink,
onStart: () => _onEventStreamController
.add(DetectorEvent(DetectorEventType.check, data: group)),
onResult: () {
_currentTask = null;
_checkStartTask();
},
onLeaked: (LeakedInfo? leakInfo) {
if (leakInfo != null && leakInfo.isNotEmpty) {
_onLeakedStreamController.add(leakInfo);
}
},
),
);
expando = null;
_checkStartTask();
});
}
}
void _checkStartTask() {
if (_checkTaskQueue.isNotEmpty && _currentTask == null) {
_currentTask = _checkTaskQueue.removeFirst();
_currentTask?.start();
}
}
addWatchObject(Object obj, String group) {
if (LeakDetector.maxRetainingPath == null) return;
_onEventStreamController
.add(DetectorEvent(DetectorEventType.addObject, data: group));
_checkType(obj);
String key = group;
Expando? expando = _watchGroup[key];
expando ??= Expando('LeakChecker$key');
expando[obj] = true;
_watchGroup[key] = expando;
}
static _checkType(object) {
if ((object == null) ||
(object is bool) ||
(object is num) ||
(object is String) ||
(object is Pointer) ||
(object is Struct)) {
throw new ArgumentError.value(object,
"Expandos are not allowed on strings, numbers, booleans, null, Pointers, Structs or Unions.");
}
}
}
class DetectorEvent {
final DetectorEventType type;
final dynamic data;
@override
String toString() {
return '$type, $data';
}
DetectorEvent(this.type, {this.data});
}
enum DetectorEventType {
addObject,
check,
startGC,
endGc,
startAnalyze,
endAnalyze,
}