feat(notify): 重构通信内核并补齐 stream/bulk/record/transfer 能力
- 引入 LogicalConn/TransportConn 分层,ClientConn 保留兼容适配层 - 新增 Stream、Bulk、RecordStream 三条数据面能力及对应控制路径 - 完成 transfer/file 传输内核与状态快照、诊断能力 - 补齐 reconnect、inbound dispatcher、modern psk 等基础模块 - 增加大规模回归、并发与基准测试覆盖 - 更新依赖库
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user