1f2e74acca
- 完善 stream/bulk DataID 分配、预留和双向命名空间,修复并发打开及 dedicated/shared 回退时的 ID 冲突 - 将收发、回复、恢复任务和 sidecar 绑定原始会话与物理连接,防止重连后的旧消息误操作新连接 - 加强 close/reset 身份校验及实例移除检查,修复 dedicated attach 失败、通道引用和资源回收竞态 - 收紧批量发送器停止准入,确保在途入队完成后统一清理请求、缓冲区和等待者 - 修复 record 满队列死锁、取消时序号消耗及关闭竞态,确保关闭有界并返回真实错误 - 增加协商式 record 逻辑半关闭,保留反向 ACK;通过 reset 传递 RecordFailure,避免背压掩盖原始失败原因 - 补齐帧长度、批次数量、序号溢出和未确认窗口校验,提前拒绝超限数据并按字节预算拆批 - 为入站分发增加全局及单连接的条数、字节预算和阻塞背压,关闭时唤醒等待者,消除正常断连日志噪音 - 完善 bulk 窗口释放失败处理与传输诊断,补充并发、重连、背压、协议边界及真实 TCP 回归覆盖
846 lines
20 KiB
Go
846 lines
20 KiB
Go
package notify
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
const bulkDedicatedLaneMicroBatchWait = 50 * time.Microsecond
|
|
|
|
type bulkDedicatedLaneBatchRequest struct {
|
|
Ctx context.Context
|
|
DataID uint64
|
|
Items []bulkDedicatedSendRequest
|
|
Deadline time.Time
|
|
wait bool
|
|
resultCh chan error
|
|
state bulkDedicatedRequestState
|
|
aborted atomic.Bool
|
|
}
|
|
|
|
var bulkDedicatedLaneBatchRequestPool sync.Pool
|
|
|
|
type bulkDedicatedLaneSender struct {
|
|
conn net.Conn
|
|
encode func([]bulkDedicatedOutboundBatch) ([]byte, func(), error)
|
|
fail func(error)
|
|
|
|
reqCh chan *bulkDedicatedLaneBatchRequest
|
|
stopCh chan struct{}
|
|
doneCh chan struct{}
|
|
stopOnce sync.Once
|
|
admissionMu sync.Mutex
|
|
admitting sync.WaitGroup
|
|
admissionClosed bool
|
|
flushMu sync.Mutex
|
|
queued atomic.Int64
|
|
debug bool
|
|
|
|
errMu sync.Mutex
|
|
err error
|
|
}
|
|
|
|
func newBulkDedicatedLaneSender(conn net.Conn, encode func([]bulkDedicatedOutboundBatch) ([]byte, func(), error), fail func(error), debug ...bool) *bulkDedicatedLaneSender {
|
|
sender := &bulkDedicatedLaneSender{
|
|
conn: conn,
|
|
encode: encode,
|
|
fail: fail,
|
|
debug: len(debug) > 0 && debug[0],
|
|
reqCh: make(chan *bulkDedicatedLaneBatchRequest, bulkDedicatedSendQueueSize),
|
|
stopCh: make(chan struct{}),
|
|
doneCh: make(chan struct{}),
|
|
}
|
|
go sender.run()
|
|
return sender
|
|
}
|
|
|
|
func getBulkDedicatedLaneBatchRequest() *bulkDedicatedLaneBatchRequest {
|
|
if pooled, ok := bulkDedicatedLaneBatchRequestPool.Get().(*bulkDedicatedLaneBatchRequest); ok && pooled != nil {
|
|
pooled.reset()
|
|
return pooled
|
|
}
|
|
req := &bulkDedicatedLaneBatchRequest{
|
|
resultCh: make(chan error, 1),
|
|
}
|
|
req.reset()
|
|
return req
|
|
}
|
|
|
|
func (r *bulkDedicatedLaneBatchRequest) reset() {
|
|
if r == nil {
|
|
return
|
|
}
|
|
r.Ctx = nil
|
|
r.DataID = 0
|
|
r.Deadline = time.Time{}
|
|
r.wait = false
|
|
r.Items = r.Items[:0]
|
|
r.state.value.Store(bulkDedicatedRequestQueued)
|
|
r.aborted.Store(false)
|
|
if r.resultCh != nil {
|
|
select {
|
|
case <-r.resultCh:
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *bulkDedicatedLaneBatchRequest) prepare(ctx context.Context, dataID uint64, items []bulkDedicatedSendRequest, wait bool, borrowItems bool) {
|
|
if r == nil {
|
|
return
|
|
}
|
|
r.reset()
|
|
r.Ctx = ctx
|
|
r.DataID = dataID
|
|
r.wait = wait
|
|
if deadline, ok := ctx.Deadline(); ok {
|
|
r.Deadline = deadline
|
|
}
|
|
if borrowItems {
|
|
r.Items = items
|
|
return
|
|
}
|
|
if cap(r.Items) < len(items) {
|
|
r.Items = make([]bulkDedicatedSendRequest, len(items))
|
|
} else {
|
|
r.Items = r.Items[:len(items)]
|
|
}
|
|
copy(r.Items, items)
|
|
}
|
|
|
|
func (r *bulkDedicatedLaneBatchRequest) recycle() {
|
|
if r == nil {
|
|
return
|
|
}
|
|
r.reset()
|
|
bulkDedicatedLaneBatchRequestPool.Put(r)
|
|
}
|
|
|
|
func (s *bulkDedicatedLaneSender) submitData(ctx context.Context, dataID uint64, seq uint64, payload []byte) error {
|
|
if s == nil {
|
|
return errTransportDetached
|
|
}
|
|
items := []bulkDedicatedSendRequest{{
|
|
Type: bulkFastPayloadTypeData,
|
|
Seq: seq,
|
|
Payload: append([]byte(nil), payload...),
|
|
}}
|
|
return s.submitBatch(ctx, dataID, items, false, false)
|
|
}
|
|
|
|
func (s *bulkDedicatedLaneSender) submitWrite(ctx context.Context, dataID uint64, startSeq uint64, payload []byte, chunkSize int, payloadOwned bool) (int, error) {
|
|
if s == nil {
|
|
return 0, errTransportDetached
|
|
}
|
|
if len(payload) == 0 {
|
|
return 0, nil
|
|
}
|
|
if chunkSize <= 0 {
|
|
chunkSize = defaultBulkChunkSize
|
|
}
|
|
if submitted, written, err := s.tryDirectSubmitWrite(ctx, dataID, startSeq, payload, chunkSize); submitted {
|
|
return written, err
|
|
}
|
|
written := 0
|
|
seq := startSeq
|
|
for written < len(payload) {
|
|
var itemBuf [bulkDedicatedBatchMaxItems]bulkDedicatedSendRequest
|
|
items := itemBuf[:0]
|
|
batchBytes := bulkDedicatedBatchHeaderLen
|
|
start := written
|
|
for written < len(payload) && len(items) < bulkDedicatedBatchMaxItems {
|
|
end := written + chunkSize
|
|
if end > len(payload) {
|
|
end = len(payload)
|
|
}
|
|
itemLen := bulkDedicatedSendRequestLenFromPayloadLen(end - written)
|
|
if len(items) > 0 && batchBytes+itemLen > bulkDedicatedBatchMaxPlainBytes {
|
|
break
|
|
}
|
|
items = append(items, bulkDedicatedSendRequest{
|
|
Type: bulkFastPayloadTypeData,
|
|
Seq: seq,
|
|
Payload: payload[written:end],
|
|
})
|
|
batchBytes += itemLen
|
|
seq++
|
|
written = end
|
|
}
|
|
if len(items) == 0 {
|
|
end := written + chunkSize
|
|
if end > len(payload) {
|
|
end = len(payload)
|
|
}
|
|
items = append(items, bulkDedicatedSendRequest{
|
|
Type: bulkFastPayloadTypeData,
|
|
Seq: seq,
|
|
Payload: payload[written:end],
|
|
})
|
|
seq++
|
|
written = end
|
|
}
|
|
if err := s.submitWriteBatch(ctx, dataID, items, payloadOwned); err != nil {
|
|
return start, err
|
|
}
|
|
start = written
|
|
}
|
|
return written, nil
|
|
}
|
|
|
|
func (s *bulkDedicatedLaneSender) submitWriteBatch(ctx context.Context, dataID uint64, items []bulkDedicatedSendRequest, _ bool) error {
|
|
if s == nil {
|
|
return errTransportDetached
|
|
}
|
|
if len(items) == 0 {
|
|
return nil
|
|
}
|
|
if submitted, err := s.tryDirectSubmitBatch(ctx, dataID, items); submitted {
|
|
return err
|
|
}
|
|
return s.submitBatch(ctx, dataID, items, true, true)
|
|
}
|
|
|
|
func (s *bulkDedicatedLaneSender) submitControl(ctx context.Context, dataID uint64, frameType uint8, flags uint8, seq uint64, payload []byte) error {
|
|
if s == nil {
|
|
return errTransportDetached
|
|
}
|
|
items := []bulkDedicatedSendRequest{{
|
|
Type: frameType,
|
|
Flags: flags,
|
|
Seq: seq,
|
|
}}
|
|
if len(payload) > 0 {
|
|
items[0].Payload = append([]byte(nil), payload...)
|
|
}
|
|
var startedAt time.Time
|
|
if s.debug && frameType == bulkFastPayloadTypeRelease {
|
|
startedAt = time.Now()
|
|
fmt.Printf("[bulk-debug] at=%s lane release enqueue data=%d queued=%d\n", startedAt.Format(time.RFC3339Nano), dataID, s.queued.Load())
|
|
}
|
|
err := s.submitBatch(ctx, dataID, items, true, false)
|
|
if s.debug && frameType == bulkFastPayloadTypeRelease {
|
|
fmt.Printf("[bulk-debug] at=%s lane release done data=%d elapsed=%s error=%v\n", time.Now().Format(time.RFC3339Nano), dataID, time.Since(startedAt), err)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (s *bulkDedicatedLaneSender) submitBatch(ctx context.Context, dataID uint64, items []bulkDedicatedSendRequest, wait bool, borrowItems bool) error {
|
|
if s == nil {
|
|
return errTransportDetached
|
|
}
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
if err := s.errSnapshot(); err != nil {
|
|
return err
|
|
}
|
|
req := getBulkDedicatedLaneBatchRequest()
|
|
req.prepare(ctx, dataID, items, wait, borrowItems)
|
|
s.queued.Add(1)
|
|
if !s.enqueue(req) {
|
|
s.queued.Add(-1)
|
|
req.recycle()
|
|
if err := ctx.Err(); err != nil {
|
|
return normalizeStreamDeadlineError(err)
|
|
}
|
|
return s.stoppedErr()
|
|
}
|
|
if !wait {
|
|
return nil
|
|
}
|
|
return s.waitAck(req)
|
|
}
|
|
|
|
func (s *bulkDedicatedLaneSender) tryDirectSubmitWrite(ctx context.Context, dataID uint64, startSeq uint64, payload []byte, chunkSize int) (bool, int, error) {
|
|
if s == nil {
|
|
return true, 0, errTransportDetached
|
|
}
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
if len(payload) == 0 {
|
|
return true, 0, nil
|
|
}
|
|
if chunkSize <= 0 {
|
|
chunkSize = defaultBulkChunkSize
|
|
}
|
|
if err := s.errSnapshot(); err != nil {
|
|
return true, 0, err
|
|
}
|
|
select {
|
|
case <-ctx.Done():
|
|
return true, 0, normalizeStreamDeadlineError(ctx.Err())
|
|
case <-s.stopCh:
|
|
return true, 0, s.stoppedErr()
|
|
default:
|
|
}
|
|
if s.queued.Load() != 0 {
|
|
return false, 0, nil
|
|
}
|
|
if !s.flushMu.TryLock() {
|
|
return false, 0, nil
|
|
}
|
|
defer s.flushMu.Unlock()
|
|
if s.queued.Load() != 0 {
|
|
return false, 0, nil
|
|
}
|
|
if err := s.errSnapshot(); err != nil {
|
|
return true, 0, err
|
|
}
|
|
written := 0
|
|
seq := startSeq
|
|
deadline, _ := ctx.Deadline()
|
|
for written < len(payload) {
|
|
select {
|
|
case <-ctx.Done():
|
|
return true, written, normalizeStreamDeadlineError(ctx.Err())
|
|
case <-s.stopCh:
|
|
return true, written, s.stoppedErr()
|
|
default:
|
|
}
|
|
var itemBuf [bulkDedicatedBatchMaxItems]bulkDedicatedSendRequest
|
|
items := itemBuf[:0]
|
|
batchBytes := bulkDedicatedBatchHeaderLen
|
|
start := written
|
|
for written < len(payload) && len(items) < bulkDedicatedBatchMaxItems {
|
|
end := written + chunkSize
|
|
if end > len(payload) {
|
|
end = len(payload)
|
|
}
|
|
itemLen := bulkDedicatedSendRequestLenFromPayloadLen(end - written)
|
|
if len(items) > 0 && batchBytes+itemLen > bulkDedicatedBatchMaxPlainBytes {
|
|
break
|
|
}
|
|
items = append(items, bulkDedicatedSendRequest{
|
|
Type: bulkFastPayloadTypeData,
|
|
Seq: seq,
|
|
Payload: payload[written:end],
|
|
})
|
|
batchBytes += itemLen
|
|
seq++
|
|
written = end
|
|
}
|
|
if len(items) == 0 {
|
|
end := written + chunkSize
|
|
if end > len(payload) {
|
|
end = len(payload)
|
|
}
|
|
items = append(items, bulkDedicatedSendRequest{
|
|
Type: bulkFastPayloadTypeData,
|
|
Seq: seq,
|
|
Payload: payload[written:end],
|
|
})
|
|
seq++
|
|
written = end
|
|
}
|
|
select {
|
|
case <-s.stopCh:
|
|
return true, start, s.stoppedErr()
|
|
default:
|
|
}
|
|
if err := s.flush([]bulkDedicatedOutboundBatch{{
|
|
DataID: dataID,
|
|
Items: items,
|
|
}}, deadline); err != nil {
|
|
err = normalizeDedicatedBulkSendError(err)
|
|
s.markFailed(err)
|
|
s.waitAdmissions()
|
|
s.failPending(err)
|
|
if s.fail != nil {
|
|
go s.fail(err)
|
|
}
|
|
return true, start, err
|
|
}
|
|
}
|
|
return true, written, nil
|
|
}
|
|
|
|
func (s *bulkDedicatedLaneSender) tryDirectSubmitBatch(ctx context.Context, dataID uint64, items []bulkDedicatedSendRequest) (bool, error) {
|
|
if s == nil {
|
|
return true, errTransportDetached
|
|
}
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
if len(items) == 0 {
|
|
return true, nil
|
|
}
|
|
if err := s.errSnapshot(); err != nil {
|
|
return true, err
|
|
}
|
|
select {
|
|
case <-ctx.Done():
|
|
return true, normalizeStreamDeadlineError(ctx.Err())
|
|
case <-s.stopCh:
|
|
return true, s.stoppedErr()
|
|
default:
|
|
}
|
|
if s.queued.Load() != 0 {
|
|
return false, nil
|
|
}
|
|
if !s.flushMu.TryLock() {
|
|
return false, nil
|
|
}
|
|
defer s.flushMu.Unlock()
|
|
if s.queued.Load() != 0 {
|
|
return false, nil
|
|
}
|
|
if err := s.errSnapshot(); err != nil {
|
|
return true, err
|
|
}
|
|
select {
|
|
case <-ctx.Done():
|
|
return true, normalizeStreamDeadlineError(ctx.Err())
|
|
case <-s.stopCh:
|
|
return true, s.stoppedErr()
|
|
default:
|
|
}
|
|
deadline, _ := ctx.Deadline()
|
|
select {
|
|
case <-s.stopCh:
|
|
return true, s.stoppedErr()
|
|
default:
|
|
}
|
|
if err := s.flush([]bulkDedicatedOutboundBatch{{
|
|
DataID: dataID,
|
|
Items: items,
|
|
}}, deadline); err != nil {
|
|
err = normalizeDedicatedBulkSendError(err)
|
|
s.markFailed(err)
|
|
s.waitAdmissions()
|
|
s.failPending(err)
|
|
if s.fail != nil {
|
|
go s.fail(err)
|
|
}
|
|
return true, err
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (s *bulkDedicatedLaneSender) waitAck(req *bulkDedicatedLaneBatchRequest) error {
|
|
if s == nil {
|
|
return errTransportDetached
|
|
}
|
|
if req == nil {
|
|
return errTransportDetached
|
|
}
|
|
ctx := req.Ctx
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
select {
|
|
case err := <-req.resultCh:
|
|
req.recycle()
|
|
return normalizeDedicatedBulkSendError(err)
|
|
case <-ctx.Done():
|
|
if req.tryCancel() {
|
|
req.aborted.Store(true)
|
|
return normalizeStreamDeadlineError(ctx.Err())
|
|
}
|
|
err := <-req.resultCh
|
|
req.recycle()
|
|
return normalizeDedicatedBulkSendError(err)
|
|
}
|
|
}
|
|
|
|
func (s *bulkDedicatedLaneSender) stop() {
|
|
if s == nil {
|
|
return
|
|
}
|
|
s.markFailed(errTransportDetached)
|
|
s.waitAdmissions()
|
|
<-s.doneCh
|
|
// Direct submissions flush on the caller goroutine rather than run().
|
|
// Drain that path before the connection can be handed to a replacement.
|
|
s.flushMu.Lock()
|
|
s.flushMu.Unlock()
|
|
}
|
|
|
|
func (s *bulkDedicatedLaneSender) enqueue(req *bulkDedicatedLaneBatchRequest) bool {
|
|
if s == nil || req == nil {
|
|
return false
|
|
}
|
|
s.admissionMu.Lock()
|
|
if s.admissionClosed {
|
|
s.admissionMu.Unlock()
|
|
return false
|
|
}
|
|
s.admitting.Add(1)
|
|
s.admissionMu.Unlock()
|
|
defer s.admitting.Done()
|
|
select {
|
|
case <-req.Ctx.Done():
|
|
return false
|
|
case <-s.stopCh:
|
|
return false
|
|
case s.reqCh <- req:
|
|
return true
|
|
}
|
|
}
|
|
|
|
func (s *bulkDedicatedLaneSender) run() {
|
|
defer close(s.doneCh)
|
|
var carry *bulkDedicatedLaneBatchRequest
|
|
for {
|
|
req, ok := s.nextRequest(carry)
|
|
carry = nil
|
|
if !ok {
|
|
return
|
|
}
|
|
if !req.tryStart() {
|
|
s.finishRequest(req, req.canceledErr())
|
|
continue
|
|
}
|
|
if err := req.contextErr(); err != nil {
|
|
s.finishRequest(req, err)
|
|
continue
|
|
}
|
|
batchReqs := []*bulkDedicatedLaneBatchRequest{req}
|
|
batches := []bulkDedicatedOutboundBatch{{
|
|
DataID: req.DataID,
|
|
Items: req.Items,
|
|
}}
|
|
batchBytes, err := bulkDedicatedBatchesPlainLenChecked(batches)
|
|
if err != nil {
|
|
s.finishRequest(req, err)
|
|
continue
|
|
}
|
|
deadline := req.Deadline
|
|
var lockStarted time.Time
|
|
if s.debug {
|
|
lockStarted = time.Now()
|
|
}
|
|
s.flushMu.Lock()
|
|
if s.debug {
|
|
fmt.Printf("[bulk-debug] at=%s lane flush acquired data=%d lock-wait=%s queued=%d\n", time.Now().Format(time.RFC3339Nano), req.DataID, time.Since(lockStarted), s.queued.Load())
|
|
}
|
|
err = s.errSnapshot()
|
|
if err == nil {
|
|
carry, err = s.collectBatchRequests(&batchReqs, &batches, &batchBytes, &deadline)
|
|
if err == nil {
|
|
select {
|
|
case <-s.stopCh:
|
|
err = s.stoppedErr()
|
|
default:
|
|
err = s.flush(batches, deadline)
|
|
}
|
|
}
|
|
}
|
|
s.flushMu.Unlock()
|
|
if err != nil {
|
|
err = normalizeDedicatedBulkSendError(err)
|
|
s.markFailed(err)
|
|
s.finishBatchRequests(batchReqs, err)
|
|
if carry != nil {
|
|
s.finishRequest(carry, err)
|
|
carry = nil
|
|
}
|
|
s.waitAdmissions()
|
|
s.failPending(err)
|
|
if s.fail != nil {
|
|
go s.fail(err)
|
|
}
|
|
return
|
|
}
|
|
s.finishBatchRequests(batchReqs, nil)
|
|
}
|
|
}
|
|
|
|
func appendBulkDedicatedLaneBatch(batches *[]bulkDedicatedOutboundBatch, req *bulkDedicatedLaneBatchRequest) {
|
|
if req == nil {
|
|
return
|
|
}
|
|
if len(*batches) > 0 && (*batches)[len(*batches)-1].DataID == req.DataID {
|
|
last := &(*batches)[len(*batches)-1]
|
|
last.Items = append(last.Items, req.Items...)
|
|
return
|
|
}
|
|
*batches = append(*batches, bulkDedicatedOutboundBatch{
|
|
DataID: req.DataID,
|
|
Items: req.Items,
|
|
})
|
|
}
|
|
|
|
func bulkDedicatedLaneBatchItemCount(batches []bulkDedicatedOutboundBatch) int {
|
|
total := 0
|
|
for _, batch := range batches {
|
|
total += len(batch.Items)
|
|
}
|
|
return total
|
|
}
|
|
|
|
func bulkDedicatedLaneNextBatchBytes(batches []bulkDedicatedOutboundBatch, req *bulkDedicatedLaneBatchRequest, currentBytes int) int {
|
|
reqBytes := bulkDedicatedSendRequestsLen(req.Items)
|
|
if len(batches) == 0 {
|
|
return bulkDedicatedBatchHeaderLen + reqBytes
|
|
}
|
|
last := batches[len(batches)-1]
|
|
if last.DataID == req.DataID {
|
|
return currentBytes + reqBytes
|
|
}
|
|
if len(batches) == 1 {
|
|
return bulkDedicatedSuperBatchHeaderLen +
|
|
bulkDedicatedSuperBatchGroupHeaderLen + bulkDedicatedSendRequestsLen(batches[0].Items) +
|
|
bulkDedicatedSuperBatchGroupHeaderLen + reqBytes
|
|
}
|
|
return currentBytes + bulkDedicatedSuperBatchGroupHeaderLen + reqBytes
|
|
}
|
|
|
|
func (r *bulkDedicatedLaneBatchRequest) contextErr() error {
|
|
if r.Ctx == nil {
|
|
return nil
|
|
}
|
|
select {
|
|
case <-r.Ctx.Done():
|
|
return normalizeStreamDeadlineError(r.Ctx.Err())
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (r *bulkDedicatedLaneBatchRequest) tryStart() bool {
|
|
if r == nil {
|
|
return false
|
|
}
|
|
return r.state.value.CompareAndSwap(bulkDedicatedRequestQueued, bulkDedicatedRequestStarted)
|
|
}
|
|
|
|
func (r *bulkDedicatedLaneBatchRequest) tryCancel() bool {
|
|
if r == nil {
|
|
return false
|
|
}
|
|
return r.state.value.CompareAndSwap(bulkDedicatedRequestQueued, bulkDedicatedRequestCanceled)
|
|
}
|
|
|
|
func (r *bulkDedicatedLaneBatchRequest) canceledErr() error {
|
|
if r == nil {
|
|
return context.Canceled
|
|
}
|
|
if err := r.contextErr(); err != nil {
|
|
return err
|
|
}
|
|
return context.Canceled
|
|
}
|
|
|
|
func (s *bulkDedicatedLaneSender) nextRequest(carry *bulkDedicatedLaneBatchRequest) (*bulkDedicatedLaneBatchRequest, bool) {
|
|
if carry != nil {
|
|
select {
|
|
case <-s.stopCh:
|
|
err := s.stoppedErr()
|
|
s.waitAdmissions()
|
|
s.finishRequest(carry, err)
|
|
s.failPending(err)
|
|
return nil, false
|
|
default:
|
|
return carry, true
|
|
}
|
|
}
|
|
select {
|
|
case <-s.stopCh:
|
|
s.waitAdmissions()
|
|
s.failPending(s.stoppedErr())
|
|
return nil, false
|
|
case req := <-s.reqCh:
|
|
return req, true
|
|
}
|
|
}
|
|
|
|
func (s *bulkDedicatedLaneSender) collectBatchRequests(batchReqs *[]*bulkDedicatedLaneBatchRequest, batches *[]bulkDedicatedOutboundBatch, batchBytes *int, deadline *time.Time) (*bulkDedicatedLaneBatchRequest, error) {
|
|
if s == nil || bulkDedicatedLaneBatchItemCount(*batches) >= bulkDedicatedBatchMaxItems || *batchBytes >= bulkDedicatedBatchMaxPlainBytes {
|
|
return nil, nil
|
|
}
|
|
wait := bulkDedicatedLaneMicroBatchWait
|
|
var (
|
|
timer *time.Timer
|
|
timerCh <-chan time.Time
|
|
waited bool
|
|
)
|
|
if wait > 0 {
|
|
timer = time.NewTimer(wait)
|
|
timerCh = timer.C
|
|
defer timer.Stop()
|
|
}
|
|
for {
|
|
if bulkDedicatedLaneBatchItemCount(*batches) >= bulkDedicatedBatchMaxItems || *batchBytes >= bulkDedicatedBatchMaxPlainBytes {
|
|
return nil, nil
|
|
}
|
|
var (
|
|
req *bulkDedicatedLaneBatchRequest
|
|
ok bool
|
|
)
|
|
select {
|
|
case <-s.stopCh:
|
|
return nil, s.stoppedErr()
|
|
case req = <-s.reqCh:
|
|
ok = true
|
|
default:
|
|
}
|
|
if !ok {
|
|
if waited || timerCh == nil {
|
|
return nil, nil
|
|
}
|
|
waited = true
|
|
select {
|
|
case <-s.stopCh:
|
|
return nil, s.stoppedErr()
|
|
case req = <-s.reqCh:
|
|
case <-timerCh:
|
|
return nil, nil
|
|
}
|
|
}
|
|
if err := req.contextErr(); err != nil {
|
|
if req.tryCancel() {
|
|
s.finishRequest(req, err)
|
|
continue
|
|
}
|
|
s.finishRequest(req, req.canceledErr())
|
|
continue
|
|
}
|
|
nextItems := bulkDedicatedLaneBatchItemCount(*batches) + len(req.Items)
|
|
if nextItems > bulkDedicatedBatchMaxItems {
|
|
return req, nil
|
|
}
|
|
nextBytes := bulkDedicatedLaneNextBatchBytes(*batches, req, *batchBytes)
|
|
if nextBytes > bulkDedicatedBatchMaxPlainBytes {
|
|
return req, nil
|
|
}
|
|
if !req.tryStart() {
|
|
s.finishRequest(req, req.canceledErr())
|
|
continue
|
|
}
|
|
if err := req.contextErr(); err != nil {
|
|
s.finishRequest(req, err)
|
|
continue
|
|
}
|
|
*batchReqs = append(*batchReqs, req)
|
|
appendBulkDedicatedLaneBatch(batches, req)
|
|
*batchBytes = nextBytes
|
|
*deadline = earlierDeadline(*deadline, req.Deadline)
|
|
}
|
|
}
|
|
|
|
func earlierDeadline(current time.Time, next time.Time) time.Time {
|
|
if current.IsZero() {
|
|
return next
|
|
}
|
|
if next.IsZero() || current.Before(next) {
|
|
return current
|
|
}
|
|
return next
|
|
}
|
|
|
|
func (s *bulkDedicatedLaneSender) flush(batches []bulkDedicatedOutboundBatch, deadline time.Time) error {
|
|
if s == nil || s.conn == nil {
|
|
return errTransportDetached
|
|
}
|
|
var startedAt time.Time
|
|
if s.debug {
|
|
startedAt = time.Now()
|
|
}
|
|
payload, release, err := s.encode(batches)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if release != nil {
|
|
defer release()
|
|
}
|
|
if !s.debug {
|
|
return writeBulkDedicatedRecordWithDeadline(s.conn, payload, deadline)
|
|
}
|
|
encodeElapsed := time.Since(startedAt)
|
|
dataID := uint64(0)
|
|
if len(batches) > 0 {
|
|
dataID = batches[0].DataID
|
|
}
|
|
fmt.Printf("[bulk-debug] at=%s lane write begin data=%d groups=%d bytes=%d encode=%s\n", time.Now().Format(time.RFC3339Nano), dataID, len(batches), len(payload), encodeElapsed)
|
|
writeStarted := time.Now()
|
|
err = writeBulkDedicatedRecordWithDeadlineTrace(s.conn, payload, deadline, dataID)
|
|
fmt.Printf("[bulk-debug] at=%s lane write end data=%d elapsed=%s error=%v\n", time.Now().Format(time.RFC3339Nano), dataID, time.Since(writeStarted), err)
|
|
return err
|
|
}
|
|
|
|
func (s *bulkDedicatedLaneSender) finishRequest(req *bulkDedicatedLaneBatchRequest, err error) {
|
|
if s != nil {
|
|
s.queued.Add(-1)
|
|
}
|
|
if req == nil {
|
|
return
|
|
}
|
|
if req.wait && req.resultCh != nil {
|
|
if req.aborted.Load() {
|
|
req.recycle()
|
|
return
|
|
}
|
|
req.resultCh <- err
|
|
return
|
|
}
|
|
req.recycle()
|
|
}
|
|
|
|
func (s *bulkDedicatedLaneSender) finishBatchRequests(reqs []*bulkDedicatedLaneBatchRequest, err error) {
|
|
for _, req := range reqs {
|
|
s.finishRequest(req, err)
|
|
}
|
|
}
|
|
|
|
func (s *bulkDedicatedLaneSender) failPending(err error) {
|
|
for {
|
|
select {
|
|
case item := <-s.reqCh:
|
|
s.finishRequest(item, err)
|
|
default:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *bulkDedicatedLaneSender) setErr(err error) {
|
|
if s == nil || err == nil {
|
|
return
|
|
}
|
|
s.errMu.Lock()
|
|
if s.err == nil {
|
|
s.err = err
|
|
}
|
|
s.errMu.Unlock()
|
|
}
|
|
|
|
func (s *bulkDedicatedLaneSender) markFailed(err error) {
|
|
if s == nil {
|
|
return
|
|
}
|
|
s.setErr(err)
|
|
s.stopOnce.Do(func() {
|
|
s.admissionMu.Lock()
|
|
s.admissionClosed = true
|
|
close(s.stopCh)
|
|
s.admissionMu.Unlock()
|
|
})
|
|
}
|
|
|
|
func (s *bulkDedicatedLaneSender) waitAdmissions() {
|
|
if s != nil {
|
|
s.admitting.Wait()
|
|
}
|
|
}
|
|
|
|
func (s *bulkDedicatedLaneSender) errSnapshot() error {
|
|
if s == nil {
|
|
return errTransportDetached
|
|
}
|
|
s.errMu.Lock()
|
|
defer s.errMu.Unlock()
|
|
return s.err
|
|
}
|
|
|
|
func (s *bulkDedicatedLaneSender) stoppedErr() error {
|
|
if err := s.errSnapshot(); err != nil {
|
|
return err
|
|
}
|
|
return errTransportDetached
|
|
}
|