package orbit import ( "context" "sync" "time" ) // executionGuard 独立于网络控制取消处理器,已到期/取消的上下文永不恢复。 type executionGuard struct { ctx context.Context cancel context.CancelCauseFunc mu sync.Mutex deadline time.Time wake chan struct{} done chan struct{} } func newExecutionGuard(parent context.Context, lease WorkerLease) (*executionGuard, bool) { deadline, ok := lease.ExecutionDeadline() if !ok { return nil, false } ctx, cancel := context.WithCancelCause(parent) g := &executionGuard{ctx: ctx, cancel: cancel, deadline: deadline, wake: make(chan struct{}, 1), done: make(chan struct{})} go g.watch() return g, true } func (g *executionGuard) watch() { defer close(g.done) timer := time.NewTimer(time.Until(g.until())) defer timer.Stop() for { select { case <-g.ctx.Done(): return case <-g.wake: case <-timer.C: } g.mu.Lock() remaining := time.Until(g.deadline) if remaining <= 0 { g.cancel(ErrExecutionLeaseLost) g.mu.Unlock() return } timer.Reset(remaining) g.mu.Unlock() } } func (g *executionGuard) update(lease WorkerLease) bool { g.mu.Lock() defer g.mu.Unlock() if g.ctx.Err() != nil || !time.Now().Before(g.deadline) { g.cancel(ErrExecutionLeaseLost) return false } deadline, ok := lease.ExecutionDeadline() if !lease.Active || lease.State != "RUNNING" { g.cancel(ErrExecutionLeaseLost) return false } // Lease 是新鲜核验,不是历史 Receipt。新会话可能缩短有效窗口,必须遵守。 if !ok { g.cancel(ErrExecutionLeaseLost) return false } g.deadline = deadline select { case g.wake <- struct{}{}: default: } return true } func (g *executionGuard) until() time.Time { g.mu.Lock(); defer g.mu.Unlock(); return g.deadline } func (g *executionGuard) stop(cause error) { g.cancel(cause); <-g.done }