fix(client): 切后台停看门狗 — 后台 urltest 被限流不再误判节点死断连
真机(Honor/MagicOS)复现:连上后切后台 ~1min 必断,报「当前节点异常」。 logcat 铁证:进程存活、Dart 看门狗后台照跑, [Watchdog] urltest stale 59s → upstream dead → unhealthy → doStop 根因:后台(Doze)把 urltest 主动探测限流 → urltest 停更,但 speed 帧仍在流 → statsLive=true + urltest 陈旧 → 路径A 误判上游死,把正常隧道断掉。上次的 statsLive 闸只挡「stats 整体停」(macOS 睡眠),挡不住「speed 在流、只 urltest 停」。 修:看门狗是前台 UX 功能(发现节点死好让用户换),后台无 UI 可响应、urltest 必被限流 → AppLifecycleListener 在 paused/hidden 停看门狗,resumed 且仍连接则重启(基准由 _onStats 首帧/gap 重置)。真·内核掉线(路径A',事件驱动)不受影响,仍上报。 测试:connection_watchdog 新增「切后台停看门狗」回归(后台 speed 在流+urltest 停 >45s 不切节点);全 7 例过。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -203,6 +203,36 @@ void main() {
|
||||
await tester.pump();
|
||||
});
|
||||
|
||||
testWidgets('切后台停看门狗:后台 urltest 停而 speed 在流,不误判断连', (tester) async {
|
||||
final bridge = _FakeBridge();
|
||||
var fake = DateTime(2026, 1, 1);
|
||||
final c = makeContainer(bridge, now: () => fake); // 都 up,排除路径B
|
||||
addTearDown(bridge.dispose);
|
||||
addTearDown(c.dispose);
|
||||
await tester.pumpWidget(UncontrolledProviderScope(container: c, child: const SizedBox()));
|
||||
c.read(selectedNodeCodeProvider.notifier).select('HK');
|
||||
await driveOn(tester, c, bridge);
|
||||
bridge.emitStats(urltestFrame(20)); // lastOk=t0
|
||||
await tester.pump();
|
||||
// 切后台(须走合法生命周期序列 resumed→inactive→hidden→paused)。
|
||||
tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.inactive);
|
||||
tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.hidden);
|
||||
tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.paused);
|
||||
await tester.pump();
|
||||
// 复现 Android 后台:speed 帧继续(每 8s、statsLive),urltest 停,墙上时钟走 >45s。
|
||||
// 看门狗若仍在跑会误判 path A 切到 JP;已停则不动。
|
||||
for (var i = 0; i < 8; i++) {
|
||||
fake = fake.add(const Duration(seconds: 8));
|
||||
bridge.emitStats(speedOnlyFrame);
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 15));
|
||||
}
|
||||
expect(c.read(selectedNodeCodeProvider), 'HK', reason: '后台看门狗已停,不应误判 path A 切节点');
|
||||
expect(c.read(connectionProvider).phase, VpnPhase.on, reason: '隧道保持连接');
|
||||
bridge.emit(VpnStatus.off); // 收尾
|
||||
await tester.pump();
|
||||
});
|
||||
|
||||
testWidgets('节点健康 → 保持连接,不触发任何动作', (tester) async {
|
||||
final bridge = _FakeBridge();
|
||||
final c = makeContainer(bridge); // 都 up
|
||||
|
||||
Reference in New Issue
Block a user