Documentation
¶
Overview ¶
Package lifecycle provides a shared, testable scheduler for async state transitions. EC2 instances, ECS tasks, and RDS instances all need delayed state changes (e.g. pending → running, creating → available). This package provides a single abstraction instead of each service hand-rolling time.AfterFunc/goroutine/cancel patterns.
Index ¶
- func ScopedKey(region, id, transition string) string
- type Scheduler
- func (s *Scheduler) AdvanceAndSettle(mock *clock.Mock, d time.Duration)
- func (s *Scheduler) After(key string, delay time.Duration, fn func())
- func (s *Scheduler) AfterScoped(region, id, transition string, delay time.Duration, ...)
- func (s *Scheduler) Cancel(key string) bool
- func (s *Scheduler) CancelScoped(region, id, transition string) bool
- func (s *Scheduler) PendingCount() int
- func (s *Scheduler) Settle()
- func (s *Scheduler) Stop(ctx context.Context)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Scheduler ¶
type Scheduler struct {
// contains filtered or unexported fields
}
Scheduler manages keyed delayed callbacks. Each service creates its own Scheduler instance (no global state, DI-friendly).
func NewScheduler ¶
NewScheduler creates a Scheduler using the given clock. Production: clock.New(). Tests: clock.NewMock() for instant time skips.
func (*Scheduler) AdvanceAndSettle ¶
AdvanceAndSettle advances a mock clock by d and returns only once every transition that came due has run to completion. mock must be the clock this Scheduler was built with.
It exists because a bare mock.Add is not enough to observe the effects of a transition. The mock clock runs an AfterFunc callback on a goroutine of its own and then yields for a single millisecond, so a test that advances the clock and reads the store on the next line is racing the callback: it wins on an idle machine and loses on a loaded one, which is a flake that only ever reproduces in CI. Waiting on the callback itself is deterministic regardless of load.
Transitions a callback schedules while this runs are not waited for, even if they fall due within d — settling is defined over the transitions that were outstanding when the call began.
func (*Scheduler) After ¶
After schedules fn to run after delay. Key identifies the transition (e.g. "i-abc123:terminate"). If a transition with the same key is already pending, it is cancelled before the new one is scheduled.
When delay is 0 and the clock is a real clock (not a mock), fn is executed synchronously within this call. This ensures that subsequent API calls immediately see the updated state instead of racing with a goroutine. With a mock clock, 0-delay timers remain pending until clock.Add is called, preserving test-time control.
func (*Scheduler) AfterScoped ¶
func (s *Scheduler) AfterScoped(region, id, transition string, delay time.Duration, fn func(ctx context.Context))
AfterScoped schedules fn to run after delay for a resource that lives in a region-keyed store. It exists because the plain After callbacks run outside any request context: a bare context.Background() resolves to the DEFAULT region, so for a resource created in any other region the callback reads or writes the wrong store key and silently no-ops (statuses stuck at "creating"/"stopping", deferred deletes that never land).
AfterScoped closes both halves of that bug at once:
- fn receives a background context carrying region, so region-keyed store lookups inside the callback resolve the same record the request wrote;
- the scheduler key is scoped by region, so same-named resources in different regions cannot cancel or replace each other's pending transitions.
Callers pass the region resolved from the request context at schedule time (e.g. store.region(ctx)).
func (*Scheduler) Cancel ¶
Cancel cancels a pending transition by key. Returns true if a pending transition was found and cancelled.
func (*Scheduler) CancelScoped ¶
CancelScoped cancels a pending transition scheduled with AfterScoped.
func (*Scheduler) PendingCount ¶
PendingCount returns the number of currently scheduled transitions.
func (*Scheduler) Settle ¶
func (s *Scheduler) Settle()
Settle blocks until every transition outstanding when it was called has run to completion or been cancelled. It cancels nothing — on a real clock it waits the delays out — so a test can let the real transitions happen and then read what they produced, rather than polling until they show up.
Outstanding covers both the transitions still pending and the callbacks that have already fired and are still running, so a test whose setup ran long enough for some of its transitions to come due does not silently get a partial wait. Transitions scheduled after the call are not waited for.