fix: 仅向已握手连接推送并拆分调度循环

This commit is contained in:
Nixevol
2026-09-30 15:46:25 +08:00
parent 825e13cfdf
commit eca4c836f1
14 changed files with 1192 additions and 290 deletions
+39 -16
View File
@@ -456,12 +456,12 @@
- 备选方案:直接依赖 `internal/broker.Broker`。
- 影响:接线方需在握手/断线时调用 `OnHandshakeComplete`/`OnDisconnect`,登记连接,并把 `OnPublishDropped` 转到 `App`。
2. **大帧并发名额在 message 包再管一份**
2. **大帧并发名额只由 broker 按 PacketID 归还**
- 原条款:大于 64KiB 全局同时不超过 64(DEVELOPMENT 7.5);N2 broker 已有信号量。
- 实际做法:`App` 内另有容量 64 的 `largeSem`,发布前申请,确认/超时/清标记时释放。
- 原因:假 `Downlink` 不经 broker 时仍要满足上限。
- 备选方案:只依赖 broker,测试也走真实 PublishDown。
- 影响:接线真实 broker 后可能双重限流(更严,不破坏语义)。
- 实际做法(C-01):删除 message 包 `largeSem`/`largeHeld`,发布失败(含 `ErrBackpressure`/`ErrNotSubscribed`/`ErrNoConnection`)清标记并 1 秒后重推;假下行如需限流在其实现里模拟。
- 原因:消息包名额在断线/撤回/作废时泄漏;broker 已按 PacketID 在 PUBACK/断线归还。
- 备选方案:message 内改用 (connID, seq) 计数并对账。
- 影响:单元测试的 `RecordingDownlink` 不再限制大帧并发。
3. **确认超时按库内 `pushed_at` 判定,不另开每连接计时器 goroutine**
- 原条款:推送循环在内存里计时。
@@ -470,19 +470,19 @@
- 备选方案:每连接 `time.AfterFunc`。
- 影响:需周期性调用 `PushPending`(或 `WakePush`)才会触发超时。
4. **后台调度/清理循环未在 App 内自启**
4. **调度/到期/清理循环由 App.StartLoops 启动**
- 原条款:调度按 `send_at` 唤醒;清理约每秒;推送每连接一循环。
- 实际做法:导出 `DispatchDue`、`PushPending`、`CleanupOnce`、`RecoverOnStart`、`WakePush`;由接线方起 goroutine。`WakePush` 在有连接时异步 `PushPending`。
- 原因:未改 `cmd/nixmsg`;避免无 context 的后台泄漏。
- 备选方案:`App.Start(ctx)` 内启三循环。
- 影响:未接线则定时消息不会自动到点,需外部调用 `DispatchDue`。
- 实际做法(C-01):`StartLoops` 起分发(最早 `send_at` 定时 + `NotifyDispatch`)、到期每秒、清理每小时;握手启动每连接 worker(容量 1 唤醒通道)。`cmd/nixmsg` 的 `messageLoops` 只调 `StartLoops` 并 15 秒采指标。停机等待留给 L-03。
- 原因:原先单协程串行、握手前也推送、WakePush 每次新协程。
- 备选方案:继续由 serve 逐秒扫全部连接。
- 影响:未握手连接不再 claim;测试需 `LiveConn.Ready` 或 `OnHandshakeComplete`。
5. **回执推送窗口未单独记 inflight**
- 原条款:回执窗口默认 64,确认一笔再推下一笔。
- 实际做法:按 `acked=0` 取最多 `ReceiptWindow` 条尽力发布;不因未 `receipt_ack` 停推后续。
- 原因:简化;回执可重复、SDK 按 `receipt_id` 去重。
- 备选方案:内存记已推未确认回执数。
- 影响:发送方慢确认时可能多推几条回执(协议允许重复)。
5. **回执按连接记在途,产品仍允许断线后重复**
- 原条款:回执窗口默认 64,确认一笔再推下一笔;可能重复,SDK 按 `receipt_id` 去重。
- 实际做法(C-02):每连接 `rcptInflight`;发布前标记、失败撤销;超过确认超时才允许重发;先收集查询结果再 `PublishDown`。重复 ack 不唤醒;`ReceiptAck` 成功后移出在途并唤醒。
- 原因:原先每次推送全量重发最早 64 条,并发必重复,第 65 条饿死。
- 备选方案:把在途写入 receipts 表。
- 影响:断线重连后未确认回执仍各重发一次(协议允许);裸设备须 `receipt_ack` 或 `receipt:false`。
6. **`Status` 返回自建 map,非独立协议类型**
- 原条款:6.4 状态响应字段。
@@ -527,6 +527,29 @@
- 备选方案:等 C-03 迁移后改用 `completed_at`;发送方停用改用 `endpoint_disabled`(与目标停用混用)。
- 影响:发送方停用错误码为 `unauthorized`;保留期口径在 C-03 合入前对无投递的 scheduled 作废行用 `send_at` 近似。
### 复审修复 C-01
1. **只对已握手连接推送,每连接一个 worker**
- 原条款:DEVELOPMENT 6.1 / 7.5 握手完成才推送;每连接一循环。
- 实际做法:`LiveConn.Ready` / `handshook`;`PushPending`/`WakePush` 未就绪直接返回。`OnHandshakeComplete` 清本连接旧 `pushed_conn` 后启动 worker。一轮写操作 claim 全部条目再按 `(send_at, seq)` 发布。`RecoverOnStart` 只做 SQL 修正。不改 `PublishDown` 签名,不改 uplink 生命周期,serve 停机段留给 L-03。
- 原因:握手前推送会被 mochi 静默丢弃却占窗口。
- 备选方案:只靠 broker `ErrNotSubscribed` 兜底。
- 影响:分发在线判定仍含握手中(7.4)。
### 复审修复 C-02
1. **回执在途表**
- 见上文 M2/M3/M4 第 5 条。不改「可能重复」的产品约定。
### 复审修复 C-03
1. **到期与小时清理拆分,迁移避开 U-02 的 0003**
- 原条款:DEVELOPMENT 7.5 每秒到期;7.6 每小时分批删并 `wal_checkpoint`。
- 实际做法:`ExpireOnce` 每写最多 500 条,补僵尸不保留投递的宽限;`PurgeOnce` 三类删除独立写操作,结束后 `Queue.Checkpoint`。迁移 `0004_cleanup_indexes.sql`(U-02 已占用 0003,原计划 0003/0005 合并改号为 0004):`messages.completed_at`、回执/防重/完成时刻索引。收尾写入 `completed_at`;清理按 `COALESCE(completed_at, send_at)`。保留 0 天删全部 completed。断线 UPDATE 带 `endpoint_id`。读池 `MaxOpenConns=64`、`MaxIdleConns=16`、DSN `query_only`。
- 原因:每秒全表扫描加级联大删除会卡住写队列;按 `created_at` 会误删长定时刚完成的记录(C-07)。
- 备选方案:继续用 `MAX(deliveries.updated_at)` 近似完成时刻。
- 影响:旧 completed 行由迁移回填;无投递的作废行仍可能用 `send_at` 兜底。
## 身份 I
### I1 2026-09-30