package host import ( "context" "errors" "log" "sync" "time" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/hostops" "github.com/carlosframework/platform/internal/hostwrite" ) // The owning agent drains its own producers over the existing authenticated // root socket. It never receives root's evidence key or issues a stop receipt. func (a *ClaimAssembly) startWakeResetWorker(ctx context.Context) error { if a.Mode != "agent" { return nil } if a.RetirementAuthority == nil || a.Close == nil || a.Wake == nil || a.Launches == nil { return hostwrite.ErrUnavailable } actor, err := a.Identity.Verify(ctx) if err != nil { return err } transport := hostops.WakeResetTransport{Store: a.Claims, Directory: a.RetirementAuthority.Directory, Now: time.Now} worker, cancel := context.WithCancel(ctx) var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() poll := resetPollMin for worker.Err() == nil { listed, err := transport.Scan(worker, actor.Box, a.stopResetAttempt) if err != nil && worker.Err() == nil { log.Printf("host claim reset: %v", err) } poll = nextResetPoll(poll, listed) timer := time.NewTimer(poll) select { case <-worker.Done(): timer.Stop() return case <-timer.C: } } }() closeOwners := a.Close a.Close = func(ctx context.Context) error { cancel(); wg.Wait(); return closeOwners(ctx) } return nil } // The worker lists its box's reset prefix once a second while requests are // present and backs off to resetPollMax while idle, so a box with no resets // does not LIST the bucket every second. const ( resetPollMin = time.Second resetPollMax = 8 * time.Second ) func nextResetPoll(prev time.Duration, listed int) time.Duration { if listed > 0 || prev < resetPollMin { return resetPollMin } return min(2*prev, resetPollMax) } func (a ClaimAssembly) stopResetAttempt(ctx context.Context, o hostclaim.Observed, attempt hostclaim.WakeRetryAttempt) error { if o.Claim == nil || o.Claim.Holder == nil || o.Claim.Holder.WakeRetry == nil || o.Claim.Holder.WakeRetry.Attempt == nil || *o.Claim.Holder.WakeRetry.Attempt != attempt { return hostwrite.ErrUnavailable } local, err := a.localStopIdentity(ctx) if err != nil { return err } if local.Actor.Box != attempt.Box || local.Actor.Enrollment != attempt.Enrollment { return hostwrite.ErrUnavailable } h := o.Claim.Holder entries, err := a.Launches.SessionLaunches(ctx, o.Host, h.Incarnation, h.Stamp, h.Session, attempt.Box, attempt.Enrollment) if err != nil { return err } for _, entry := range entries { owner := ownerOf(entry) if owner.BootID == attempt.BootID && owner.ControllerID == attempt.ControllerID { _, err = a.collectCapturedStop(ctx, o, local, owner, false) return err } } return errors.Join(hostops.ErrWakeResetUncertain, ErrClaimStopInventory) }