create: WebSocket 上的 MQTT:测试客户端拆包/写锁导致误判死锁,以及 mochi 配额递归读锁真死锁(2026-09-30 实证)
MemRelay-Operation: memory-save:nixmsg-exp-ws-mqtt-mochi-20260930-a1 MemRelay-Resource: f5fa2d41-41c6-4399-ac35-b46679d94b97
This commit is contained in:
+41
@@ -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)。
|
||||||
Reference in New Issue
Block a user