编号:K-04 严重级:critical 工作线:SDK(sdk/*) 来源:审查 S-01、S-02、S-05、S-06、S-10、S-11、S-12、S-13、S-14、S-15、S-18、S-19 依赖:K-00 (#57)(对外语义条目) 被依赖:无
本 issue 汇总涉及 Java SDK 的条目,由 Java SDK 负责人一次改完(集中在 Client.java、Transport.java、Types.java)。下方"问题明细"是跨四套的原文,请只看 Java 的部分。
Client.java
Transport.java
Types.java
bad_credentials
extractConnAckReason
setState
request()
synchronized(lock)
sendAt
java.util.Date
sendAtMs
bad_request
.keepAlive(30)
session_invalid
not_connected
META-INF
Types.java:4,63-71
java.util.Base64
Body.ofBytes
NoClassDefFoundError
logout()
Client.java:227-232
updateSelf(String, Integer defaultDelayMs)
Client.java:391-403
max_schedule_seconds
Long
标"补充"的条目来自第二轮 SDK 审查,总审查人已对照代码核实,证据写在条目里(没有对应的原文段落)。
sdk/java/src/main/java/asia/asio/nixmsg/Client.java、Transport.java、Types.java、Protocol.java,pom.xml,sdk/java/src/test/*,README。
sdk/java/src/main/java/asia/asio/nixmsg/Client.java
Protocol.java
pom.xml
sdk/java/src/test/*
target
sendSync
-Drx2.computation-threads=1
send_at_ms
updateSelf(null, 30L * 24 * 3600 * 1000)
default_delay_ms
以下是本次复审各区审查报告的原文段落。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=""
dispatchSend
select
ch
connGone
c.ctx.Done()
close
end(true)
sdk/go/send.go
connect.go
sdk/js/src/client.ts
mqtt.ts
sdk/python/src/nixmsg/client.py
sdk/java/.../Client.java
connack
connectionfailed
ConnectionFailedException
Transport.java:343-351
Client.java:708-724
ws://127.0.0.1:1/mqtt
RECONNECTING network
AUTH_FAILED bad_credentials
connectSync
sdk/java/.../Transport.java
_cb_lock
threading.Lock
close()
change_login_password()
ack()
_lock
send()
_request
lock
.get()
transport.publish()
dispatchResp
cbMu
ChangeLoginPassword
Ack
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
sdk/go/receive.go
api.go
client.py:756-758,798-803,836-851
Client.java:884-887,928-934,965-984
client.ts:352-360
receive.go:136-141,202-206
push.go:88-237,456-495,549-560
client.py
client.ts
clockSkewMs
send_at
Client.java:288-289
Types.java:90-100
client.py:292-299
send.go:83-87
client.ts:472-473
mqtt.ts:57-69
client.js:63
Transport.java:313-321
Mqtt5Connect.DEFAULT_KEEP_ALIVE=60
keepalive: 30
Func(0)=0
auth_failed(session_invalid)
failAuth
"0x8E"
taken_over
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
receive.go
transport_mqtt.go
Logout
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
PacketTimeout
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
PacketTimeout = ConnectTimeout
backoff.go
https://
mqtt://
transport.py:204-245
Transport.java:239-301
transport_mqtt.go:206-237
mqtt.ts:151-164
transport.py
protocol.py
client.ts:478-482
send.go:96-102
复审基线:main 4059a15(2026-09-30)。编号说明、各工作线的合并顺序、共享文件归属见总览 #7。
4059a15
按第二轮 SDK 审查补充 3 条(java.util.Base64 需要 Android API 26、logout() 吞掉请求失败、updateSelf 的默认延迟用 Integer);S-15 改为按 K-00 (#57) 修订后的退避算法实现。补充条目已由总审查人对照代码核实,详见正文。
updateSelf
Integer
已合入 origin/main 0c9b459。落地提交 c6b7b6e fix: 按 K-00 约定修复 Java SDK 断线重交与退避 (#61)。
0c9b459
c6b7b6e
No dependencies set.
The note is not visible to the blocked user.
编号:K-04 严重级:critical 工作线:SDK(sdk/*) 来源:审查 S-01、S-02、S-05、S-06、S-10、S-11、S-12、S-13、S-14、S-15、S-18、S-19
依赖:K-00 (#57)(对外语义条目) 被依赖:无
结论与统一方案
本 issue 汇总涉及 Java SDK 的条目,由 Java SDK 负责人一次改完(集中在
Client.java、Transport.java、Types.java)。下方"问题明细"是跨四套的原文,请只看 Java 的部分。bad_credentials并永久停止重连(已复现)extractConnAckReason取到的 BAD_USER_NAME_OR_PASSWORD / NOT_AUTHORIZED / BANNED 判定停止;删除按文本判断的兜底,其余一律视为网络问题并继续重连setState在锁内调回调、request()在synchronized(lock)里等 PUBACK 最多 10 秒(已复现 onConnection 死锁)request()锁内只登记 pending,锁外 publish,不在调用线程等 PUBACKsendAt(epoch 毫秒或java.util.Date,Android API 24 没有 java.time),入队时加偏差;Javadoc 写明sendAtMs是服务器时间;两者同时设置报bad_request.keepAlive(30)session_invalid再报真实原因not_connected立即失败非发送请求META-INFTypes.java:4,63-71用java.util.Base64,Android 从 API 26 才提供,而 D30 规定最低 API 24;在 API 24/25 上Body.ofBytes和 base64 正文的本地长度检查会抛NoClassDefFoundErrorlogout()吞掉请求失败(Client.java:227-232),离线时服务端没有作废令牌,应用却以为已经退出updateSelf(String, Integer defaultDelayMs)(Client.java:391-403)最多约 24.8 天,达不到max_schedule_seconds默认的 365 天Long(公开 API 变更,README 同步)标"补充"的条目来自第二轮 SDK 审查,总审查人已对照代码核实,证据写在条目里(没有对应的原文段落)。
改动文件
sdk/java/src/main/java/asia/asio/nixmsg/Client.java、Transport.java、Types.java、Protocol.java,pom.xml,sdk/java/src/test/*,README。与其他问题的交互 / 冲突说明
target等产物。验收与测试
sendSync不卡住;用-Drx2.computation-threads=1并发 ack 压测不出现 busy。send_at_ms约等于本机时间加 62 秒。logout():future 以异常完成,之后不再重连。updateSelf(null, 30L * 24 * 3600 * 1000):帧里default_delay_ms为 2592000000。问题明细(各区审查原文,证据含文件与行号)
[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-02] Java 把“连不上服务器”判为 bad_credentials 并永久停止重连(Java)
connack或connectionfailed就判为认证失败并停止重连。HiveMQ 对连接被拒、连接超时、DNS 失败都抛ConnectionFailedException,于是服务器重启、断网、切网时,Java/Android 端会永久掉线,并对用户报“密码错误”。Transport.java:343-351、Client.java:708-724。ws://127.0.0.1:1/mqtt,状态依次为RECONNECTING network→AUTH_FAILED bad_credentials,之后 6 秒内不再重试,connectSync抛“认证失败”。extractConnAckReason取到的 BAD_USER_NAME_OR_PASSWORD / NOT_AUTHORIZED / BANNED 判定停止;删除按文本判断的兜底,其余情况一律视为网络问题并继续重连。sdk/java/.../Transport.java[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-06] 确认失败后没有恢复:重推被忽略、重复回执不再确认、手动 ack 吞掉失败(Python / Java / JS)
_request直接返回 NotConnected),这条回执就永远占着窗口;积满 64 条后,该发送方再也收不到新回执。ack()和 Java 的ack()失败时不报错(Java 的 future 正常完成),应用无从得知。client.py:756-758,798-803,836-851。Client.java:884-887,928-934,965-984。client.ts:352-360。receive.go:136-141,202-206。push.go:88-237,456-495,549-560。client.py、JavaClient.java、JSclient.ts[S-10] Java 定时发送不做时钟偏差校正(Java;Python 的
send_at_ms也原样透传)sendAtMs,写入帧时不加clockSkewMs。设备时钟偏差多少,定时消息就早到或晚到多少。Python 的send_at会校正,但send_at_ms原样透传,而且两者的语义没有说明。Client.java:288-289、Types.java:90-100;Pythonclient.py:292-299;对照 Gosend.go:83-87、JSclient.ts:472-473。send_at都做了校正。sendAt(epoch 毫秒或java.util.Date,Android API 24 没有 java.time),入队时加上偏差;在 Javadoc 明确sendAtMs表示服务器时间,两者同时设置时报 bad_request。Python 在文档里注明send_at_ms是服务器时间。Types.java、Client.java;Python 文档send_at_ms约等于本机时间加 62 秒。[S-11] JS 和 Java 的心跳是 60 秒,文档规定 30 秒(JS / Java)
mqtt.ts:57-69加 MQTT.jsclient.js:63;Transport.java:313-321加Mqtt5Connect.DEFAULT_KEEP_ALIVE=60。keepalive: 30,Java 设.keepAlive(30)。mqtt.ts、Transport.java[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-18] Python / Java 的 URL scheme 处理:https 走明文、mqtt:// 未开启也走裸 TCP(Python / Java)
https://会以明文连接(没写端口时连 80);服务器仅接受 TLS 时连接会失败(默认安全),但若服务器同时允许明文,凭据就会明文传输。mqtt://不需要开启选项就走裸 TCP。transport.py:204-245、Transport.java:239-301;对照 Gotransport_mqtt.go:206-237、JSmqtt.ts:151-164。transport.py、protocol.py;JavaTransport.java、Protocol.java[S-19] 本地整帧检查有缺口(四套)
client.ts:478-482;Gosend.go:96-102。复审基线:main
4059a15(2026-09-30)。编号说明、各工作线的合并顺序、共享文件归属见总览 #7。按第二轮 SDK 审查补充 3 条(
java.util.Base64需要 Android API 26、logout()吞掉请求失败、updateSelf的默认延迟用Integer);S-15 改为按 K-00 (#57) 修订后的退避算法实现。补充条目已由总审查人对照代码核实,详见正文。已合入 origin/main
0c9b459。落地提交c6b7b6efix: 按 K-00 约定修复 Java SDK 断线重交与退避 (#61)。