[总览] 2026-09-30 全量复审:问题清单、统一方案与并行修复计划 #7

Closed
opened 2026-09-30 13:56:50 +08:00 by nixevol · 2 comments
Owner

结论

基线 main 4059a15。本次全量复审整理出 58 个工作包(critical 7、high 13、medium 29、low 9),每个工作包一个 issue,均附证据(文件与行号)、文档依据、统一方案、改动文件、冲突说明与验收测试。请负责人审核后再按下面的分工并行修复。

最需要先处理的问题:

  • B-01:客户端 CONNECT 带 Receive Maximum 时,mochi 的发送配额路径递归读锁死锁。已用压测复现(31 条内卡死),单个客户端即可冻结全站下发。已验证的修法是在 OnConnect 里把发送配额置 0(6 轮×30 秒、每轮约 250 万条无卡顿)。
  • B-02:broker 的大帧名额在 PUBACK 时从不归还,累计 64 条大于 64 KiB 的消息后全局循环永久阻塞。
  • B-05:认证失败的连接永久留在连接表,无需凭据即可耗尽内存。
  • K-01 至 K-04:四套 SDK 断线后已发出的发送永不重交;Java 把连不上服务器判成密码错误并永久停止重连;Python 被顶号后两端每秒互踢;JS 在 Node 下确认失败会让进程崩溃。
  • B-03:慢客户端写阻塞时,向它发布 QoS 1 会卡住调用线程。已实证第 18 次发布阻塞超过 10 秒,messageLoops 会被连带冻结。
  • #3 的结论已更正(T-01):它不是服务端死锁,而是测试用 WebSocket 客户端的两个缺陷(不按字节流拆包、并发写不加锁)。已用对照实验验证,修好后去掉群事件 20ms 绕过,同步下发 20/20 通过。

审查方式

  • 按目录分 5 个区独立审查:A 管理后台与网页、M 消息核心、I 身份认证群在线、P 传输平台部署、S SDK。各区对照 PRD、DEVELOPMENT、DEVIATIONS 逐条区分真实缺陷、故意设计与误报。
  • 总审查人另做死锁、mochi 配额、慢消费者三组实证实验,并对各区结论抽查复核(管理后台 7 条、消息核心 2 条、身份区 2 条、传输区 4 条,全部成立)。
  • 多个区报告的同一问题已合并;方案不一致时由总审查人统一裁决,写在各 issue 的"结论与统一方案"与下方"统一取舍"。
  • SDK 区另做了第二轮审查,新增发现经总审查人对照代码核实后补进 K-00 至 K-04(各 issue 方案表里标"补充"的条目),工作包数量不变。
  • 审查全程只读,没有改动仓库代码;诊断用的临时工作树与测试文件已删除。

编号说明

  • 工作包(issue)编号:B broker、L 监听与 HTTP、D 存储配置命令行部署、C 消息核心、U 身份群在线认证、H 管理后端、W 后台网页、K SDK、T 测试与文档。
  • 原始发现编号:A、M、I、P、S 开头,附在各 issue 的"问题明细"里,便于追溯。
  • 标签:P0-critical、P1-high、P2-medium、P3-low 表示严重级;lane/* 表示工作线;review-2026-09-30 表示本次复审。

工作包清单

编号 issue 严重级 工作线 标题 依赖
B-01 #8 critical broker 客户端 CONNECT 带 Receive Maximum 时 mochi 发送配额路径递归读锁死锁,单个客户端即可冻结全站下发 无
B-02 #9 critical broker broker 大帧名额收到 PUBACK 时从不归还,累计 64 条大帧后 PublishDown 永久阻塞、messageLoops 冻结 B-01 (#8)(同文件,先合)
B-03 #10 high broker 慢客户端写阻塞时,向它发布 QoS1 会卡住调用线程,messageLoops 与其他端的处理被连带冻结 B-01 (#8)、B-02 (#9)(同文件,先合)
B-04 #11 medium broker 停用/删除/重置密码/退出登录靠固定 sleep 等下行写出再断开,且有两条断开路径赛跑,fatal 与 resp 可能丢失 B-03 (#10)(使用每连接发送队列)
B-05 #12 critical broker 认证失败的连接永久留在 broker 连接表,无需凭据即可耗尽内存并拖慢所有按连接查找 无
B-06 #13 medium broker PublishDown 不校验目标是否仍是当前且已订阅的连接:顶号窗口内旧连接的帧发给新连接、握手前的帧推进空主题 B-03 (#10)(校验放在入队之前)
B-07 #14 medium broker mochi 报错日志会写出整个包:CONNECT 里的密码与会话令牌、PUBLISH 正文进入日志 无
B-08 #15 medium broker broker 上行队列按编号创建后永不回收;停机时关闭通道可能 panic(为 L-03 提供 Broker.Shutdown) 无
B-09 #16 high broker 连接生命周期没有串行化:握手与断线交错时断开的连接被标成在线,在线判定多处来源互相不一致 B-05 (#12)(同改 OnDisconnect)
B-10 #17 high broker + 身份群在线 密码校验与写库不是原子的:重置、停用、删除期间的登录、改密、退出会越过管理员操作 B-09 (#16)(同改 handleHello)
B-11 #18 high broker 令牌"30 天没用"按最后一次认证时间算,一直在线的设备重连会被判失效 无(依赖消息线维护的上下线字段,B-09 (#16) 后仍由消息线写)
B-12 #19 medium broker + 身份群在线 认证没有超时与并发上限:argon2 排队无界、并发尝试可绕过锁定、PHC 参数写死 无
L-01 #20 high 监听与 HTTP 握手前阶段没有超时与并发上限:TLS 握手、CONNECT 之前、WebSocket 升级后、HTTP 空闲长连接都能无限占用资源 无
L-02 #21 high 监听与 HTTP accept 出一次错就永久停止接受新连接 无
L-03 #22 high 监听与 HTTP 有 MQTT 连接时停机退不出,还可能向已关闭通道发送而 panic B-08 (#15)(Broker.Shutdown)、D-02 (#28)(写队列关闭安全)、C-01 (#32)(可等待退出的消息循环)
L-04 #23 high 监听与 HTTP NixMsg 直接终止 TLS 时 r.TLS 为空,后台 Cookie 不带 Secure 无
L-05 #24 medium 监听与 HTTP 后台静态页没有前端路由回退:刷新或直接打开二级页面返回 404,访问目录会列出文件 无
L-06 #25 medium 监听与 HTTP 健康检查只会明文:配了证书并关闭明文时必失败,Docker 示例配置因此默认开着明文 无
L-07 #26 low 监听与 HTTP 监听与 HTTP 小问题合集:XFF 解析可伪造、每连接新建 tls.Config、/metrics 令牌非常量时间比较、重复创建 listener 无
D-01 #27 medium 存储配置 写队列 busy 状态一旦置位就永不恢复,/readyz 永久返回 503 无
D-02 #28 medium 存储配置 写队列关闭不安全且缺少非事务执行接口:停机时可能 panic,清理后无法执行 wal_checkpoint 无
D-03 #29 medium 存储配置 读连接池没有上限且只保留 2 个空闲连接:群发或批量唤醒时出现 SQLite 连接风暴 C-01 (#32)、C-02 (#33)(推送改为先收集结果再发布、推送并发有上限)
D-04 #30 medium 存储配置 备份与恢复不安全:库不存在时静默新建空库并报成功,恢复步骤没要求清理 -wal/-shm 无
D-05 #31 low 存储配置 存储、配置与构建小问题合集:迁移失败反复全量备份、版本号未注入、显式 0 被改回默认、命令行改管理员密码不作废会话 无
C-01 #32 high 消息核心 推送与调度循环重构:握手前就推送、同端并发推送、单协程串行循环被慢操作冻结、到点分发每秒最多 100 条 B-02 (#9)(第 5 点删除消息包大帧名额需在其后)
C-02 #33 high 消息核心 回执没有在途标记:每次推送都重发最早 64 条未确认回执,并发时必然重复,第 65 条起可能永远发不出 C-01 (#32)(在途表挂在每连接 worker 上)
C-03 #34 high 消息核心 保留清理放在每秒循环里:全表扫描、单事务大删除、到期处理无 LIMIT、没有 wal_checkpoint;断线清标记 SQL 缺条件;僵尸不保留投递永不过期 C-01 (#32)(清理循环调度)、D-02 (#28)(非事务执行接口)
C-04 #35 high 消息核心 退群、踢人、解散、停用、删除作废投递时不写回执、不收尾:消息永远停在 dispatched、正文不删、占发送方配额 无
C-05 #36 medium 消息核心 每端请求限速只作用于 send,撤回、状态、目录、群、unlock 等请求都不限速 无
C-06 #37 medium 消息核心 推送时 meta 里的数字被转成 float64,超过 2^53 的整数被静默篡改 无
C-07 #38 low 消息核心 消息校验与小问题合集:ttl 可为 0 或负数、delay 溢出变立即发送、提交不检查发送方已停用、群分发不按入群时间过滤、保留期按创建时间算 第 4 点依赖 C-03 (#34)、C-04 (#35)
U-01 #39 high 身份群在线 + 后台网页 开启自助注册时不要求安全码,空安全码即可注册 无
U-02 #40 medium 身份群在线 群操作的校验不在写事务里;建群输入不去重;后台建群不校验群主 C-04 (#35)(同改 group 包,C-04 (#35) 先合)
U-03 #41 medium 身份群在线 对话密码锁定不一致:按对计数键三处各不相同、锁定期内不带密码也报 rate_limited、后台改对话密码与删除端不清锁定 无
U-04 #42 medium 身份群在线 锁定计数表从不清理,轮换 IP 可让内存持续增长 U-03 (#41)(同文件)
U-05 #43 low 身份群在线 目录搜索的 LIKE 通配符没有转义;端编号 inline 与 mochi 内联客户端同名 无
H-01 #44 medium 管理后端 管理接口请求体大小与读取时间不受限制,公开的登录接口可被打爆内存 无
H-02 #45 medium 管理后端 审计日志可能整体丢失,令牌身份与操作内容记录不清,登录失败与批量失败不记录 无
H-03 #46 medium 管理后端 + 后台网页 端编辑先直接写 enabled 再走级联停用:停用可能半生效,并发停用会被旧值悄悄重新启用 无
H-04 #47 medium 管理后端 + 后台网页 批量导入串行算哈希、校验与插入之间有竞态;CSV 报错行号不准 无
H-05 #48 medium 管理后端 + 后台网页 端的默认延迟没有上限:后台可设出永远发不出消息的端,极大值还会溢出 无
H-06 #49 low 管理后端 改管理员密码时旧密码校验不计入锁定;过期管理员会话不清理,作废其他会话失败被忽略 无
H-07 #50 low 管理后端 重置登录密码时踢线失败被吞掉,审计仍记 ok B-04 (#11)
W-01 #51 medium 后台网页 后台错误处理:不处理 401、每个错误弹两次、网络错误显示英文、表单错误只闪 toast、页面加载失败一片空白 无
W-02 #52 medium 后台网页 投递记录页缺少时间筛选、原因列、完整统计与接收端分页 无
W-03 #53 medium 后台网页 群页面:详情只显示前 50 个成员,且与后端能力不一致(编号输入框、搜索提示、无确认、失败无原因) 无
W-04 #54 medium 后台网页 一次性密钥展示:导入结果 CSV 不转义会错列、手填密码时显示 undefined、复制失败无提示、导入弹窗无下载按钮 无
W-05 #55 medium 后台网页 端列表页:只有 IP 锁定时无法点解锁、批量选择跨筛选保留、编辑弹窗总提交 enabled 且停用无确认、导入无进度、默认延迟无上限提示 W-04 (#54)(同文件)
W-06 #56 low 后台网页 界面布局与文案:表格高度写死不随窗口自适应、直接显示英文枚举与配置键名、概览缺自助注册数 建议在 W-01 (#51) 至 W-05 (#55) 之后
K-00 #57 medium SDK + 测试与文档 四套 SDK 对外语义不一致,需要先定一份统一的 SDK 行为约定(连接超时、错误码、返回值、取消、退避、心跳、URL 映射) 无
K-01 #58 critical SDK Go SDK:断线后已发出的发送永不重交、回调持锁导致重入死锁、收包路径被阻塞、重连不恢复上下线订阅、fatal 原因可能丢失 K-00 (#57)(对外语义条目)
K-02 #59 critical SDK JS SDK:Node 下确认失败导致进程崩溃、浏览器因全局 Buffer 无法连接、断线后发送挂起、确认失败后不再重试 K-00 (#57)(对外语义条目)
K-03 #60 critical SDK Python SDK:没关 paho 自动重连导致被顶号后两端互踢、回调在锁下执行会死锁、断线后发送挂起、async 回调不被等待就确认 K-00 (#57)(对外语义条目)
K-04 #61 critical SDK Java SDK:把"连不上服务器"判成密码错误并永久停止重连、持锁等待 PUBACK 与回调重入会死锁、断线后发送不重交 K-00 (#57)(对外语义条目)
K-05 #62 low SDK + 测试与文档 SDK 工程门禁缺失:四套 SDK 都不在任何检查里、Go SDK 与服务端 Go 版本不一致 无
T-01 #3 medium 测试与文档 测试 WS 客户端不按字节流拆包、并发写不加锁,被误判为服务端死锁;移除群事件 20ms 绕过 无
T-02 #63 medium 测试与文档 验收对照表把只测了部分子项的功能标成"通过",交付说明据此写"通过 23" T-01 (#3)(可靠的 WS 测试客户端)
T-03 #64 low 测试与文档 压测工具只是连接骨架(MQTT 3.1.1、不带认证、不收发),无法执行 PRD 第 8 节的规模与吞吐验收 T-01 (#3)

并行分工与合并顺序

同一工作线内按箭头顺序合入;不同工作线可以并行开发,按"跨线依赖"合入。

工作线 建议人手 线内顺序
测试与文档 1 T-01 最先合入(其他修复的 WebSocket 集成测试依赖它)→ T-03;T-02 随各修复推进,最后定稿
broker · 下行通道 1 B-01 → B-02 → B-07 → B-03 → B-06 → B-04 → B-08
broker · 生命周期与认证 1 B-05 → B-09 → B-10 → B-11 → B-12
监听与 HTTP 1 L-02 → L-01 → L-04 → L-05 → L-06 → L-07 → L-03(最后,依赖 B-08、D-02、C-01)
存储、配置、命令行 1 D-01 → D-02 → D-04 → D-05 → D-03(依赖 C-01、C-02)
消息核心 1–2 C-04 → C-05 → C-06 → C-01(依赖 B-02)→ C-02 → C-03(依赖 D-02)→ C-07
身份、群、在线 1 U-01 → U-03 → U-04 → U-05 → U-02(依赖 C-04)
管理后端 1 H-01 → H-03 → H-04 → H-05 → H-06 → H-07(依赖 B-04)→ H-02(最后,改动面最广)
后台网页 1 U-01 的界面部分 → W-01 → W-04 → W-05 → W-02 → W-03 → W-06
SDK 每套 1 K-00 先定约定 → K-01、K-02、K-03、K-04 并行 → K-05

跨线依赖:

  • B-02 → C-01(C-01 删除消息包那份大帧名额)
  • B-03 与 C-01:以 PublishDown 签名不变为契约并行开发;消息线把 ErrBackpressure、ErrNotSubscribed、ErrNoConnection 一律当作发布失败处理
  • B-08、D-02、C-01 → L-03(停机总装)
  • D-02 → C-03(清理后执行 wal_checkpoint)
  • C-01、C-02 → D-03(读连接池上限)
  • C-04 → U-02(同改 group 包)、C-07 第 4 点
  • B-04 → H-07
  • B-05 → B-09 → B-10(同改 hooks.go 与 handleHello)
  • K-00 → K-01 至 K-04

共享文件归属(避免多人改同一处)

文件 位置 负责的 issue
cmd/nixmsg/serve.go messageLoops C-01
runServe 末尾停机段 L-03
staticFileHandler L-05
listener 装配段 L-07
identity.New 的 ConnControl B-04
admin.Deps 的 SecureCookies / 审计 logger L-04 / H-02
cmd/nixmsg/uplink.go OnSessionEstablished、OnHandshakeComplete、OnDisconnect B-09
HandleUplink(分发前限速) C-05
publishResp(大小上限) B-06
internal/broker/hooks.go OnConnect B-01、B-05、B-12(按 broker 线顺序)
OnQosPublish / OnQosComplete / OnQosDropped B-02
OnSessionEstablished / OnDisconnect B-03、B-05、B-09
OnSessionEstablish(新增) B-06
internal/broker/session.go 下发通道 / fatalKick、logout / handleHello、HandleDisconnect B-03 / B-04 / B-09、B-10
internal/broker/ws.go AttachWS 前读超时 / 客户端 IP L-01 / L-07
internal/app/message/push.go 推送与调度 C-01,其后 C-02
internal/app/message/submit.go 限速 / 参数校验与停用检查 / 对话密码函数 C-05 / C-07 / U-03
internal/app/group/app.go emit / 校验逻辑 / checkAddMember T-01 / U-02 / U-03
internal/app/group/void.go、identity/lifecycle.go 作废与收尾函数 / 第 131–138 行断开 goroutine / 删除端清锁 C-04 / B-04 / U-03
internal/auth/locks.go 对话密码键与按编号清锁 / 过期清理 U-03 → U-04
internal/store/queue.go busy 恢复 / 关闭安全与非事务接口 D-01 → D-02
internal/admin/endpoints.go PATCH 启停 / 默认延迟校验 / 重置踢线 / 对话密码接口 / 审计调用 H-03 / H-05 / H-07 / U-03 / H-02
web/src/views/EndpointsView.vue 一次性密钥 / 列表与编辑 / 布局文案 W-04 → W-05 → W-06
迁移文件 0003 索引 / 可选外键 / completed_at C-03 / U-02(0004)/ C-07(0005,或并入 0003)
docs/DEVIATIONS.md 每个修复在文末追加"### 复审修复 {编号}",只追加不改旧节;合并冲突时保留双方 全部

统一取舍(多份审查方案不一致时的裁决)

  • 大帧名额:按 PacketID 在 PUBACK、丢弃、断线时归还,只在 broker 保留一份(B-02、C-01);不采用"发布完成即归还"。
  • 慢客户端:broker 做每连接异步下发(B-03),消息线做每连接推送 worker(C-01),两者互补。
  • 握手前推送:主修在 C-01(只对已握手连接推送),B-06 让 broker 在误推时返回错误作兜底。
  • 请求限速:message 导出 AllowRequest,uplink 在分发前统一检查,Submit 不再单独扣(C-05)。
  • 在线判定:presence 以 broker 的已握手状态为准(B-09);消息分发仍按 DEVELOPMENT 7.4 把握手中算在线。
  • 先发 fatal 再断开:只保留一条断开路径,由 broker 提供"写出后断开"的原语,不靠固定 sleep(B-04)。
  • 作废投递的终态:统一由 RejectPendingTx / TryFinalizeTx 处理,group 与 identity 都改为调用(C-04)。
  • 对话密码按对计数:统一为 {发送方, 对方},不带 IP(U-03)。
  • 直连 TLS 的 Secure Cookie:TLS 包装连接实现 ConnectionState()(L-04)。
  • mochi 发送配额:在 OnConnect 置 0,不 fork mochi(B-01)。
  • SDK 重连退避:四套按 K-00 表里的明确算法实现,不以任何一套现有代码为参照(JS 现有实现有双重翻倍);限速和断线后的重交每次都重新生成 rid(K-00)。
  • #3:按测试客户端缺陷修(T-01),不合入 feat/fix-3-downlink-deadlock 与 E:\code\NixMsg-wt\fix3 的 broker 改动(基于错误假设,且改变了 PUBACK 时序)。

每个修复的通用要求

  • 在自己的 feat/fix-{编号}-{简述} 分支上工作;补测试 → task check 通过 → 提交 → 推送;合入前 rebase 到 origin/main 并快进合并,不强推 main。
  • 涉及 WebSocket 集成测试的修复,在 T-01 合入后再做最终验证。
  • 不改变 PRD 规定的产品行为;需要改文档的,按 issue 指定的小节修改。
  • 完成后在 issue 评论里写明合入的 commit,再关闭 issue。

看过但判为故意设计或正确(未建 issue)

  • 已记录的偏差:
    • WebSocket 的 InsecureSkipVerify 与子协议 mqtt、OnPublish 返回 CodeSuccessIgnore、InlineClient 放行;
    • 上行队列满 256 时阻塞读循环(DEVELOPMENT §5 规定的背压);
    • 注册安全码明文存库、后台可见(D14);API 令牌权限等同管理员但不能改管理员密码、不能管理令牌(D27);
    • 回执"可能重复,SDK 按 receipt_id 去重"(DEVELOPMENT 6.4;C-02 只消除每秒全量重发,不改变这条约定)。
  • 核对后正确:
    • 登录锁定阈值(编号+IP 5 分钟 10 次、编号 1 小时 50 次,令牌重连不受影响);
    • 会话令牌只存 SHA-256、常量时间比较、密码登录换新令牌并以 0x8E 顶掉旧连接;
    • 四套 SDK 的 WebSocket 都按字节流解析,CONNECT 都不带 Receive Maximum;
    • #1、#2、#6 的修复本身正确;#4、#5 在各自 issue(B-04、H-07、C-01)里补全。

待核实(未建 issue,建议在修复过程中顺带确认)

  • mochi 顶号竞态:旧连接正常断开与同编号新连接接入同时发生时,新连接可能被从 mochi 客户端表与订阅表里删掉(P 待核实 1)。
  • 1Panel 证书部分写入:截断的 fullchain 只装入叶证书(P 待核实 4)。
  • 每秒 COUNT(*) 统计在千万级 pending 下的耗时(P 待核实 3;C-01 已把采样改为 10–15 秒)。
  • 1000 人建群时 member_added 的 O(N²) 扇出(I 待核实 3)。
  • presence.watch 与断线的极小时序窗口(I 待核实 4)。
  • 两次密码登录重叠时,在线设备可能持有已被覆盖的令牌(I 待核实 1)。
  • 投递记录在 PRD §8 容量下能否 1 秒返回、1000 行 CSV 的真实 argon2 耗时、两种分辨率下的实际布局、Firefox/Safari 下载(A 待核实)。
  • 需要负责人确认:消息详情返回的 meta 是否算"正文"(A 待核实);记录保留 0 天时迟到的 ack 拿不到撤回结果,是否接受"依赖尽力发送的 revoked"并记入 DEVIATIONS(M 待核实 3)。
  • -race 全量检测(本机无 CGO,建议在 golang 官方镜像里对 broker、listener、store、cmd/nixmsg 跑一遍)。
## 结论 基线 main `4059a15`。本次全量复审整理出 **58 个工作包(critical 7、high 13、medium 29、low 9)**,每个工作包一个 issue,均附证据(文件与行号)、文档依据、统一方案、改动文件、冲突说明与验收测试。请负责人审核后再按下面的分工并行修复。 最需要先处理的问题: - **B-01**:客户端 CONNECT 带 Receive Maximum 时,mochi 的发送配额路径递归读锁死锁。已用压测复现(31 条内卡死),单个客户端即可冻结全站下发。已验证的修法是在 `OnConnect` 里把发送配额置 0(6 轮×30 秒、每轮约 250 万条无卡顿)。 - **B-02**:broker 的大帧名额在 PUBACK 时从不归还,累计 64 条大于 64 KiB 的消息后全局循环永久阻塞。 - **B-05**:认证失败的连接永久留在连接表,无需凭据即可耗尽内存。 - **K-01 至 K-04**:四套 SDK 断线后已发出的发送永不重交;Java 把连不上服务器判成密码错误并永久停止重连;Python 被顶号后两端每秒互踢;JS 在 Node 下确认失败会让进程崩溃。 - **B-03**:慢客户端写阻塞时,向它发布 QoS 1 会卡住调用线程。已实证第 18 次发布阻塞超过 10 秒,`messageLoops` 会被连带冻结。 - **#3 的结论已更正(T-01)**:它不是服务端死锁,而是测试用 WebSocket 客户端的两个缺陷(不按字节流拆包、并发写不加锁)。已用对照实验验证,修好后去掉群事件 20ms 绕过,同步下发 20/20 通过。 ## 审查方式 - 按目录分 5 个区独立审查:A 管理后台与网页、M 消息核心、I 身份认证群在线、P 传输平台部署、S SDK。各区对照 PRD、DEVELOPMENT、DEVIATIONS 逐条区分真实缺陷、故意设计与误报。 - 总审查人另做死锁、mochi 配额、慢消费者三组实证实验,并对各区结论抽查复核(管理后台 7 条、消息核心 2 条、身份区 2 条、传输区 4 条,全部成立)。 - 多个区报告的同一问题已合并;方案不一致时由总审查人统一裁决,写在各 issue 的"结论与统一方案"与下方"统一取舍"。 - SDK 区另做了第二轮审查,新增发现经总审查人对照代码核实后补进 K-00 至 K-04(各 issue 方案表里标"补充"的条目),工作包数量不变。 - 审查全程只读,没有改动仓库代码;诊断用的临时工作树与测试文件已删除。 ## 编号说明 - **工作包(issue)编号**:B broker、L 监听与 HTTP、D 存储配置命令行部署、C 消息核心、U 身份群在线认证、H 管理后端、W 后台网页、K SDK、T 测试与文档。 - **原始发现编号**:A、M、I、P、S 开头,附在各 issue 的"问题明细"里,便于追溯。 - 标签:`P0-critical`、`P1-high`、`P2-medium`、`P3-low` 表示严重级;`lane/*` 表示工作线;`review-2026-09-30` 表示本次复审。 ## 工作包清单 | 编号 | issue | 严重级 | 工作线 | 标题 | 依赖 | |---|---|---|---|---|---| | B-01 | #8 | critical | broker | 客户端 CONNECT 带 Receive Maximum 时 mochi 发送配额路径递归读锁死锁,单个客户端即可冻结全站下发 | 无 | | B-02 | #9 | critical | broker | broker 大帧名额收到 PUBACK 时从不归还,累计 64 条大帧后 PublishDown 永久阻塞、messageLoops 冻结 | B-01 (#8)(同文件,先合) | | B-03 | #10 | high | broker | 慢客户端写阻塞时,向它发布 QoS1 会卡住调用线程,messageLoops 与其他端的处理被连带冻结 | B-01 (#8)、B-02 (#9)(同文件,先合) | | B-04 | #11 | medium | broker | 停用/删除/重置密码/退出登录靠固定 sleep 等下行写出再断开,且有两条断开路径赛跑,fatal 与 resp 可能丢失 | B-03 (#10)(使用每连接发送队列) | | B-05 | #12 | critical | broker | 认证失败的连接永久留在 broker 连接表,无需凭据即可耗尽内存并拖慢所有按连接查找 | 无 | | B-06 | #13 | medium | broker | PublishDown 不校验目标是否仍是当前且已订阅的连接:顶号窗口内旧连接的帧发给新连接、握手前的帧推进空主题 | B-03 (#10)(校验放在入队之前) | | B-07 | #14 | medium | broker | mochi 报错日志会写出整个包:CONNECT 里的密码与会话令牌、PUBLISH 正文进入日志 | 无 | | B-08 | #15 | medium | broker | broker 上行队列按编号创建后永不回收;停机时关闭通道可能 panic(为 L-03 提供 Broker.Shutdown) | 无 | | B-09 | #16 | high | broker | 连接生命周期没有串行化:握手与断线交错时断开的连接被标成在线,在线判定多处来源互相不一致 | B-05 (#12)(同改 OnDisconnect) | | B-10 | #17 | high | broker + 身份群在线 | 密码校验与写库不是原子的:重置、停用、删除期间的登录、改密、退出会越过管理员操作 | B-09 (#16)(同改 handleHello) | | B-11 | #18 | high | broker | 令牌"30 天没用"按最后一次认证时间算,一直在线的设备重连会被判失效 | 无(依赖消息线维护的上下线字段,B-09 (#16) 后仍由消息线写) | | B-12 | #19 | medium | broker + 身份群在线 | 认证没有超时与并发上限:argon2 排队无界、并发尝试可绕过锁定、PHC 参数写死 | 无 | | L-01 | #20 | high | 监听与 HTTP | 握手前阶段没有超时与并发上限:TLS 握手、CONNECT 之前、WebSocket 升级后、HTTP 空闲长连接都能无限占用资源 | 无 | | L-02 | #21 | high | 监听与 HTTP | accept 出一次错就永久停止接受新连接 | 无 | | L-03 | #22 | high | 监听与 HTTP | 有 MQTT 连接时停机退不出,还可能向已关闭通道发送而 panic | B-08 (#15)(Broker.Shutdown)、D-02 (#28)(写队列关闭安全)、C-01 (#32)(可等待退出的消息循环) | | L-04 | #23 | high | 监听与 HTTP | NixMsg 直接终止 TLS 时 r.TLS 为空,后台 Cookie 不带 Secure | 无 | | L-05 | #24 | medium | 监听与 HTTP | 后台静态页没有前端路由回退:刷新或直接打开二级页面返回 404,访问目录会列出文件 | 无 | | L-06 | #25 | medium | 监听与 HTTP | 健康检查只会明文:配了证书并关闭明文时必失败,Docker 示例配置因此默认开着明文 | 无 | | L-07 | #26 | low | 监听与 HTTP | 监听与 HTTP 小问题合集:XFF 解析可伪造、每连接新建 tls.Config、/metrics 令牌非常量时间比较、重复创建 listener | 无 | | D-01 | #27 | medium | 存储配置 | 写队列 busy 状态一旦置位就永不恢复,/readyz 永久返回 503 | 无 | | D-02 | #28 | medium | 存储配置 | 写队列关闭不安全且缺少非事务执行接口:停机时可能 panic,清理后无法执行 wal_checkpoint | 无 | | D-03 | #29 | medium | 存储配置 | 读连接池没有上限且只保留 2 个空闲连接:群发或批量唤醒时出现 SQLite 连接风暴 | C-01 (#32)、C-02 (#33)(推送改为先收集结果再发布、推送并发有上限) | | D-04 | #30 | medium | 存储配置 | 备份与恢复不安全:库不存在时静默新建空库并报成功,恢复步骤没要求清理 -wal/-shm | 无 | | D-05 | #31 | low | 存储配置 | 存储、配置与构建小问题合集:迁移失败反复全量备份、版本号未注入、显式 0 被改回默认、命令行改管理员密码不作废会话 | 无 | | C-01 | #32 | high | 消息核心 | 推送与调度循环重构:握手前就推送、同端并发推送、单协程串行循环被慢操作冻结、到点分发每秒最多 100 条 | B-02 (#9)(第 5 点删除消息包大帧名额需在其后) | | C-02 | #33 | high | 消息核心 | 回执没有在途标记:每次推送都重发最早 64 条未确认回执,并发时必然重复,第 65 条起可能永远发不出 | C-01 (#32)(在途表挂在每连接 worker 上) | | C-03 | #34 | high | 消息核心 | 保留清理放在每秒循环里:全表扫描、单事务大删除、到期处理无 LIMIT、没有 wal_checkpoint;断线清标记 SQL 缺条件;僵尸不保留投递永不过期 | C-01 (#32)(清理循环调度)、D-02 (#28)(非事务执行接口) | | C-04 | #35 | high | 消息核心 | 退群、踢人、解散、停用、删除作废投递时不写回执、不收尾:消息永远停在 dispatched、正文不删、占发送方配额 | 无 | | C-05 | #36 | medium | 消息核心 | 每端请求限速只作用于 send,撤回、状态、目录、群、unlock 等请求都不限速 | 无 | | C-06 | #37 | medium | 消息核心 | 推送时 meta 里的数字被转成 float64,超过 2^53 的整数被静默篡改 | 无 | | C-07 | #38 | low | 消息核心 | 消息校验与小问题合集:ttl 可为 0 或负数、delay 溢出变立即发送、提交不检查发送方已停用、群分发不按入群时间过滤、保留期按创建时间算 | 第 4 点依赖 C-03 (#34)、C-04 (#35) | | U-01 | #39 | high | 身份群在线 + 后台网页 | 开启自助注册时不要求安全码,空安全码即可注册 | 无 | | U-02 | #40 | medium | 身份群在线 | 群操作的校验不在写事务里;建群输入不去重;后台建群不校验群主 | C-04 (#35)(同改 group 包,C-04 (#35) 先合) | | U-03 | #41 | medium | 身份群在线 | 对话密码锁定不一致:按对计数键三处各不相同、锁定期内不带密码也报 rate_limited、后台改对话密码与删除端不清锁定 | 无 | | U-04 | #42 | medium | 身份群在线 | 锁定计数表从不清理,轮换 IP 可让内存持续增长 | U-03 (#41)(同文件) | | U-05 | #43 | low | 身份群在线 | 目录搜索的 LIKE 通配符没有转义;端编号 inline 与 mochi 内联客户端同名 | 无 | | H-01 | #44 | medium | 管理后端 | 管理接口请求体大小与读取时间不受限制,公开的登录接口可被打爆内存 | 无 | | H-02 | #45 | medium | 管理后端 | 审计日志可能整体丢失,令牌身份与操作内容记录不清,登录失败与批量失败不记录 | 无 | | H-03 | #46 | medium | 管理后端 + 后台网页 | 端编辑先直接写 enabled 再走级联停用:停用可能半生效,并发停用会被旧值悄悄重新启用 | 无 | | H-04 | #47 | medium | 管理后端 + 后台网页 | 批量导入串行算哈希、校验与插入之间有竞态;CSV 报错行号不准 | 无 | | H-05 | #48 | medium | 管理后端 + 后台网页 | 端的默认延迟没有上限:后台可设出永远发不出消息的端,极大值还会溢出 | 无 | | H-06 | #49 | low | 管理后端 | 改管理员密码时旧密码校验不计入锁定;过期管理员会话不清理,作废其他会话失败被忽略 | 无 | | H-07 | #50 | low | 管理后端 | 重置登录密码时踢线失败被吞掉,审计仍记 ok | B-04 (#11) | | W-01 | #51 | medium | 后台网页 | 后台错误处理:不处理 401、每个错误弹两次、网络错误显示英文、表单错误只闪 toast、页面加载失败一片空白 | 无 | | W-02 | #52 | medium | 后台网页 | 投递记录页缺少时间筛选、原因列、完整统计与接收端分页 | 无 | | W-03 | #53 | medium | 后台网页 | 群页面:详情只显示前 50 个成员,且与后端能力不一致(编号输入框、搜索提示、无确认、失败无原因) | 无 | | W-04 | #54 | medium | 后台网页 | 一次性密钥展示:导入结果 CSV 不转义会错列、手填密码时显示 undefined、复制失败无提示、导入弹窗无下载按钮 | 无 | | W-05 | #55 | medium | 后台网页 | 端列表页:只有 IP 锁定时无法点解锁、批量选择跨筛选保留、编辑弹窗总提交 enabled 且停用无确认、导入无进度、默认延迟无上限提示 | W-04 (#54)(同文件) | | W-06 | #56 | low | 后台网页 | 界面布局与文案:表格高度写死不随窗口自适应、直接显示英文枚举与配置键名、概览缺自助注册数 | 建议在 W-01 (#51) 至 W-05 (#55) 之后 | | K-00 | #57 | medium | SDK + 测试与文档 | 四套 SDK 对外语义不一致,需要先定一份统一的 SDK 行为约定(连接超时、错误码、返回值、取消、退避、心跳、URL 映射) | 无 | | K-01 | #58 | critical | SDK | Go SDK:断线后已发出的发送永不重交、回调持锁导致重入死锁、收包路径被阻塞、重连不恢复上下线订阅、fatal 原因可能丢失 | K-00 (#57)(对外语义条目) | | K-02 | #59 | critical | SDK | JS SDK:Node 下确认失败导致进程崩溃、浏览器因全局 Buffer 无法连接、断线后发送挂起、确认失败后不再重试 | K-00 (#57)(对外语义条目) | | K-03 | #60 | critical | SDK | Python SDK:没关 paho 自动重连导致被顶号后两端互踢、回调在锁下执行会死锁、断线后发送挂起、async 回调不被等待就确认 | K-00 (#57)(对外语义条目) | | K-04 | #61 | critical | SDK | Java SDK:把"连不上服务器"判成密码错误并永久停止重连、持锁等待 PUBACK 与回调重入会死锁、断线后发送不重交 | K-00 (#57)(对外语义条目) | | K-05 | #62 | low | SDK + 测试与文档 | SDK 工程门禁缺失:四套 SDK 都不在任何检查里、Go SDK 与服务端 Go 版本不一致 | 无 | | T-01 | #3 | medium | 测试与文档 | 测试 WS 客户端不按字节流拆包、并发写不加锁,被误判为服务端死锁;移除群事件 20ms 绕过 | 无 | | T-02 | #63 | medium | 测试与文档 | 验收对照表把只测了部分子项的功能标成"通过",交付说明据此写"通过 23" | T-01 (#3)(可靠的 WS 测试客户端) | | T-03 | #64 | low | 测试与文档 | 压测工具只是连接骨架(MQTT 3.1.1、不带认证、不收发),无法执行 PRD 第 8 节的规模与吞吐验收 | T-01 (#3) | ## 并行分工与合并顺序 同一工作线内按箭头顺序合入;不同工作线可以并行开发,按"跨线依赖"合入。 | 工作线 | 建议人手 | 线内顺序 | |---|---|---| | 测试与文档 | 1 | **T-01 最先合入**(其他修复的 WebSocket 集成测试依赖它)→ T-03;T-02 随各修复推进,最后定稿 | | broker · 下行通道 | 1 | B-01 → B-02 → B-07 → B-03 → B-06 → B-04 → B-08 | | broker · 生命周期与认证 | 1 | B-05 → B-09 → B-10 → B-11 → B-12 | | 监听与 HTTP | 1 | L-02 → L-01 → L-04 → L-05 → L-06 → L-07 → L-03(最后,依赖 B-08、D-02、C-01) | | 存储、配置、命令行 | 1 | D-01 → D-02 → D-04 → D-05 → D-03(依赖 C-01、C-02) | | 消息核心 | 1–2 | C-04 → C-05 → C-06 → C-01(依赖 B-02)→ C-02 → C-03(依赖 D-02)→ C-07 | | 身份、群、在线 | 1 | U-01 → U-03 → U-04 → U-05 → U-02(依赖 C-04) | | 管理后端 | 1 | H-01 → H-03 → H-04 → H-05 → H-06 → H-07(依赖 B-04)→ H-02(最后,改动面最广) | | 后台网页 | 1 | U-01 的界面部分 → W-01 → W-04 → W-05 → W-02 → W-03 → W-06 | | SDK | 每套 1 | K-00 先定约定 → K-01、K-02、K-03、K-04 并行 → K-05 | 跨线依赖: - B-02 → C-01(C-01 删除消息包那份大帧名额) - B-03 与 C-01:以 `PublishDown` 签名不变为契约并行开发;消息线把 `ErrBackpressure`、`ErrNotSubscribed`、`ErrNoConnection` 一律当作发布失败处理 - B-08、D-02、C-01 → L-03(停机总装) - D-02 → C-03(清理后执行 wal_checkpoint) - C-01、C-02 → D-03(读连接池上限) - C-04 → U-02(同改 group 包)、C-07 第 4 点 - B-04 → H-07 - B-05 → B-09 → B-10(同改 hooks.go 与 handleHello) - K-00 → K-01 至 K-04 ## 共享文件归属(避免多人改同一处) | 文件 | 位置 | 负责的 issue | |---|---|---| | `cmd/nixmsg/serve.go` | `messageLoops` | C-01 | | | `runServe` 末尾停机段 | L-03 | | | `staticFileHandler` | L-05 | | | listener 装配段 | L-07 | | | `identity.New` 的 `ConnControl` | B-04 | | | `admin.Deps` 的 `SecureCookies` / 审计 logger | L-04 / H-02 | | `cmd/nixmsg/uplink.go` | `OnSessionEstablished`、`OnHandshakeComplete`、`OnDisconnect` | B-09 | | | `HandleUplink`(分发前限速) | C-05 | | | `publishResp`(大小上限) | B-06 | | `internal/broker/hooks.go` | `OnConnect` | B-01、B-05、B-12(按 broker 线顺序) | | | `OnQosPublish` / `OnQosComplete` / `OnQosDropped` | B-02 | | | `OnSessionEstablished` / `OnDisconnect` | B-03、B-05、B-09 | | | `OnSessionEstablish`(新增) | B-06 | | `internal/broker/session.go` | 下发通道 / `fatalKick`、logout / `handleHello`、`HandleDisconnect` | B-03 / B-04 / B-09、B-10 | | `internal/broker/ws.go` | `AttachWS` 前读超时 / 客户端 IP | L-01 / L-07 | | `internal/app/message/push.go` | 推送与调度 | C-01,其后 C-02 | | `internal/app/message/submit.go` | 限速 / 参数校验与停用检查 / 对话密码函数 | C-05 / C-07 / U-03 | | `internal/app/group/app.go` | `emit` / 校验逻辑 / `checkAddMember` | T-01 / U-02 / U-03 | | `internal/app/group/void.go`、`identity/lifecycle.go` | 作废与收尾函数 / 第 131–138 行断开 goroutine / 删除端清锁 | C-04 / B-04 / U-03 | | `internal/auth/locks.go` | 对话密码键与按编号清锁 / 过期清理 | U-03 → U-04 | | `internal/store/queue.go` | busy 恢复 / 关闭安全与非事务接口 | D-01 → D-02 | | `internal/admin/endpoints.go` | PATCH 启停 / 默认延迟校验 / 重置踢线 / 对话密码接口 / 审计调用 | H-03 / H-05 / H-07 / U-03 / H-02 | | `web/src/views/EndpointsView.vue` | 一次性密钥 / 列表与编辑 / 布局文案 | W-04 → W-05 → W-06 | | 迁移文件 | `0003` 索引 / 可选外键 / `completed_at` | C-03 / U-02(0004)/ C-07(0005,或并入 0003) | | `docs/DEVIATIONS.md` | 每个修复在文末追加"### 复审修复 {编号}",只追加不改旧节;合并冲突时保留双方 | 全部 | ## 统一取舍(多份审查方案不一致时的裁决) - **大帧名额**:按 PacketID 在 PUBACK、丢弃、断线时归还,只在 broker 保留一份(B-02、C-01);不采用"发布完成即归还"。 - **慢客户端**:broker 做每连接异步下发(B-03),消息线做每连接推送 worker(C-01),两者互补。 - **握手前推送**:主修在 C-01(只对已握手连接推送),B-06 让 broker 在误推时返回错误作兜底。 - **请求限速**:message 导出 `AllowRequest`,uplink 在分发前统一检查,`Submit` 不再单独扣(C-05)。 - **在线判定**:presence 以 broker 的已握手状态为准(B-09);消息分发仍按 DEVELOPMENT 7.4 把握手中算在线。 - **先发 fatal 再断开**:只保留一条断开路径,由 broker 提供"写出后断开"的原语,不靠固定 sleep(B-04)。 - **作废投递的终态**:统一由 `RejectPendingTx` / `TryFinalizeTx` 处理,group 与 identity 都改为调用(C-04)。 - **对话密码按对计数**:统一为 {发送方, 对方},不带 IP(U-03)。 - **直连 TLS 的 Secure Cookie**:TLS 包装连接实现 `ConnectionState()`(L-04)。 - **mochi 发送配额**:在 `OnConnect` 置 0,不 fork mochi(B-01)。 - **SDK 重连退避**:四套按 K-00 表里的明确算法实现,不以任何一套现有代码为参照(JS 现有实现有双重翻倍);限速和断线后的重交每次都重新生成 rid(K-00)。 - **#3**:按测试客户端缺陷修(T-01),不合入 `feat/fix-3-downlink-deadlock` 与 `E:\code\NixMsg-wt\fix3` 的 broker 改动(基于错误假设,且改变了 PUBACK 时序)。 ## 每个修复的通用要求 - 在自己的 `feat/fix-{编号}-{简述}` 分支上工作;补测试 → `task check` 通过 → 提交 → 推送;合入前 rebase 到 `origin/main` 并快进合并,不强推 main。 - 涉及 WebSocket 集成测试的修复,在 T-01 合入后再做最终验证。 - 不改变 PRD 规定的产品行为;需要改文档的,按 issue 指定的小节修改。 - 完成后在 issue 评论里写明合入的 commit,再关闭 issue。 ## 看过但判为故意设计或正确(未建 issue) - **已记录的偏差**: - WebSocket 的 `InsecureSkipVerify` 与子协议 mqtt、`OnPublish` 返回 `CodeSuccessIgnore`、InlineClient 放行; - 上行队列满 256 时阻塞读循环(DEVELOPMENT §5 规定的背压); - 注册安全码明文存库、后台可见(D14);API 令牌权限等同管理员但不能改管理员密码、不能管理令牌(D27); - 回执"可能重复,SDK 按 receipt_id 去重"(DEVELOPMENT 6.4;C-02 只消除每秒全量重发,不改变这条约定)。 - **核对后正确**: - 登录锁定阈值(编号+IP 5 分钟 10 次、编号 1 小时 50 次,令牌重连不受影响); - 会话令牌只存 SHA-256、常量时间比较、密码登录换新令牌并以 0x8E 顶掉旧连接; - 四套 SDK 的 WebSocket 都按字节流解析,CONNECT 都不带 Receive Maximum; - #1、#2、#6 的修复本身正确;#4、#5 在各自 issue(B-04、H-07、C-01)里补全。 ## 待核实(未建 issue,建议在修复过程中顺带确认) - mochi 顶号竞态:旧连接正常断开与同编号新连接接入同时发生时,新连接可能被从 mochi 客户端表与订阅表里删掉(P 待核实 1)。 - 1Panel 证书部分写入:截断的 fullchain 只装入叶证书(P 待核实 4)。 - 每秒 `COUNT(*)` 统计在千万级 pending 下的耗时(P 待核实 3;C-01 已把采样改为 10–15 秒)。 - 1000 人建群时 `member_added` 的 O(N²) 扇出(I 待核实 3)。 - `presence.watch` 与断线的极小时序窗口(I 待核实 4)。 - 两次密码登录重叠时,在线设备可能持有已被覆盖的令牌(I 待核实 1)。 - 投递记录在 PRD §8 容量下能否 1 秒返回、1000 行 CSV 的真实 argon2 耗时、两种分辨率下的实际布局、Firefox/Safari 下载(A 待核实)。 - **需要负责人确认**:消息详情返回的 `meta` 是否算"正文"(A 待核实);记录保留 0 天时迟到的 ack 拿不到撤回结果,是否接受"依赖尽力发送的 revoked"并记入 DEVIATIONS(M 待核实 3)。 - `-race` 全量检测(本机无 CGO,建议在 golang 官方镜像里对 broker、listener、store、cmd/nixmsg 跑一遍)。
nixevol added the review-2026-09-30tracking labels 2026-09-30 13:56:50 +08:00
Author
Owner

复审修复已线性合入 origin/main HEAD 0c9b459(相对 4059a15 快进 44 个提交)。未强推 main。未合 feat/fix-3-downlink-deadlock,保留工作树 E:\code\NixMsg-wt\fix3。

已关闭: (#3) T-01; (#8) – (#19) B-01–B-12; (#20) – (#26) L-01–L-07; (#27) – (#31) D-01–D-05; (#32) – (#38) C-01–C-07; (#39) – (#43) U-01–U-05; (#44) – (#50) H-01–H-07; (#51) – (#56) W-01–W-06; (#57) – (#62) K-00–K-05; (#63) T-02; (#64) T-03。

task check(web build + golangci-lint + go test ./...)通过。TestQ2 在 8f2ebc7 补 WakePush 后通过。迁移 0003(U-02)与 0004(C-03)均在。

本总览 issue 保持开放,供阶段 3 收尾。

复审修复已线性合入 origin/main HEAD `0c9b459`(相对 `4059a15` 快进 44 个提交)。未强推 main。未合 `feat/fix-3-downlink-deadlock`,保留工作树 E:\code\NixMsg-wt\fix3。 已关闭: (#3) T-01; (#8) – (#19) B-01–B-12; (#20) – (#26) L-01–L-07; (#27) – (#31) D-01–D-05; (#32) – (#38) C-01–C-07; (#39) – (#43) U-01–U-05; (#44) – (#50) H-01–H-07; (#51) – (#56) W-01–W-06; (#57) – (#62) K-00–K-05; (#63) T-02; (#64) T-03。 task check(web build + golangci-lint + go test ./...)通过。TestQ2 在 `8f2ebc7` 补 WakePush 后通过。迁移 0003(U-02)与 0004(C-03)均在。 本总览 issue 保持开放,供阶段 3 收尾。
Author
Owner

origin/main 已快进到 0c9b459(0c9b459fb1828e913f39d6368eda826968cfdda2)。子 issue 已关闭。阶段 3 未做。F03/F08/F21/F22 仍部分通过。关闭本总览 (#7)。

origin/main 已快进到 `0c9b459`(`0c9b459fb1828e913f39d6368eda826968cfdda2`)。子 issue 已关闭。阶段 3 未做。F03/F08/F21/F22 仍部分通过。关闭本总览 (#7)。
Sign in to join this conversation.