notify/bulk_dedicated.go
starainrt 4f760f2807
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 等回归测试
2026-04-16 17:27:48 +08:00

782 lines
22 KiB
Go

package notify
import (
"b612.me/notify/internal/transport"
"b612.me/stario"
"bytes"
"context"
cryptorand "crypto/rand"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"io"
"net"
"sync/atomic"
"time"
)
const (
systemBulkAttachKey = "_notify_bulk_attach"
bulkDedicatedRecordMagic = "NBR1"
bulkDedicatedRecordHeaderLen = 8
bulkDedicatedAttachTimeout = 5 * time.Second
bulkDedicatedAttachFrameMagicSize = 8
bulkDedicatedAttachFrameHeaderLen = 14
bulkDedicatedAttachFrameVersionOffset = 12
bulkDedicatedAttachFrameFlagsOffset = 13
bulkDedicatedAttachFrameVersionV1 = 1
bulkDedicatedAttachFrameFlagsNone = 0
)
var bulkDedicatedAttachFrameMagic = [bulkDedicatedAttachFrameMagicSize]byte{11, 27, 19, 96, 12, 25, 2, 20}
type bulkAttachRequest struct {
PeerID string
BulkID string
AttachToken string
}
type bulkAttachResponse struct {
Accepted bool
Error string
}
func newBulkAttachToken() string {
var buf [16]byte
if _, err := cryptorand.Read(buf[:]); err == nil {
return hex.EncodeToString(buf[:])
}
return ""
}
func decodeBulkAttachRequest(decodeFn func([]byte) (interface{}, error), data MsgVal) (bulkAttachRequest, error) {
var req bulkAttachRequest
if decodeFn == nil {
decodeFn = Decode
}
raw := []byte(data)
value, err := decodeFn(raw)
if err != nil {
return req, err
}
switch typed := value.(type) {
case bulkAttachRequest:
return typed, nil
case *bulkAttachRequest:
if typed == nil {
return req, errors.New("bulk attach request is nil")
}
return *typed, nil
default:
return req, errors.New("invalid bulk attach payload")
}
}
func decodeBulkAttachResponse(decodeFn func([]byte) (interface{}, error), data MsgVal) (bulkAttachResponse, error) {
var resp bulkAttachResponse
if decodeFn == nil {
decodeFn = Decode
}
raw := []byte(data)
value, err := decodeFn(raw)
if err != nil {
return resp, err
}
switch typed := value.(type) {
case bulkAttachResponse:
return typed, nil
case *bulkAttachResponse:
if typed == nil {
return resp, errors.New("bulk attach response is nil")
}
return *typed, nil
default:
return resp, errors.New("invalid bulk attach response")
}
}
func encodeDirectSignalFrame(queue *stario.StarQueue, sequenceEn func(interface{}) ([]byte, error), msgEn func([]byte, []byte) []byte, secretKey []byte, msg TransferMsg) ([]byte, error) {
if queue == nil {
queue = stario.NewQueue()
}
env, err := wrapTransferMsgEnvelope(msg, sequenceEn)
if err != nil {
return nil, err
}
plain, err := sequenceEn(env)
if err != nil {
return nil, err
}
payload := msgEn(secretKey, plain)
if payload == nil && len(plain) != 0 {
return nil, errTransportPayloadEncryptFailed
}
return queue.BuildMessage(payload), nil
}
func decodeDirectSignalPayload(sequenceDe func([]byte) (interface{}, error), msgDe func([]byte, []byte) []byte, secretKey []byte, payload []byte) (TransferMsg, error) {
plain := msgDe(secretKey, payload)
if plain == nil && len(payload) != 0 {
return TransferMsg{}, errTransportPayloadDecryptFailed
}
value, err := sequenceDe(plain)
if err != nil {
return TransferMsg{}, err
}
env, ok := value.(Envelope)
if !ok {
return TransferMsg{}, errors.New("invalid signal envelope")
}
return unwrapTransferMsgEnvelope(env, sequenceDe)
}
func readDirectSignalFramePayload(conn net.Conn) ([]byte, error) {
if conn == nil {
return nil, net.ErrClosed
}
var header [bulkDedicatedAttachFrameHeaderLen]byte
if _, err := io.ReadFull(conn, header[:]); err != nil {
return nil, err
}
if !bytes.Equal(header[:bulkDedicatedAttachFrameMagicSize], bulkDedicatedAttachFrameMagic[:]) {
return nil, stario.ErrQueueDataFormat
}
if got := header[bulkDedicatedAttachFrameVersionOffset]; got != bulkDedicatedAttachFrameVersionV1 {
return nil, stario.ErrQueueUnsupportedVersion
}
if got := header[bulkDedicatedAttachFrameFlagsOffset]; got != bulkDedicatedAttachFrameFlagsNone {
return nil, stario.ErrQueueUnsupportedFlags
}
length := binary.BigEndian.Uint32(header[bulkDedicatedAttachFrameMagicSize : bulkDedicatedAttachFrameMagicSize+4])
maxInt := int(^uint(0) >> 1)
if uint64(length) > uint64(maxInt) {
return nil, stario.ErrQueueMessageTooLarge
}
payload := make([]byte, int(length))
if _, err := io.ReadFull(conn, payload); err != nil {
return nil, err
}
return payload, nil
}
func writeBulkDedicatedRecord(conn net.Conn, payload []byte) error {
return writeBulkDedicatedRecordWithDeadline(conn, payload, time.Time{})
}
func writeBulkDedicatedRecordWithDeadline(conn net.Conn, payload []byte, deadline time.Time) error {
if conn == nil {
return net.ErrClosed
}
return withRawConnWriteLockDeadline(conn, deadline, func(conn net.Conn) error {
var header [bulkDedicatedRecordHeaderLen]byte
copy(header[:4], bulkDedicatedRecordMagic)
binary.BigEndian.PutUint32(header[4:8], uint32(len(payload)))
return writeNetBuffersFullUnlocked(conn, net.Buffers{header[:], payload})
})
}
func readBulkDedicatedRecord(conn net.Conn) ([]byte, error) {
if conn == nil {
return nil, net.ErrClosed
}
var header [bulkDedicatedRecordHeaderLen]byte
if _, err := io.ReadFull(conn, header[:]); err != nil {
return nil, err
}
if string(header[:4]) != bulkDedicatedRecordMagic {
return nil, fmt.Errorf("%w: record magic=%x", errBulkFastPayloadInvalid, header[:4])
}
size := int(binary.BigEndian.Uint32(header[4:8]))
if size < 0 {
return nil, errBulkFastPayloadInvalid
}
payload := make([]byte, size)
if _, err := io.ReadFull(conn, payload); err != nil {
return nil, err
}
return payload, nil
}
func (c *ClientCommon) dialDedicatedBulkConn(ctx context.Context) (net.Conn, error) {
source := c.clientConnectSourceSnapshot()
if source != nil && source.canReconnect() {
return source.dial(ctx)
}
conn := c.clientTransportConnSnapshot()
if conn == nil || conn.RemoteAddr() == nil {
return nil, errClientReconnectSourceUnavailable
}
return transport.Dial(conn.RemoteAddr().Network(), conn.RemoteAddr().String())
}
func (c *ClientCommon) attachDedicatedBulkSidecar(ctx context.Context, bulk *bulkHandle) error {
if c == nil || bulk == nil || !bulk.Dedicated() || bulk.dedicatedAttachedSnapshot() {
return nil
}
if ctx == nil {
ctx = context.Background()
}
ctx, cancel := context.WithTimeout(ctx, bulkDedicatedAttachTimeout)
defer cancel()
conn, err := c.dialDedicatedBulkConn(ctx)
if err != nil {
return err
}
resp, err := c.sendDedicatedBulkAttachRequest(ctx, conn, bulk)
if err != nil {
_ = conn.Close()
return err
}
if !resp.Accepted {
_ = conn.Close()
if resp.Error != "" {
return errors.New(resp.Error)
}
return errors.New("bulk attach rejected")
}
if err := bulk.attachDedicatedConn(conn); err != nil {
_ = conn.Close()
return err
}
go c.readDedicatedBulkLoop(bulk, conn)
return nil
}
func (c *ClientCommon) sendDedicatedBulkAttachRequest(ctx context.Context, conn net.Conn, bulk *bulkHandle) (bulkAttachResponse, error) {
if c == nil {
return bulkAttachResponse{}, errBulkClientNil
}
if bulk == nil {
return bulkAttachResponse{}, errBulkIDEmpty
}
defer func() {
_ = conn.SetReadDeadline(time.Time{})
}()
reqPayload, err := c.sequenceEn(bulkAttachRequest{
PeerID: c.ensureClientPeerIdentity(),
BulkID: bulk.ID(),
AttachToken: bulk.dedicatedAttachTokenSnapshot(),
})
if err != nil {
return bulkAttachResponse{}, err
}
msg := TransferMsg{
ID: atomic.AddUint64(&c.msgID, 1),
Key: systemBulkAttachKey,
Value: reqPayload,
Type: MSG_SYS_WAIT,
}
frame, err := encodeDirectSignalFrame(stario.NewQueue(), c.sequenceEn, c.msgEn, c.SecretKey, msg)
if err != nil {
return bulkAttachResponse{}, err
}
if err := writeFullToConn(conn, frame); err != nil {
return bulkAttachResponse{}, err
}
if deadline, ok := ctx.Deadline(); ok {
_ = conn.SetReadDeadline(deadline)
}
replyPayload, err := readDirectSignalFramePayload(conn)
if err != nil {
return bulkAttachResponse{}, err
}
transfer, err := decodeDirectSignalPayload(c.sequenceDe, c.msgDe, c.SecretKey, replyPayload)
if err != nil {
return bulkAttachResponse{}, err
}
if transfer.Key != systemBulkAttachKey || transfer.Type != MSG_SYS_REPLY || transfer.ID != msg.ID {
return bulkAttachResponse{}, errors.New("invalid bulk attach reply")
}
return decodeBulkAttachResponse(c.sequenceDe, transfer.Value)
}
func (c *ClientCommon) readDedicatedBulkLoop(bulk *bulkHandle, conn net.Conn) {
for {
payload, err := readBulkDedicatedRecord(conn)
if err != nil {
handleDedicatedBulkReadError(bulk, err)
return
}
plain, err := c.decryptTransportPayload(payload)
if err != nil {
_ = c.sendDedicatedBulkReset(context.Background(), bulk, err.Error())
bulk.markReset(err)
return
}
items, err := decodeDedicatedBulkInboundItems(bulk.dataIDSnapshot(), plain)
if err != nil {
_ = c.sendDedicatedBulkReset(context.Background(), bulk, err.Error())
bulk.markReset(err)
return
}
for _, item := range items {
if err := dispatchDedicatedBulkInboundItem(bulk, item); err != nil {
if !errors.Is(err, io.EOF) {
_ = c.sendDedicatedBulkReset(context.Background(), bulk, err.Error())
bulk.markReset(err)
}
return
}
if bulk.Context().Err() != nil {
return
}
}
}
}
func (s *ServerCommon) handleBulkAttachSystemMessage(message Message) bool {
if message.Key != systemBulkAttachKey {
return false
}
current := messageLogicalConnSnapshot(&message)
resp := bulkAttachResponse{}
var (
req bulkAttachRequest
logical *LogicalConn
bulk *bulkHandle
err error
)
req, err = decodeBulkAttachRequest(s.sequenceDe, message.Value)
if err == nil {
logical, bulk, err = s.resolveInboundDedicatedBulk(current, req)
}
if err != nil {
resp.Error = err.Error()
if current != nil {
_ = s.replyDedicatedBulkAttach(current, message, resp)
}
return true
}
if current != nil {
if attachErr := s.finishInboundDedicatedBulkAttach(current, logical, bulk, message); attachErr != nil {
bulk.markReset(attachErr)
}
}
return true
}
func (s *ServerCommon) resolveInboundDedicatedBulk(current *LogicalConn, req bulkAttachRequest) (*LogicalConn, *bulkHandle, error) {
if s == nil {
return nil, nil, errBulkServerNil
}
if current == nil {
return nil, nil, errBulkLogicalConnNil
}
if req.PeerID == "" || req.BulkID == "" || req.AttachToken == "" {
return nil, nil, errBulkIDEmpty
}
logical := s.GetLogicalConn(req.PeerID)
if logical == nil {
return nil, nil, errBulkLogicalConnNil
}
runtime := s.getBulkRuntime()
if runtime == nil {
return nil, nil, errBulkRuntimeNil
}
bulk, ok := runtime.lookup(serverFileScope(logical), req.BulkID)
if !ok {
return nil, nil, errBulkNotFound
}
if !bulk.Dedicated() {
return nil, nil, errors.New("bulk is not dedicated")
}
if bulk.dedicatedAttachTokenSnapshot() != req.AttachToken {
return nil, nil, errors.New("bulk attach token mismatch")
}
return logical, bulk, nil
}
func (s *ServerCommon) finishInboundDedicatedBulkAttach(current *LogicalConn, logical *LogicalConn, bulk *bulkHandle, message Message) error {
if current == nil || logical == nil || bulk == nil {
return errBulkLogicalConnNil
}
conn, err := current.detachTransportForTransfer()
if err != nil {
return err
}
fail := func(reason string, err error) error {
if conn != nil {
_ = conn.Close()
}
current.markSessionStopped(reason, err)
s.removeLogical(current)
return err
}
if err := s.replyDedicatedBulkAttachDetached(current, conn, message, bulkAttachResponse{Accepted: true}); err != nil {
return fail("bulk dedicated attach reply failed", err)
}
if err := bulk.attachDedicatedConn(conn); err != nil {
return fail("bulk dedicated attach failed", err)
}
go s.readDedicatedBulkLoop(logical, bulk, conn)
current.markSessionStopped("bulk dedicated attach", nil)
s.removeLogical(current)
return nil
}
func (s *ServerCommon) replyDedicatedBulkAttachDetached(client *LogicalConn, conn net.Conn, message Message, resp bulkAttachResponse) error {
if s == nil || client == nil {
return errBulkServerNil
}
if conn == nil {
return net.ErrClosed
}
msgEn := client.msgEnSnapshot()
if msgEn == nil {
return errTransportPayloadEncryptFailed
}
encoded, err := s.sequenceEn(resp)
if err != nil {
return err
}
reply := TransferMsg{
ID: message.ID,
Key: systemBulkAttachKey,
Value: encoded,
Type: MSG_SYS_REPLY,
}
frame, err := encodeDirectSignalFrame(stario.NewQueue(), s.sequenceEn, msgEn, client.secretKeySnapshot(), reply)
if err != nil {
return err
}
return withRawConnWriteLockDeadline(conn, writeDeadlineFromTimeout(client.maxWriteTimeoutSnapshot()), func(conn net.Conn) error {
return writeFullToConnUnlocked(conn, frame)
})
}
func (s *ServerCommon) replyDedicatedBulkAttach(client *LogicalConn, message Message, resp bulkAttachResponse) error {
if s == nil || client == nil {
return errBulkServerNil
}
encoded, err := s.sequenceEn(resp)
if err != nil {
return err
}
reply := TransferMsg{
ID: message.ID,
Key: systemBulkAttachKey,
Value: encoded,
Type: MSG_SYS_REPLY,
}
if message.inboundConn != nil {
return s.sendTransferInbound(client, messageTransportConnSnapshot(&message), message.inboundConn, reply)
}
_, err = s.sendLogical(client, reply)
return err
}
func (s *ServerCommon) readDedicatedBulkLoop(logical *LogicalConn, bulk *bulkHandle, conn net.Conn) {
for {
payload, err := readBulkDedicatedRecord(conn)
if err != nil {
handleDedicatedBulkReadError(bulk, err)
return
}
plain, err := s.decryptTransportPayloadLogical(logical, payload)
if err != nil {
_ = s.sendDedicatedBulkReset(context.Background(), logical, bulk, err.Error())
bulk.markReset(err)
return
}
items, err := decodeDedicatedBulkInboundItems(bulk.dataIDSnapshot(), plain)
if err != nil {
_ = s.sendDedicatedBulkReset(context.Background(), logical, bulk, err.Error())
bulk.markReset(err)
return
}
for _, item := range items {
if err := dispatchDedicatedBulkInboundItem(bulk, item); err != nil {
if !errors.Is(err, io.EOF) {
_ = s.sendDedicatedBulkReset(context.Background(), logical, bulk, err.Error())
bulk.markReset(err)
}
return
}
if bulk.Context().Err() != nil {
return
}
}
}
}
func handleDedicatedBulkReadError(bulk *bulkHandle, err error) {
if bulk == nil {
return
}
if bulk.Context().Err() != nil || bulk.remoteClosedSnapshot() {
return
}
if errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) {
if bulk.Dedicated() || bulk.localClosedSnapshot() {
bulk.markRemoteClosed()
return
}
}
bulk.markReset(transportDetachedError("dedicated bulk read error", err))
}
func (c *ClientCommon) dedicatedBulkSender(bulk *bulkHandle) (*bulkDedicatedSender, error) {
if c == nil || bulk == nil {
return nil, errBulkClientNil
}
if sender := bulk.dedicatedSenderSnapshot(); sender != nil {
return sender, nil
}
conn := bulk.dedicatedConnSnapshot()
if conn == nil {
return nil, transportDetachedError("dedicated bulk sidecar not attached", nil)
}
sender := newBulkDedicatedSender(conn, bulk.dataIDSnapshot(), c.encryptTransportPayload, func(items []bulkDedicatedSendRequest) ([]byte, error) {
return c.encodeDedicatedBulkBatchPayload(bulk.dataIDSnapshot(), items)
}, func(err error) {
bulk.markReset(err)
})
actual := bulk.installDedicatedSender(sender)
if actual != sender {
sender.stop()
}
return actual, nil
}
func (c *ClientCommon) sendDedicatedBulkData(ctx context.Context, bulk *bulkHandle, chunk []byte) error {
if c == nil || bulk == nil {
return errBulkClientNil
}
sender, err := c.dedicatedBulkSender(bulk)
if err != nil {
return err
}
return sender.submitData(ctx, bulk.nextOutboundDataSeq(), chunk)
}
func (c *ClientCommon) sendDedicatedBulkWrite(ctx context.Context, bulk *bulkHandle, payload []byte) (int, error) {
if c == nil || bulk == nil {
return 0, errBulkClientNil
}
sender, err := c.dedicatedBulkSender(bulk)
if err != nil {
return 0, err
}
return sender.submitWrite(ctx, bulk.nextOutboundDataSeq(), payload, bulk.chunkSize)
}
func (c *ClientCommon) sendDedicatedBulkClose(ctx context.Context, bulk *bulkHandle, full bool) error {
if c == nil || bulk == nil {
return errBulkClientNil
}
sendCtx, cancel, err := bulkWriteContext(ctx, bulk.writeTimeout)
if err != nil {
return err
}
defer cancel()
flags := uint8(0)
if full {
flags = bulkFastPayloadFlagFullClose
}
sender, err := c.dedicatedBulkSender(bulk)
if err != nil {
return err
}
return sender.submitControl(sendCtx, bulkFastPayloadTypeClose, flags, 0, nil)
}
func (c *ClientCommon) sendDedicatedBulkReset(ctx context.Context, bulk *bulkHandle, message string) error {
if c == nil || bulk == nil {
return errBulkClientNil
}
sendCtx, cancel, err := bulkWriteContext(ctx, bulk.writeTimeout)
if err != nil {
return err
}
defer cancel()
sender, err := c.dedicatedBulkSender(bulk)
if err != nil {
return err
}
return sender.submitControl(sendCtx, bulkFastPayloadTypeReset, 0, 0, []byte(message))
}
func (c *ClientCommon) sendDedicatedBulkRelease(ctx context.Context, bulk *bulkHandle, bytes int64, chunks int) error {
if c == nil || bulk == nil {
return errBulkClientNil
}
payload, err := encodeBulkDedicatedReleasePayload(bytes, chunks)
if err != nil {
return err
}
if err := bulk.waitDedicatedReady(ctx); err != nil {
return err
}
sendCtx, cancel, err := bulkWriteContext(ctx, bulk.writeTimeout)
if err != nil {
return err
}
defer cancel()
frame, err := c.encodeDedicatedBulkBatchPayload(bulk.dataIDSnapshot(), []bulkDedicatedSendRequest{{
Type: bulkFastPayloadTypeRelease,
Payload: payload,
}})
if err != nil {
return err
}
conn := bulk.dedicatedConnSnapshot()
if conn == nil {
return transportDetachedError("dedicated bulk sidecar not attached", nil)
}
deadline, _ := sendCtx.Deadline()
return writeBulkDedicatedRecordWithDeadline(conn, frame, deadline)
}
func (s *ServerCommon) dedicatedBulkSender(logical *LogicalConn, bulk *bulkHandle) (*bulkDedicatedSender, error) {
if s == nil || bulk == nil {
return nil, errBulkServerNil
}
if logical == nil {
logical = bulk.LogicalConn()
}
if logical == nil {
return nil, errBulkLogicalConnNil
}
if sender := bulk.dedicatedSenderSnapshot(); sender != nil {
return sender, nil
}
conn := bulk.dedicatedConnSnapshot()
if conn == nil {
return nil, transportDetachedError("dedicated bulk sidecar not attached", nil)
}
sender := newBulkDedicatedSender(conn, bulk.dataIDSnapshot(), func(plain []byte) ([]byte, error) {
return s.encryptTransportPayloadLogical(logical, plain)
}, func(items []bulkDedicatedSendRequest) ([]byte, error) {
return s.encodeDedicatedBulkBatchPayload(logical, bulk.dataIDSnapshot(), items)
}, func(err error) {
bulk.markReset(err)
})
actual := bulk.installDedicatedSender(sender)
if actual != sender {
sender.stop()
}
return actual, nil
}
func (s *ServerCommon) sendDedicatedBulkData(ctx context.Context, logical *LogicalConn, bulk *bulkHandle, chunk []byte) error {
if s == nil || bulk == nil {
return errBulkServerNil
}
sender, err := s.dedicatedBulkSender(logical, bulk)
if err != nil {
return err
}
return sender.submitData(ctx, bulk.nextOutboundDataSeq(), chunk)
}
func (s *ServerCommon) sendDedicatedBulkWrite(ctx context.Context, logical *LogicalConn, bulk *bulkHandle, payload []byte) (int, error) {
if s == nil || bulk == nil {
return 0, errBulkServerNil
}
sender, err := s.dedicatedBulkSender(logical, bulk)
if err != nil {
return 0, err
}
return sender.submitWrite(ctx, bulk.nextOutboundDataSeq(), payload, bulk.chunkSize)
}
func (s *ServerCommon) sendDedicatedBulkClose(ctx context.Context, logical *LogicalConn, bulk *bulkHandle, full bool) error {
if s == nil || bulk == nil {
return errBulkServerNil
}
sendCtx, cancel, err := bulkWriteContext(ctx, bulk.writeTimeout)
if err != nil {
return err
}
defer cancel()
flags := uint8(0)
if full {
flags = bulkFastPayloadFlagFullClose
}
sender, err := s.dedicatedBulkSender(logical, bulk)
if err != nil {
return err
}
return sender.submitControl(sendCtx, bulkFastPayloadTypeClose, flags, 0, nil)
}
func (s *ServerCommon) sendDedicatedBulkReset(ctx context.Context, logical *LogicalConn, bulk *bulkHandle, message string) error {
if s == nil || bulk == nil {
return errBulkServerNil
}
sendCtx, cancel, err := bulkWriteContext(ctx, bulk.writeTimeout)
if err != nil {
return err
}
defer cancel()
sender, err := s.dedicatedBulkSender(logical, bulk)
if err != nil {
return err
}
return sender.submitControl(sendCtx, bulkFastPayloadTypeReset, 0, 0, []byte(message))
}
func (s *ServerCommon) sendDedicatedBulkRelease(ctx context.Context, logical *LogicalConn, bulk *bulkHandle, bytes int64, chunks int) error {
if s == nil || bulk == nil {
return errBulkServerNil
}
payload, err := encodeBulkDedicatedReleasePayload(bytes, chunks)
if err != nil {
return err
}
if err := bulk.waitDedicatedReady(ctx); err != nil {
return err
}
sendCtx, cancel, err := bulkWriteContext(ctx, bulk.writeTimeout)
if err != nil {
return err
}
defer cancel()
frame, err := s.encodeDedicatedBulkBatchPayload(logical, bulk.dataIDSnapshot(), []bulkDedicatedSendRequest{{
Type: bulkFastPayloadTypeRelease,
Payload: payload,
}})
if err != nil {
return err
}
conn := bulk.dedicatedConnSnapshot()
if conn == nil {
return transportDetachedError("dedicated bulk sidecar not attached", nil)
}
deadline, _ := sendCtx.Deadline()
return writeBulkDedicatedRecordWithDeadline(conn, frame, deadline)
}
func (c *ClientCommon) encodeDedicatedBulkBatchPayload(dataID uint64, items []bulkDedicatedSendRequest) ([]byte, error) {
if c == nil {
return nil, errBulkClientNil
}
if c.fastPlainEncode != nil {
return encodeBulkDedicatedBatchPayloadFast(c.fastPlainEncode, c.SecretKey, dataID, items)
}
plain, err := encodeBulkDedicatedBatchPlain(dataID, items)
if err != nil {
return nil, err
}
return c.encryptTransportPayload(plain)
}
func (s *ServerCommon) encodeDedicatedBulkBatchPayload(logical *LogicalConn, dataID uint64, items []bulkDedicatedSendRequest) ([]byte, error) {
if s == nil {
return nil, errBulkServerNil
}
if logical == nil {
return nil, errBulkLogicalConnNil
}
if fastPlainEncode := logical.fastPlainEncodeSnapshot(); fastPlainEncode != nil {
return encodeBulkDedicatedBatchPayloadFast(fastPlainEncode, logical.secretKeySnapshot(), dataID, items)
}
plain, err := encodeBulkDedicatedBatchPlain(dataID, items)
if err != nil {
return nil, err
}
return s.encryptTransportPayloadLogical(logical, plain)
}