编号:K-01 严重级:critical 工作线:SDK(sdk/*) 来源:审查 S-01、S-05、S-08、S-09、S-12、S-13、S-14、S-15、S-19、S-20 依赖:K-00 (#57)(对外语义条目) 被依赖:无
本 issue 汇总审查中涉及 Go SDK 的条目,由 Go SDK 负责人一次改完(它们集中在 send.go、receive.go、connect.go、client.go、api.go,拆开会互相冲突)。下方"问题明细"是跨四套的原文,请只看其中 Go 的部分。
send.go
receive.go
connect.go
client.go
api.go
send_at_ms
dispatchSend
select
ch
connGone
c.ctx.Done()
cbMu
ChangeLoginPassword
Ack
handleDown
{ids, all}
taken_over
Logout
not_connected
Close
PacketTimeout
PacketTimeout = ConnectTimeout
MarkOffline
!wasOnline
OnConnectionDown
max_frame_bytes
export_test.go
SendResult
SendAtMs
types.go:93-97
send.go:199-200
json:"id"
json:"send_at_ms"
json:"state"
receive.go:130-134
:242
dedupOrd
container/list
failAuth
failKicked
connect.go:97-133
tr.Stop
cm.Disconnect
send.go:156
:186-194
标"补充"的条目来自第二轮 SDK 审查,总审查人已对照代码核实,证据写在条目里(没有对应的原文段落)。
sdk/go/send.go、receive.go、connect.go、client.go、api.go、types.go、backoff.go、transport_mqtt.go、transport_fake.go、example/minimal/main.go、README.md。
sdk/go/send.go
types.go
backoff.go
transport_mqtt.go
transport_fake.go
example/minimal/main.go
README.md
gofmt -l
go test ./...
{"id":"m1","send_at_ms":123,"state":"scheduled"}
以下是本次复审各区审查报告的原文段落。A、M、I、P、S 开头的是原始发现编号(A 管理后台与网页、M 消息核心、I 身份认证群在线、P 传输平台部署、S SDK)。解决方案以本 issue 上方的"结论与统一方案"为准;原文里的方案与之不一致时,按上方执行。
Send
wait(timeout=None)
reconnectPeriod:0
pumpSends
send.go:170-180
rf := <-ch
send.go:141-153
!it.inflight
connect.go:57-64
client.ts:494-543
client.ts:174-177
mqtt.ts:93-99
end
client.js:380-384
:882-904
:985-987
:490-496
client.py:861-873
rid==""
client.py:605-640
client.py:323
Client.java:1000-1006
Client.java:1019-1029
Transport.java:383-391
pending.rid=""
close
end(true)
sdk/js/src/client.ts
mqtt.ts
sdk/python/src/nixmsg/client.py
sdk/java/.../Client.java
_cb_lock
threading.Lock
close()
logout()
change_login_password()
ack()
_lock
send()
_request
setState
lock
sendSync
.get()
request()
synchronized(lock)
transport.publish()
dispatchResp
emitRevoked
client.py:109
:192,220,573-578,605-640
_set_state
:951-959
:787-788
Client.java:659-665,913-915,1034-1054,1108-1120
Transport.java:377-391
MqttRxClient.publishes/publishUnsafe
observeOn(applicationScheduler)
Schedulers.computation()
receive.go:121-125,247-253
api.go:134-139
sdk/python/.../client.py
Transport.java
sdk/go/receive.go
-Drx2.computation-threads=1
OnPublishReceived
downCh
receive.go:38-42,168-177,218-223
client.go:100
client.go:441-474
push.go:456-495
api.go:74-82
connect.go:148-216
client.ts:601-606
client.py:585-593
Client.java:672-683
client.ts
Func(0)=0
auth_failed(session_invalid)
"0x8E"
session.go:316-336
broker.go:274-292
receive.go:38-42,75-87
connect.go:120,135-146
backoff.go:27-29
transport_mqtt.go:81-92
t.stopped
client.py:652-655,735-745
Client.java:752,863-875
client.ts:207,304-311
client.py
Client.java
logout
client.ts:413-437,718-725
receive.go:322-337
connect.go:232-245
client.py:913
Client.java:1055
client.py:682-689
Client.java:797-805
send.go:186-195
client.ts:528-533
backoff.go:24-39
net.go:43-52
transport_mqtt.go:115-133
client.go:186-188,272
client.py:458-499
Client.java:522-581
client.ts:478-482
send.go:96-102
client.go:72
receive.go:201-208
client.ts:93
复审基线:main 4059a15(2026-09-30)。编号说明、各工作线的合并顺序、共享文件归属见总览 #7。
4059a15
按第二轮 SDK 审查补充 4 条(SendAtMs 恒为 0、去重顺序表重复记键、认证失败白等 3 秒、限速重交复用 rid),并按 K-00 (#57) 修订后的退避算法更新了 S-15 的改法和退避测试。补充条目已由总审查人对照代码核实,详见正文。
已合入 origin/main 0c9b459。落地提交 f6f8ccf fix: 按 K-00 约定修复 Go SDK 断线重交与退避 (#58)。
0c9b459
f6f8ccf
No dependencies set.
The note is not visible to the blocked user.
编号:K-01 严重级:critical 工作线:SDK(sdk/*) 来源:审查 S-01、S-05、S-08、S-09、S-12、S-13、S-14、S-15、S-19、S-20
依赖:K-00 (#57)(对外语义条目) 被依赖:无
结论与统一方案
本 issue 汇总审查中涉及 Go SDK 的条目,由 Go SDK 负责人一次改完(它们集中在
send.go、receive.go、connect.go、client.go、api.go,拆开会互相冲突)。下方"问题明细"是跨四套的原文,请只看其中 Go 的部分。send_at_ms);dispatchSend改为select等待ch、每次连接一个的connGone、c.ctx.Done()三路cbMu执行,回调里调ChangeLoginPassword、手动Ack(撤回先到)会重入死锁handleDown永不阻塞(业务帧进无界 FIFO,presence/group_event 超阈值丢最旧);fatal 在收包路径同步处理;ack/receipt_ack 发出后异步处理结果{ids, all},每次握手成功后异步重发 presence.watchtaken_overLogout不收尾not_connected失败非发送请求;默认请求超时 60 秒;Logout与Close同样收尾PacketTimeout默认 10 秒使连接超时失效PacketTimeout = ConnectTimeout;MarkOffline里的!wasOnline分支在 Go 中不会被调用(只有OnConnectionDown调它),改写时一并清理max_frame_bytes检查export_test.go暴露注入点);执行 gofmt;示例只打印令牌前缀。门禁与 Go 版本对齐见 K-05SendResult没有 json 标签,send_at_ms解析不到,SendAtMs恒为 0(types.go:93-97、send.go:199-200)json:"id"、json:"send_at_ms"、json:"state"标签receive.go:130-134)和撤回(:242)只删 map、不删dedupOrd,同一键再次登记后顺序表里有两份,淘汰旧的那份时会误删新条目container/list加 map),删除时同步移除节点failAuth、failKicked(connect.go:97-133)在 autopaho 回调里同步调用tr.Stop,cm.Disconnect要等主循环退出,而回调就在主循环里,于是固定白等 3 秒(代码推导,未实测)tr.Stop;Close用自己的超时send.go:156、:186-194),同一连接内 rid 重复send_at_ms不变;和 S-01 的重交共用同一段代码标"补充"的条目来自第二轮 SDK 审查,总审查人已对照代码核实,证据写在条目里(没有对应的原文段落)。
改动文件
sdk/go/send.go、receive.go、connect.go、client.go、api.go、types.go、backoff.go、transport_mqtt.go、transport_fake.go、example/minimal/main.go、README.md。与其他问题的交互 / 冲突说明
验收与测试
send_at_ms重发并完成;循环 150 次后仍能发送。gofmt -l为空;go test ./...通过(sdk/go 目录)。{"id":"m1","send_at_ms":123,"state":"scheduled"}:SendResult三个字段都正确。failAuth在 100 毫秒内返回,原因只上报一次。send_at_ms相同。问题明细(各区审查原文,证据含文件与行号)
[S-01] 断线时已发出、未收到 resp 的发送永不重交,调用方挂起,在途额度泄漏(Go / JS / Python / Java)
Send在 ctx 没有截止时间时永久阻塞;JS 的 Promise 永远不会完成;Python 调用时若已在线,会wait(timeout=None)永久阻塞;Java 等 1 小时后抛 busy,但条目仍留在队列里。reconnectPeriod:0下,非主动断开时只清掉 volatile 回调,QoS1 publish 的回调永不触发,所以在 PUBACK 之前断线也会卡住。pumpSends直接把这条发送判为失败并出队,不会重交。send.go:170-180(publish 成功后rf := <-ch既无超时,也没有断线出口)、send.go:141-153(只挑!it.inflight)、connect.go:57-64(OnOffline 没有处理在途条目)。client.ts:494-543、client.ts:174-177、mqtt.ts:93-99(断线后新建 client,不end旧的);MQTT.jsclient.js:380-384、:882-904、:985-987、:490-496。client.py:861-873(只挑rid=="")、client.py:605-640、client.py:323。Client.java:1000-1006、Client.java:1019-1029(publish 抛异常就判失败并出队)、Transport.java:383-391。pending.rid=""),按实际状态重算在途数;帧内容保持原样(原 id、原send_at_ms)。服务器按消息号防重,会返回原结果。dispatchSend改为select三路:ch、每次连接一个的connGone、c.ctx.Done()。close事件里对旧 client 调end(true),并加 generation 编号,防止同一条被重复完成。sdk/go/send.go、connect.go;sdk/js/src/client.ts、mqtt.ts;sdk/python/src/nixmsg/client.py;sdk/java/.../Client.javasend_at_ms重发并完成;循环 150 次后仍能发送;Java 模拟 publish 抛异常,断言重交而非失败。[S-05] 回调在 SDK 内部锁下执行,回调里调用 SDK 方法会死锁;Java 还持全局锁等 PUBACK(Go / Python / Java)
_cb_lock是不可重入的threading.Lock,所有回调都在它下面执行。在回调里调用close()、logout()、change_login_password(),或手动ack()时撤回先到,都会自锁。_lock:回调里调send()会永久卡住;调_request类方法会卡 60 秒,期间 paho 网络线程拿不到锁,心跳也停。_cb_lock与_lock交叉加锁(ABBA)的死锁。setState在lock内调用回调,而 ONLINE 回调就运行在 runLoop 线程上。回调里调sendSync会卡 1 小时,调.get()会永久卡住。request()在synchronized(lock)里调用transport.publish(),最多阻塞 10 秒等 PUBACK。结果所有 SDK 操作都被串行到网络往返上。dispatchResp的那条线程,就会死锁 10 秒,然后以 busy 失败,而服务器其实已经处理了请求。cbMu不可重入,每个回调期间都持有。ChangeLoginPassword和Ack(结果为撤回时会emitRevoked)都会再次加这把锁。手动确认模式下,在 OnMessage 末尾调Ack若遇上撤回先到(PRD F13 本身就有这个竞态),downLoop 会永久卡死。client.py:109、:192,220,573-578,605-640(持_lock调_set_state)、:951-959、:787-788。Client.java:659-665,913-915,1034-1054,1108-1120、Transport.java:377-391;javap 可见MqttRxClient.publishes/publishUnsafe用observeOn(applicationScheduler),默认为Schedulers.computation()。receive.go:121-125,247-253、api.go:134-139。close(),永不返回、锁一直被占;调send()同样卡死。Java 在 onConnection(ONLINE) 里调sendSync,5 秒后 nixmsg-client 线程仍处于 TIMED_WAITING。request()在锁内只登记 pending,锁外 publish,且不在调用线程里等待 PUBACK。sdk/python/.../client.py;sdk/java/.../Client.java、Transport.java;sdk/go/receive.go、connect.go、api.go-Drx2.computation-threads=1做并发 ack 压测。[S-08] Go 下行分发会阻塞 paho 收包协程,与 downLoop 同步等 resp 形成队头阻塞(Go)
OnPublishReceived,SDK 在里面对非 resp 帧阻塞写downCh(容量 256)。receive.go:38-42,168-177,218-223、client.go:100;pahoclient.go:441-474;服务端push.go:456-495。handleDown永不阻塞:业务帧进无界 FIFO;presence/group_event 超过阈值时丢最旧的(本来就是尽力送达)。sdk/go/receive.go、client.go[S-09] Go / JS 重连后不恢复上下线订阅(Go / JS)
api.go:74-82、connect.go:148-216;JSclient.ts:601-606;对照 Pythonclient.py:585-593、JavaClient.java:672-683。{ids, all},每次握手成功后异步重发 presence.watch。api.go、connect.go;JSclient.ts[S-12] fatal 与顶号的处理时序、原因上报不一致(Go / Python / Java;Go / JS 原因名)
Func(0)=0也立刻重连,拿作废令牌连上得到 0x86,于是先上报auth_failed(session_invalid);Python/Java 随后再报一次真实原因;Go 的failAuth调 cancel 后,downLoop 在 select 里可能直接退出,fatal 原因根本没上报。JS 在收包回调里同步处理 fatal,是正确的。"0x8E",应为taken_over。session.go:316-336、broker.go:274-292;Goreceive.go:38-42,75-87、connect.go:120,135-146、backoff.go:27-29、transport_mqtt.go:81-92(只看t.stopped,fatal 没有设置它);Pythonclient.py:652-655,735-745;JavaClient.java:752,863-875;JSclient.ts:207,304-311。taken_over。receive.go、connect.go、transport_mqtt.go;Pythonclient.py;JavaClient.java;JSclient.ts[S-13] 断线时非发送请求不失败、JS 请求无超时;Go / JS 的 logout 不收尾(Go / JS;Python / Java 部分)
Logout不失败发送队列、不停止传输、不发 offline 事件;JS 的logout不失败队列、不发 offline 事件。client.ts:413-437,718-725;Goreceive.go:322-337、connect.go:232-245;Pythonclient.py:913;JavaClient.java:1055。receive.go、connect.go;JSclient.ts;Pythonclient.py、JavaClient.java(断线失败挂起请求)[S-14] 发送收到 rate_limited 后重交没有退避(Python / Java 热循环;Go / JS 固定 1 秒)
client.py:682-689;JavaClient.java:797-805;Gosend.go:186-195;JSclient.ts:528-533。[S-15] 重连退避与连接超时四套不一致(Go / Python / Java)
Func(0)=0);paho 的PacketTimeout默认 10 秒,限制了等 CONNACK 的时间,SDK 设置的 30 秒连接超时实际不生效——这正是文档点名要改的情况。backoff.go:24-39、autopahonet.go:43-52、transport_mqtt.go:115-133(没设 PacketTimeout)、pahoclient.go:186-188,272;Pythonclient.py:458-499;JavaClient.java:522-581。PacketTimeout = ConnectTimeout。Python/Java 移植与 Go/JS 相同的退避对象。backoff.go、transport_mqtt.go;Pythonclient.py;JavaClient.java[S-19] 本地整帧检查有缺口(四套)
client.ts:478-482;Gosend.go:96-102。[S-20] Go / JS 的回执去重集合没有上限(Go / JS)
client.go:72、receive.go:201-208;JSclient.ts:93。client.go、receive.go;JSclient.ts复审基线:main
4059a15(2026-09-30)。编号说明、各工作线的合并顺序、共享文件归属见总览 #7。按第二轮 SDK 审查补充 4 条(
SendAtMs恒为 0、去重顺序表重复记键、认证失败白等 3 秒、限速重交复用 rid),并按 K-00 (#57) 修订后的退避算法更新了 S-15 的改法和退避测试。补充条目已由总审查人对照代码核实,详见正文。已合入 origin/main
0c9b459。落地提交f6f8ccffix: 按 K-00 约定修复 Go SDK 断线重交与退避 (#58)。