fix(notify): 根治低带宽控制面阻塞与传输写入卡死
- 为控制消息增加优先级、公平调度、队列字节预算和自适应批处理 - 支持可取消的写门等待,收紧 shared/dedicated bulk、stream 和 Reply 写入边界 - 修复 bulk reset/close、连接 handoff 和安全 profile 切换时序 - 保留旧取消与超时错误契约,新增阶段化 TransportSendError - 增加 ReplyCtx、ReplyObjCtx、写超时配置及黑洞连接和竞态回归测试
This commit is contained in:
+199
-1
@@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -818,6 +819,61 @@ func TestBulkWritePrefersResetErrorOverContextCanceled(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBulkResetWakesLocalStateBeforeRemoteNotificationCompletes(t *testing.T) {
|
||||
wantErr := errors.New("local reset must win")
|
||||
resetStarted := make(chan struct{})
|
||||
resetUnblock := make(chan struct{})
|
||||
var unblockOnce sync.Once
|
||||
unblock := func() { unblockOnce.Do(func() { close(resetUnblock) }) }
|
||||
defer unblock()
|
||||
|
||||
bulk := newBulkHandle(context.Background(), nil, "test", BulkOpenRequest{
|
||||
BulkID: "bulk-reset-local-first",
|
||||
DataID: 1,
|
||||
Range: BulkRange{
|
||||
Length: 1,
|
||||
},
|
||||
}, 0, nil, nil, 0, nil, func(context.Context, *bulkHandle, string) error {
|
||||
close(resetStarted)
|
||||
<-resetUnblock
|
||||
return nil
|
||||
}, nil, nil, nil)
|
||||
|
||||
readDone := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := bulk.Read(make([]byte, 1))
|
||||
readDone <- err
|
||||
}()
|
||||
resetDone := make(chan error, 1)
|
||||
go func() {
|
||||
resetDone <- bulk.Reset(wantErr)
|
||||
}()
|
||||
select {
|
||||
case <-resetStarted:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("remote reset notification did not start")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-bulk.Context().Done():
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
t.Fatal("bulk context stayed live while remote reset notification was blocked")
|
||||
}
|
||||
select {
|
||||
case err := <-readDone:
|
||||
if !errors.Is(err, wantErr) {
|
||||
t.Fatalf("bulk Read error=%v, want %v", err, wantErr)
|
||||
}
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
t.Fatal("bulk Read stayed blocked while remote reset notification was blocked")
|
||||
}
|
||||
|
||||
unblock()
|
||||
if err := <-resetDone; err != nil {
|
||||
t.Fatalf("Reset returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDedicatedBulkWaitReadyPrefersClosedPipeOverContextCanceled(t *testing.T) {
|
||||
bulk := newBulkHandle(context.Background(), nil, "test", BulkOpenRequest{
|
||||
BulkID: "bulk-dedicated-ready-close",
|
||||
@@ -892,6 +948,111 @@ func TestBulkReadWaitingLocalClosePrefersClosedPipeOverContextCanceled(t *testin
|
||||
}
|
||||
}
|
||||
|
||||
func TestBulkDefaultWriteContextDoesNotAddTimer(t *testing.T) {
|
||||
parent := context.Background()
|
||||
ctx, cancel, err := bulkWriteContext(parent, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("bulkWriteContext returned error: %v", err)
|
||||
}
|
||||
defer cancel()
|
||||
if ctx != parent {
|
||||
t.Fatal("zero WriteTimeout wrapped the parent context")
|
||||
}
|
||||
if _, ok := ctx.Deadline(); ok {
|
||||
t.Fatal("zero WriteTimeout added a per-write deadline")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBulkDefaultCloseContextIsBounded(t *testing.T) {
|
||||
ctx, cancel := bulkCloseContext(context.Background(), 0)
|
||||
defer cancel()
|
||||
deadline, ok := ctx.Deadline()
|
||||
if !ok {
|
||||
t.Fatal("default bulk close context has no deadline")
|
||||
}
|
||||
remaining := time.Until(deadline)
|
||||
if remaining < defaultBulkDataWriteTimeout-time.Second || remaining > defaultBulkDataWriteTimeout+time.Second {
|
||||
t.Fatalf("default bulk close deadline remaining=%v, want about %v", remaining, defaultBulkDataWriteTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBulkCloseWriteHonorsBulkContextCancellation(t *testing.T) {
|
||||
parent, cancelParent := context.WithCancel(context.Background())
|
||||
defer cancelParent()
|
||||
closeStarted := make(chan struct{})
|
||||
bulk := newBulkHandle(
|
||||
parent,
|
||||
nil,
|
||||
clientFileScope(),
|
||||
BulkOpenRequest{BulkID: "close-write-context-bound", DataID: 1},
|
||||
0,
|
||||
nil,
|
||||
nil,
|
||||
0,
|
||||
func(ctx context.Context, _ *bulkHandle, _ bool) error {
|
||||
close(closeStarted)
|
||||
<-ctx.Done()
|
||||
return ctx.Err()
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- bulk.CloseWrite() }()
|
||||
select {
|
||||
case <-closeStarted:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("bulk close notification did not start")
|
||||
}
|
||||
cancelParent()
|
||||
|
||||
select {
|
||||
case err := <-done:
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
t.Fatalf("Bulk.CloseWrite error=%v, want context canceled", err)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("Bulk.CloseWrite ignored the canceled bulk context")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBulkCloseAfterPeerEOFUsesBoundedCleanupContext(t *testing.T) {
|
||||
closeCalled := false
|
||||
bulk := newBulkHandle(
|
||||
context.Background(),
|
||||
nil,
|
||||
clientFileScope(),
|
||||
BulkOpenRequest{BulkID: "close-after-peer-eof", DataID: 1},
|
||||
0,
|
||||
nil,
|
||||
nil,
|
||||
0,
|
||||
func(ctx context.Context, _ *bulkHandle, full bool) error {
|
||||
if !full {
|
||||
return errors.New("expected full close after peer EOF")
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
closeCalled = true
|
||||
return nil
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
bulk.markPeerClosed()
|
||||
if err := bulk.Close(); err != nil {
|
||||
t.Fatalf("Bulk.Close after peer EOF failed: %v", err)
|
||||
}
|
||||
if !closeCalled {
|
||||
t.Fatal("Bulk.Close skipped the final peer notification after EOF")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBulkReleaseControlRoundTripTransport(t *testing.T) {
|
||||
server := NewServer().(*ServerCommon)
|
||||
if err := UseModernPSKServer(server, integrationSharedSecret, integrationModernPSKOptions()); err != nil {
|
||||
@@ -951,7 +1112,7 @@ func TestBulkReleaseControlRoundTripTransport(t *testing.T) {
|
||||
clientHandle.outboundInFlight = 1
|
||||
clientHandle.mu.Unlock()
|
||||
|
||||
if err := sendBulkReleaseServerTransport(server, accepted.TransportConn, BulkReleaseRequest{
|
||||
if err := sendBulkReleaseServerTransport(context.Background(), server, accepted.TransportConn, BulkReleaseRequest{
|
||||
BulkID: serverHandle.ID(),
|
||||
DataID: serverHandle.dataIDSnapshot(),
|
||||
Bytes: chunkSize,
|
||||
@@ -1549,6 +1710,43 @@ func TestBulkDedicatedClientFullCloseAfterCloseWriteDoesNotResetTCP(t *testing.T
|
||||
}
|
||||
}
|
||||
|
||||
func TestBulkCloseWriteUsesWriteTimeoutForControlNotification(t *testing.T) {
|
||||
const timeout = 40 * time.Millisecond
|
||||
bulk := newBulkHandle(
|
||||
context.Background(),
|
||||
nil,
|
||||
clientFileScope(),
|
||||
BulkOpenRequest{
|
||||
BulkID: "close-write-timeout",
|
||||
WriteTimeout: timeout,
|
||||
Range: BulkRange{Length: 1},
|
||||
},
|
||||
0,
|
||||
nil,
|
||||
nil,
|
||||
0,
|
||||
func(ctx context.Context, _ *bulkHandle, _ bool) error {
|
||||
<-ctx.Done()
|
||||
return ctx.Err()
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- bulk.CloseWrite() }()
|
||||
select {
|
||||
case err := <-done:
|
||||
if !errors.Is(err, context.DeadlineExceeded) {
|
||||
t.Fatalf("Bulk.CloseWrite error=%v, want deadline exceeded", err)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("Bulk.CloseWrite remained blocked past WriteTimeout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBulkSharedConcurrentWritersWithSlowReceiver(t *testing.T) {
|
||||
server := NewServer().(*ServerCommon)
|
||||
if err := UseModernPSKServer(server, integrationSharedSecret, integrationModernPSKOptions()); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user