feat(notify): 重构通信内核并补齐 stream/bulk/record/transfer 能力

- 引入 LogicalConn/TransportConn 分层,ClientConn 保留兼容适配层
  - 新增 Stream、Bulk、RecordStream 三条数据面能力及对应控制路径
  - 完成 transfer/file 传输内核与状态快照、诊断能力
  - 补齐 reconnect、inbound dispatcher、modern psk 等基础模块
  - 增加大规模回归、并发与基准测试覆盖
  - 更新依赖库
This commit is contained in:
2026-04-15 15:24:36 +08:00
parent d14d13c393
commit 09d972c7b7
216 changed files with 51374 additions and 1715 deletions
+366
View File
@@ -0,0 +1,366 @@
package transfer
import (
"errors"
"sort"
"sync"
"time"
)
var (
ErrTransferIDEmpty = errors.New("transfer id is empty")
ErrTransferExists = errors.New("transfer already exists")
ErrTransferNotFound = errors.New("transfer not found")
ErrTransferBytesInvalid = errors.New("transfer bytes must be non-negative")
)
type Manager struct {
mu sync.Mutex
now func() time.Time
transfers map[string]*managedTransfer
}
type managedTransfer struct {
snapshot Snapshot
}
func NewManager() *Manager {
return NewManagerWithClock(time.Now)
}
func NewManagerWithClock(now func() time.Time) *Manager {
if now == nil {
now = time.Now
}
return &Manager{
now: now,
transfers: make(map[string]*managedTransfer),
}
}
func (m *Manager) StartOutgoing(desc Descriptor) (Snapshot, error) {
return m.start(desc, DirectionSend, StateNegotiating)
}
func (m *Manager) StartIncoming(desc Descriptor) (Snapshot, error) {
return m.start(desc, DirectionReceive, StatePrepared)
}
func (m *Manager) Activate(id string) (Snapshot, error) {
return m.update(id, func(snapshot *Snapshot) error {
snapshot.State = StateActive
return nil
})
}
func (m *Manager) Pause(id string) (Snapshot, error) {
return m.update(id, func(snapshot *Snapshot) error {
if snapshot.State.Terminal() {
return nil
}
snapshot.State = StatePaused
return nil
})
}
func (m *Manager) Resume(id string, confirmedBytes int64) (Snapshot, error) {
if confirmedBytes < 0 {
return Snapshot{}, ErrTransferBytesInvalid
}
return m.update(id, func(snapshot *Snapshot) error {
switch snapshot.Direction {
case DirectionSend:
if confirmedBytes > snapshot.SentBytes {
snapshot.SentBytes = confirmedBytes
}
snapshot.AckedBytes = confirmedBytes
if snapshot.Size > 0 && snapshot.AckedBytes > snapshot.Size {
snapshot.AckedBytes = snapshot.Size
}
case DirectionReceive:
if confirmedBytes > snapshot.ReceivedBytes {
snapshot.ReceivedBytes = confirmedBytes
}
if snapshot.Size > 0 && snapshot.ReceivedBytes > snapshot.Size {
snapshot.ReceivedBytes = snapshot.Size
}
}
snapshot.State = StateActive
snapshot.InflightBytes = inflightBytes(*snapshot)
return nil
})
}
func (m *Manager) RecordSend(id string, sentBytes int64) (Snapshot, error) {
if sentBytes < 0 {
return Snapshot{}, ErrTransferBytesInvalid
}
return m.update(id, func(snapshot *Snapshot) error {
snapshot.SentBytes += sentBytes
if snapshot.Size > 0 && snapshot.SentBytes > snapshot.Size {
snapshot.SentBytes = snapshot.Size
}
snapshot.InflightBytes = inflightBytes(*snapshot)
if !snapshot.State.Terminal() {
snapshot.State = StateActive
}
return nil
})
}
func (m *Manager) RecordReceive(id string, recvBytes int64) (Snapshot, error) {
if recvBytes < 0 {
return Snapshot{}, ErrTransferBytesInvalid
}
return m.update(id, func(snapshot *Snapshot) error {
snapshot.ReceivedBytes += recvBytes
if snapshot.Size > 0 && snapshot.ReceivedBytes > snapshot.Size {
snapshot.ReceivedBytes = snapshot.Size
}
if !snapshot.State.Terminal() {
snapshot.State = StateActive
}
return nil
})
}
func (m *Manager) SetAckedBytes(id string, ackedBytes int64) (Snapshot, error) {
if ackedBytes < 0 {
return Snapshot{}, ErrTransferBytesInvalid
}
return m.update(id, func(snapshot *Snapshot) error {
snapshot.AckedBytes = ackedBytes
if snapshot.Size > 0 && snapshot.AckedBytes > snapshot.Size {
snapshot.AckedBytes = snapshot.Size
}
if snapshot.AckedBytes > snapshot.SentBytes {
snapshot.SentBytes = snapshot.AckedBytes
}
snapshot.InflightBytes = inflightBytes(*snapshot)
return nil
})
}
func (m *Manager) BeginCommit(id string) (Snapshot, error) {
return m.update(id, func(snapshot *Snapshot) error {
snapshot.State = StateCommitting
return nil
})
}
func (m *Manager) BeginVerify(id string) (Snapshot, error) {
return m.update(id, func(snapshot *Snapshot) error {
snapshot.State = StateVerifying
return nil
})
}
func (m *Manager) Complete(id string) (Snapshot, error) {
now := m.currentTime()
return m.updateWithTime(id, now, func(snapshot *Snapshot, now time.Time) error {
snapshot.State = StateDone
snapshot.CompletedAt = now.UnixNano()
snapshot.InflightBytes = inflightBytes(*snapshot)
return nil
})
}
func (m *Manager) Abort(id string, err error) (Snapshot, error) {
return m.finishWithError(id, StateAborted, err)
}
func (m *Manager) Fail(id string, err error) (Snapshot, error) {
return m.finishWithError(id, StateFailed, err)
}
func (m *Manager) RecordRetry(id string) (Snapshot, error) {
return m.update(id, func(snapshot *Snapshot) error {
snapshot.RetryCount++
return nil
})
}
func (m *Manager) RecordTimeout(id string) (Snapshot, error) {
return m.update(id, func(snapshot *Snapshot) error {
snapshot.TimeoutCount++
return nil
})
}
func (m *Manager) SetStage(id string, stage string) (Snapshot, error) {
return m.update(id, func(snapshot *Snapshot) error {
snapshot.Stage = stage
return nil
})
}
func (m *Manager) SetFailureStage(id string, stage string) (Snapshot, error) {
return m.update(id, func(snapshot *Snapshot) error {
snapshot.LastFailureStage = stage
if stage != "" {
snapshot.Stage = stage
}
return nil
})
}
func (m *Manager) MergeMetadata(id string, metadata Metadata) (Snapshot, error) {
return m.update(id, func(snapshot *Snapshot) error {
if len(metadata) == 0 {
return nil
}
if snapshot.Metadata == nil {
snapshot.Metadata = make(Metadata, len(metadata))
}
for key, value := range metadata {
if value == "" {
delete(snapshot.Metadata, key)
continue
}
snapshot.Metadata[key] = value
}
return nil
})
}
func (m *Manager) RecordTelemetry(id string, delta TelemetryDelta) (Snapshot, error) {
return m.update(id, func(snapshot *Snapshot) error {
if delta.SourceReadDuration > 0 {
snapshot.SourceReadDuration += delta.SourceReadDuration
}
if delta.StreamWriteDuration > 0 {
snapshot.StreamWriteDuration += delta.StreamWriteDuration
}
if delta.SinkWriteDuration > 0 {
snapshot.SinkWriteDuration += delta.SinkWriteDuration
}
if delta.SyncDuration > 0 {
snapshot.SyncDuration += delta.SyncDuration
}
if delta.VerifyDuration > 0 {
snapshot.VerifyDuration += delta.VerifyDuration
}
if delta.CommitDuration > 0 {
snapshot.CommitDuration += delta.CommitDuration
}
if delta.CommitWaitDuration > 0 {
snapshot.CommitWaitDuration += delta.CommitWaitDuration
}
if delta.SourceReadCount > 0 {
snapshot.SourceReadCount += delta.SourceReadCount
}
if delta.StreamWriteCount > 0 {
snapshot.StreamWriteCount += delta.StreamWriteCount
}
if delta.SinkWriteCount > 0 {
snapshot.SinkWriteCount += delta.SinkWriteCount
}
return nil
})
}
func (m *Manager) Snapshot(id string) (Snapshot, bool) {
m.mu.Lock()
defer m.mu.Unlock()
transfer, ok := m.transfers[id]
if !ok {
return Snapshot{}, false
}
return cloneSnapshot(transfer.snapshot), true
}
func (m *Manager) Snapshots() []Snapshot {
m.mu.Lock()
defer m.mu.Unlock()
out := make([]Snapshot, 0, len(m.transfers))
for _, transfer := range m.transfers {
out = append(out, cloneSnapshot(transfer.snapshot))
}
sort.Slice(out, func(i int, j int) bool {
return out[i].ID < out[j].ID
})
return out
}
func (m *Manager) Restore(snapshot Snapshot) (Snapshot, error) {
if snapshot.ID == "" {
return Snapshot{}, ErrTransferIDEmpty
}
m.mu.Lock()
defer m.mu.Unlock()
m.transfers[snapshot.ID] = &managedTransfer{snapshot: cloneSnapshot(snapshot)}
return cloneSnapshot(snapshot), nil
}
func (m *Manager) start(desc Descriptor, direction Direction, state State) (Snapshot, error) {
if desc.ID == "" {
return Snapshot{}, ErrTransferIDEmpty
}
now := m.currentTime()
m.mu.Lock()
defer m.mu.Unlock()
if _, exists := m.transfers[desc.ID]; exists {
return Snapshot{}, ErrTransferExists
}
snapshot := Snapshot{
ID: desc.ID,
Direction: direction,
Channel: normalizeChannel(desc.Channel),
State: state,
Size: desc.Size,
Checksum: desc.Checksum,
Metadata: cloneMetadata(desc.Metadata),
StartedAt: now.UnixNano(),
UpdatedAt: now.UnixNano(),
}
m.transfers[desc.ID] = &managedTransfer{snapshot: snapshot}
return cloneSnapshot(snapshot), nil
}
func (m *Manager) finishWithError(id string, state State, err error) (Snapshot, error) {
now := m.currentTime()
return m.updateWithTime(id, now, func(snapshot *Snapshot, now time.Time) error {
snapshot.State = state
snapshot.CompletedAt = now.UnixNano()
if err != nil {
snapshot.LastError = err.Error()
}
snapshot.InflightBytes = inflightBytes(*snapshot)
return nil
})
}
func (m *Manager) update(id string, fn func(*Snapshot) error) (Snapshot, error) {
return m.updateWithTime(id, m.currentTime(), func(snapshot *Snapshot, _ time.Time) error {
return fn(snapshot)
})
}
func (m *Manager) updateWithTime(id string, now time.Time, fn func(*Snapshot, time.Time) error) (Snapshot, error) {
m.mu.Lock()
defer m.mu.Unlock()
transfer, ok := m.transfers[id]
if !ok {
return Snapshot{}, ErrTransferNotFound
}
snapshot := &transfer.snapshot
if err := fn(snapshot, now); err != nil {
return Snapshot{}, err
}
snapshot.UpdatedAt = now.UnixNano()
return cloneSnapshot(*snapshot), nil
}
func (m *Manager) currentTime() time.Time {
return m.now()
}
func inflightBytes(snapshot Snapshot) int64 {
if snapshot.Direction != DirectionSend {
return 0
}
if snapshot.SentBytes <= snapshot.AckedBytes {
return 0
}
return snapshot.SentBytes - snapshot.AckedBytes
}
+193
View File
@@ -0,0 +1,193 @@
package transfer
import (
"errors"
"testing"
"time"
)
type fakeClock struct {
now time.Time
}
func (f *fakeClock) Now() time.Time {
return f.now
}
func (f *fakeClock) Advance(d time.Duration) {
f.now = f.now.Add(d)
}
func TestManagerOutgoingLifecycle(t *testing.T) {
clock := &fakeClock{now: time.Unix(100, 0)}
manager := NewManagerWithClock(clock.Now)
snapshot, err := manager.StartOutgoing(Descriptor{
ID: "tx-1",
Size: 100,
Checksum: "sum-1",
Metadata: Metadata{"kind": "file"},
})
if err != nil {
t.Fatalf("StartOutgoing failed: %v", err)
}
if got, want := snapshot.State, StateNegotiating; got != want {
t.Fatalf("start state = %v, want %v", got, want)
}
if got, want := snapshot.Channel, DataChannel; got != want {
t.Fatalf("channel = %q, want %q", got, want)
}
clock.Advance(time.Second)
if _, err := manager.Activate("tx-1"); err != nil {
t.Fatalf("Activate failed: %v", err)
}
clock.Advance(time.Second)
if _, err := manager.RecordSend("tx-1", 60); err != nil {
t.Fatalf("RecordSend failed: %v", err)
}
clock.Advance(time.Second)
if _, err := manager.SetAckedBytes("tx-1", 40); err != nil {
t.Fatalf("SetAckedBytes failed: %v", err)
}
if _, err := manager.RecordRetry("tx-1"); err != nil {
t.Fatalf("RecordRetry failed: %v", err)
}
if _, err := manager.RecordTimeout("tx-1"); err != nil {
t.Fatalf("RecordTimeout failed: %v", err)
}
if _, err := manager.Pause("tx-1"); err != nil {
t.Fatalf("Pause failed: %v", err)
}
clock.Advance(time.Second)
if _, err := manager.Resume("tx-1", 40); err != nil {
t.Fatalf("Resume failed: %v", err)
}
if _, err := manager.BeginCommit("tx-1"); err != nil {
t.Fatalf("BeginCommit failed: %v", err)
}
clock.Advance(time.Second)
snapshot, err = manager.Complete("tx-1")
if err != nil {
t.Fatalf("Complete failed: %v", err)
}
if got, want := snapshot.State, StateDone; got != want {
t.Fatalf("complete state = %v, want %v", got, want)
}
if got, want := snapshot.SentBytes, int64(60); got != want {
t.Fatalf("sent bytes = %d, want %d", got, want)
}
if got, want := snapshot.AckedBytes, int64(40); got != want {
t.Fatalf("acked bytes = %d, want %d", got, want)
}
if got, want := snapshot.InflightBytes, int64(20); got != want {
t.Fatalf("inflight bytes = %d, want %d", got, want)
}
if got, want := snapshot.RetryCount, 1; got != want {
t.Fatalf("retry count = %d, want %d", got, want)
}
if got, want := snapshot.TimeoutCount, 1; got != want {
t.Fatalf("timeout count = %d, want %d", got, want)
}
if _, err := manager.SetStage("tx-1", "chunk"); err != nil {
t.Fatalf("SetStage failed: %v", err)
}
if _, err := manager.SetFailureStage("tx-1", "chunk"); err != nil {
t.Fatalf("SetFailureStage failed: %v", err)
}
if snapshot.CompletedAt == 0 {
t.Fatal("completed timestamp should be set")
}
if got := snapshot.Metadata["kind"]; got != "file" {
t.Fatalf("metadata kind = %q, want file", got)
}
snapshot, ok := manager.Snapshot("tx-1")
if !ok {
t.Fatal("snapshot should still exist")
}
if got, want := snapshot.Stage, "chunk"; got != want {
t.Fatalf("stage = %q, want %q", got, want)
}
if got, want := snapshot.LastFailureStage, "chunk"; got != want {
t.Fatalf("last failure stage = %q, want %q", got, want)
}
}
func TestManagerIncomingResumeAndVerify(t *testing.T) {
clock := &fakeClock{now: time.Unix(200, 0)}
manager := NewManagerWithClock(clock.Now)
snapshot, err := manager.StartIncoming(Descriptor{
ID: "rx-1",
Channel: ControlChannel,
Size: 64,
})
if err != nil {
t.Fatalf("StartIncoming failed: %v", err)
}
if got, want := snapshot.State, StatePrepared; got != want {
t.Fatalf("prepared state = %v, want %v", got, want)
}
clock.Advance(time.Second)
snapshot, err = manager.Resume("rx-1", 16)
if err != nil {
t.Fatalf("Resume failed: %v", err)
}
if got, want := snapshot.ReceivedBytes, int64(16); got != want {
t.Fatalf("received bytes after resume = %d, want %d", got, want)
}
if _, err := manager.RecordReceive("rx-1", 20); err != nil {
t.Fatalf("RecordReceive failed: %v", err)
}
if _, err := manager.BeginVerify("rx-1"); err != nil {
t.Fatalf("BeginVerify failed: %v", err)
}
clock.Advance(time.Second)
snapshot, err = manager.Complete("rx-1")
if err != nil {
t.Fatalf("Complete failed: %v", err)
}
if got, want := snapshot.State, StateDone; got != want {
t.Fatalf("complete state = %v, want %v", got, want)
}
if got, want := snapshot.ReceivedBytes, int64(36); got != want {
t.Fatalf("received bytes = %d, want %d", got, want)
}
if got, want := snapshot.Channel, ControlChannel; got != want {
t.Fatalf("channel = %q, want %q", got, want)
}
}
func TestManagerValidatesIDsAndSortedSnapshots(t *testing.T) {
manager := NewManager()
if _, err := manager.StartOutgoing(Descriptor{}); !errors.Is(err, ErrTransferIDEmpty) {
t.Fatalf("empty id error = %v, want %v", err, ErrTransferIDEmpty)
}
if _, err := manager.StartOutgoing(Descriptor{ID: "b"}); err != nil {
t.Fatalf("StartOutgoing b failed: %v", err)
}
if _, err := manager.StartIncoming(Descriptor{ID: "a"}); err != nil {
t.Fatalf("StartIncoming a failed: %v", err)
}
if _, err := manager.StartOutgoing(Descriptor{ID: "b"}); !errors.Is(err, ErrTransferExists) {
t.Fatalf("duplicate id error = %v, want %v", err, ErrTransferExists)
}
if _, err := manager.RecordSend("missing", 1); !errors.Is(err, ErrTransferNotFound) {
t.Fatalf("missing transfer error = %v, want %v", err, ErrTransferNotFound)
}
if _, err := manager.RecordReceive("a", -1); !errors.Is(err, ErrTransferBytesInvalid) {
t.Fatalf("negative bytes error = %v, want %v", err, ErrTransferBytesInvalid)
}
snapshots := manager.Snapshots()
if len(snapshots) != 2 {
t.Fatalf("snapshot count = %d, want 2", len(snapshots))
}
if snapshots[0].ID != "a" || snapshots[1].ID != "b" {
t.Fatalf("snapshot order = [%s %s], want [a b]", snapshots[0].ID, snapshots[1].ID)
}
}
+188
View File
@@ -0,0 +1,188 @@
package transfer
import "time"
type Channel string
const (
ControlChannel Channel = "control"
DataChannel Channel = "data"
)
type Direction uint8
const (
DirectionSend Direction = iota
DirectionReceive
)
type State uint8
const (
StateInit State = iota
StateNegotiating
StatePrepared
StateActive
StatePaused
StateCommitting
StateVerifying
StateDone
StateAborted
StateFailed
)
func (s State) Terminal() bool {
switch s {
case StateDone, StateAborted, StateFailed:
return true
default:
return false
}
}
type Range struct {
Offset int64
Length int64
}
type Metadata map[string]string
type TelemetryDelta struct {
SourceReadDuration time.Duration
StreamWriteDuration time.Duration
SinkWriteDuration time.Duration
SyncDuration time.Duration
VerifyDuration time.Duration
CommitDuration time.Duration
CommitWaitDuration time.Duration
SourceReadCount int
StreamWriteCount int
SinkWriteCount int
}
type Descriptor struct {
ID string
Direction Direction
Channel Channel
Size int64
Checksum string
Metadata Metadata
}
type Snapshot struct {
ID string
Direction Direction
Channel Channel
State State
Stage string
LastFailureStage string
Size int64
Checksum string
Metadata Metadata
SentBytes int64
AckedBytes int64
ReceivedBytes int64
InflightBytes int64
RetryCount int
TimeoutCount int
LastError string
SourceReadDuration time.Duration
StreamWriteDuration time.Duration
SinkWriteDuration time.Duration
SyncDuration time.Duration
VerifyDuration time.Duration
CommitDuration time.Duration
CommitWaitDuration time.Duration
SourceReadCount int
StreamWriteCount int
SinkWriteCount int
StartedAt int64
UpdatedAt int64
CompletedAt int64
}
type Begin struct {
TransferID string
Channel Channel
Size int64
Checksum string
Metadata Metadata
}
type BeginAck struct {
TransferID string
Accepted bool
NextOffset int64
Missing []Range
Error string
}
type Resume struct {
TransferID string
}
type ResumeAck struct {
TransferID string
Accepted bool
NextOffset int64
Missing []Range
Error string
}
type Commit struct {
TransferID string
Size int64
Checksum string
}
type CommitAck struct {
TransferID string
Accepted bool
Error string
}
type Abort struct {
TransferID string
Stage string
Offset int64
Error string
}
type Segment struct {
TransferID string
Channel Channel
Offset int64
Payload []byte
Flags uint32
}
type Ack struct {
TransferID string
NextOffset int64
Missing []Range
Final bool
Error string
}
func normalizeChannel(channel Channel) Channel {
if channel == "" {
return DataChannel
}
return channel
}
func cloneMetadata(src Metadata) Metadata {
if len(src) == 0 {
return nil
}
dst := make(Metadata, len(src))
for key, value := range src {
dst[key] = value
}
return dst
}
func cloneSnapshot(src Snapshot) Snapshot {
src.Metadata = cloneMetadata(src.Metadata)
return src
}