Files
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

94 lines
2.4 KiB
Go

package daemon
import (
"context"
"errors"
"fmt"
"log/slog"
"sync"
"time"
)
const (
runtimeConvergenceScanInterval = 500 * time.Millisecond
runtimeConvergenceBatchSize = 32
runtimeConvergenceParallelism = 4
runtimeConvergenceTimeout = 2 * time.Minute
)
// runRuntimeConvergenceWorker continuously repairs durable Desired/Observed
// drift. The periodic scan is authoritative; command scheduling only reduces
// latency and is therefore not required for crash recovery.
func (host *Host) runRuntimeConvergenceWorker(ctx context.Context) {
ticker := time.NewTicker(runtimeConvergenceScanInterval)
defer ticker.Stop()
for {
if err := host.convergeDueRuntimes(ctx); err != nil && !errors.Is(err, context.Canceled) {
slog.Warn("connector runtime convergence scan failed", "error", err)
}
select {
case <-ctx.Done():
return
case <-host.runtimeConvergenceWake:
case <-ticker.C:
}
}
}
func (host *Host) notifyRuntimeConvergence() {
select {
case host.runtimeConvergenceWake <- struct{}{}:
default:
}
}
func (host *Host) convergeDueRuntimes(ctx context.Context) error {
host.bootstrapMu.Lock()
bootstrapped, scope := host.bootstrapped, host.bootstrapScope
planning := make(map[string]struct{}, len(host.runtimeRecoveryPending))
for connectorKey := range host.runtimeRecoveryPending {
planning[connectorKey] = struct{}{}
}
host.bootstrapMu.Unlock()
if !bootstrapped {
return nil
}
due, err := host.Application.DueRuntimeConvergences(ctx, scope, runtimeConvergenceBatchSize)
if err != nil || len(due) == 0 {
return err
}
semaphore := make(chan struct{}, runtimeConvergenceParallelism)
errorsFound := make(chan error, len(due))
var wait sync.WaitGroup
for _, convergence := range due {
connectorKey := convergence.Desired.ConnectorKey
if _, pending := planning[connectorKey]; pending {
continue
}
wait.Add(1)
go func() {
defer wait.Done()
select {
case semaphore <- struct{}{}:
defer func() { <-semaphore }()
case <-ctx.Done():
errorsFound <- ctx.Err()
return
}
convergeContext, cancel := context.WithTimeout(ctx, runtimeConvergenceTimeout)
err := host.Application.ConvergeRuntime(convergeContext, scope, connectorKey)
cancel()
if err != nil && !errors.Is(err, context.Canceled) {
errorsFound <- fmt.Errorf("%s: %w", connectorKey, err)
}
}()
}
wait.Wait()
close(errorsFound)
var result error
for err := range errorsFound {
result = errors.Join(result, err)
}
return result
}