fix: 修复 dedicated bulk attach 竞态并优化 short write 补写路径
- 客户端 dedicated attach 回复改为精确读取单帧,避免 attach reply 与后续 NBR1 数据粘连后被误解析 - 服务端 accepted attach 改为先 detach transport,再直接回 attach reply,随后立即切入 dedicated bulk read loop - transport 读循环在 stop 或 transport ownership 失效后不再继续上推已读数据,避免 handoff 后首包被旧 reader 吃掉 - dedicated bulk record 写路径改为 full-write,消除 short write 导致的 invalid bulk fast payload - 优化 vectored write 补写策略:先尝试一次 writev,未写完时直接顺序补完剩余 buffers,减少重复 WriteTo 开销 - 放宽 vectored write 能力识别,支持通过 UnwrapConn/WriteBuffers 命中 fast path - 修复 dedicated batch 排队路径 payload 复用问题,改为深拷贝 queued items - 补齐 dedicated attach、short write、payload clone、transport stop/handoff 等回归测试
This commit is contained in:
@@ -2,8 +2,10 @@ package notify
|
||||
|
||||
import (
|
||||
"b612.me/stario"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -146,6 +148,113 @@ func (c *blockingPacketWriteConn) Write(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
type vectoredShortWriteConn struct {
|
||||
steps []int64
|
||||
idx int
|
||||
buf bytes.Buffer
|
||||
writes int
|
||||
writev int
|
||||
}
|
||||
|
||||
func (c *vectoredShortWriteConn) Read([]byte) (int, error) { return 0, io.EOF }
|
||||
func (c *vectoredShortWriteConn) Write(p []byte) (int, error) {
|
||||
c.writes++
|
||||
return c.buf.Write(p)
|
||||
}
|
||||
func (c *vectoredShortWriteConn) Close() error { return nil }
|
||||
func (c *vectoredShortWriteConn) LocalAddr() net.Addr { return nil }
|
||||
func (c *vectoredShortWriteConn) RemoteAddr() net.Addr { return nil }
|
||||
func (c *vectoredShortWriteConn) SetDeadline(time.Time) error { return nil }
|
||||
func (c *vectoredShortWriteConn) SetReadDeadline(time.Time) error { return nil }
|
||||
func (c *vectoredShortWriteConn) SetWriteDeadline(time.Time) error { return nil }
|
||||
|
||||
func (c *vectoredShortWriteConn) WriteBuffers(bufs *net.Buffers) (int64, error) {
|
||||
c.writev++
|
||||
if c.idx >= len(c.steps) {
|
||||
return 0, io.ErrNoProgress
|
||||
}
|
||||
remaining := c.steps[c.idx]
|
||||
c.idx++
|
||||
written := int64(0)
|
||||
for len(*bufs) > 0 && remaining > 0 {
|
||||
part := (*bufs)[0]
|
||||
if len(part) == 0 {
|
||||
(*bufs)[0] = nil
|
||||
*bufs = (*bufs)[1:]
|
||||
continue
|
||||
}
|
||||
n := int64(len(part))
|
||||
if n > remaining {
|
||||
n = remaining
|
||||
}
|
||||
_, _ = c.buf.Write(part[:n])
|
||||
written += n
|
||||
remaining -= n
|
||||
if n == int64(len(part)) {
|
||||
(*bufs)[0] = nil
|
||||
*bufs = (*bufs)[1:]
|
||||
continue
|
||||
}
|
||||
(*bufs)[0] = part[n:]
|
||||
break
|
||||
}
|
||||
return written, nil
|
||||
}
|
||||
|
||||
type unwrapVectoredConn struct {
|
||||
inner net.Conn
|
||||
}
|
||||
|
||||
func (c *unwrapVectoredConn) Read(p []byte) (int, error) { return c.inner.Read(p) }
|
||||
func (c *unwrapVectoredConn) Write(p []byte) (int, error) { return c.inner.Write(p) }
|
||||
func (c *unwrapVectoredConn) Close() error { return c.inner.Close() }
|
||||
func (c *unwrapVectoredConn) LocalAddr() net.Addr { return c.inner.LocalAddr() }
|
||||
func (c *unwrapVectoredConn) RemoteAddr() net.Addr { return c.inner.RemoteAddr() }
|
||||
func (c *unwrapVectoredConn) SetDeadline(t time.Time) error { return c.inner.SetDeadline(t) }
|
||||
func (c *unwrapVectoredConn) SetReadDeadline(t time.Time) error { return c.inner.SetReadDeadline(t) }
|
||||
func (c *unwrapVectoredConn) SetWriteDeadline(t time.Time) error { return c.inner.SetWriteDeadline(t) }
|
||||
func (c *unwrapVectoredConn) UnwrapConn() net.Conn { return c.inner }
|
||||
|
||||
func TestWriteNetBuffersFullUnlockedFallsBackToDirectWritesAfterFirstPartialVectoredWrite(t *testing.T) {
|
||||
conn := &vectoredShortWriteConn{steps: []int64{3}}
|
||||
header := []byte("head")
|
||||
payload := []byte("payload")
|
||||
if err := writeNetBuffersFullUnlocked(conn, net.Buffers{header, payload}); err != nil {
|
||||
t.Fatalf("writeNetBuffersFullUnlocked failed: %v", err)
|
||||
}
|
||||
if got, want := conn.writev, 1; got != want {
|
||||
t.Fatalf("vectored write calls = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := conn.writes, 2; got != want {
|
||||
t.Fatalf("fallback direct writes = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := conn.buf.String(), "headpayload"; got != want {
|
||||
t.Fatalf("written bytes = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteNetBuffersFullUnlockedReturnsNoProgressWhenVectoredWriteDoesNotAdvance(t *testing.T) {
|
||||
conn := &vectoredShortWriteConn{steps: []int64{0}}
|
||||
err := writeNetBuffersFullUnlocked(conn, net.Buffers{[]byte("head"), []byte("payload")})
|
||||
if !errors.Is(err, io.ErrNoProgress) {
|
||||
t.Fatalf("writeNetBuffersFullUnlocked error = %v, want %v", err, io.ErrNoProgress)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteNetBuffersFullUnlockedUsesUnwrappedVectoredConn(t *testing.T) {
|
||||
inner := &vectoredShortWriteConn{steps: []int64{100}}
|
||||
conn := &unwrapVectoredConn{inner: inner}
|
||||
if err := writeNetBuffersFullUnlocked(conn, net.Buffers{[]byte("head"), []byte("payload")}); err != nil {
|
||||
t.Fatalf("writeNetBuffersFullUnlocked failed: %v", err)
|
||||
}
|
||||
if got, want := inner.writev, 1; got != want {
|
||||
t.Fatalf("unwrapped vectored write calls = %d, want %d", got, want)
|
||||
}
|
||||
if got := inner.writes; got != 0 {
|
||||
t.Fatalf("unexpected fallback direct writes = %d, want 0", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBulkBatchSenderSkipsQueuedCanceledRequest(t *testing.T) {
|
||||
conn := newBlockingPacketWriteConn()
|
||||
binding := newTransportBinding(conn, stario.NewQueue())
|
||||
|
||||
Reference in New Issue
Block a user