fix: 按 K-00 约定修复 Go SDK 断线重交与退避

This commit is contained in:
Nixevol
2026-09-30 16:24:13 +08:00
parent 3749b9bdf1
commit f6f8ccf269
20 changed files with 1544 additions and 324 deletions
+98 -27
View File
@@ -11,11 +11,16 @@ func (c *Client) Send(ctx context.Context, to Target, body Body, opt SendOptions
c.mu.Lock()
limits := c.limits
skew := c.clockSkew
handshook := c.handshook
maxQ := c.opts.SendQueueSize
qLen := len(c.sendQ)
stopped := c.stopReconnect || c.closed
stopErr := c.stopErrLocked()
c.mu.Unlock()
if stopped {
return SendResult{}, stopErr
}
if body.Enc == "" {
body.Enc = "utf8"
}
@@ -39,10 +44,7 @@ func (c *Client) Send(ctx context.Context, to Target, body Body, opt SendOptions
if maxBody <= 0 {
maxBody = 262144
}
if handshook && n > maxBody {
return SendResult{}, apiErr(CodeBodyTooLarge, "正文超限")
}
if !handshook && n > 262144 {
if n > maxBody {
return SendResult{}, apiErr(CodeBodyTooLarge, "正文超限")
}
@@ -81,7 +83,6 @@ func (c *Client) Send(ctx context.Context, to Target, body Body, opt SendOptions
return SendResult{}, apiErr(CodeBadRequest, "sendAt 与 delay 互斥")
}
if opt.SendAt != nil {
// sendAt 使用本机时间 + 服务器偏差,换算后写入,重交不重算
ms := opt.SendAt.UnixMilli() + skew
sendAtMs = &ms
frame["send_at_ms"] = ms
@@ -97,7 +98,7 @@ func (c *Client) Send(ctx context.Context, to Target, body Body, opt SendOptions
if maxFrame <= 0 {
maxFrame = 786432
}
if handshook && len(payload) > maxFrame {
if len(payload) > maxFrame {
return SendResult{}, apiErr(CodeFrameTooLarge, "整帧超限")
}
@@ -111,10 +112,11 @@ func (c *Client) Send(ctx context.Context, to Target, body Body, opt SendOptions
c.mu.Lock()
if c.closed || c.stopReconnect {
err := c.stopErrLocked()
c.mu.Unlock()
return SendResult{}, apiErr(CodeClosed, "已关闭")
return SendResult{}, err
}
if qLen >= maxQ {
if qLen >= maxQ || len(c.sendQ) >= maxQ {
c.mu.Unlock()
return SendResult{}, apiErr(CodeQueueFull, "发送队列已满")
}
@@ -125,7 +127,21 @@ func (c *Client) Send(ctx context.Context, to Target, body Body, opt SendOptions
select {
case <-ctx.Done():
return SendResult{}, ctx.Err()
c.mu.Lock()
if !item.inflight && !item.abandoned {
nq := c.sendQ[:0]
for _, it := range c.sendQ {
if it != item {
nq = append(nq, it)
}
}
c.sendQ = nq
c.mu.Unlock()
return SendResult{}, ctx.Err()
}
item.abandoned = true
c.mu.Unlock()
return SendResult{}, apiErr(CodeResultUnknown, "结果未知,请用同一消息号重试")
case out := <-item.result:
return out.res, out.err
}
@@ -134,13 +150,17 @@ func (c *Client) Send(ctx context.Context, to Target, body Body, opt SendOptions
func (c *Client) drainSendQueue() {
for {
c.mu.Lock()
if !c.handshook || c.transport == nil {
if !c.handshook || c.transport == nil || c.stopReconnect {
c.mu.Unlock()
return
}
maxFrame := c.limits.MaxFrameBytes
if maxFrame <= 0 {
maxFrame = 786432
}
var next *sendItem
for _, it := range c.sendQ {
if !it.inflight {
if !it.inflight && !it.abandoned {
next = it
break
}
@@ -149,48 +169,93 @@ func (c *Client) drainSendQueue() {
c.mu.Unlock()
return
}
if len(next.payload) > maxFrame {
c.mu.Unlock()
c.finishSend(next, SendResult{}, apiErr(CodeFrameTooLarge, "整帧超限"))
continue
}
next.epoch++
captured := next.epoch
next.inflight = true
c.inflight++
tr := c.transport
payload := next.payload
rid, _ := next.frame["rid"].(string)
item := next
lost := c.connLost
c.mu.Unlock()
go c.dispatchSend(tr, item, rid, payload)
go c.dispatchSend(tr, item, rid, payload, captured, lost)
}
}
func (c *Client) dispatchSend(tr transport, item *sendItem, rid string, payload []byte) {
func (c *Client) dispatchSend(tr transport, item *sendItem, rid string, payload []byte, epoch uint64, lost <-chan struct{}) {
ch := make(chan respFrame, 1)
c.mu.Lock()
c.pending[rid] = &pendingReq{rid: rid, ch: ch}
if item.epoch != epoch || !item.inflight {
c.mu.Unlock()
return
}
c.pending[rid] = &pendingReq{rid: rid, ch: ch, isSend: true}
c.mu.Unlock()
if err := tr.PublishUp(payload); err != nil {
c.mu.Lock()
delete(c.pending, rid)
item.inflight = false
c.inflight--
if item.epoch == epoch && item.inflight {
item.inflight = false
if c.inflight > 0 {
c.inflight--
}
if c.stopReconnect {
errStop := c.stopErrLocked()
c.mu.Unlock()
c.finishSend(item, SendResult{}, errStop)
return
}
}
c.mu.Unlock()
// 网络错误:保留队列等重连
return
}
rf := <-ch
if lost == nil {
lost = make(chan struct{})
}
var rf respFrame
select {
case rf = <-ch:
case <-lost:
return
case <-c.ctx.Done():
return
}
c.mu.Lock()
valid := item.epoch == epoch && item.inflight
c.mu.Unlock()
if !valid {
return
}
if !rf.OK {
code, msg := CodeBadRequest, "发送失败"
if rf.Error != nil {
code, msg = rf.Error.Code, rf.Error.Message
}
if code == CodeRateLimited {
// 自动重交:保持同一 payload(含 id / send_at_ms)
c.mu.Lock()
item.inflight = false
c.inflight--
if c.inflight > 0 {
c.inflight--
}
delete(c.pending, rid)
item.rateN++
n := item.rateN
c.regenerateSendLocked(item)
c.mu.Unlock()
time.AfterFunc(time.Second, func() { c.drainSendQueue() })
d := backoffJitter(nominalDelay(n))
time.AfterFunc(d, func() { c.drainSendQueue() })
return
}
c.finishSend(item, SendResult{}, apiErr(code, msg))
@@ -206,8 +271,8 @@ func (c *Client) dispatchSend(tr transport, item *sendItem, rid string, payload
func (c *Client) finishSend(item *sendItem, res SendResult, err error) {
c.mu.Lock()
// 从队列移除
out := item.result
abandoned := item.abandoned
nq := c.sendQ[:0]
for _, it := range c.sendQ {
if it != item {
@@ -216,13 +281,19 @@ func (c *Client) finishSend(item *sendItem, res SendResult, err error) {
}
c.sendQ = nq
if item.inflight {
c.inflight--
if c.inflight > 0 {
c.inflight--
}
item.inflight = false
}
rid, _ := item.frame["rid"].(string)
delete(c.pending, rid)
c.mu.Unlock()
select {
case out <- sendOutcome{res: res, err: err}:
default:
if !abandoned {
select {
case out <- sendOutcome{res: res, err: err}:
default:
}
}
c.drainSendQueue()
}