VPNCheap · 工程冲刺合集 · 2026-04 → 2026-05

从用户反馈到崩溃归零
一份完整的工程进度报告

把三件重要工程产出汇总在一份页面里:三周内关掉 18 个 GitHub issue; 通过 5 轮 codex 循环把五大平台的兼容性冲刺到零回归; 把 Sentry 上 28 个未解崩溃清理干净,并通过 7 轮代码审查把所有修复打磨到位。

18
用户 issue 修复
9
Sentry 已修复
12
codex 审查轮次
0
范围内未结案
01 / 用户问题修复

用户反馈 → 部署修复:三周内关掉 18 个 issue

GitHub Issue 模板上线后(2026-04-21),vpncheap 主仓库 + apple-native 一共收到 22 个用户反馈。 截至 2026-05-09:已落地修复 18 个,3 个为非问题或重复,1 个仍在分析中。 本节按主题分组展示修复,并对三类影响最大的根因做实时模拟。

已修复 · 已发布

Windows 启动黑框 / 白屏 / 双击吞掉

#1, #2, #3, #13, #16 · Windows · 1.3.4–1.3.6

4 起独立报告,根因是同一组打包 + 启动恢复缺陷:VC++ Redist 14.40+ DLL 没打进安装包、sing-box.dll 缺关键 build tag、应用实例已运行时 二次启动只调一次 FindWindow 后退出。修复:打包脚本拷贝 14 个 VC redist DLL、sing-box.dllwith_clash_api + with_gvisor + with_quic 完整 tag 重建并以 LFS 跟踪、main.cpp 改用 BringWindowToTop + SetForegroundWindow 拉起已有窗口。

commit a75a35bf · 2a3077e4 · e229f742v1.3.7+
已修复 · 已发布

Linux deb / AppImage 缺失 libapp.so

#18 · Linux · 1.3.6

Flutter 引擎报 Invalid ELF path:打包脚本的 fallback 允许在 release bundle 中省略 lib/libapp.so(AOT 数据)。修复:把 AOT 装载路径硬编码到 build/lib/libapp.so、删除 fallback、在 release workflow 与 build_linux.sh 中加一道 "libapp.so 缺失则失败" 的打包守卫。

commit 60301615v1.3.7
已修复 · 已发布

规则模式下 YouTube / 翻墙类应用不通

#17 · Windows · 1.3.6

DNS 路由没对齐 VilaNet PAC,海外站会先匹配 CN 段直连规则导致解析错节点。 修复:让规则模式 DNS 路由与 PAC 一致,海外解析强制走代理 DNS,内置 ruleset 缺失时回退到打包默认。

commit 4b1c2dbd · ce16ad8dv1.3.7+
已修复 · 已发布

iOS 链式代理失效 / 无法编辑

#9 · iOS · 1.0.1

点链式代理后 IP 没更新、点 Edit 没保存原内容、首页缺连接引导。 修复:恢复链式代理编辑器的状态恢复,改用 live apply 立即重连; 从底部导航移除链式代理(放进设置二级页),首页加显式连接按钮。

commit fcdb75d0 · 4752dfde · 0477165fv1.0.2
已修复 · 已发布

UI 全部按 iOS 布局重做

#4, #8 · 跨平台 · 1.3.4

把 Flutter 端 7 个核心页面 1:1 移植成 iOS 原生布局:首页连接卡片、节点列表、模式切换、 设置层级、订阅页、链式代理、关于页。phase 1-7 共合并 1 个 PR,后续根据 review 收紧 spacing、还原信号条、放大模式按钮高度到 84/52。

PR #27 · commit be786518v1.3.8
已修复 · 已发布

在线更新签名不一致 / 必须卸载重装

#5 · 跨平台 · 1.3.4

更新流程没传递正确的签名 hash,客户端校验报"签名不一致"。 修复:统一更新管道用 release 签名 hash,延迟显示从 0ms-aware 模式重写避免与更新检测争 UI。

commit a47e2e94 · 3cd06097v1.3.7
已修复 · 已发布

初次自动连接没有网络

#12 · Android · 1.3.5

节点没刷新就触发自动连接,选到了过期或无效的节点。 修复:auto-select 流程在选择节点前强制刷新订阅、空节点时给出明确错误而不是连一个空 fd。

commit dca57a4 · 239321fv1.3.7
已修复 · 已发布

代理链空状态可打开 / 节点信号显示错

#6, #11 · 跨平台 · 1.3.5

代理链没有任何条目时仍可打开开关、节点信号会过段时间变黄但选中状态还是绿。 修复:把代理链从首页移除(只保留设置入口);切换信号读取自最近一次 ping, 而不是节点元数据里的静态值。

commit 4752dfde · 06fc90fav1.3.7+
已修复 · 已发布

Windows 开机自启动无法关闭

#23 · Windows · feature

设置中加入"开机自启动"切换,写入 HKCU\Software\Microsoft\Windows\CurrentVersion\Run;关闭即清除。

commit f702ecdev1.3.8
已修复 · 已发布

0ms 延迟显示 bug

#29 · 跨平台 · UI

延迟缺失 / 亚毫秒噪声会显示为 0ms。改成显示 "—" 或最小 1ms,与节点行的信号条同步刷新。

PR #29 · commit 40783973v1.3.9
修复已合并 · 待发布

规则模式下微信卡顿 / 无法发图

#25 · Windows · 1.3.6

路由链 [2] 位置的 {port:853} OR {network:udp, port:443} reject 块挡在 geosite-cn 之前,腾讯/阿里/京东 App 在国内用 QUIC over UDP:443 被错杀。修复:把 reject 块挪到 CN 直连规则之后,并补上 ip_is_private:direct 让 LAN 流量绕过代理。

PR #34 · commit eadb6807v1.3.10
修复已合并 · 待发布

桌面端最小化按钮 + 节点刷新按钮

#30 · macOS / Windows · feature

桌面 chrome 之前只有关闭按钮,且 PC 上只有"下拉刷新"无法用。 修复:启用 macOS / Windows 标准三按钮,加节点列表的显式刷新按钮; 路由切换后会重置 chrome state,所以加了 route-change observer 在每次路由切换时 重新断言窗口控件存在。

PR #32 · #36 · commit a656c745 · 5daf162cv1.3.10
修复已合并 · 待发布

macOS 断开重连后 VPN 无网络

#33 · macOS · 1.3.9

两层根因:(A) restoreUserAutoReconnectPreference(true)isOnDemandEnabled=true 写进 macOS VPN profile,旧版 disconnect 只调 stopVPNTunnel(),NetworkExtension 立刻 on-demand 重连, 第二次连接停在 .invalid。(B) disconnect 在 NE 还在 .disconnecting 时就返回。 修复:disconnect 先关 on-demand → saveToPreferences(.configurationStale 重试) → 等待 .disconnected,再让用户连下一次。

PR #35 · commit e094a6a0v1.3.10
修复已合并 · 待发布

苹果支付订单丢失 / 套餐未到账

apple-native #26 · iOS · 1.0.7

用户付款 com.vpncheap.iosnative.premium.month(txn 490002847866567), webhook 到达 xboard 时 app_user_id 是 RevenueCat 的 $RCAnonymousID:e484…,后端报 "User not found",收据成为孤儿。 根因:购买时 RC SDK 仍然是匿名身份(loginWithXboardUser 没跑或静默失败)。 三层防御:(1) ensureXboardUser() 幂等同步身份; (2) purchase() 前断言 appUserID == userId; (3) 设置 $email + xboard_user_id subscriber attributes。

PR #29 · commit ada419bv1.0.8
已关闭 · feature

UI 修正 / 代理链可见性

#4, #11 · 跨平台

非首页页面整体改 iOS 风格;代理链从一级导航移到设置(避免空配置可打开造成的困惑)。已并入 #8 的 1:1 iOS UI 移植。

已合入 #27v1.3.8
关闭 · 不修复

用户自定义代理规则集

#26 · Windows · feature

关闭原因:与"规则模式默认 PAC"路线冲突,用户级自定义规则需要全新的 UI / 校验链路 / 测试矩阵,投入产出比低,暂不在 v1.x 路线图。

关闭于 2026-05-02not planned

深度模拟:三大代表性根因

挑选影响面最广、最难单看代码就理解的三个修复,做交互式模拟。点击触发原问题,然后应用修复看正常路径。

#25 · 规则模式微信卡顿 — UDP:443 reject 顺序错

3 个数据包穿过 sing-box 路由链;reject [2] 在 geosite-cn [4] 之前 → 国内 QUIC 被错杀

#33 · macOS 重连后停留 .invalid

NetworkExtension 状态机:on-demand=true 时 stop 之后 NE 立刻自动重启,新 connect 卡在 .invalid

apple-native #26 · 苹果支付订单孤儿

RevenueCat appUserID 与 xboard userId 没同步,webhook 带匿名 ID 到达 → 后端 "User not found"

#18 · Linux libapp.so 缺失 — 包结构对比

同一个 deb 解包后的目录树:修复前没有 libapp.so → Flutter Invalid ELF path

完整修复清单

所有标 closed 的 issue 在表格中按时间倒序列出;待发布的修复(#25, #30, #33, AN-#26)将随 v1.3.10 / v1.0.8 关闭对应 issue。

# 标题 平台 修复 commit 状态
#33macOS 断开重连无网络macOSe094a6a0v1.3.10 待发
#30桌面端最小化 + 节点刷新桌面a656c745v1.3.10 待发
AN#26苹果支付订单孤儿iOSada419bv1.0.8 待发
#290ms 延迟 UI bug跨平台40783973v1.3.9 已发
#26用户自定义规则集(feature)Windowsnot planned
#25规则模式微信卡顿Windowseadb6807v1.3.10 待发
#23Windows 开机自启动开关Windowsf702ecdev1.3.8 已发
#18Linux deb / AppImage 缺 libapp.soLinux60301615v1.3.7 已发
#17规则模式 YouTube 不通Windows4b1c2dbdv1.3.7 已发
#16Win10 客户端无响应Windowsa75a35bfv1.3.7 已发
#13Win10 白屏Windowsa75a35bfv1.3.7 已发
#12初次自动连接没网络Androiddca57a4v1.3.7 已发
#11代理链空状态可打开跨平台4752dfdev1.3.7 已发
#9iOS 链式代理失效 / 编辑丢失iOSfcdb75d0v1.0.2 已发
#8UI 全部按 iOS 布局跨平台be786518v1.3.8 已发
#6代理链滞后 / 节点信号不对Windows06fc90fav1.3.7 已发
#5在线更新签名不一致跨平台a47e2e94v1.3.7 已发
#4UI 修正(并入 #8)跨平台be786518v1.3.8 已发
#3Win10 黑框消失Windows2a3077e4v1.3.7 已发
#2Windows 双击吞掉Windows2a3077e4v1.3.7 已发
#1连接慢 + CPU 100%Windowsa75a35bfv1.3.7 已发
02 / 兼容性冲刺

跨平台兼容性冲刺:5 平台、5 轮 codex 审查、零回归

一次多智能体审查→修复冲刺:降低 Apple 部署目标、修复 Android 安全漏洞、重新生成 CocoaPods 状态、扩大 Linux 发行版覆盖、修正 Plasma 6 检测、让 Windows 清单说真话、 清理 17 个未使用的 Flutter 依赖 —— 每轮都被 codex 反复审查,直到没有发现为止。

支持的 OS 版本窗口:Before vs After

iOS
−1.6 个版本
15.6+
14.0+
12.0
13.0
14.0
15.0
16.0
17.0
18.0
UNNotificationContent.interruptionLevel 上加了一处 #available(iOS 15.0, *) 包裹;部署目标在 Podfile、Runner.xcodeproj(9 处)、AppFrameworkInfo.plist 以及 25 个 Pod 中统一下调。修复前主工程降到 14.0,Pods 还在以 15.6 编译。 + 多覆盖约 3-4% 的活跃 iPhone 用户
macOS
−2 个大版本
11.0+
10.15+
10.14
10.15
11
12
13
14
15
16
VPNManager.swift 中两处 Logger(subsystem:category:)(仅 macOS 11+)改写为已存在的 os_log 模式。Info-SystemExt.plist 中的 LSMinimumSystemVersion 现在绑定到 $(MACOSX_DEPLOYMENT_TARGET)。补上了 DeveloperID 配置缺失的 Hardened Runtime,否则后续 notarize 会静默失败。 + 覆盖 Catalina(macOS 10.15)用户和最后一代纯 x86 Intel Mac
Linux
前移 2 个 LTS
glibc ≥ 2.34 (Ubuntu 22.04+)
glibc ≥ 2.31 (Ubuntu 20.04+)
2.27
2.28
2.31
2.32
2.34
2.35
2.36
2.37
新建了 linux/Dockerfile.compat,基于 ubuntu:20.04 (glibc 2.31)产出公开发布产物。之前的构建容器是 Ubuntu 22.04 (glibc 2.35),静默地产出了在 Ubuntu 20.04、Debian 11、RHEL 8、AL2 上无法 装载的二进制文件。同时修了 docker-compose.yml 指向不存在的 Dockerfile 的引用。 + 覆盖 Debian 11 Bullseye、Ubuntu 20.04 LTS
Windows
说真话
Win 7+(声称,但实际不能跑)
Win 10 1607+(真实)
7
8
8.1
10·1507
10·1607
10·1809
10·22H2
11
runner.exe.manifest 中删掉了 Win 7/8/8.1 的 supportedOS GUID —— 这是个谎言。Go 1.24.7(用于构建 sing-box.dll)早在 Go 1.21 就放弃了 Win7-8.1。捆绑的 VC redist 14.40+ 也只在 Win 10 上能运行。清单现在和现实一致;同时增加 PerMonitorV2,PerMonitor 以及兼容 Win10 1607 的 dpiAware true/pm 后备。 + 终于给用户一个诚实的安装矩阵(不再让 Win7 用户失望)
Android
安全加固
API 24+(带安全漏洞)
API 24+(已加固)
21
23
24
26
28
30
33
35
P1 安全修复:"stop_vpn" 广播被以 RECEIVER_EXPORTED 全局导出且没有 setPackage, 任何已安装的 App 都能停掉 VPN。现已切换到 RECEIVER_NOT_EXPORTED + 签名级权限保护 + 包级作用域发送方; 同时加上 Android 13+ 的 POST_NOTIFICATIONS 运行时申请、移除了 Lollipop 死分支、删掉了 multidex 依赖、清理了被 Android NSC 静默忽略的 CIDR <domain> 条目。 + 第三方 App 再也无法停掉 VPN;Android 13+ 上能看到状态通知

codexloop 在第 5 轮收敛

每一轮 codex 审查 diff 并标出兼容性问题,我应用同意的修复,然后再跑一轮。当 codex 返回 clean 时退出 —— 不是凑够计数。

1
第 1 轮
4 个发现 P1P1P2P3
CocoaPods 重新生成 · Dockerfile Flutter 钉版本 · Plasma 用绝对路径 · Windows DPI 后备
2
第 2 轮
1 个发现 P1
Android API 24-32 动态 receiver 权限 —— ContextCompat 强制要求
3
第 3 轮
1 个发现 P2
Dockerfile.compat 头注释 —— 之前夸大了对 RHEL 8 / AL2 的覆盖
4
第 4 轮
2 个发现 P3P3
objcopy 建议修正 · pubspec 残留平台标记
第 5 轮
clean review
"看起来不错 —— 没有更多兼容性发现"
发现曲线4 · 1 · 1 · 2 · 0
codex tokens5 轮共 in 19.9M / out 96.5K
每轮验证关卡git diff --check · flutter analyze · ./gradlew :app:compileDebugKotlin · xmllint · plutil · ruby -c
退出原因clean review(第 5 轮 codex 没有更多兼容性发现)

10 项修复一览

实施团队加上所有 codexloop 轮次的全部变更,按平台分组。

FIX 01
Pods 与降低后的部署目标重新对齐
Apple
在 ios/ 和 macos/ 同时跑了 pod install。修复前主工程已降到 iOS 14 / macOS 10.15,但 540 个被跟踪的 Pod 文件仍然以 15.6 / 11.0 为目标。现在已统一对齐;同时移除了 17 个被裁剪的 Flutter 插件对应的 Pod 项。
ios/Podfile.lock, macos/Pods/Pods.xcodeproj
FIX 02
把 iOS 部署目标降到 14.0
iOS
if #available(iOS 15.0, *) 包裹 content.interruptionLevel = .active —— 这是范围内唯一的 iOS-15-only API。同时更新 Podfile 平台行、post_install 钩子、9 处 pbxproj 条目、AppFrameworkInfo.plist 的 MinimumOSVersion。
ios/tunnel/ExtensionPlatformInterface.swift:634
FIX 03
把 macOS 部署目标降到 10.15
macOS
把两处 Logger(subsystem:category:) 改写成 os_log(Logger 需 macOS 11+;os_log 一直可用)。LSMinimumSystemVersion 绑到 $(MACOSX_DEPLOYMENT_TARGET);Podfile、post_install、8 处 pbxproj 同步更新。
macos/pt-macos/ExtensionPlatformInterface.swift, macos/pt-macos/CoreLogBuffer.swift
FIX 04
DeveloperID 配置补上 Hardened Runtime
macOS
给 DeveloperID 构建配置加上 ENABLE_HARDENED_RUNTIME = YES,其它 release 配置已有。否则直接跑 xcodebuild -configuration DeveloperID 后续 notarize 会静默失败。
macos/Runner.xcodeproj/project.pbxproj:790-808
FIX 05
Android:stop_vpn 广播加固(P1 安全)
Android
action 改名为 com.novamindllc.vpncheap.action.STOP_VPN,通过 ContextCompat 切到 RECEIVER_NOT_EXPORTED、发送方加上 setPackage(packageName),并声明 AndroidX 在 API 24-32 上要求的 DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION(signature 保护级别)—— 否则 ContextCompat 会抛异常。
LibboxVpnService.kt, MainActivity.kt, AndroidManifest.xml
FIX 06
Android:运行时申请 POST_NOTIFICATIONS
Android
在 API 33+ 加入原生 ActivityCompat.requestPermissions。否则全新安装的 Android 13/14/15 上,VPN 状态通知会被静默压制。已先确认 Dart 层没在做同样的事再加。
MainActivity.kt
FIX 07
Android:NSC 死配置 + multidex 清理
Android
network_security_config.xml 移除 CIDR <domain> 条目 —— Android NSC 静默忽略 CIDR 段,所以"LAN cleartext"规则其实啥也没干。同时清理不可达的 Lollipop_MR1 分支,并去掉 androidx.multidex + multiDexEnabled true(minSdk 24 后已是死代码)。
network_security_config.xml, Application.kt, app/build.gradle
FIX 08
Flutter:裁掉 17 个未用依赖,SDK 下限说真话
Flutter
先确认 lib/test/ 中没有 import,再批量移除:app_links、screen_retriever、geocoding、sensors_plus、wakelock_plus、battery_plus、network_info_plus、upgrader、flutter_login、flutter_downloader、local_auth、camera、image_picker、flutter_local_notifications、permission_handler、cronet_http、cupertino_http(后两者由 native_dio_adapter 传递引入,本地多余)。pubspec.yaml SDK 约束从虚假的 >=3.0.0 改成真实的 >=3.9.0
pubspec.yaml, pubspec.lock(重生成,−562 行)
FIX 09
Linux:为旧 glibc 新增 Dockerfile.compat
Linux
新增 linux/Dockerfile.compat(ubuntu:20.04,glibc 2.31)用于公开发布产物。把 Dockerfile.modernDockerfile.amd64 的 Flutter 钉版从 3.29.2 升到 3.41.2 以匹配 CI;修复 docker-compose.yml 错误的 Dockerfile 引用;在 proxy_manager.cc 中实现 Plasma 6 检测并使用绝对路径探测,适配 PATH 被裁剪的环境。
linux/Dockerfile.compat(新), linux/src/proxy_manager.cc
FIX 10
Build/CI:NDK 对齐 + Win 清单说真话 + gomobile 钉版
Build/CI
CI 工作流改装 NDK 28.2.13676358(原来是 27.0,与 App 自身钉版不一致)。从 runner.exe.manifest 删掉 Win 7/8/8.1 的 supportedOS GUID(Go 1.21+ 已放弃 Win7-8.1)。补上 PerMonitorV2,PerMonitor 以及 dpiAware true/pm 后备;CLAUDE.md 中 gomobile 引用从 v0.1.4 修正为 v0.1.12(对齐 sing-box 1.13.6 官方钉版及 CI)。
.github/workflows/{manual-android-build,release}.yml, runner.exe.manifest

兼容性模拟:看见行为差异

两项最高影响修复的可视化:切换"修复前 / 修复后"可看到目标 OS 上的运行时行为变化。

Android 7 — VPN 服务 onCreate()
API 24 · ContextCompat.registerReceiver · stop_vpn 广播
$ adb logcat | grep LibboxVpnService
─ 在 Galaxy S5(Android 7.0)上启动 App
Linux — 二进制能装载吗?
readelf -W --version-info libbox.so · 依赖的最高 GLIBC 版本

最终验证关卡 — 全绿

每个关卡都在第 5 轮之后的最终状态上跑过。

flutter analyze
无问题(28 秒)
android :app:compileDebugKotlin
BUILD SUCCESSFUL(11 秒)
XML(manifests)
3 个文件 · 合法
plist(plutil -lint)
2 个文件 · OK
Podfile(ruby -c)
ios + macos · 语法 OK
pubspec.yaml YAML
解析通过
iOS 部署目标
14.0(主工程 + Pods 对齐)
macOS 部署目标
10.15(主工程 + Pods 对齐)
libbox.so 16 KB 页对齐
LOAD aligned 0x4000
codex 第 5 轮
"看起来不错 — 没有更多兼容性发现"
静态关卡未验证(需要真机或构建管线):
  • iOS/macOS 完整的 xcodebuild —— codex 撞到了签名/沙箱限制;静态分析 + Pods 重生成的一致性顶替
  • Linux/Windows CMake 构建 —— 仅静态分析验证
  • Android 7-12 真机测试新加的 DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION 路径
  • 真正 docker build 一次 Dockerfile.compat —— 本地 Docker daemon 没启动
  • 超出范围的预存问题:ios/Runner/Info-Profile.plist 是个 23 字节的非法 plist(只有 $(FLUTTER_BUILD_NUMBER)),已提交于 2025-09。Profile 构建从那时起一直静默失败。建议另开一个修复:把 Info-Release.plist 复制过去。
03 / Sentry 审查

Sentry 深度审查:从 28 个未解崩溃到清零

一次多智能体调查,把 Sentry 上每个 issue 重新审了一遍:发现一个之前漏掉的 27-event WebView bug、修正了一处跨平台错误归类、并把所有修复经过 7 轮 codex 审查直至 clean。所有 issue 标记为 resolved-in-next-release · Vpncheap@1.3.9+79

置信度变化

每个卡片是一个 Sentry issue 的重新调查结果。条形显示置信度从旧值到新值(1–100)。80 线是"可执行修复计划"的门槛。

已过 80 · 已修复

macOS Swift Concurrency over-release

FLUTTER-1F / 1C · macOS · 2 events

Task 闭包跨 actor 边界捕获了非 Sendable 的 FlutterEventSink。关闭过程中,引擎释放了闭包,但 actor task 仍持有 → RefCounts::doDecrementSlow → abort

conf 78 → 83+5
已过 80 · 已修复

dart:io setRange RangeError

FLUTTER-15 · Android 16 · 1 event

与 FLUTTER-1R/1Y 同一根因 —— 主 isolate 上调用 Process.runSync('uname')。已在 update_service.dart:673 中解决。

conf 42 → 92+50
归类修正 · 已修复

Bionic abort/syscall

FLUTTER-1N · Android 11 · 原误判为 Windows

原审查把它当成 Windows DLL race。重新分析栈帧后发现这其实是同一个 Android Process.runSync 家族的第三种失败模式。

0(归错平台) → 83+83
已过 80 · 过滤已上线

VPN 连上后 HandshakeException

FLUTTER-P · Windows · 60 events

5 个 event 中 4 个确认是连接建立后短窗口内的时序问题。静态审查已排除证书 pinning 误配。在 lib/main.dart beforeSend 里过滤是正确选择。

conf 72 → 87+15
新发现 · 已修复

WebView setState 缺少 mounted 守卫

Issue 7400674663 · Android · 27 events

原审查没覆盖。dart-network-investigator 子智能体找到的:onPageStarted / onPageFinished 直接 setState,没有 mounted 守卫。

(新) → 85+85
未达 80 · 仍发布

Windows FreeLibrary 集群

FLUTTER-D / 1J · Windows · 代码层确认 race

符号化受阻,只到 74/65 confidence,但"FreeLibrary 时 Go 运行时线程仍存活"这条机械路径明显。退出时跳过 FreeLibrary;OS 会在进程退出时回收。

conf 74 / 60 → 74 / 65已发布
未达 80 · 已发布缓解

SyslogOutputWriter 中的 SIGPIPE

FLUTTER-1H · macOS · Flutter 框架 bug

45 帧栈 100% 在 FlutterMacOS 框架内。便宜的缓解方式:在 AppDelegate 启动时第一时间 signal(SIGPIPE, SIG_IGN)

conf 65 → 70已发布
假设被推翻

App Hang 集群(~11 IDs)

macOS · Watchdog ≥ 2000ms

原审查归咎于我们的 runBlocking semaphore。重新看栈:Thread 0 全是 CoreLocation / Pasteboard / 字体 XPC —— 系统框架调用,不是我们的代码。还需上传 dSYM 才能进一步定位。

conf 72 → 62需要 dSYM
未达 80 · 符号受阻

Android libbox SIGABRT

FLUTTER-8 · Android · 8 events

按 ASLR 模式看是两个不同的崩溃点。1.3.x 上事件数为 0,可能 libbox 升级已修;次要修复:把 attemptReconnect() 的双重关闭做了同步。

conf 52 → 58次要修复已发
未达 80 · Source Map 受阻

无栈帧信息的 Null check

FLUTTER-1X · 1 event · Android

2 帧 in-app 100% 混淆。上传 Source map 到 Sentry 即可解锁。静态审查最大嫌疑是 api_path_service.dart:103,但没有栈帧无法确认。

conf 20 → 20受阻

竞态条件模拟

追踪到的四个核心 bug 的实时动画。点击"触发竞态"看出问题的过程,然后"应用修复"看锁结构是怎么挡住它的。

Swift Concurrency over-release

FLUTTER-1F / 1C · Task 闭包跨 actor 边界捕获了非 Sendable FlutterEventSink

FreeLibrary 时 Go 运行时线程仍在跑

FLUTTER-D / 1J · Cleanup() 卸载了 sing-box.dll;goroutine 在缺失代码页时崩溃

Android stopVPN ⇄ attemptReconnect 竞态

FLUTTER-8 · 两个线程都收养同一个 fd,导致 commandServer 双重关闭

线程已发布但尚未启动的窗口

codex 第 3 轮 · waitForCommandServerClose 看到 closeThread 已设但 isAlive==false

代码 Before / After

Android 锁所有权修复在 4 轮 codex 之间逐步演进,每一轮都揪出一个更微妙的残留竞态。

第 1→2 轮:在锁内快照 vpnFd

修复前
val serverToClose = synchronized(vpnStartStopLock) { ... }
// vpnFd 在锁外关闭 —— 与 stopVPN 竞态!
if (vpnFd != -1) {
    ParcelFileDescriptor.adoptFd(vpnFd)?.close()
    vpnFd = -1
}
修复后
// 锁内原子快照两个字段
val snapshot = synchronized(vpnStartStopLock) {
    val server = commandServer
    val fd = vpnFd
    commandServer = null
    vpnFd = -1
    StopVpnSnapshot(server, fd, ...)
}

第 3→4 轮:close 线程要在锁内 start

修复前
commandServerCloseThread = thread
// 锁在这里释放
}
snapshot.closeThread?.start()
// 竞态:thread 已发布但 !isAlive →
// waitForCommandServerClose 提前清空标志
修复后
}.also { thread ->
    isCommandServerClosing = true
    commandServerCloseThread = thread
    thread.start()  // 锁内
}

外层 catch 身份检查(审查第 2 轮)

修复前 — 会盖掉新 server
} catch (e: Exception) {
    // 不区分身份的 null —— 可能盖掉
    // 一个并发 start 刚发布的 server!
    commandServer = null
    throw e
}
修复后 — 用身份检查
} catch (e: Exception) {
    createdServer?.let { ours ->
        val shouldClose = synchronized(vpnStartStopLock) {
            if (commandServer === ours) {
                commandServer = null; true
            } else false
        }
        if (shouldClose) { ours.close() }
    }
    throw e
}

遥测

审查中的置信度变化,以及修复落在哪些代码区域。

置信度:before → after

每个 issue 一行。越过 80 虚线 = 越过"可执行修复计划"门槛。

修复落点

14 个文件,跨所有平台。

codex 审查环

两阶段共 7 轮审查。每轮都揪出上一轮漏掉的真实残留 bug。

阶段 1 · 第 1 轮

11 文件改动初次审查

找到 1 个高严重度问题:vpnFdstopVPN()attemptReconnect() 都在锁外关闭 —— 同一个 fd 可能被收养两次。

阶段 1 · 第 2 轮

快照修复后

又找到 2 个:(a) isCommandServerClosing 在锁内置位但 commandServerCloseThread 在锁外赋值;(b) attemptReconnect() 外层守卫在锁外读取字段。

阶段 1 · 第 3 轮

原子发布修复后

找到 1 个高:thread.start() 在锁释放后才调用,存在 isAlive 返回 false 的窗口,会让 waitForCommandServerClose() 提前清空标志。

阶段 1 · 第 4 轮

start-inside-lock 修复后

✓ Clean。"FLUTTER-8 范围内已干净,无残留竞态。"

阶段 2 · 审查第 1 轮

预存的"未加锁访问"审查

codex 列出 4 处:vpnFd 在 584、1182、2078,commandServer 在 758。3 处归为真实 bug,1 处只是理论问题。

阶段 2 · 审查第 2 轮

3 处锁审计修复后

找到 1 处我引入的回归:startCommandServer 外层 catch 没做身份检查,可能盖掉并发 start 刚发布的 server。

阶段 2 · 审查第 3 轮

提升 createdServer 后

✓ Clean。"对该范围已确认干净。"

Sentry 当前状态

所有 issue 已标记为 resolved-in-next-release(Vpncheap@1.3.9+79)。如果有更高版本仍上报同样事件,Sentry 会自动重新打开。

Short ID 标题 事件数 状态
FLUTTER-7_loadOrders 中的 Null check4resolved-in-next-release
FLUTTER-1RProcess.runSync ANR(Android 16)3resolved-in-next-release
FLUTTER-1YProcess.runSync ANR(Android 16)3resolved-in-next-release
FLUTTER-1PVPNConnectionException 'No product selected'1resolved-in-next-release
FLUTTER-PVPN 连上后 HandshakeException60resolved-in-next-release
FLUTTER-16iOS PacketTunnel EXC_BAD_ACCESS1resolved-in-next-release
FLUTTER-1KmacOS NSAlert.runModal 在 async Task 内1resolved-in-next-release
FLUTTER-15RangeError 0..8: 1001resolved-in-next-release
FLUTTER-1Nbionic abort/syscall(原误归类)1resolved-in-next-release