lifecycle

package
v0.0.1-alpha.30 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 5, 2026 License: MIT Imports: 5 Imported by: 0

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func ScopedKey

func ScopedKey(region, id, transition string) string

ScopedKey builds the region-scoped scheduler key AfterScoped and CancelScoped use. Exported for callers that need to inspect or test pending transitions.

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

func NewScheduler(clk clock.Clock) *Scheduler

NewScheduler creates a Scheduler using the given clock. Production: clock.New(). Tests: clock.NewMock() for instant time skips.

func (*Scheduler) AdvanceAndSettle

func (s *Scheduler) AdvanceAndSettle(mock *clock.Mock, d time.Duration)

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

func (s *Scheduler) After(key string, delay time.Duration, fn func())

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

func (s *Scheduler) Cancel(key string) bool

Cancel cancels a pending transition by key. Returns true if a pending transition was found and cancelled.

func (*Scheduler) CancelScoped

func (s *Scheduler) CancelScoped(region, id, transition string) bool

CancelScoped cancels a pending transition scheduled with AfterScoped.

func (*Scheduler) PendingCount

func (s *Scheduler) PendingCount() int

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.

func (*Scheduler) Stop

func (s *Scheduler) Stop(ctx context.Context)

Stop cancels all pending transitions and waits for any in-flight callbacks to complete. Respects ctx for timeout on the wait.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL