diff --git a/projects/25464650-ed5d-440e-b362-190f2852d451/WebSocket 上的 MQTT:测试客户端拆包-写锁导致误判死锁,以及 mochi 配额递归读锁真死锁(2026-09-30 实证).md b/projects/25464650-ed5d-440e-b362-190f2852d451/WebSocket 上的 MQTT:测试客户端拆包-写锁导致误判死锁,以及 mochi 配额递归读锁真死锁(2026-09-30 实证).md new file mode 100644 index 0000000..a0ac49a --- /dev/null +++ b/projects/25464650-ed5d-440e-b362-190f2852d451/WebSocket 上的 MQTT:测试客户端拆包-写锁导致误判死锁,以及 mochi 配额递归读锁真死锁(2026-09-30 实证).md @@ -0,0 +1,41 @@ +--- +title: WebSocket 上的 MQTT:测试客户端拆包/写锁导致误判死锁,以及 mochi 配额递归读锁真死锁(2026-09-30 实证) +type: experience +permalink: main/projects/25464650-ed5d-440e-b362-190f2852d451/web-socket-上的-mqtt-测试客户端拆包-写锁导致误判死锁以及-mochi-配额递归读锁真死锁-2026-09-30-实证 +stable_id: f5fa2d41-41c6-4399-ac35-b46679d94b97 +scope: project +memory_type: experience +project_id: 25464650-ed5d-440e-b362-190f2852d451 +usage_profile_id: 0c9feb46-d06e-43cd-9ef0-c5813c84a998 +status: active +revision: 1 +request_id: nixmsg-exp-ws-mqtt-mochi-20260930-a1 +created_at: '2026-09-30T06:04:00.258136+00:00' +updated_at: '2026-09-30T06:04:00.258136+00:00' +tags: +- deadlock +- mochi +- websocket +- mqtt +- test-harness +- issue-3 +--- + +## 误判:issue #3「服务端下发死锁」 +- 现象:集成测试偶发 resp 回不来(如 TestUplinkDMOfflineGroupRecall),曾被判为服务端死锁,并在 group emit 加了 goroutine + 20ms sleep 绕过。 +- 真实根因是测试用 WS 客户端 test/harness/mqtt.go: + 1. Recv 把一个 WS 二进制帧当作恰好一个 MQTT 包。mochi 在 outbound 有积压时会把多个包一次 Write,合成一个 WS 帧(MQTT5 §6 [MQTT-6.0.0-3] 允许),客户端只解出第一个包。 + 2. Send 把帧头和载荷分两次 Write,且不加锁;读循环回 PUBACK/PONG 与测试线程发请求交错,写乱发往服务端的流,服务端卡在读一个错乱长度的包,而且没有报错日志。 +- 做法:接收端按字节流缓冲拆包(含 continuation 帧);发送端加互斥锁,整帧一次 Write。修好后同步下发 20/20 通过。任何自写的 WS-MQTT 客户端(测试、诊断、SDK)都必须这样做。 +- 判断技巧:在客户端记录「一个 WS 帧里有几个 MQTT 包」,出现大于 1 就说明服务端合帧;对照实验每次只改一个变量。 + +## 真死锁:mochi v2.7.9 发送配额路径 +- 客户端 CONNECT 带 Receive Maximum 时 sendQuota>0,processPacket(server.go 719-728)走 Inflight.NextImmediate:持 RLock 时又调 GetAll 再取 RLock(递归读锁)。它与 Inflight.Set 的写锁排队后永久互等,冻结该连接的读循环以及所有向它下发的调用。压测 31 条以内即可复现。 +- 规避:在 OnConnect 中调用 cl.State.Inflight.ResetSendQuota(0)。实测 6 轮×30 秒、每轮约 250 万条无卡顿;不需要 fork mochi。四套 SDK 当前不带 Receive Maximum,裸 MQTT 设备可能带。对应 issue B-01(#8)。 + +## 慢消费者 +- mochi WritePacket 在网络写期间持有 cl.Lock,对端不读时第 18 次 256KiB 发布阻塞超过 10 秒,调用线程被卡住(会连带冻结 messageLoops)。需要每连接异步下发,对应 B-03(#10)。 + +## 诊断工程经验 +- 修改 mochi 做实验:在临时工作树里 vendoring 到 third_party,加 replace;用完删除工作树。 +- PowerShell 5.1 的 Add-Content 默认编码会写坏 UTF-8 的 Go 源码(invalid UTF-8 encoding)。 \ No newline at end of file