Files
tutti/packages/agent/daemon/runtime/process_replay_input_barrier.go
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

206 lines
5.2 KiB
Go

package agentruntime
import (
"context"
"errors"
"fmt"
"sync"
"time"
sessionreplay "github.com/tutti-os/tutti/packages/agent/session-replay"
)
var ErrReplayProviderOvershot = errors.New("checkpoint_provider_overshot")
// errReplaySyntheticPending tells the ACP read loop to drain pending synthetic
// optional-probe responses before retrying a checkpoint barrier wait.
var errReplaySyntheticPending = errors.New("replay synthetic response pending")
type replayProviderInputBarrier struct {
mu sync.Mutex
active bool
targets map[string]sessionreplay.ProviderUnitPosition
handled map[string]sessionreplay.ProviderUnitPosition
changed chan struct{}
}
func newReplayProviderInputBarrier() *replayProviderInputBarrier {
return &replayProviderInputBarrier{
targets: map[string]sessionreplay.ProviderUnitPosition{},
handled: map[string]sessionreplay.ProviderUnitPosition{},
changed: make(chan struct{}),
}
}
func (b *replayProviderInputBarrier) setTargets(
targets []sessionreplay.ProviderUnitPosition,
) error {
next := make(map[string]sessionreplay.ProviderUnitPosition, len(targets))
for _, target := range targets {
if target.ConnectionID == "" {
return errors.New("replay provider target connection is empty")
}
if _, duplicate := next[target.ConnectionID]; duplicate {
return fmt.Errorf("duplicate replay provider target %q", target.ConnectionID)
}
next[target.ConnectionID] = target
}
b.mu.Lock()
defer b.mu.Unlock()
for connectionID, handled := range b.handled {
target, ok := next[connectionID]
if !ok || compareReplayProviderPosition(handled, target) > 0 {
return fmt.Errorf(
"%w: connection %s already handled %d/%d",
ErrReplayProviderOvershot,
connectionID,
handled.ChunkSeq,
handled.UnitIndex,
)
}
}
b.active = true
b.targets = next
b.signalChangedLocked()
return nil
}
func (b *replayProviderInputBarrier) clearTargets() {
b.mu.Lock()
defer b.mu.Unlock()
b.active = false
b.targets = map[string]sessionreplay.ProviderUnitPosition{}
b.signalChangedLocked()
}
func (b *replayProviderInputBarrier) complete(
ctx context.Context,
unit ProviderInputUnit,
closed <-chan struct{},
interrupt <-chan struct{},
) error {
for {
b.mu.Lock()
previous := b.handled[unit.Position.ConnectionID]
if compareReplayProviderPosition(unit.Position, previous) < 0 {
b.mu.Unlock()
return fmt.Errorf(
"%w: connection %s input position moved backward",
ErrReplayProviderOvershot,
unit.Position.ConnectionID,
)
}
if compareReplayProviderPosition(unit.Position, previous) > 0 {
b.handled[unit.Position.ConnectionID] = unit.Position
b.signalChangedLocked()
}
if !b.active {
b.mu.Unlock()
return nil
}
target, ok := b.targets[unit.Position.ConnectionID]
if !ok || compareReplayProviderPosition(unit.Position, target) > 0 {
b.mu.Unlock()
return fmt.Errorf(
"%w: connection %s handled %d/%d past target",
ErrReplayProviderOvershot,
unit.Position.ConnectionID,
unit.Position.ChunkSeq,
unit.Position.UnitIndex,
)
}
if compareReplayProviderPosition(unit.Position, target) < 0 {
b.mu.Unlock()
return nil
}
changed := b.changed
b.mu.Unlock()
select {
case <-ctx.Done():
return ctx.Err()
case <-closed:
return context.Canceled
case <-changed:
case <-interrupt:
return errReplaySyntheticPending
}
}
}
// waitForProgressDuration measures only time in which this connection can
// consume provider input. Checkpoint barriers deliberately stop that progress;
// wall-clock recovery timers must not expire while replay is parked there.
func (b *replayProviderInputBarrier) waitForProgressDuration(
ctx context.Context,
connectionID string,
duration time.Duration,
closed <-chan struct{},
) error {
remaining := duration
for remaining > 0 {
b.mu.Lock()
handled := b.handled[connectionID]
target, targeted := b.targets[connectionID]
blocked := b.active && targeted && compareReplayProviderPosition(handled, target) >= 0
changed := b.changed
b.mu.Unlock()
if blocked {
if err := waitForReplayPlaybackChange(ctx, closed, changed, nil); err != nil {
return err
}
continue
}
started := time.Now()
timer := time.NewTimer(remaining)
select {
case <-ctx.Done():
stopReplayPlaybackTimer(timer)
return ctx.Err()
case <-closed:
stopReplayPlaybackTimer(timer)
return context.Canceled
case <-changed:
stopReplayPlaybackTimer(timer)
remaining -= time.Since(started)
case <-timer.C:
return nil
}
}
return nil
}
func (b *replayProviderInputBarrier) state() map[string]sessionreplay.ProviderUnitPosition {
b.mu.Lock()
defer b.mu.Unlock()
result := make(map[string]sessionreplay.ProviderUnitPosition, len(b.handled))
for connectionID, position := range b.handled {
result[connectionID] = position
}
return result
}
func (b *replayProviderInputBarrier) signalChangedLocked() {
close(b.changed)
b.changed = make(chan struct{})
}
func compareReplayProviderPosition(
left, right sessionreplay.ProviderUnitPosition,
) int {
if left.ChunkSeq < right.ChunkSeq {
return -1
}
if left.ChunkSeq > right.ChunkSeq {
return 1
}
if left.UnitIndex < right.UnitIndex {
return -1
}
if left.UnitIndex > right.UnitIndex {
return 1
}
return 0
}