Leadaxe 记录 LxBox 空闲后停止卡住,内核原因仍待抓栈确认

Leadaxe 在 sing-box-lx #50 中记录:一份 Android LxBox 代理模式(无 TUN)、使用 WireGuard/AmneziaWG/WARP 端点的现场报告称,设备空闲约 6 小时后停止服务会一直停在 Stopping;目前没有 logcat 或 goroutine dump。作者说明 LxBox 端已有强停绕过处理,但内核关闭仍会阻塞。仅按代码阅读,他怀疑端点关闭的锁或等待、以及 detour 连接的 ReadFrom 未被唤醒,并提出关闭期限、带期限获取锁和主动关闭连接等候选方案。他明确要求先等 doStop 看门狗抓到设备栈再按证据修复;上述原因及改法尚未确认。

作者原文

Symptom

LxBox on Android, proxy mode (no tun), config with WireGuard/AmneziaWG/WARP endpoints. After ~6 h of device idle, stopping the service never completes: closeCommandServerAtomic → CommandServer.closeService() does not return, status stays Stopping until the process is killed. One field report, no logcat or goroutine dump.

LxBox already works around it on its side (LxBox task 663: force-stop reaches the service even after its receiver is gone; a follow-up adds timeouts and a stuck-step watchdog with a goroutine dump to doStop). The core itself still blocks.

Hypothesis (code reading only, not confirmed)

started_service.go:341 instance.Close → box.go:698 scope.Close → adapter/endpoint/manager_close_lx.go:51 group.Run (no deadline) → WireGuard Endpoint.Close blocks on one of:

  • protocol/wireguard/endpoint.go:513 bindAccess.Lock
  • transport/wireguard/endpoint.go:463 pauseOpAccess.Lock
  • wireguard-go/device.go:789 stopping.Wait

held by a receive worker parked in client_bind.go:180 ReadFrom on a detour conn that Close() does not wake (same class as SPEC 072; its "deliberately not done" assumed bounded dial, but ReadFrom on an established detour conn is not covered by that deadline). Overnight Doze pause/wake cycles and network changes (route/network.go:468) make this likelier. Since CloseService clears s.instance before Close (started_service.go:338), later wake/resetNetwork calls from the platform become no-ops, so it never recovers.

Secondary: CloseService waiting on lifecycleAccess (started_service.go:331) held by a stuck StartOrReloadService (:252) whose old-instance Close is the same hang.

To do once a device dump arrives

Fix by evidence, not by hypothesis. Wait for a goroutine dump from LxBox's doStop watchdog (stuck at closeCommandServer + PProfClient.goroutineDump). Expected stacks if the hypothesis holds: closeBindLocked → WaitGroup.Wait with a worker in ClientBind.receive/ReadFrom, or Endpoint.Close → Mutex.Lock with the holder in updateBind/resumeOnDial.

Candidate fixes:

  • closeEndpoints: run with a context deadline instead of an unbounded group.Run;
  • Endpoint.Close: TryLock on bindAccess with a deadline, and close the detour conn so ReadFrom returns;
  • CloseService: bounded wait on lifecycleAccess.