fix: close stream adaptive gaps and switch notify to stario v0.1.1
- make stream fast path honor adaptive soft payload limits end-to-end - split oversized fast-stream payloads into sequential frames before batching - use adaptive soft cap when encoding stream batch payloads - move timeout-like error detection into production code for adaptive tx - tune notify FrameReader read size explicitly to avoid throughput regression - drop local stario replace and depend on released b612.me/stario v0.1.1
This commit is contained in:
+187
-12
@@ -1,8 +1,10 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -15,6 +17,7 @@ const (
|
||||
streamFastPayloadVersion = 1
|
||||
streamFastPayloadTypeData = 1
|
||||
streamFastPayloadHeaderLen = 28
|
||||
streamFastBatchDirectLimit = 512 * 1024
|
||||
)
|
||||
|
||||
type streamFastDataFrame struct {
|
||||
@@ -24,6 +27,56 @@ type streamFastDataFrame struct {
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
func streamAdaptiveFramePayloadLimit(binding *transportBinding) int {
|
||||
if binding == nil {
|
||||
return 0
|
||||
}
|
||||
limit := binding.streamAdaptiveSoftPayloadBytesSnapshot() - streamFastPayloadHeaderLen
|
||||
if limit <= 0 {
|
||||
return 1
|
||||
}
|
||||
maxPayload := streamFastBatchMaxPlainBytes - streamFastPayloadHeaderLen
|
||||
if limit > maxPayload {
|
||||
return maxPayload
|
||||
}
|
||||
return limit
|
||||
}
|
||||
|
||||
func streamFastSplitFrameCount(size int, maxPayload int) int {
|
||||
if size <= 0 || maxPayload <= 0 {
|
||||
return 1
|
||||
}
|
||||
return (size + maxPayload - 1) / maxPayload
|
||||
}
|
||||
|
||||
func buildStreamFastSplitFrames(dataID uint64, startSeq uint64, chunk []byte, maxPayload int) []streamFastDataFrame {
|
||||
if len(chunk) == 0 {
|
||||
return nil
|
||||
}
|
||||
if maxPayload <= 0 || len(chunk) <= maxPayload {
|
||||
return []streamFastDataFrame{{
|
||||
DataID: dataID,
|
||||
Seq: startSeq,
|
||||
Payload: chunk,
|
||||
}}
|
||||
}
|
||||
frames := make([]streamFastDataFrame, 0, streamFastSplitFrameCount(len(chunk), maxPayload))
|
||||
seq := startSeq
|
||||
for offset := 0; offset < len(chunk); offset += maxPayload {
|
||||
end := offset + maxPayload
|
||||
if end > len(chunk) {
|
||||
end = len(chunk)
|
||||
}
|
||||
frames = append(frames, streamFastDataFrame{
|
||||
DataID: dataID,
|
||||
Seq: seq,
|
||||
Payload: chunk[offset:end],
|
||||
})
|
||||
seq++
|
||||
}
|
||||
return frames
|
||||
}
|
||||
|
||||
func encodeStreamFastDataFrameHeader(dst []byte, dataID uint64, seq uint64, payloadLen int) error {
|
||||
if dataID == 0 {
|
||||
return errStreamFastDataIDEmpty
|
||||
@@ -51,6 +104,31 @@ func encodeStreamFastDataFrame(dataID uint64, seq uint64, payload []byte) ([]byt
|
||||
return frame, nil
|
||||
}
|
||||
|
||||
func encodeStreamFastFramePayload(frame streamFastDataFrame) ([]byte, error) {
|
||||
framePayload := make([]byte, streamFastPayloadHeaderLen+len(frame.Payload))
|
||||
if err := encodeStreamFastDataFrameHeader(framePayload, frame.DataID, frame.Seq, len(frame.Payload)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
framePayload[6] = frame.Flags
|
||||
copy(framePayload[streamFastPayloadHeaderLen:], frame.Payload)
|
||||
return framePayload, nil
|
||||
}
|
||||
|
||||
func encodeStreamFastFramePayloadFast(encode transportFastPlainEncoder, secretKey []byte, frame streamFastDataFrame) ([]byte, error) {
|
||||
if encode == nil {
|
||||
return nil, errTransportPayloadEncryptFailed
|
||||
}
|
||||
plainLen := streamFastPayloadHeaderLen + len(frame.Payload)
|
||||
return encode(secretKey, plainLen, func(dst []byte) error {
|
||||
if err := encodeStreamFastDataFrameHeader(dst, frame.DataID, frame.Seq, len(frame.Payload)); err != nil {
|
||||
return err
|
||||
}
|
||||
dst[6] = frame.Flags
|
||||
copy(dst[streamFastPayloadHeaderLen:], frame.Payload)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func decodeStreamFastDataFrame(payload []byte) (streamFastDataFrame, bool, error) {
|
||||
if len(payload) < 4 || string(payload[:4]) != streamFastPayloadMagic {
|
||||
return streamFastDataFrame{}, false, nil
|
||||
@@ -77,18 +155,66 @@ func decodeStreamFastDataFrame(payload []byte) (streamFastDataFrame, bool, error
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
func (c *ClientCommon) encodeFastStreamDataPayload(dataID uint64, seq uint64, chunk []byte) ([]byte, error) {
|
||||
if c != nil && c.fastStreamEncode != nil {
|
||||
return c.fastStreamEncode(c.SecretKey, dataID, seq, chunk)
|
||||
func (c *ClientCommon) encodeFastStreamPayload(frame streamFastDataFrame) ([]byte, error) {
|
||||
if c != nil && c.fastStreamEncode != nil && frame.Flags == 0 {
|
||||
return c.fastStreamEncode(c.SecretKey, frame.DataID, frame.Seq, frame.Payload)
|
||||
}
|
||||
plain, err := encodeStreamFastDataFrame(dataID, seq, chunk)
|
||||
if c != nil && c.fastPlainEncode != nil {
|
||||
return encodeStreamFastFramePayloadFast(c.fastPlainEncode, c.SecretKey, frame)
|
||||
}
|
||||
plain, err := encodeStreamFastFramePayload(frame)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.encryptTransportPayload(plain)
|
||||
}
|
||||
|
||||
func (c *ClientCommon) sendFastStreamData(dataID uint64, seq uint64, chunk []byte) error {
|
||||
func (c *ClientCommon) encodeFastStreamDataPayload(dataID uint64, seq uint64, chunk []byte) ([]byte, error) {
|
||||
return c.encodeFastStreamPayload(streamFastDataFrame{
|
||||
DataID: dataID,
|
||||
Seq: seq,
|
||||
Payload: chunk,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ClientCommon) encodeFastStreamBatchPayload(frames []streamFastDataFrame) ([]byte, error) {
|
||||
if c == nil {
|
||||
return nil, errStreamClientNil
|
||||
}
|
||||
if c.fastPlainEncode != nil {
|
||||
return encodeStreamFastBatchPayloadFast(c.fastPlainEncode, c.SecretKey, frames)
|
||||
}
|
||||
plain, err := encodeStreamFastBatchPlain(frames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.encryptTransportPayload(plain)
|
||||
}
|
||||
|
||||
func (c *ClientCommon) sendFastStreamData(ctx context.Context, stream *streamHandle, chunk []byte) error {
|
||||
if stream == nil {
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
dataID := stream.dataIDSnapshot()
|
||||
fastPathVersion := stream.fastPathVersionSnapshot()
|
||||
if binding := c.clientTransportBindingSnapshot(); binding != nil && streamFastPathSupportsBatch(fastPathVersion) {
|
||||
if sender := binding.clientStreamBatchSenderSnapshot(c); sender != nil {
|
||||
if maxPayload := streamAdaptiveFramePayloadLimit(binding); maxPayload > 0 && len(chunk) > maxPayload {
|
||||
startSeq := stream.reserveOutboundDataSeqs(streamFastSplitFrameCount(len(chunk), maxPayload))
|
||||
return sender.submitFrames(ctx, fastPathVersion, buildStreamFastSplitFrames(dataID, startSeq, chunk, maxPayload))
|
||||
}
|
||||
seq := stream.reserveOutboundDataSeqs(1)
|
||||
if len(chunk) < streamFastBatchDirectLimit {
|
||||
return sender.submitData(ctx, dataID, seq, fastPathVersion, chunk)
|
||||
}
|
||||
payload, err := c.encodeFastStreamDataPayload(dataID, seq, chunk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sender.submitEncoded(ctx, fastPathVersion, payload)
|
||||
}
|
||||
}
|
||||
seq := stream.reserveOutboundDataSeqs(1)
|
||||
payload, err := c.encodeFastStreamDataPayload(dataID, seq, chunk)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -96,29 +222,78 @@ func (c *ClientCommon) sendFastStreamData(dataID uint64, seq uint64, chunk []byt
|
||||
return c.writePayloadToTransport(payload)
|
||||
}
|
||||
|
||||
func (s *ServerCommon) encodeFastStreamDataPayloadLogical(logical *LogicalConn, dataID uint64, seq uint64, chunk []byte) ([]byte, error) {
|
||||
if logical != nil {
|
||||
if fastStreamEncode := logical.fastStreamEncodeSnapshot(); fastStreamEncode != nil {
|
||||
return fastStreamEncode(logical.secretKeySnapshot(), dataID, seq, chunk)
|
||||
}
|
||||
func (s *ServerCommon) encodeFastStreamPayloadLogical(logical *LogicalConn, frame streamFastDataFrame) ([]byte, error) {
|
||||
if logical == nil {
|
||||
return nil, errTransportDetached
|
||||
}
|
||||
plain, err := encodeStreamFastDataFrame(dataID, seq, chunk)
|
||||
if fastStreamEncode := logical.fastStreamEncodeSnapshot(); fastStreamEncode != nil && frame.Flags == 0 {
|
||||
return fastStreamEncode(logical.secretKeySnapshot(), frame.DataID, frame.Seq, frame.Payload)
|
||||
}
|
||||
if fastPlainEncode := logical.fastPlainEncodeSnapshot(); fastPlainEncode != nil {
|
||||
return encodeStreamFastFramePayloadFast(fastPlainEncode, logical.secretKeySnapshot(), frame)
|
||||
}
|
||||
plain, err := encodeStreamFastFramePayload(frame)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.encryptTransportPayloadLogical(logical, plain)
|
||||
}
|
||||
|
||||
func (s *ServerCommon) sendFastStreamDataTransport(logical *LogicalConn, transport *TransportConn, dataID uint64, seq uint64, chunk []byte) error {
|
||||
func (s *ServerCommon) encodeFastStreamDataPayloadLogical(logical *LogicalConn, dataID uint64, seq uint64, chunk []byte) ([]byte, error) {
|
||||
return s.encodeFastStreamPayloadLogical(logical, streamFastDataFrame{
|
||||
DataID: dataID,
|
||||
Seq: seq,
|
||||
Payload: chunk,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *ServerCommon) encodeFastStreamBatchPayloadLogical(logical *LogicalConn, frames []streamFastDataFrame) ([]byte, error) {
|
||||
if logical == nil {
|
||||
return nil, errTransportDetached
|
||||
}
|
||||
if fastPlainEncode := logical.fastPlainEncodeSnapshot(); fastPlainEncode != nil {
|
||||
return encodeStreamFastBatchPayloadFast(fastPlainEncode, logical.secretKeySnapshot(), frames)
|
||||
}
|
||||
plain, err := encodeStreamFastBatchPlain(frames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.encryptTransportPayloadLogical(logical, plain)
|
||||
}
|
||||
|
||||
func (s *ServerCommon) sendFastStreamDataTransport(ctx context.Context, logical *LogicalConn, transport *TransportConn, stream *streamHandle, chunk []byte) error {
|
||||
if err := s.ensureServerTransportSendReady(transport); err != nil {
|
||||
return err
|
||||
}
|
||||
if stream == nil {
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
if logical == nil && transport != nil {
|
||||
logical = transport.logicalConnSnapshot()
|
||||
}
|
||||
if logical == nil {
|
||||
return errTransportDetached
|
||||
}
|
||||
dataID := stream.dataIDSnapshot()
|
||||
fastPathVersion := stream.fastPathVersionSnapshot()
|
||||
if binding := logical.transportBindingSnapshot(); binding != nil && binding.queueSnapshot() != nil && streamFastPathSupportsBatch(fastPathVersion) {
|
||||
if sender := binding.serverStreamBatchSenderSnapshot(logical); sender != nil {
|
||||
if maxPayload := streamAdaptiveFramePayloadLimit(binding); maxPayload > 0 && len(chunk) > maxPayload {
|
||||
startSeq := stream.reserveOutboundDataSeqs(streamFastSplitFrameCount(len(chunk), maxPayload))
|
||||
return sender.submitFrames(ctx, fastPathVersion, buildStreamFastSplitFrames(dataID, startSeq, chunk, maxPayload))
|
||||
}
|
||||
seq := stream.reserveOutboundDataSeqs(1)
|
||||
if len(chunk) < streamFastBatchDirectLimit {
|
||||
return sender.submitData(ctx, dataID, seq, fastPathVersion, chunk)
|
||||
}
|
||||
payload, err := s.encodeFastStreamDataPayloadLogical(logical, dataID, seq, chunk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sender.submitEncoded(ctx, fastPathVersion, payload)
|
||||
}
|
||||
}
|
||||
seq := stream.reserveOutboundDataSeqs(1)
|
||||
payload, err := s.encodeFastStreamDataPayloadLogical(logical, dataID, seq, chunk)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user