generation

package
v1.7.0 Latest Latest
Warning

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

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

Documentation

Overview

Package generation provides atomic collector generation swaps. Work leases stay attached to the generation in which they started, and a swap seals the old generation so its value can be frozen once every lease it pinned is done.

Waiting for a sealed generation is a channel receive rather than a sync.Cond: a lease that never completes — a SQL query that never returns — must not be able to park the waiting goroutine forever. sync.Cond.Wait cannot be interrupted by a context, so a cond-based wait makes the runctl.GenerationCollector drain contract impossible to honour.

Index

Constants

View Source
const DefaultCompatWait = runctl.DrainBudget

DefaultCompatWait bounds SwapAndSnapshot, the context-free compatibility entry point. Work that never completes must not park its caller for the lifetime of the process, so the shim eventually gives up and freezes what the sealed generation holds at that moment. Callers that have a context should use SwapAndSnapshotContext, or Swap and Sealed.Wait, which abandon the wait exactly when the caller says so.

It is runctl.DrainBudget because the compatibility swap is exactly the drain step of a run boundary expressed through the pre-generation API, and the two must not be able to disagree about how long work that never finishes is worth waiting for. runctl is the single authority for these numbers; inventing an independent one here is what made "why did my collector get cut off?" have two answers. TestDefaultCompatWaitIsTheSharedDrainBudget fails if the two ever diverge again.

Variables

This section is empty.

Functions

This section is empty.

Types

type Frozen

type Frozen[S any] struct {
	Generation int64
	Value      S
	// CutShort reports that the snapshot was taken while work pinned to the
	// generation was still in flight, so it may be missing rows that work would
	// have contributed. It is the difference between a section that is complete
	// and one that is partial, and a caller that cannot see it reports a
	// truncated measurement as a whole one.
	//
	// It is conservative in one direction only: work that finishes during the
	// snapshot can produce CutShort with complete data, but complete data is
	// never the reason CutShort is false.
	CutShort bool
}

Frozen is an immutable-by-contract snapshot of one generation.

type Lease

type Lease[T any, S any] struct {
	// contains filtered or unexported fields
}

Lease pins work to the generation in which it started.

func (*Lease[T, S]) Done

func (l *Lease[T, S]) Done()

Done releases the lease. It is safe to call more than once.

func (*Lease[T, S]) Generation

func (l *Lease[T, S]) Generation() int64

Generation returns the pinned generation number.

func (*Lease[T, S]) Value

func (l *Lease[T, S]) Value() T

Value returns the mutable collector owned by the pinned generation.

type Manager

type Manager[T any, S any] struct {
	// contains filtered or unexported fields
}

Manager owns a current generation and serializes compatibility resets.

func New

func New[T any, S any](factory func() T, snapshot func(T) S) *Manager[T, S]

New constructs a manager whose first generation is 1.

func (*Manager[T, S]) Acquire

func (m *Manager[T, S]) Acquire() *Lease[T, S]

Acquire pins and returns the current generation.

func (*Manager[T, S]) CurrentGeneration

func (m *Manager[T, S]) CurrentGeneration() int64

CurrentGeneration returns the generation accepting new work.

func (*Manager[T, S]) SetCompatWait added in v1.2.0

func (m *Manager[T, S]) SetCompatWait(wait time.Duration)

SetCompatWait bounds the wait performed by SwapAndSnapshot. A non-positive value restores DefaultCompatWait. It exists for callers whose own operation budget is tighter than the run controller's, and for tests that must exercise the give-up path without waiting out a real drain budget; the default is already bounded, so leaving it alone is safe.

func (*Manager[T, S]) Snapshot

func (m *Manager[T, S]) Snapshot() Frozen[S]

Snapshot takes a best-effort snapshot of the current generation. The value must provide its own synchronization against active leases.

CutShort is false: the current generation is still accepting work, so there was no bounded wait for it to give up on. A live snapshot is incomplete by construction, which is a different thing from a drain that was truncated.

func (*Manager[T, S]) Swap added in v1.2.0

func (m *Manager[T, S]) Swap() Sealed[T, S]

Swap publishes a new empty generation and returns the previous one, sealed. It only moves a pointer, so it never blocks on in-flight work: the caller decides how long to wait for the sealed generation, and with which context.

func (*Manager[T, S]) SwapAndSnapshot

func (m *Manager[T, S]) SwapAndSnapshot() Frozen[S]

SwapAndSnapshot publishes a new empty generation, waits for work pinned to the old generation, and then freezes the old value. Concurrent swaps are serialized so frozen generations are returned in order.

It is the compatibility entry point for callers with no context at all. A caller that has one should use SwapAndSnapshotContext: this one can only be bounded by the manager's own budget, so a caller whose request was cancelled long ago still pays for the whole of it.

func (*Manager[T, S]) SwapAndSnapshotContext added in v1.2.0

func (m *Manager[T, S]) SwapAndSnapshotContext(ctx context.Context) Frozen[S]

SwapAndSnapshotContext is SwapAndSnapshot bounded by the caller's context as well as by the manager's budget, whichever ends first. A nil context means the budget alone.

The wait is bounded in both directions for the same reason: this call runs on the /reset path, which holds the process-wide reset lock and the operation slot, so work that never finishes — a query that never returns — would otherwise head-of-line-block every other admin endpoint for the whole bound. Consulting the caller's context is what lets an abandoned request stop paying for it, and the returned Frozen.CutShort is what lets the caller say the section it got is partial.

type Sealed added in v1.2.0

type Sealed[T any, S any] struct {
	// contains filtered or unexported fields
}

Sealed is a generation that has been swapped out of the current position. It accepts no new work, so its in-flight count can only fall.

func (Sealed[T, S]) Freeze added in v1.2.0

func (s Sealed[T, S]) Freeze() Frozen[S]

Freeze snapshots the sealed generation's value. After a Wait that returned nil the value is fixed; called earlier it is a best-effort read that relies on the value's own synchronization, exactly like Snapshot. Frozen.CutShort tells the two apart, so a caller that froze on a give-up can mark its section partial instead of publishing a truncated measurement as a whole one.

func (Sealed[T, S]) Generation added in v1.2.0

func (s Sealed[T, S]) Generation() int64

Generation returns the sealed generation's number.

func (Sealed[T, S]) Settled added in v1.2.0

func (s Sealed[T, S]) Settled() bool

Settled reports whether every lease pinned to the sealed generation is done, without waiting.

func (Sealed[T, S]) Wait added in v1.2.0

func (s Sealed[T, S]) Wait(ctx context.Context) error

Wait blocks until every lease pinned to the sealed generation is done, or until ctx is done, whichever comes first. A nil context waits indefinitely.

Abandoning the wait leaves no goroutine behind and costs the caller nothing later: work that finishes after the wait was abandoned writes to its own sealed value and to nothing else. Completion wins over an already-expired context, because a generation with nothing left in flight has nothing to wait for and reporting a timeout for it would drop complete data.

Jump to

Keyboard shortcuts

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