Files
MemRelay/projects/25464650-ed5d-440e-b362-190f2852d451/WebSocket 上的 MQTT:测试客户端拆包-写锁导致误判死锁,以及 mochi 配额递归读锁真死锁(2026-09-30 实证).md
T
memrelay bc46d5f8bd 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
2026-09-30 14:04:02 +08:00

41 lines
3.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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)。