fix(notify): 根治低带宽控制面阻塞与传输写入卡死
- 为控制消息增加优先级、公平调度、队列字节预算和自适应批处理 - 支持可取消的写门等待,收紧 shared/dedicated bulk、stream 和 Reply 写入边界 - 修复 bulk reset/close、连接 handoff 和安全 profile 切换时序 - 保留旧取消与超时错误契约,新增阶段化 TransportSendError - 增加 ReplyCtx、ReplyObjCtx、写超时配置及黑洞连接和竞态回归测试
This commit is contained in:
@@ -11,6 +11,10 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// A zero public WriteTimeout preserves the legacy option contract, but a
|
||||
// physical bulk write still needs a finite bound so a black-hole peer cannot
|
||||
// hold the async worker (and CloseWrite) forever. The default is constant so
|
||||
// concurrent bulk operations cannot observe a process-wide timeout change.
|
||||
const (
|
||||
BulkOpenSignalKey = "notify.bulk.open"
|
||||
BulkCloseSignalKey = "notify.bulk.close"
|
||||
@@ -26,6 +30,8 @@ const (
|
||||
defaultBulkControlReadTimeout = 0
|
||||
defaultBulkControlWriteTimeout = 0
|
||||
defaultBulkAcceptReadyTimeout = 10 * time.Second
|
||||
defaultBulkResetNotifyTimeout = 30 * time.Second
|
||||
defaultBulkDataWriteTimeout = 2 * time.Minute
|
||||
)
|
||||
|
||||
type BulkMetadata map[string]string
|
||||
@@ -1278,9 +1284,13 @@ func (b *bulkHandle) close(full bool) error {
|
||||
return nil
|
||||
}
|
||||
closeFn := b.closeFn
|
||||
writeTimeout := b.writeTimeout
|
||||
b.mu.Unlock()
|
||||
if closeFn != nil && !b.dedicatedWriteHalfClosedSnapshot() {
|
||||
if err := closeFn(context.Background(), b, true); err != nil && !errors.Is(err, errBulkNotFound) && !b.canIgnoreDedicatedCloseSendError(err) {
|
||||
closeCtx, cancel := b.closeContext(writeTimeout)
|
||||
err := closeFn(closeCtx, b, true)
|
||||
cancel()
|
||||
if err != nil && !errors.Is(err, errBulkNotFound) && !b.canIgnoreDedicatedCloseSendError(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -1302,12 +1312,19 @@ func (b *bulkHandle) close(full bool) error {
|
||||
return nil
|
||||
}
|
||||
closeFn := b.closeFn
|
||||
writeTimeout := b.writeTimeout
|
||||
b.mu.Unlock()
|
||||
if err := b.waitPendingAsyncWrites(context.Background()); err != nil {
|
||||
drainCtx, drainCancel := b.closeContext(writeTimeout)
|
||||
err := b.waitPendingAsyncWrites(drainCtx)
|
||||
drainCancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if closeFn != nil {
|
||||
if err := closeFn(context.Background(), b, full); err != nil && !errors.Is(err, errBulkNotFound) && !b.canIgnoreDedicatedCloseSendError(err) {
|
||||
closeCtx, cancel := b.closeContext(writeTimeout)
|
||||
err := closeFn(closeCtx, b, full)
|
||||
cancel()
|
||||
if err != nil && !errors.Is(err, errBulkNotFound) && !b.canIgnoreDedicatedCloseSendError(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -1348,13 +1365,29 @@ func (b *bulkHandle) Reset(err error) error {
|
||||
}
|
||||
resetFn := b.resetFn
|
||||
b.mu.Unlock()
|
||||
if resetFn != nil {
|
||||
if sendErr := resetFn(context.Background(), b, bulkResetMessage(resetErr)); sendErr != nil {
|
||||
return sendErr
|
||||
}
|
||||
if !b.applyResetState(resetErr) {
|
||||
return b.resetErrSnapshot()
|
||||
}
|
||||
defer b.finalize()
|
||||
if resetFn == nil {
|
||||
return nil
|
||||
}
|
||||
timeout := defaultBulkResetNotifyTimeout
|
||||
if b.writeTimeout > 0 && b.writeTimeout < timeout {
|
||||
timeout = b.writeTimeout
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- resetFn(ctx, b, bulkResetMessage(resetErr))
|
||||
}()
|
||||
select {
|
||||
case sendErr := <-done:
|
||||
return sendErr
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
b.markReset(resetErr)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *bulkHandle) Snapshot() BulkSnapshot {
|
||||
@@ -1396,18 +1429,34 @@ func (b *bulkHandle) markReset(err error) {
|
||||
if b == nil {
|
||||
return
|
||||
}
|
||||
resetErr := bulkResetError(err)
|
||||
b.mu.Lock()
|
||||
if b.resetErr == nil {
|
||||
b.resetErr = resetErr
|
||||
b.clearBufferedDataLocked()
|
||||
b.closeWriteStateLocked()
|
||||
b.applyResetState(bulkResetError(err))
|
||||
b.finalize()
|
||||
}
|
||||
|
||||
func (b *bulkHandle) applyResetState(resetErr error) bool {
|
||||
if b == nil {
|
||||
return false
|
||||
}
|
||||
b.mu.Lock()
|
||||
if b.resetErr != nil {
|
||||
b.notifyFlowLocked()
|
||||
b.mu.Unlock()
|
||||
return false
|
||||
}
|
||||
b.resetErr = bulkResetError(resetErr)
|
||||
b.clearBufferedDataLocked()
|
||||
b.closeWriteStateLocked()
|
||||
b.notifyFlowLocked()
|
||||
b.mu.Unlock()
|
||||
b.markAcceptReady(resetErr)
|
||||
b.markAcceptReady(b.resetErrSnapshot())
|
||||
b.notifyReadable()
|
||||
b.finalize()
|
||||
if b.cancel != nil {
|
||||
b.cancel()
|
||||
}
|
||||
if b.writeCtxCancel != nil {
|
||||
b.writeCtxCancel()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *bulkHandle) pushChunk(chunk []byte) error {
|
||||
@@ -1818,7 +1867,12 @@ func (b *bulkHandle) finalize() {
|
||||
if b.writeCtxCancel != nil {
|
||||
b.writeCtxCancel()
|
||||
}
|
||||
if sender := b.clearDedicatedSender(); sender != nil {
|
||||
sender := b.clearDedicatedSender()
|
||||
conn, owned := b.clearDedicatedConn()
|
||||
if conn != nil && owned {
|
||||
_ = conn.Close()
|
||||
}
|
||||
if sender != nil {
|
||||
sender.stop()
|
||||
}
|
||||
if b.client != nil && b.releaseDedicatedActiveReserved() {
|
||||
@@ -1827,9 +1881,6 @@ func (b *bulkHandle) finalize() {
|
||||
if b.client != nil {
|
||||
b.client.releaseBulkDedicatedLane(b.dedicatedLaneIDSnapshot())
|
||||
}
|
||||
if conn, owned := b.clearDedicatedConn(); conn != nil && owned {
|
||||
_ = conn.Close()
|
||||
}
|
||||
if b.runtime != nil {
|
||||
b.runtime.remove(b.runtimeScope, b.id)
|
||||
}
|
||||
@@ -2296,6 +2347,33 @@ func bulkWriteContext(parent context.Context, timeout time.Duration) (context.Co
|
||||
return ctx, cancel, nil
|
||||
}
|
||||
|
||||
func bulkCloseContext(parent context.Context, timeout time.Duration) (context.Context, context.CancelFunc) {
|
||||
if parent == nil {
|
||||
parent = context.Background()
|
||||
}
|
||||
if timeout <= 0 {
|
||||
return context.WithTimeout(parent, defaultBulkDataWriteTimeout)
|
||||
}
|
||||
return context.WithTimeout(parent, timeout)
|
||||
}
|
||||
|
||||
func (b *bulkHandle) closeContext(timeout time.Duration) (context.Context, context.CancelFunc) {
|
||||
if b == nil {
|
||||
return bulkCloseContext(nil, timeout)
|
||||
}
|
||||
parent := b.Context()
|
||||
b.mu.Lock()
|
||||
gracefulPeerClose := b.remoteClosed || b.peerReadClosed
|
||||
b.mu.Unlock()
|
||||
if gracefulPeerClose {
|
||||
// A peer half/full close cancels the bulk context as part of normal EOF
|
||||
// delivery. Keep the final close notification alive so the peer can
|
||||
// complete the protocol handshake, while retaining its write timeout.
|
||||
parent = context.Background()
|
||||
}
|
||||
return bulkCloseContext(parent, timeout)
|
||||
}
|
||||
|
||||
func normalizeBulkOpenRequest(req BulkOpenRequest) BulkOpenRequest {
|
||||
req.Range = normalizeBulkRange(req.Range)
|
||||
req.Metadata = cloneBulkMetadata(req.Metadata)
|
||||
|
||||
Reference in New Issue
Block a user