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
- type Frozen
- type Lease
- type Manager
- func (m *Manager[T, S]) Acquire() *Lease[T, S]
- func (m *Manager[T, S]) CurrentGeneration() int64
- func (m *Manager[T, S]) SetCompatWait(wait time.Duration)
- func (m *Manager[T, S]) Snapshot() Frozen[S]
- func (m *Manager[T, S]) Swap() Sealed[T, S]
- func (m *Manager[T, S]) SwapAndSnapshot() Frozen[S]
- func (m *Manager[T, S]) SwapAndSnapshotContext(ctx context.Context) Frozen[S]
- type Sealed
Constants ¶
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 ¶
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 ¶
Generation returns the pinned generation number.
type Manager ¶
Manager owns a current generation and serializes compatibility resets.
func (*Manager[T, S]) CurrentGeneration ¶
CurrentGeneration returns the generation accepting new work.
func (*Manager[T, S]) SetCompatWait ¶ added in v1.2.0
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 ¶
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
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 ¶
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
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
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
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
Generation returns the sealed generation's number.
func (Sealed[T, S]) Settled ¶ added in v1.2.0
Settled reports whether every lease pinned to the sealed generation is done, without waiting.
func (Sealed[T, S]) Wait ¶ added in v1.2.0
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.