- 引入 LogicalConn/TransportConn 分层,ClientConn 保留兼容适配层 - 新增 Stream、Bulk、RecordStream 三条数据面能力及对应控制路径 - 完成 transfer/file 传输内核与状态快照、诊断能力 - 补齐 reconnect、inbound dispatcher、modern psk 等基础模块 - 增加大规模回归、并发与基准测试覆盖 - 更新依赖库
1079 lines
24 KiB
Go
1079 lines
24 KiB
Go
package notify
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
StreamOpenSignalKey = "notify.stream.open"
|
|
StreamCloseSignalKey = "notify.stream.close"
|
|
StreamResetSignalKey = "notify.stream.reset"
|
|
)
|
|
|
|
type StreamChannel string
|
|
|
|
const (
|
|
StreamControlChannel StreamChannel = "control"
|
|
StreamDataChannel StreamChannel = "data"
|
|
StreamRecordChannel StreamChannel = "record"
|
|
)
|
|
|
|
type StreamMetadata map[string]string
|
|
|
|
type StreamOpenOptions struct {
|
|
ID string
|
|
Channel StreamChannel
|
|
Metadata StreamMetadata
|
|
ReadTimeout time.Duration
|
|
WriteTimeout time.Duration
|
|
}
|
|
|
|
type StreamAcceptInfo struct {
|
|
ID string
|
|
DataID uint64
|
|
Channel StreamChannel
|
|
Metadata StreamMetadata
|
|
LogicalConn *LogicalConn
|
|
TransportConn *TransportConn
|
|
TransportGeneration uint64
|
|
Stream Stream
|
|
}
|
|
|
|
type Stream interface {
|
|
io.Reader
|
|
io.Writer
|
|
io.Closer
|
|
|
|
ID() string
|
|
Channel() StreamChannel
|
|
Metadata() StreamMetadata
|
|
Context() context.Context
|
|
|
|
LogicalConn() *LogicalConn
|
|
TransportConn() *TransportConn
|
|
TransportGeneration() uint64
|
|
LocalAddr() net.Addr
|
|
RemoteAddr() net.Addr
|
|
|
|
CloseWrite() error
|
|
Reset(error) error
|
|
SetDeadline(time.Time) error
|
|
SetReadDeadline(time.Time) error
|
|
SetWriteDeadline(time.Time) error
|
|
}
|
|
|
|
var (
|
|
errStreamClientNil = errors.New("stream client is nil")
|
|
errStreamServerNil = errors.New("stream server is nil")
|
|
errStreamLogicalConnNil = errors.New("stream logical connection is nil")
|
|
errStreamTransportNil = errors.New("stream transport connection is nil")
|
|
errStreamRuntimeNil = errors.New("stream runtime is nil")
|
|
errStreamIDEmpty = errors.New("stream id is empty")
|
|
errStreamAlreadyExists = errors.New("stream already exists")
|
|
errStreamNotFound = errors.New("stream not found")
|
|
errStreamHandlerNotConfigured = errors.New("stream handler is not configured")
|
|
errStreamDataPathNotReady = errors.New("stream data path is not implemented yet")
|
|
errStreamRejected = errors.New("stream open rejected")
|
|
errStreamReset = errors.New("stream reset")
|
|
errStreamBackpressureExceeded = errors.New("stream inbound backpressure exceeded")
|
|
)
|
|
|
|
type streamCloseSender func(context.Context, *streamHandle, bool) error
|
|
type streamResetSender func(context.Context, *streamHandle, string) error
|
|
type streamDataSender func(context.Context, *streamHandle, []byte) error
|
|
|
|
type streamHandle struct {
|
|
runtime *streamRuntime
|
|
runtimeScope string
|
|
id string
|
|
dataID uint64
|
|
outboundSeq uint64
|
|
channel StreamChannel
|
|
metadata StreamMetadata
|
|
sessionEpoch uint64
|
|
client *ClientCommon
|
|
logical *LogicalConn
|
|
transport *TransportConn
|
|
transportGeneration uint64
|
|
readTimeout time.Duration
|
|
writeTimeout time.Duration
|
|
closeFn streamCloseSender
|
|
resetFn streamResetSender
|
|
sendDataFn streamDataSender
|
|
chunkSize int
|
|
inboundQueueLimit int
|
|
inboundBytesLimit int
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
localAddr net.Addr
|
|
remoteAddr net.Addr
|
|
createdAt time.Time
|
|
|
|
writeMu sync.Mutex
|
|
mu sync.Mutex
|
|
localClosed bool
|
|
localReadClosed bool
|
|
remoteClosed bool
|
|
peerReadClosed bool
|
|
resetErr error
|
|
readQueue [][]byte
|
|
readBuf []byte
|
|
bufferedBytes int
|
|
readNotify chan struct{}
|
|
readDeadline time.Time
|
|
writeDeadline time.Time
|
|
readDeadlineOverride bool
|
|
writeDeadlineOverride bool
|
|
readDeadlineNotify chan struct{}
|
|
writeDeadlineNotify chan struct{}
|
|
bytesRead int64
|
|
bytesWritten int64
|
|
readCalls int64
|
|
writeCalls int64
|
|
lastReadAt time.Time
|
|
lastWriteAt time.Time
|
|
}
|
|
|
|
func newStreamHandle(parent context.Context, runtime *streamRuntime, runtimeScope string, req StreamOpenRequest, sessionEpoch uint64, logical *LogicalConn, transport *TransportConn, transportGeneration uint64, closeFn streamCloseSender, resetFn streamResetSender, sendDataFn streamDataSender, cfg streamConfig) *streamHandle {
|
|
if parent == nil {
|
|
parent = context.Background()
|
|
}
|
|
ctx, cancel := context.WithCancel(parent)
|
|
if transportGeneration == 0 && transport != nil {
|
|
transportGeneration = transport.TransportGeneration()
|
|
}
|
|
if transportGeneration == 0 && logical != nil {
|
|
transportGeneration = logical.transportGenerationSnapshot()
|
|
}
|
|
cfg = normalizeStreamConfig(cfg)
|
|
return &streamHandle{
|
|
runtime: runtime,
|
|
runtimeScope: runtimeScope,
|
|
id: req.StreamID,
|
|
dataID: req.DataID,
|
|
channel: normalizeStreamChannel(req.Channel),
|
|
metadata: cloneStreamMetadata(req.Metadata),
|
|
sessionEpoch: sessionEpoch,
|
|
logical: logical,
|
|
transport: transport,
|
|
transportGeneration: transportGeneration,
|
|
readTimeout: req.ReadTimeout,
|
|
writeTimeout: req.WriteTimeout,
|
|
closeFn: closeFn,
|
|
resetFn: resetFn,
|
|
sendDataFn: sendDataFn,
|
|
chunkSize: cfg.ChunkSize,
|
|
inboundQueueLimit: cfg.InboundQueueLimit,
|
|
inboundBytesLimit: cfg.InboundBufferedBytesLimit,
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
readNotify: make(chan struct{}, 1),
|
|
localAddr: streamLocalAddrSnapshot(logical, transport),
|
|
remoteAddr: streamRemoteAddrSnapshot(logical, transport),
|
|
createdAt: time.Now(),
|
|
readDeadlineNotify: make(chan struct{}),
|
|
writeDeadlineNotify: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
func (s *streamHandle) SessionEpoch() uint64 {
|
|
if s == nil {
|
|
return 0
|
|
}
|
|
return s.sessionEpoch
|
|
}
|
|
|
|
func (s *streamHandle) acceptsClientSessionEpoch(epoch uint64) bool {
|
|
if s == nil {
|
|
return false
|
|
}
|
|
if s.sessionEpoch == 0 || epoch == 0 {
|
|
return true
|
|
}
|
|
return s.sessionEpoch == epoch
|
|
}
|
|
|
|
func (s *streamHandle) acceptsTransportGeneration(transport *TransportConn) bool {
|
|
if s == nil {
|
|
return false
|
|
}
|
|
if s.transportGeneration == 0 || transport == nil {
|
|
return true
|
|
}
|
|
return s.transportGeneration == transport.TransportGeneration()
|
|
}
|
|
|
|
func (s *streamHandle) ID() string {
|
|
if s == nil {
|
|
return ""
|
|
}
|
|
return s.id
|
|
}
|
|
|
|
func (s *streamHandle) dataIDSnapshot() uint64 {
|
|
if s == nil {
|
|
return 0
|
|
}
|
|
return s.dataID
|
|
}
|
|
|
|
func (s *streamHandle) nextOutboundDataSeq() uint64 {
|
|
if s == nil {
|
|
return 0
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.outboundSeq++
|
|
return s.outboundSeq
|
|
}
|
|
|
|
func (s *streamHandle) Channel() StreamChannel {
|
|
if s == nil {
|
|
return StreamDataChannel
|
|
}
|
|
return s.channel
|
|
}
|
|
|
|
func (s *streamHandle) Metadata() StreamMetadata {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
return cloneStreamMetadata(s.metadata)
|
|
}
|
|
|
|
func (s *streamHandle) Context() context.Context {
|
|
if s == nil {
|
|
return context.Background()
|
|
}
|
|
return s.ctx
|
|
}
|
|
|
|
func (s *streamHandle) LogicalConn() *LogicalConn {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
return s.logical
|
|
}
|
|
|
|
func (s *streamHandle) TransportConn() *TransportConn {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
return s.transport
|
|
}
|
|
|
|
func (s *streamHandle) TransportGeneration() uint64 {
|
|
if s == nil {
|
|
return 0
|
|
}
|
|
return s.transportGeneration
|
|
}
|
|
|
|
func (s *streamHandle) LocalAddr() net.Addr {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.localAddr
|
|
}
|
|
|
|
func (s *streamHandle) RemoteAddr() net.Addr {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.remoteAddr
|
|
}
|
|
|
|
func (s *streamHandle) readTimeoutSnapshot() time.Duration {
|
|
if s == nil {
|
|
return 0
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.readTimeout
|
|
}
|
|
|
|
func (s *streamHandle) writeTimeoutSnapshot() time.Duration {
|
|
if s == nil {
|
|
return 0
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.writeTimeout
|
|
}
|
|
|
|
func (s *streamHandle) Read(p []byte) (int, error) {
|
|
if len(p) == 0 {
|
|
return 0, nil
|
|
}
|
|
if s == nil {
|
|
return 0, io.ErrClosedPipe
|
|
}
|
|
for {
|
|
s.mu.Lock()
|
|
localReadClosed := s.localReadClosed
|
|
if len(s.readBuf) > 0 {
|
|
n := copy(p, s.readBuf)
|
|
s.readBuf = s.readBuf[n:]
|
|
s.bufferedBytes -= n
|
|
if s.bufferedBytes < 0 {
|
|
s.bufferedBytes = 0
|
|
}
|
|
s.recordReadLocked(n, time.Now())
|
|
s.mu.Unlock()
|
|
return n, nil
|
|
}
|
|
if len(s.readQueue) > 0 {
|
|
s.readBuf = s.readQueue[0]
|
|
s.readQueue[0] = nil
|
|
s.readQueue = s.readQueue[1:]
|
|
s.mu.Unlock()
|
|
continue
|
|
}
|
|
resetErr := s.resetErr
|
|
remoteClosed := s.remoteClosed
|
|
deadline := s.effectiveReadDeadlineLocked(time.Now())
|
|
ctx := s.ctx
|
|
notify := s.readNotify
|
|
deadlineNotify := s.readDeadlineNotify
|
|
s.mu.Unlock()
|
|
if localReadClosed {
|
|
return 0, io.ErrClosedPipe
|
|
}
|
|
if resetErr != nil {
|
|
return 0, resetErr
|
|
}
|
|
if remoteClosed {
|
|
return 0, io.EOF
|
|
}
|
|
if err := s.waitReadable(ctx, notify, deadlineNotify, deadline); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *streamHandle) Write(p []byte) (int, error) {
|
|
if len(p) == 0 {
|
|
return 0, nil
|
|
}
|
|
if s == nil {
|
|
return 0, io.ErrClosedPipe
|
|
}
|
|
s.writeMu.Lock()
|
|
defer s.writeMu.Unlock()
|
|
s.mu.Lock()
|
|
resetErr := s.resetErr
|
|
localClosed := s.localClosed
|
|
peerReadClosed := s.peerReadClosed
|
|
sendDataFn := s.sendDataFn
|
|
chunkSize := s.chunkSize
|
|
writeTimeout := s.writeTimeout
|
|
streamCtx := s.ctx
|
|
runtime := s.runtime
|
|
s.mu.Unlock()
|
|
if resetErr != nil {
|
|
return 0, resetErr
|
|
}
|
|
if localClosed || peerReadClosed {
|
|
return 0, io.ErrClosedPipe
|
|
}
|
|
if sendDataFn == nil {
|
|
return 0, errStreamDataPathNotReady
|
|
}
|
|
if chunkSize <= 0 {
|
|
chunkSize = defaultFileChunkSize
|
|
}
|
|
written := 0
|
|
for written < len(p) {
|
|
end := written + chunkSize
|
|
if end > len(p) {
|
|
end = len(p)
|
|
}
|
|
chunk := p[written:end]
|
|
sendCtx, cancel, deadlineChanged, err := s.newWriteContext(streamCtx, writeTimeout)
|
|
if err != nil {
|
|
if written > 0 {
|
|
s.recordWrite(written, time.Now())
|
|
}
|
|
return written, err
|
|
}
|
|
release, err := acquireStreamOutboundBudget(runtime, sendCtx, len(chunk))
|
|
if err != nil {
|
|
cancel()
|
|
if streamDeadlineChanged(deadlineChanged) {
|
|
continue
|
|
}
|
|
if written > 0 {
|
|
s.recordWrite(written, time.Now())
|
|
}
|
|
return written, s.normalizeWriteError(err)
|
|
}
|
|
err = sendDataFn(sendCtx, s, chunk)
|
|
release()
|
|
cancel()
|
|
if err != nil {
|
|
if streamDeadlineChanged(deadlineChanged) {
|
|
continue
|
|
}
|
|
if written > 0 {
|
|
s.recordWrite(written, time.Now())
|
|
}
|
|
return written, s.normalizeWriteError(err)
|
|
}
|
|
written = end
|
|
}
|
|
if written > 0 {
|
|
s.recordWrite(written, time.Now())
|
|
}
|
|
return written, nil
|
|
}
|
|
|
|
func (s *streamHandle) SetDeadline(deadline time.Time) error {
|
|
if err := s.SetReadDeadline(deadline); err != nil {
|
|
return err
|
|
}
|
|
return s.SetWriteDeadline(deadline)
|
|
}
|
|
|
|
func (s *streamHandle) SetReadDeadline(deadline time.Time) error {
|
|
if s == nil {
|
|
return io.ErrClosedPipe
|
|
}
|
|
s.mu.Lock()
|
|
s.readDeadline = deadline
|
|
s.readDeadlineOverride = true
|
|
signalStreamDeadlineChangeLocked(&s.readDeadlineNotify)
|
|
s.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
func (s *streamHandle) SetWriteDeadline(deadline time.Time) error {
|
|
if s == nil {
|
|
return io.ErrClosedPipe
|
|
}
|
|
s.mu.Lock()
|
|
s.writeDeadline = deadline
|
|
s.writeDeadlineOverride = true
|
|
signalStreamDeadlineChangeLocked(&s.writeDeadlineNotify)
|
|
s.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
func (s *streamHandle) setAddrSnapshot(local net.Addr, remote net.Addr) {
|
|
if s == nil {
|
|
return
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if local != nil {
|
|
s.localAddr = local
|
|
}
|
|
if remote != nil {
|
|
s.remoteAddr = remote
|
|
}
|
|
}
|
|
|
|
func (s *streamHandle) setClientSnapshotOwner(client *ClientCommon) {
|
|
if s == nil {
|
|
return
|
|
}
|
|
s.client = client
|
|
}
|
|
|
|
func (s *streamHandle) recordReadLocked(n int, now time.Time) {
|
|
if s == nil || n <= 0 {
|
|
return
|
|
}
|
|
s.bytesRead += int64(n)
|
|
s.readCalls++
|
|
s.lastReadAt = now
|
|
}
|
|
|
|
func (s *streamHandle) recordWrite(n int, now time.Time) {
|
|
if s == nil || n <= 0 {
|
|
return
|
|
}
|
|
s.mu.Lock()
|
|
s.bytesWritten += int64(n)
|
|
s.writeCalls++
|
|
s.lastWriteAt = now
|
|
s.mu.Unlock()
|
|
}
|
|
|
|
func (s *streamHandle) effectiveReadDeadlineLocked(now time.Time) time.Time {
|
|
if s == nil {
|
|
return time.Time{}
|
|
}
|
|
if s.readDeadlineOverride {
|
|
return s.readDeadline
|
|
}
|
|
return streamEffectiveDeadline(now, s.readTimeout, time.Time{})
|
|
}
|
|
|
|
func (s *streamHandle) effectiveWriteDeadlineLocked(now time.Time, writeTimeout time.Duration) time.Time {
|
|
if s == nil {
|
|
return time.Time{}
|
|
}
|
|
if s.writeDeadlineOverride {
|
|
return s.writeDeadline
|
|
}
|
|
return streamEffectiveDeadline(now, writeTimeout, time.Time{})
|
|
}
|
|
|
|
func (s *streamHandle) newWriteContext(parent context.Context, writeTimeout time.Duration) (context.Context, func(), <-chan struct{}, error) {
|
|
if parent == nil {
|
|
parent = context.Background()
|
|
}
|
|
s.mu.Lock()
|
|
deadline := s.effectiveWriteDeadlineLocked(time.Now(), writeTimeout)
|
|
deadlineNotify := s.writeDeadlineNotify
|
|
s.mu.Unlock()
|
|
if !deadline.IsZero() && !deadline.After(time.Now()) {
|
|
return nil, func() {}, nil, os.ErrDeadlineExceeded
|
|
}
|
|
baseCtx := parent
|
|
baseCancel := func() {}
|
|
if !deadline.IsZero() {
|
|
baseCtx, baseCancel = context.WithDeadline(parent, deadline)
|
|
} else {
|
|
baseCtx, baseCancel = context.WithCancel(parent)
|
|
}
|
|
changed := make(chan struct{})
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
select {
|
|
case <-baseCtx.Done():
|
|
case <-deadlineNotify:
|
|
close(changed)
|
|
baseCancel()
|
|
}
|
|
}()
|
|
cancel := func() {
|
|
baseCancel()
|
|
<-done
|
|
}
|
|
return baseCtx, cancel, changed, nil
|
|
}
|
|
|
|
func (s *streamHandle) Close() error {
|
|
return s.close(true)
|
|
}
|
|
|
|
func (s *streamHandle) CloseWrite() error {
|
|
return s.close(false)
|
|
}
|
|
|
|
func (s *streamHandle) close(full bool) error {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
s.writeMu.Lock()
|
|
defer s.writeMu.Unlock()
|
|
s.mu.Lock()
|
|
if s.resetErr != nil {
|
|
err := s.resetErr
|
|
s.mu.Unlock()
|
|
return err
|
|
}
|
|
if s.localClosed {
|
|
if !full || s.localReadClosed {
|
|
s.mu.Unlock()
|
|
return nil
|
|
}
|
|
closeFn := s.closeFn
|
|
s.mu.Unlock()
|
|
|
|
if closeFn != nil {
|
|
if err := closeFn(context.Background(), s, true); err != nil && !errors.Is(err, errStreamNotFound) {
|
|
return err
|
|
}
|
|
}
|
|
|
|
s.mu.Lock()
|
|
if s.localReadClosed {
|
|
s.mu.Unlock()
|
|
return nil
|
|
}
|
|
s.localReadClosed = true
|
|
s.clearBufferedDataLocked()
|
|
shouldFinalize := s.shouldFinalizeLocked()
|
|
s.mu.Unlock()
|
|
s.notifyReadable()
|
|
if shouldFinalize {
|
|
s.finalize()
|
|
}
|
|
return nil
|
|
}
|
|
closeFn := s.closeFn
|
|
s.mu.Unlock()
|
|
|
|
if closeFn != nil {
|
|
if err := closeFn(context.Background(), s, full); err != nil && !errors.Is(err, errStreamNotFound) {
|
|
return err
|
|
}
|
|
}
|
|
|
|
s.mu.Lock()
|
|
if s.localClosed {
|
|
s.mu.Unlock()
|
|
return nil
|
|
}
|
|
s.localClosed = true
|
|
if full {
|
|
s.localReadClosed = true
|
|
s.clearBufferedDataLocked()
|
|
}
|
|
shouldFinalize := s.shouldFinalizeLocked()
|
|
s.mu.Unlock()
|
|
if full {
|
|
s.notifyReadable()
|
|
}
|
|
if shouldFinalize {
|
|
s.finalize()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *streamHandle) Reset(err error) error {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
resetErr := streamResetError(err)
|
|
|
|
s.mu.Lock()
|
|
if s.resetErr != nil {
|
|
err := s.resetErr
|
|
s.mu.Unlock()
|
|
return err
|
|
}
|
|
resetFn := s.resetFn
|
|
s.mu.Unlock()
|
|
|
|
if resetFn != nil {
|
|
if sendErr := resetFn(context.Background(), s, streamResetMessage(resetErr)); sendErr != nil {
|
|
return sendErr
|
|
}
|
|
}
|
|
s.markReset(resetErr)
|
|
return nil
|
|
}
|
|
|
|
func (s *streamHandle) markRemoteClosed() {
|
|
if s == nil {
|
|
return
|
|
}
|
|
s.mu.Lock()
|
|
s.remoteClosed = true
|
|
shouldFinalize := s.shouldFinalizeLocked()
|
|
s.mu.Unlock()
|
|
s.notifyReadable()
|
|
if shouldFinalize {
|
|
s.finalize()
|
|
}
|
|
}
|
|
|
|
func (s *streamHandle) markPeerClosed() {
|
|
if s == nil {
|
|
return
|
|
}
|
|
s.mu.Lock()
|
|
s.remoteClosed = true
|
|
s.peerReadClosed = true
|
|
shouldFinalize := s.shouldFinalizeLocked()
|
|
s.mu.Unlock()
|
|
s.notifyReadable()
|
|
if shouldFinalize {
|
|
s.finalize()
|
|
}
|
|
}
|
|
|
|
func (s *streamHandle) markReset(err error) {
|
|
if s == nil {
|
|
return
|
|
}
|
|
s.mu.Lock()
|
|
if s.resetErr == nil {
|
|
s.resetErr = streamResetError(err)
|
|
s.clearBufferedDataLocked()
|
|
}
|
|
s.mu.Unlock()
|
|
s.notifyReadable()
|
|
s.finalize()
|
|
}
|
|
|
|
func (s *streamHandle) resetErrSnapshot() error {
|
|
if s == nil {
|
|
return io.ErrClosedPipe
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.resetErr
|
|
}
|
|
|
|
func (s *streamHandle) localClosedSnapshot() bool {
|
|
if s == nil {
|
|
return true
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.localClosed
|
|
}
|
|
|
|
func (s *streamHandle) remoteClosedSnapshot() bool {
|
|
if s == nil {
|
|
return true
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.remoteClosed
|
|
}
|
|
|
|
func (s *streamHandle) localReadClosedSnapshot() bool {
|
|
if s == nil {
|
|
return true
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.localReadClosed
|
|
}
|
|
|
|
func (s *streamHandle) peerReadClosedSnapshot() bool {
|
|
if s == nil {
|
|
return true
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.peerReadClosed
|
|
}
|
|
|
|
func (s *streamHandle) writeStateErrorSnapshot() error {
|
|
if s == nil {
|
|
return io.ErrClosedPipe
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if s.resetErr != nil {
|
|
return s.resetErr
|
|
}
|
|
if s.localClosed || s.peerReadClosed {
|
|
return io.ErrClosedPipe
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *streamHandle) shouldFinalizeLocked() bool {
|
|
return s.resetErr != nil || s.localReadClosed || (s.peerReadClosed && s.remoteClosed) || (s.localClosed && s.remoteClosed)
|
|
}
|
|
|
|
func (s *streamHandle) pushChunk(chunk []byte) error {
|
|
return s.pushChunkWithOwnership(chunk, false)
|
|
}
|
|
|
|
func (s *streamHandle) pushOwnedChunk(chunk []byte) error {
|
|
return s.pushChunkWithOwnership(chunk, true)
|
|
}
|
|
|
|
func (s *streamHandle) pushChunkWithOwnership(chunk []byte, owned bool) error {
|
|
if s == nil {
|
|
return io.ErrClosedPipe
|
|
}
|
|
if len(chunk) == 0 {
|
|
return nil
|
|
}
|
|
stored := chunk
|
|
if !owned {
|
|
stored = append([]byte(nil), chunk...)
|
|
}
|
|
s.mu.Lock()
|
|
if s.resetErr != nil {
|
|
err := s.resetErr
|
|
s.mu.Unlock()
|
|
return err
|
|
}
|
|
if s.inboundQueueLimit > 0 && s.bufferedChunkCountLocked() >= s.inboundQueueLimit {
|
|
err := s.markResetLocked(errStreamBackpressureExceeded)
|
|
s.mu.Unlock()
|
|
s.notifyReadable()
|
|
s.finalize()
|
|
return err
|
|
}
|
|
if s.inboundBytesLimit > 0 && s.bufferedBytes+len(stored) > s.inboundBytesLimit {
|
|
err := s.markResetLocked(errStreamBackpressureExceeded)
|
|
s.mu.Unlock()
|
|
s.notifyReadable()
|
|
s.finalize()
|
|
return err
|
|
}
|
|
s.readQueue = append(s.readQueue, stored)
|
|
s.bufferedBytes += len(stored)
|
|
s.notifyReadableLocked()
|
|
s.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
func (s *streamHandle) markResetLocked(err error) error {
|
|
if s == nil {
|
|
return io.ErrClosedPipe
|
|
}
|
|
if s.resetErr == nil {
|
|
s.resetErr = streamResetError(err)
|
|
s.clearBufferedDataLocked()
|
|
}
|
|
return s.resetErr
|
|
}
|
|
|
|
func (s *streamHandle) clearBufferedDataLocked() {
|
|
if s == nil {
|
|
return
|
|
}
|
|
for i := range s.readQueue {
|
|
s.readQueue[i] = nil
|
|
}
|
|
s.readQueue = nil
|
|
s.readBuf = nil
|
|
s.bufferedBytes = 0
|
|
}
|
|
|
|
func (s *streamHandle) bufferedChunkCountLocked() int {
|
|
if s == nil {
|
|
return 0
|
|
}
|
|
count := len(s.readQueue)
|
|
if len(s.readBuf) > 0 {
|
|
count++
|
|
}
|
|
return count
|
|
}
|
|
|
|
func (s *streamHandle) snapshot() StreamSnapshot {
|
|
if s == nil {
|
|
return StreamSnapshot{}
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
snapshot := StreamSnapshot{
|
|
ID: s.id,
|
|
DataID: s.dataID,
|
|
Scope: normalizeFileScope(s.runtimeScope),
|
|
Channel: s.channel,
|
|
Metadata: cloneStreamMetadata(s.metadata),
|
|
SessionEpoch: s.sessionEpoch,
|
|
TransportGeneration: s.transportGeneration,
|
|
LocalClosed: s.localClosed,
|
|
LocalReadClosed: s.localReadClosed,
|
|
RemoteClosed: s.remoteClosed,
|
|
PeerReadClosed: s.peerReadClosed,
|
|
BufferedChunks: s.bufferedChunkCountLocked(),
|
|
BufferedBytes: s.bufferedBytes,
|
|
ReadTimeout: s.readTimeout,
|
|
WriteTimeout: s.writeTimeout,
|
|
BytesRead: s.bytesRead,
|
|
BytesWritten: s.bytesWritten,
|
|
ReadCalls: s.readCalls,
|
|
WriteCalls: s.writeCalls,
|
|
OpenedAt: s.createdAt,
|
|
LastReadAt: s.lastReadAt,
|
|
LastWriteAt: s.lastWriteAt,
|
|
ReadDeadline: s.readDeadline,
|
|
WriteDeadline: s.writeDeadline,
|
|
}
|
|
if s.localAddr != nil {
|
|
snapshot.LocalAddress = s.localAddr.String()
|
|
}
|
|
if s.remoteAddr != nil {
|
|
snapshot.RemoteAddress = s.remoteAddr.String()
|
|
}
|
|
if s.logical != nil {
|
|
snapshot.LogicalClientID = s.logical.ID()
|
|
if addr := s.logical.RemoteAddr(); addr != nil {
|
|
snapshot.RemoteAddress = addr.String()
|
|
}
|
|
}
|
|
if snapshot.RemoteAddress == "" && s.transport != nil && s.transport.RemoteAddr() != nil {
|
|
snapshot.RemoteAddress = s.transport.RemoteAddr().String()
|
|
}
|
|
if s.resetErr != nil {
|
|
snapshot.ResetError = s.resetErr.Error()
|
|
}
|
|
var diag snapshotBindingDiagnostics
|
|
switch {
|
|
case s.logical != nil || s.transport != nil:
|
|
diag = snapshotBindingDiagnosticsFromLogical(s.logical, s.transport, s.transportGeneration)
|
|
case s.client != nil:
|
|
diag = snapshotBindingDiagnosticsFromClient(s.client, s.sessionEpoch)
|
|
}
|
|
snapshot.BindingOwner = diag.BindingOwner
|
|
snapshot.BindingAlive = diag.BindingAlive
|
|
snapshot.BindingCurrent = diag.BindingCurrent
|
|
snapshot.BindingReason = diag.BindingReason
|
|
snapshot.BindingError = diag.BindingError
|
|
snapshot.TransportAttached = diag.TransportAttached
|
|
snapshot.TransportHasRuntimeConn = diag.TransportHasRuntimeConn
|
|
snapshot.TransportCurrent = diag.TransportCurrent
|
|
snapshot.TransportDetachReason = diag.TransportDetachReason
|
|
snapshot.TransportDetachKind = diag.TransportDetachKind
|
|
snapshot.TransportDetachGeneration = diag.TransportDetachGeneration
|
|
snapshot.TransportDetachError = diag.TransportDetachError
|
|
snapshot.TransportDetachedAt = diag.TransportDetachedAt
|
|
snapshot.ReattachEligible = diag.ReattachEligible
|
|
return snapshot
|
|
}
|
|
|
|
func streamRuntimeCloseError(err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return errServiceShutdown
|
|
}
|
|
|
|
func (s *streamHandle) finalize() {
|
|
if s == nil {
|
|
return
|
|
}
|
|
if s.cancel != nil {
|
|
s.cancel()
|
|
}
|
|
if s.runtime != nil {
|
|
s.runtime.remove(s.runtimeScope, s.id)
|
|
}
|
|
}
|
|
|
|
func (s *streamHandle) waitReadable(ctx context.Context, notify <-chan struct{}, deadlineNotify <-chan struct{}, deadline time.Time) error {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
if deadline.IsZero() {
|
|
select {
|
|
case <-notify:
|
|
return nil
|
|
case <-deadlineNotify:
|
|
return nil
|
|
case <-ctx.Done():
|
|
if resetErr := s.resetErrSnapshot(); resetErr != nil {
|
|
return resetErr
|
|
}
|
|
if s.localReadClosedSnapshot() {
|
|
return io.ErrClosedPipe
|
|
}
|
|
if s.remoteClosedSnapshot() {
|
|
return nil
|
|
}
|
|
return ctx.Err()
|
|
}
|
|
}
|
|
if !deadline.After(time.Now()) {
|
|
return os.ErrDeadlineExceeded
|
|
}
|
|
timer := time.NewTimer(time.Until(deadline))
|
|
defer timer.Stop()
|
|
select {
|
|
case <-notify:
|
|
return nil
|
|
case <-deadlineNotify:
|
|
return nil
|
|
case <-ctx.Done():
|
|
if resetErr := s.resetErrSnapshot(); resetErr != nil {
|
|
return resetErr
|
|
}
|
|
if s.localReadClosedSnapshot() {
|
|
return io.ErrClosedPipe
|
|
}
|
|
if s.remoteClosedSnapshot() {
|
|
return nil
|
|
}
|
|
return ctx.Err()
|
|
case <-timer.C:
|
|
return os.ErrDeadlineExceeded
|
|
}
|
|
}
|
|
|
|
func (s *streamHandle) normalizeWriteError(err error) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if stateErr := s.writeStateErrorSnapshot(); stateErr != nil {
|
|
return stateErr
|
|
}
|
|
return normalizeStreamDeadlineError(err)
|
|
}
|
|
|
|
func (s *streamHandle) notifyReadable() {
|
|
if s == nil {
|
|
return
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.notifyReadableLocked()
|
|
}
|
|
|
|
func (s *streamHandle) notifyReadableLocked() {
|
|
if s == nil || s.readNotify == nil {
|
|
return
|
|
}
|
|
select {
|
|
case s.readNotify <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
|
|
func normalizeStreamChannel(channel StreamChannel) StreamChannel {
|
|
switch channel {
|
|
case "", StreamDataChannel:
|
|
return StreamDataChannel
|
|
case StreamControlChannel:
|
|
return StreamControlChannel
|
|
case StreamRecordChannel:
|
|
return StreamRecordChannel
|
|
default:
|
|
return channel
|
|
}
|
|
}
|
|
|
|
func cloneStreamMetadata(src StreamMetadata) StreamMetadata {
|
|
if len(src) == 0 {
|
|
return nil
|
|
}
|
|
dst := make(StreamMetadata, len(src))
|
|
for key, value := range src {
|
|
dst[key] = value
|
|
}
|
|
return dst
|
|
}
|
|
|
|
func acquireStreamOutboundBudget(runtime *streamRuntime, ctx context.Context, size int) (func(), error) {
|
|
if runtime == nil {
|
|
return func() {}, nil
|
|
}
|
|
return runtime.acquireOutbound(ctx, size)
|
|
}
|
|
|
|
func normalizeStreamOpenRequest(req StreamOpenRequest) StreamOpenRequest {
|
|
req.Channel = normalizeStreamChannel(req.Channel)
|
|
req.Metadata = cloneStreamMetadata(req.Metadata)
|
|
return req
|
|
}
|
|
|
|
func streamResetError(err error) error {
|
|
if err == nil {
|
|
return errStreamReset
|
|
}
|
|
return err
|
|
}
|
|
|
|
func streamResetMessage(err error) string {
|
|
if err == nil {
|
|
return ""
|
|
}
|
|
return err.Error()
|
|
}
|