You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Dart如何检测未完成Future及泄漏资源解决程序挂起问题

Dart CLI应用挂起资源泄漏检测方案

开发调用第三方库的Dart控制台应用时,第三方方法执行返回后CLI会挂起约10秒才退出,根因是第三方库创建的资源未正常关闭、异步任务未执行完成。目前已通过runZonedGuarded配合ZoneSpecification实现了未取消定时器的检测与自动取消,简单场景下验证有效,但覆盖场景不全,需要补充未完成Future、其他类型资源泄漏的检测能力。

已实现的定时器检测试验代码

#! /usr/bin/env dcli

import 'dart:async';

import 'package:dcli/dcli.dart';
import 'package:onepub/src/pub/global_packages.dart';
import 'package:onepub/src/pub/system_cache.dart';
import 'package:onepub/src/version/version.g.dart';
import 'package:pub_semver/pub_semver.dart';

void main(List<String> arguments) async {
  print(orange('OnePub version: $packageVersion '));

  print('');

  print(globals);

  // await globals.repairActivatedPackages();

  await runZonedGuarded(() async {
    Timer(Duration(seconds: 20), () => print('timer done'));
    unawaited(Future.delayed(Duration(seconds: 20)));
    var completer = Completer();
    unawaited(
        Future.delayed(Duration(seconds: 20), () => completer.complete()));

    //   await globals.activateHosted(
    //     'dcli_unit_tester',
    //     VersionConstraint.any,
    //     null, // all executables
    //     overwriteBinStubs: true,
    //     url: null, // hostedUrl,
    //   );
    print('end activate');
  }, (error, stackTrace) {
    print('Uncaught error: $error');
  }, zoneSpecification: buildZoneSpec());

  print('end');

  checkLeaks();

  // await entrypoint(arguments, CommandSet.ONEPUB, 'onepub');
}

late final SystemCache cache = SystemCache(isOffline: false);
GlobalPackages? _globals;
GlobalPackages get globals => _globals ??= GlobalPackages(cache);

List<void Function()> actions = [];
List<Source<Timer>> timers = [];

int testCounter = 0;
int timerCount = 0;
int periodicCallbacksCount = 0;
int microtasksCount = 0;

ZoneSpecification buildZoneSpec() {
  return ZoneSpecification(
    createTimer: (source, parent, zone, duration, f) {
      timerCount += 1;
      final result = parent.createTimer(zone, duration, f);
      timers.add(Source(result));
      return result;
    },
    createPeriodicTimer: (source, parent, zone, period, f) {
      periodicCallbacksCount += 1;
      final result = parent.createPeriodicTimer(zone, period, f);
      timers.add(Source(result));
      return result;
    },
    scheduleMicrotask: (source, parent, zone, f) {
      microtasksCount += 1;
      actions.add(f);
      final result = parent.scheduleMicrotask(zone, f);
      return result;
    },
  );
}

void checkLeaks() {
  print(actions.length);
  print(timers.length);

  print('testCounter $testCounter');
  print('timerCount $timerCount');
  print('periodicCallbacksCount $periodicCallbacksCount');
  print('microtasksCount $microtasksCount');

  for (var timer in timers) {
    if (timer.source.isActive) {
      print('Active Timer: ${timer.st}');
      timer.source.cancel();
    }
  }
}

class Source<T> {
  Source(this.source) {
    st = StackTrace.current;
  }
  T source;
  late StackTrace st;
}

已验证的问题场景

实际测试中捕获到HTTP连接空闲保活定时器导致的挂起,对应堆栈如下,印证了HTTP连接未正确关闭的问题:

Active Timer: #0      new Source (file:///home/bsutton/git/onepub/onepub/bin/onepub.dart:105:21)
#1      buildZoneSpec.<anonymous closure> (file:///home/bsutton/git/onepub/onepub/bin/onepub.dart:68:18)
#2      _CustomZone.createTimer (dart:async/zone.dart:1388:19)
#3      new Timer (dart:async/timer.dart:54:10)
#4      _HttpClientConnection.startTimer (dart:_http/http_impl.dart:2320:18)
#5      _ConnectionTarget.returnConnection (dart:_http/http_impl.dart:2381:16)
#6      _HttpClient._returnConnection (dart:_http/http_impl.dart:2800:41)
#7      _HttpClientConnection.send.<anonymous closure>.<anonymous closure>.<anonymous closure> (dart:_http/http_impl.dart:2171:25)
#8      _rootRunUnary (dart:async/zone.dart:1434:47)

分类型泄漏检测实现方案

1. 未完成Future检测

Dart没有提供直接枚举所有未完成Future的内置API,必须通过Zone层拦截包装实现追踪

  • 在ZoneSpecification中重写registerCallback、registerUnaryCallback、registerBinaryCallback三个回调注册入口,给所有Zone内注册的Future回调绑定创建堆栈、分配唯一追踪ID
  • 给追踪Zone内创建的Future统一注册完成回调,Future正常执行完成/报错时标记为已结束,泄漏检查阶段输出所有未标记完成的Future的创建堆栈
  • 针对Completer实例做额外拦截:包装completer.complete、completer.completeError方法,追踪未触发完成的Completer——绝大多数长期挂起的Future本质都是未执行complete的Completer实例

2. 其他常见资源泄漏检测

  • HTTP客户端类资源:除了已捕获的连接保活定时器,可在Zone中拦截HttpClient的创建逻辑,统一维护当前Zone内创建的客户端实例列表,执行泄漏检查时遍历调用close(force: true)强制关闭,避免空闲连接保活逻辑阻塞程序退出
  • 文件句柄/网络IO资源:拦截File.open、File.openRead、File.openWrite、Socket、RawSocket、RawDatagramSocket等IO资源的创建方法,追踪未调用close()的活跃句柄,退出阶段统一释放
  • Isolate泄漏:拦截Isolate.spawn、Isolate.spawnUri的创建逻辑,记录所有派生的子Isolate,退出检查阶段遍历调用kill(priority: Isolate.immediate)强制终止,避免子Isolate事件循环存活阻塞主Isolate退出
  • 流订阅泄漏:拦截Stream.listen调用,记录当前活跃的StreamSubscription实例,退出阶段统一调用cancel()取消订阅,避免流持有资源不释放

3. 快速退出兜底方案

如果不需要保证所有资源优雅释放,业务逻辑执行完成后可直接调用exit(0)立即终止进程,跳过Dart VM等待所有异步资源清空的逻辑。注意该方法会直接终止进程,不会执行任何未完成的异步回调、IO flush操作,需根据业务场景选择使用。

内容的提问来源于stack exchange,提问作者Brett Sutton

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 00:36:20