Documentation
¶
Overview ¶
Package driver defines the backend-agnostic contract that azync storage drivers implement. It is the frozen public surface third-party drivers build against: a single Store interface over one unified job table plus optional capability interfaces discovered by type assertion.
The contract carries no SQL, no locking primitives and no clock: durations are time.Duration values that each backend resolves against its own clock, and identifiers are opaque. A driver that implements only Store is fully functional through polling; the optional capabilities (Notifier, ChangeNotifier, LeaderElector, Migrator, TxStore, DAGStore, TxDAGStore, WorkflowStore) unlock push wakeups, row-change hints, leader-elected cron, migrations, transactional enqueues, DAG executions and workflow-as-code respectively.
Index ¶
- Constants
- Variables
- func IsNotFound(err error) bool
- func NewNotFound(op string) error
- type AttemptError
- type Change
- type ChangeEntity
- type ChangeNotifier
- type Config
- type DAGDep
- type DAGFailure
- type DAGFilter
- type DAGParams
- type DAGSignalParams
- type DAGState
- type DAGStore
- type DAGTask
- type DAGView
- type DailyCount
- type Depths
- type DequeueParams
- type EnqueueParams
- type EventAdminRow
- type EventFilter
- type EventRecord
- type HistoryEvent
- type Job
- type JobFilter
- type JobState
- type LeaderElector
- type LeadershipLease
- type LeaseElector
- type Migrator
- type Notifier
- type NukeReport
- type OnFailurePolicy
- type Opener
- type OperationState
- type OpsStats
- type PublishParams
- type ReplayFilter
- type ScheduleOperationParams
- type SignalParams
- type Source
- type StalledWorkflow
- type Store
- type Subscriber
- type SubscriberView
- type TaskResult
- type TxDAGStore
- type TxStore
- type UncertainDecision
- type UnimplementedStore
- func (UnimplementedStore) Ack(context.Context, uuid.UUID, uuid.UUID) error
- func (UnimplementedStore) AllDaily(context.Context, Source) ([]DailyCount, error)
- func (UnimplementedStore) ArchiveJob(context.Context, Source, uuid.UUID) error
- func (UnimplementedStore) Close(context.Context) error
- func (UnimplementedStore) Dead(context.Context, uuid.UUID, uuid.UUID, string) error
- func (UnimplementedStore) DeleteAll(context.Context, Source, string, JobState) (int64, error)
- func (UnimplementedStore) DeleteJob(context.Context, Source, uuid.UUID, JobState) error
- func (UnimplementedStore) DeleteSubscriber(context.Context, string, string) (int64, error)
- func (UnimplementedStore) DequeueBatch(context.Context, Source, DequeueParams) ([]Job, error)
- func (UnimplementedStore) Enqueue(context.Context, EnqueueParams) (bool, error)
- func (UnimplementedStore) ExtendLease(context.Context, uuid.UUID, uuid.UUID, time.Duration) error
- func (UnimplementedStore) GetEvent(context.Context, uuid.UUID) (*EventAdminRow, error)
- func (UnimplementedStore) GetJob(context.Context, Source, uuid.UUID) (*Job, error)
- func (UnimplementedStore) JobAttempts(context.Context, Source, uuid.UUID) ([]AttemptError, error)
- func (UnimplementedStore) KindDepths(context.Context, Source) (map[string]Depths, error)
- func (UnimplementedStore) ListEvents(context.Context, EventFilter, int, int) ([]EventAdminRow, int64, error)
- func (UnimplementedStore) ListJobs(context.Context, Source, JobFilter, int, int) ([]Job, int64, error)
- func (UnimplementedStore) ListKinds(context.Context, Source) ([]string, error)
- func (UnimplementedStore) ListSubscriberViews(context.Context, string) ([]SubscriberView, error)
- func (UnimplementedStore) NukeAll(context.Context, Source) (NukeReport, error)
- func (UnimplementedStore) OpsStats(context.Context) (OpsStats, error)
- func (UnimplementedStore) PauseJob(context.Context, Source, uuid.UUID) error
- func (UnimplementedStore) PromoteDue(context.Context, Source, []string) (int64, error)
- func (UnimplementedStore) Publish(context.Context, PublishParams) (int, error)
- func (UnimplementedStore) ReapExpired(context.Context, Source, []string, int) (int64, int64, error)
- func (UnimplementedStore) RegisterSubscriber(context.Context, Subscriber) error
- func (UnimplementedStore) Release(context.Context, uuid.UUID, uuid.UUID) error
- func (UnimplementedStore) Replay(context.Context, ReplayFilter) (int64, error)
- func (UnimplementedStore) Reschedule(context.Context, uuid.UUID, uuid.UUID, time.Duration, string) error
- func (UnimplementedStore) ResumeJob(context.Context, Source, uuid.UUID) error
- func (UnimplementedStore) Retain(context.Context, time.Time, int) (int64, error)
- func (UnimplementedStore) RetryAllDead(context.Context, Source, string) (int64, error)
- func (UnimplementedStore) RetryJob(context.Context, Source, uuid.UUID) error
- func (UnimplementedStore) RunNow(context.Context, Source, uuid.UUID) error
- func (UnimplementedStore) Skip(context.Context, uuid.UUID, uuid.UUID, string) error
- func (UnimplementedStore) Snooze(context.Context, uuid.UUID, uuid.UUID, time.Duration, string) (bool, error)
- func (UnimplementedStore) Stats(context.Context, Source, string) (Depths, []DailyCount, error)
- func (UnimplementedStore) Subscribers(context.Context, string) ([]Subscriber, error)
- func (UnimplementedStore) VacuumCompleted(context.Context, Source, time.Duration) (int64, error)
- func (UnimplementedStore) VacuumDead(context.Context, Source, string, time.Duration) (int64, error)
- func (UnimplementedStore) VacuumIdempotency(context.Context, Source) (int64, error)
- func (UnimplementedStore) VacuumStats(context.Context, Source, time.Duration) (int64, error)
- type Wake
- type WorkflowExecutionView
- type WorkflowStartParams
- type WorkflowState
- type WorkflowStore
Constants ¶
const ( // KindSleep is the reserved kind of a durable timer task. It is born // blocked (or scheduled when it has no dependencies) and, once unblocked, // sits scheduled with run_at = now()+SleepFor until CompleteDueSleeps marks // it succeeded. A task with SignalName set can be woken early by Signal. KindSleep = "$sleep" // KindSignal is the reserved kind of a wait-for-signal task. It is born // blocked (or waiting when it has no dependencies) and, once unblocked, // sits in StateWaiting until Signal completes it with the signal payload as // its result. KindSignal = "$signal" // TaskKeyCompensationPrefix prefixes the task key of every compensation // task ("comp:<original key>"). User task keys must never carry it. TaskKeyCompensationPrefix = "comp:" )
Internal task kinds and reserved key prefixes. The internal kinds are resolved entirely by the workflow scheduler (DAGStore.CompleteDueSleeps and DAGStore.Signal); they are never registered on a worker, so the engine's PromoteDue (which promotes only registered kinds) can never move them to pending and no handler ever runs for them.
Variables ¶
var ErrNotSupported = errors.New("azync: capability not supported by driver")
ErrNotSupported reports that a driver does not implement an optional capability. Core.Migrate wraps it when the driver is not a Migrator, and UnimplementedStore returns it for every method.
Functions ¶
func IsNotFound ¶
IsNotFound reports whether err is (or wraps) the contract's not-found error.
func NewNotFound ¶
NewNotFound builds the contract's not-found error for the named operation. Drivers return it from settlement and admin methods whose target row was absent or in an unexpected state (e.g. lease-token fencing failed).
Types ¶
type AttemptError ¶
AttemptError is one recorded failure in a job's retry history. Every failed transition (reschedule, exhaustion to dead, reap to dead) records one so the full "why did each attempt fail" trail survives, not just the last error.
type Change ¶ added in v0.0.8
type Change struct {
// Entity says which table changed, or ChangeReset for a gap signal.
Entity ChangeEntity
// Source partitions job changes (queue|event|dag|workflow); empty for
// dag-header and ledger-event changes.
Source Source
// ID is the changed job, DAG or event id; zero for bulk and reset hints.
ID uuid.UUID
// DAGID links a job change to its owning DAG when the row is a task.
DAGID uuid.UUID
// Kind is the job kind, the event type, or the DAG definition name.
Kind string
// TaskKey is set on job changes that belong to a DAG.
TaskKey string
// State is the row's new state. Job and DAG state vocabularies share the
// field; ledger events carry none.
State string
// At is the backend transaction time of the change.
At time.Time
// Bulk marks a coalesced hint: one statement changed more rows than the
// backend's per-statement cap, so per-row hints were replaced by this one
// summary. ID/Kind/TaskKey/State are empty; refetch the whole partition
// identified by Entity (and Source for jobs).
Bulk bool
// Count is the number of rows behind a Bulk hint.
Count int
}
Change is a best-effort, at-most-once row-change hint. It carries only identifiers, kind/name, state and a timestamp — never payloads, results or metadata, which routinely carry PII; consumers read full rows through the Manager surfaces behind their own authorization.
type ChangeEntity ¶ added in v0.0.8
type ChangeEntity string
ChangeEntity discriminates which entity a Change describes, or that the stream itself needs attention (ChangeReset).
const ( // ChangeJob marks a change to a job row (any Source). ChangeJob ChangeEntity = "job" // ChangeDAG marks a change to a DAG header row. ChangeDAG ChangeEntity = "dag" // ChangeEvent marks an append to the event ledger. ChangeEvent ChangeEntity = "event" // ChangeReset signals a possible delivery gap: the stream just became // live, reconnected after a loss, or dropped hints under backpressure. // Consumers must refetch anything they care about. Every subscription // receives one ChangeReset before any other change. ChangeReset ChangeEntity = "reset" )
type ChangeNotifier ¶ added in v0.0.8
type ChangeNotifier interface {
// Changes returns a stream of change hints. The channel is closed when ctx
// ends or the store closes. The first delivery on every subscription is a
// [ChangeReset], sent once the push channel is live: a change committed
// after that reset is received is observed, or its loss is announced by a
// further reset — so "refetch on the reset" leaves no silent gap. A nil
// channel with nil error means the backend cannot push changes
// (poll-only).
Changes(ctx context.Context) (<-chan Change, error)
}
ChangeNotifier is the optional row-change push capability: a stream of Change hints describing state transitions of jobs, DAGs and ledger events, for external observers (ops UIs, SSE bridges) that would otherwise poll. Hints are best-effort and at-most-once — they may be dropped under backpressure or lost across a backend reconnect, and every gap is announced in-band as a ChangeReset change. A consumer treats hints as invalidation signals and refetches authoritative state through the Manager surfaces; it must never treat the stream as a durable feed.
type Config ¶
type Config struct {
// Schema is the backend namespace to isolate azync's tables in. Empty means
// the backend default (for example the public schema in PostgreSQL).
Schema string
// NotifyChannel is the wakeup channel name a [Notifier] driver listens on.
// Empty means the driver's default.
NotifyChannel string
// MigrationsTable is the version-tracking table a [Migrator] driver uses.
// Empty means the driver's default (azync_migrations in the pg driver).
MigrationsTable string
// PollOnly disables push wakeups even on a Notifier-capable driver, forcing
// the correctness path of polling.
PollOnly bool
// Logger is the structured logger the driver should use. Nil means
// slog.Default().
Logger *slog.Logger
}
Config carries the infrastructure settings the core resolves from options and hands to an Opener. Every field has a documented zero value so a driver can apply sensible defaults.
type DAGFailure ¶
type DAGFailure struct {
DAGID uuid.UUID
// Policy is the policy that was applied.
Policy OnFailurePolicy
// DeadTasks are the task keys whose death triggered the policy, sorted.
DeadTasks []string
}
DAGFailure reports one workflow the failure policy acted on in an ApplyFailurePolicy pass.
type DAGParams ¶
type DAGParams struct {
// ID is the caller-assigned primary key; drivers must not overwrite it.
ID uuid.UUID
// Name is the workflow definition name; dedupe scopes to it.
Name string
// OnFailure is the declared failure policy. Drivers treat an empty value
// as OnFailureCancel.
OnFailure OnFailurePolicy
// IdempotencyKey deduplicates within Name across live (running, suspended
// or compensating) executions. Empty disables dedupe; a terminal workflow
// frees the key.
IdempotencyKey string
// Meta carries string-valued annotations, propagated onto every task job.
Meta map[string]string
// Tasks is the static task set. Task keys must be unique within the
// workflow.
Tasks []DAGTask
// Deps are the DAG edges: each entry blocks TaskKey until DependsOnKey
// succeeded.
Deps []DAGDep
}
DAGParams is the durable input for one workflow: its header plus the full static DAG (tasks and dependency edges) declared at creation time.
type DAGSignalParams ¶ added in v0.0.6
type DAGSignalParams struct {
DAGID uuid.UUID
// Name is the signal name (the target task's key through the dag
// runtime).
Name string
// MessageID, when non-empty, deduplicates within (DAGID, Name): a repeat
// delivery of the same id is accepted and dropped. Empty disables dedupe.
// Use the sender's event id for at-least-once webhooks.
MessageID string
Payload json.RawMessage
}
DAGSignalParams delivers (or buffers) one named signal on a workflow.
type DAGState ¶
type DAGState string
DAGState is the persisted lifecycle state of a workflow.
const ( // DAGRunning marks a workflow whose DAG is executing. DAGRunning DAGState = "running" // DAGSuspended marks a workflow parked for a manual decision (retry, // compensate or cancel), either by the suspend failure policy or by a dead // compensation task. DAGSuspended DAGState = "suspended" // DAGCompensating marks a workflow whose compensation chain is // executing. DAGCompensating DAGState = "compensating" // DAGPaused marks a workflow an operator froze (PauseDAG): non-terminal, // its live tasks held out of the ready set, nothing promotes or runs // until Manager.Retry resumes it. Distinct from DAGSuspended, which // records a failure — a paused workflow is healthy, just deliberately // stopped (say, while its provider is down). DAGPaused DAGState = "paused" // DAGSucceeded is the terminal state of a workflow whose tasks all // succeeded. DAGSucceeded DAGState = "succeeded" // DAGFailed is the terminal state of a failed workflow (after its // compensations, if any, finished). DAGFailed DAGState = "failed" // DAGCancelled is the terminal state of an operator-cancelled // workflow. DAGCancelled DAGState = "cancelled" )
type DAGStore ¶
type DAGStore interface {
// CreateDAG atomically inserts the workflow header, its tasks and its
// dependency edges, and signals workers for immediately runnable tasks —
// one transaction, all or nothing. Initial task states: a task with
// dependencies is blocked; a dependency-free task is pending, except the
// internal kinds (a root KindSleep is scheduled with run_at =
// now()+SleepFor, a root KindSignal is waiting). Task keys are unique per
// workflow.
//
// When p.IdempotencyKey is set and a workflow with the same (Name,
// IdempotencyKey) is live (running, suspended or compensating), nothing is
// inserted and the existing execution's id is returned as (false,
// existingID, nil). A terminal workflow frees the key. existingID is
// meaningful only when inserted is false.
CreateDAG(ctx context.Context, p DAGParams) (inserted bool, existingID uuid.UUID, err error)
// Signal delivers (or buffers) one named signal on a live workflow,
// atomically: the delivery is appended to the workflow's signal inbox —
// deduplicated by MessageID when set: a repeat of the same id is accepted
// and dropped with deduplicated=true and no other effect — then
// immediately consumed when a matching task is deliverable (a waiting
// KindSignal task completes as succeeded with the payload persisted as
// its result; a scheduled KindSleep task is woken early, run_at = now()).
// delivered is the number of tasks completed or woken now; delivered == 0
// with deduplicated == false means the signal was accepted and buffered —
// DeliverBufferedSignals hands it to its task once that task becomes
// deliverable, so a signal racing the scheduler's promotion is never
// lost. It returns a not-found error (see IsNotFound) for a missing or
// terminal workflow.
Signal(ctx context.Context, p DAGSignalParams) (delivered int64, deduplicated bool, err error)
// DeliverBufferedSignals consumes buffered inbox signals whose target
// task has become deliverable (a waiting KindSignal task, or a scheduled
// KindSleep task, of a running or compensating workflow), oldest signal
// first per task, and returns the number delivered. Set-based and
// idempotent; the scheduler calls it right after PromoteUnblocked so a
// signal buffered while its task was still blocked lands within one tick.
DeliverBufferedSignals(ctx context.Context) (int64, error)
// FindDAGByKey resolves the live workflow (running, suspended,
// compensating or paused) holding (name, idempotencyKey) — the business
// key a webhook handler knows, without bookkeeping the run UUID
// anywhere. At most one live workflow holds a key (the dedupe barrier);
// terminal workflows free it, so a missing result is a not-found error
// (see IsNotFound) — the right answer for a webhook arriving after the
// run settled.
FindDAGByKey(ctx context.Context, name, idempotencyKey string) (uuid.UUID, error)
// PauseDAG freezes a RUNNING workflow: the header moves to DAGPaused with
// reason recorded, and its pending/scheduled tasks move to StatePaused,
// atomically. Blocked and waiting tasks keep their states — with the
// header out of running/compensating nothing promotes them, and incoming
// signals buffer in the inbox for delivery after resume. An active
// (leased) task keeps its lease and settles on its own; its successors
// simply never start. Time spent paused never burns a task's snooze
// budget: RetryDAG — the one resume verb — clears the stamped deadline
// when it releases the paused tasks. It returns a not-found error for a
// missing workflow or one in any state but running.
PauseDAG(ctx context.Context, id uuid.UUID, reason string) error
// PromoteUnblocked moves every blocked task whose dependencies are all
// satisfied to its runnable state, chosen by kind: KindSignal to waiting,
// KindSleep to scheduled with run_at = now()+SleepFor, anything else to
// pending. A dependency is satisfied when it succeeded; for a task with
// IgnoreDeadDeps, dead and cancelled dependencies also count as satisfied.
// It returns the number of tasks promoted.
PromoteUnblocked(ctx context.Context) (int64, error)
// CompleteDueSleeps marks every scheduled KindSleep task whose run_at is
// due as succeeded (stamping completed_at) without running any handler,
// and returns the count.
CompleteDueSleeps(ctx context.Context) (int64, error)
// ApplyFailurePolicy applies each running workflow's OnFailure policy when
// it has at least one triggering dead task. A dead task triggers the policy
// unless every one of its dependents declares IgnoreDeadDeps — that lone
// exemption lets a fully tolerant branch keep running instead of being
// cancelled. The exemption is never vacuous: a dead task with no dependents
// (a leaf) always triggers, as there is no tolerant branch to preserve.
// OnFailureCancel cancels the workflow's non-terminal tasks (pending,
// scheduled, blocked, waiting), inserts the compensation chain — one
// "comp:<key>" task per succeeded task that declared a compensation, chained
// via dependencies in reverse completed_at order, the first pending and the
// rest blocked — and moves the workflow to compensating (or straight to
// failed when there is nothing to compensate). OnFailureSuspend moves the
// workflow to suspended, leaving its tasks untouched. Both record the dead
// tasks in FailureReason. It returns one DAGFailure per workflow acted
// on.
//
// A task that is active (leased by a worker) when the policy fires is left
// alone: the lease belongs to the worker, so it is neither cancelled nor
// compensated. Should it complete after the policy pass, it settles on its
// own and stays outside the compensation chain already inserted — an
// accepted v1 limitation.
ApplyFailurePolicy(ctx context.Context) ([]DAGFailure, error)
// CompleteDAGs settles dags whose work is finished. A running
// workflow settles once all of its tasks are terminal (succeeded or dead)
// AND every dead task is tolerated — each has at least one dependent and all
// of its dependents declare IgnoreDeadDeps: it becomes succeeded when none
// died, or failed — with FailureReason listing the dead task keys — when at
// least one task died but every death was tolerated (the policy never fired
// and the tolerant branches ran to completion). A running workflow that is
// all-terminal but carries a NON-tolerated dead task (a dead leaf, or a dead
// task some dependent does not tolerate) is left running for
// ApplyFailurePolicy to run its OnFailure policy — cancel inserts the
// compensation chain, suspend parks it. This tolerance re-check is
// authoritative: correctness does NOT depend on ApplyFailurePolicy having run
// earlier in the same worker tick. The two are separate transactions, and a
// task can die in the window between them (a live worker acking an Abort), so
// relying on intra-tick ordering would let a Cancel-policy saga settle failed
// with its compensations silently skipped. Running ApplyFailurePolicy before
// CompleteDAGs on each tick remains recommended hygiene — it settles a
// triggering death one tick sooner — but is not required for correctness. A
// compensating workflow settles once its compensation tasks
// are all terminal: it becomes failed — or cancelled when the compensation
// was triggered through CancelDAG — and a compensating workflow with a
// dead compensation task becomes suspended for a manual decision. It returns
// the number of dags transitioned.
CompleteDAGs(ctx context.Context) (int64, error)
// TaskResults returns the settled outcomes of the workflow's succeeded
// and skipped tasks, keyed by task key, restricted to keys when non-empty
// (an empty keys slice returns every settled task). A succeeded task
// without a result maps to an entry with a nil Result; a skipped task
// maps to an entry with Skipped=true and no result — distinguishable, so
// a consumer never mistakes "deliberately did nothing" for "succeeded
// without output". Tasks not yet settled are absent.
TaskResults(ctx context.Context, dagID uuid.UUID, keys []string) (map[string]TaskResult, error)
// AckTaskResult completes an active task exactly like Store.Ack and
// additionally persists result as the task's durable output, atomically.
// Same lease-token fencing: it returns a not-found error (see IsNotFound)
// when the token no longer owns an active row.
AckTaskResult(ctx context.Context, id, leaseToken uuid.UUID, result json.RawMessage) error
// GetDAG returns one workflow header by id, or a not-found error (see
// IsNotFound) when none matches.
GetDAG(ctx context.Context, id uuid.UUID) (*DAGView, error)
// ListDAGs lists dags matching filter, newest first (created_at
// descending), paginated, returning the page and the total matching count.
ListDAGs(ctx context.Context, filter DAGFilter, offset, limit int) ([]DAGView, int64, error)
// DAGTasks returns every task job of the workflow (compensation tasks
// included) ordered by creation time — tasks created later (a compensation
// chain) follow tasks created earlier, but the relative order of tasks
// inserted in the same atomic batch (the initial DAG) is stable, not the
// declaration order. It returns a not-found error when the workflow does
// not exist.
DAGTasks(ctx context.Context, id uuid.UUID) ([]Job, error)
// DAGDeps returns every dependency edge of the workflow — the static edges
// declared at Run plus the compensation-chain links inserted when
// compensation starts — ordered by (task_key, depends_on_key). It is the
// read half of what CreateDAG persists: the scheduler only ever asks these
// rows "is this task unblocked?", so without it the graph is unreadable
// from outside and an admin surface has to guess the shape of a run from
// timestamps, which silently turns a parallel fan-out into a chain.
//
// An unknown id returns no rows rather than an error: callers pair this
// with DAGTasks, which already reports absence.
DAGDeps(ctx context.Context, id uuid.UUID) ([]DAGDep, error)
// DAGNameStateCounts returns how many dags sit in each state, per
// definition name, counting every dag the backend still retains. Names and
// states with no dags may be absent rather than present with a zero.
//
// It is keyed by name rather than global because the set of names is
// itself an answer nothing else provides: ListDAGs filters BY name but
// never enumerates them, so an admin surface offering a definition
// navigator (as the queue one does over ListKinds) would otherwise have to
// learn names from whichever page happened to be on screen. Summing the
// inner maps gives the global per-state counts, so this is one read, not
// two.
DAGNameStateCounts(ctx context.Context) (map[string]map[DAGState]int64, error)
// DAGTaskCounts returns, per requested dag, how many of its tasks sit in
// each task state — the one read that lets a listing show each run's task
// breakdown without a DAGTasks call per row. Ids with no tasks (unknown
// ones included) may be absent from the map rather than present with an
// empty one; an empty ids slice returns an empty map without querying.
DAGTaskCounts(ctx context.Context, ids []uuid.UUID) (map[uuid.UUID]map[JobState]int64, error)
// RetryDAG resumes a non-terminal workflow after failures: dead tasks
// are reset to pending with a fresh budget (attempt and reap_count
// cleared) and a suspended workflow resumes — to running, or back to
// compensating when a compensation chain exists (only the dead
// compensation tasks are reset then, so original tasks never rerun after
// compensating started). A compensating workflow stays compensating. It
// returns a not-found error for a missing or terminal workflow.
RetryDAG(ctx context.Context, id uuid.UUID) error
// CompensateDAG manually triggers compensation on a running or
// suspended workflow: exactly like the OnFailureCancel policy, it cancels
// the non-terminal tasks, inserts the compensation chain and moves the
// workflow to compensating (or failed when there is nothing to
// compensate). It returns a not-found error for a missing workflow or one
// in any other state.
CompensateDAG(ctx context.Context, id uuid.UUID) error
// CancelDAG cancels a non-terminal workflow without compensating
// (compensation is its own verb): the non-terminal tasks (pending,
// scheduled, blocked, waiting) are cancelled and the workflow becomes
// cancelled. On a compensating workflow the in-flight compensation is
// allowed to settle first: the workflow keeps compensating and
// CompleteDAGs lands it on cancelled instead of failed. It returns a
// not-found error for a missing or already terminal workflow.
CancelDAG(ctx context.Context, id uuid.UUID) error
// VacuumDAGs deletes terminal dags completed before retention
// ago, cascading to their task jobs and dependency edges, and returns the
// number of dags removed. A retention <= 0 retains all and removes
// nothing.
VacuumDAGs(ctx context.Context, retention time.Duration) (int64, error)
}
DAGStore is the optional workflow capability: the DAG scheduler and admin contract a backend implements on top of Store to support the workflow runtime. A backend without it simply cannot run dags; queue and event runtimes never require it.
The scheduler methods (PromoteUnblocked, CompleteDueSleeps, ApplyFailurePolicy, CompleteDAGs) are set-based and idempotent: the workflow worker calls them on a fixed tick from every instance without leader election, so an operation observing no eligible rows must be a no-op. All time comparisons resolve against the backend's own clock.
Implementations must be safe for concurrent use.
type DAGTask ¶
type DAGTask struct {
// Key identifies the task within its workflow (unique, caller-validated
// against the reserved "$" and "comp:" prefixes).
Key string
// Kind is the handler kind, or an internal kind (KindSleep, KindSignal).
Kind string
// Payload is the opaque handler argument.
Payload json.RawMessage
// MaxAttempts is the retry budget. Zero defers to the runtime default,
// resolved durably on the first lease.
MaxAttempts int
// CompensationKind, when set, declares a compensation for this task: on a
// compensating workflow a "comp:<Key>" task of this kind is inserted with
// CompensationPayload once this task succeeded.
CompensationKind string
CompensationPayload json.RawMessage
// SignalName names the signal this task reacts to: a KindSignal task
// completes on it, a KindSleep task is woken early by it. Empty for tasks
// that ignore signals.
SignalName string
// SleepFor is the KindSleep duration, resolved against the backend clock
// when the timer starts (at creation for a root task, at promotion
// otherwise).
SleepFor time.Duration
// IgnoreDeadDeps lets this task be promoted even when a dependency ended
// dead or cancelled, treating those dependencies as satisfied. It also
// exempts a dead dependency from the failure policy when every dependent of
// that dead task declares IgnoreDeadDeps (see ApplyFailurePolicy): the
// tolerant branch is allowed to run instead of being cancelled. The
// exemption is never vacuous — a dead task with no dependents (a leaf)
// always triggers the policy — and a workflow that runs to completion with
// any dead task still settles failed, not succeeded (see CompleteDAGs).
IgnoreDeadDeps bool
// Deadline, when positive, bounds the task's snooze/NotReady loop: the
// driver persists it as the job's snooze budget, and the FIRST Snooze
// stamps deadline_at = now()+Deadline on the backend clock — the budget
// measures time spent waiting for the resource, not the workflow's age.
// A Snooze settled past the deadline dead-letters the task, triggering
// the workflow's OnFailure policy on the next scheduler pass. Zero means
// the task can snooze forever. Compensation tasks never inherit it, and
// RetryDAG clears the stamped deadline so a retried task waits with a
// fresh budget.
Deadline time.Duration
}
DAGTask is one declared task of a workflow DAG.
type DAGView ¶
type DAGView struct {
ID uuid.UUID
Name string
State DAGState
OnFailure OnFailurePolicy
IdempotencyKey string
// FailureReason describes why the workflow left the happy path (the dead
// tasks that triggered the failure policy, or a dead compensation).
FailureReason string
// Meta carries string-valued annotations. On reads it is never nil: a
// workflow with no annotations returns an empty (non-nil) map.
Meta map[string]string
// CreatedAt and UpdatedAt are lifecycle timestamps; CompletedAt is zero
// until the workflow reaches a terminal state.
CreatedAt time.Time
UpdatedAt time.Time
CompletedAt time.Time
}
DAGView is the backend-neutral projection of a workflow header for the admin and manager surfaces.
type DailyCount ¶
type DailyCount struct {
// Date is midnight (UTC) of the counted day.
Date time.Time
Enqueued int64
Processed int64
Failed int64
Reaped int64
}
DailyCount is one day of throughput counters for a kind, summed across the backend's internal shards.
type Depths ¶
type Depths struct {
Pending int64
Scheduled int64
Active int64
Dead int64
Paused int64
Succeeded int64
// OldestPendingAge is how long the oldest currently-pending job has been
// waiting (now - its run_at, computed against the backend's own clock to
// avoid client/server skew). Zero when Pending is zero.
OldestPendingAge time.Duration
}
Depths are the instantaneous per-state job counters of one kind.
type DequeueParams ¶
type DequeueParams struct {
// Kind selects the fetch partition; for event deliveries it is the
// subscriber name.
Kind string
// Limit caps the number of jobs leased; a value <= 0 leases nothing.
Limit int
// Lease is how long the claim is held before the job becomes reclaimable by
// ReapExpired.
Lease time.Duration
// DefaultMaxAttempts is the runtime's retry budget, applied durably on a
// job's first lease unless the job was enqueued with an explicit budget
// (EnqueueParams.MaxAttemptsExplicit) or OverrideDefault is false.
DefaultMaxAttempts int
// OverrideDefault enables applying DefaultMaxAttempts on the first lease. When
// false the job's stored MaxAttempts is kept even on the first lease.
OverrideDefault bool
}
DequeueParams controls a single DequeueBatch claim within one (Source, Kind) partition.
type EnqueueParams ¶
type EnqueueParams struct {
// ID is the caller-assigned primary key; drivers must not overwrite it.
ID uuid.UUID
// Kind names the job type and selects the fetch partition.
Kind string
// Payload is the opaque handler argument, stored verbatim. Required for
// queue jobs (never nil).
Payload json.RawMessage
// Meta carries string-valued annotations propagated to the handler.
Meta map[string]string
// RunAt is an absolute schedule (from At). Zero delegates to now()+Delay.
RunAt time.Time
// Delay is a relative schedule resolved against the backend clock; used only
// when RunAt is zero.
Delay time.Duration
// MaxAttempts is the retry budget. It is durable only when
// MaxAttemptsExplicit is true; otherwise the first lease may replace it with
// the runtime default (see DequeueParams.DefaultMaxAttempts).
MaxAttempts int
// MaxAttemptsExplicit records that the caller set MaxAttempts deliberately,
// pinning it against divergent runtime defaults on the first lease.
MaxAttemptsExplicit bool
// IdempotencyKey deduplicates within (Source, Kind). Empty disables dedupe.
IdempotencyKey string
// IdempotencyTTL extends the dedupe window: the live-job uniqueness check
// (a duplicate is rejected only while a prior job with the key is still
// alive) always applies. Setting IdempotencyTTL greater than zero ADDS a
// fixed time-window reservation on top of that check, one that keeps the
// key rejected for the given duration regardless of the prior job's state
// (including after it settles to succeeded or dead). Zero relies on the
// live-job check alone.
IdempotencyTTL time.Duration
}
EnqueueParams is the durable input for a single queue job (Source SourceQueue). Scheduling resolves against the backend clock: when RunAt is set it wins; otherwise the backend computes its own now()+Delay so a client clock skewed against the database can never hide a job from dequeue.
type EventAdminRow ¶
type EventAdminRow struct {
ID uuid.UUID
Type string
AggregateType string
AggregateID string
Version int64
OccurredAt time.Time
// DispatchedAt is zero when the event has no deliveries; otherwise it equals
// OccurredAt, since Publish creates deliveries atomically with the event.
DispatchedAt time.Time
Meta map[string]string
Payload json.RawMessage
// Deliveries is the number of delivery jobs fanned out from this event.
Deliveries int64
}
EventAdminRow is one ledger projection for the admin list and detail views.
type EventFilter ¶
EventFilter selects events for the ledger admin list. Zero values mean "no bound". Undispatched, when non-nil, keeps only events that have (false) or lack (true) any delivery.
type EventRecord ¶
type EventRecord struct {
ID uuid.UUID
Type string
AggregateType string
AggregateID string
Version int64
OccurredAt time.Time
Payload json.RawMessage
// Meta carries string-valued annotations. On reads it is never nil: an event
// with no annotations returns an empty (non-nil) map.
Meta map[string]string
}
EventRecord is a rehydrated row from the append-only event ledger. It is the single source of truth for an event's body, so replay reconstructs deliveries from it without the original publish call.
type HistoryEvent ¶
type HistoryEvent struct {
WorkflowID uuid.UUID
Seq int64
Type string
Payload json.RawMessage
CreatedAt time.Time
}
HistoryEvent is one durable history record.
type Job ¶
type Job struct {
// ID is the primary key.
ID uuid.UUID
// Source is the partition discriminator.
Source Source
// Kind is the job type (subscriber name for event deliveries).
Kind string
// State is the current lifecycle state.
State JobState
// Attempt is the 1-based count of leases so far (0 before the first lease).
Attempt int
// MaxAttempts is the resolved retry budget.
MaxAttempts int
// ReapCount is how many times the lease has expired and been reclaimed;
// tracked separately from Attempt so a stuck worker cannot silently burn the
// retry budget.
ReapCount int
// Payload is the opaque handler argument. Nil for event deliveries, whose
// body lives in the ledger and is exposed through Event after dequeue.
Payload json.RawMessage
// Meta carries string-valued annotations. On reads it is never nil: a job
// with no annotations returns an empty (non-nil) map.
Meta map[string]string
// RunAt is when the job becomes (or became) due.
RunAt time.Time
// LeaseUntil is the current lease deadline while State is StateActive.
LeaseUntil time.Time
// LeaseToken fences settlement: only the holder of the current token may Ack,
// Reschedule, Dead, Release or ExtendLease the job.
LeaseToken uuid.UUID
// LastError is the most recent failure message.
LastError string
// EventID links an event delivery to its ledger row; uuid.Nil for queue jobs.
EventID uuid.UUID
// Replay is true for deliveries created by Replay rather than the original
// Publish fan-out.
Replay bool
// Event is the rehydrated ledger record, populated by DequeueBatch for
// Source SourceEvent jobs and nil otherwise.
Event *EventRecord
// DAGID links a DAG task to its DAG header; uuid.Nil for non-DAG jobs.
// The remaining DAG fields below are likewise zero for every source but
// SourceDAG.
DAGID uuid.UUID
// RunID links a workflow-as-code job to its execution; uuid.Nil unless
// Source is SourceWorkflow.
RunID uuid.UUID
// TaskKey is the task's key within its DAG ("comp:<key>" for a
// compensation task), or an Operation/workflow-task key for SourceWorkflow.
TaskKey string
// Result is the task's persisted output (from AckTaskResult, or the signal
// payload for a completed KindSignal task). Nil until the task succeeds
// with a result.
Result json.RawMessage
// SignalName is the signal this task reacts to, if any.
SignalName string
// CompensationKind is the declared compensation kind, empty when the task
// declared none.
CompensationKind string
// IgnoreDeadDeps marks the task promotable over dead or cancelled
// dependencies.
IgnoreDeadDeps bool
// SnoozeBudget, when positive, bounds the job's snooze/NotReady loop: the
// first Snooze stamps DeadlineAt = now()+SnoozeBudget on the backend
// clock. Zero means the job can snooze forever.
SnoozeBudget time.Duration
// DeadlineAt is the stamped snooze deadline; zero until the first Snooze
// of a job with a SnoozeBudget. A Snooze settled past it dead-letters the
// job instead of parking it.
DeadlineAt time.Time
// EnqueuedAt, FailedAt and CompletedAt are lifecycle timestamps; the latter
// two are zero until the corresponding transition occurs.
EnqueuedAt time.Time
FailedAt time.Time
CompletedAt time.Time
// StartedAt is when the CURRENT attempt was leased — rewritten by every
// lease, so for a settled job CompletedAt.Sub(StartedAt) is the duration of
// the last attempt (earlier ones are timestamped in the attempt history).
// Zero before the first lease, and for rows written before the backend
// recorded it.
StartedAt time.Time
}
Job is the backend-neutral persisted representation of a job returned by dequeue and the admin surface.
type JobFilter ¶
JobFilter selects jobs for the admin list. A zero field means "no bound": empty Kind lists across every kind of the source, and empty State lists every state.
type JobState ¶
type JobState string
JobState is the persisted lifecycle state of a job.
const ( // StatePending marks a job ready to be leased once its run_at is due. StatePending JobState = "pending" // StateScheduled marks a job whose run_at is in the future; PromoteDue moves // it to pending when due. StateScheduled JobState = "scheduled" // StateActive marks a job currently leased by a worker. StateActive JobState = "active" // StateDead marks a job that aborted or exhausted its retry budget; it is // retained for inspection and manual retry. StateDead JobState = "dead" // StatePaused marks a job an operator held out of the ready set. StatePaused JobState = "paused" // StateSucceeded is a terminal history state: completed jobs are retained // (not deleted) until VacuumCompleted trims them, so the ops UI can show a // success history. StateSucceeded JobState = "succeeded" // StateBlocked marks a workflow task whose dependencies are not all // satisfied yet; PromoteUnblocked releases it (SourceDAG only). StateBlocked JobState = "blocked" // StateWaiting marks a workflow signal task parked until Signal completes // it (SourceDAG only). StateWaiting JobState = "waiting" // StateCancelled is the terminal state of a workflow task cancelled by the // failure policy or an operator verb (SourceDAG only). StateCancelled JobState = "cancelled" // StateUncertain marks a workflow-as-code Operation whose outcome could // not be proven (timeout after send, ambiguous transport). It is not // retried automatically; Manager.ResolveUncertain settles it. StateUncertain JobState = "uncertain" // StateSkipped is a terminal state for a task that deliberately did no // work (the handler judged it unnecessary — say, the resource was already // in the target state). It satisfies dependencies like StateSucceeded, // carries no result and never compensates, and is first-class in the ops // surfaces so "ran and worked" and "ran and had nothing to do" stay // distinguishable (SourceDAG only). StateSkipped JobState = "skipped" )
type LeaderElector ¶
type LeaderElector interface {
// AcquireLeadership tries to take the named leadership. acquired=false means
// another instance leads. When acquired, release relinquishes it.
AcquireLeadership(ctx context.Context, name string) (release func(), acquired bool, err error)
}
LeaderElector is the optional cluster-wide leadership capability. Cron scheduling requires it; without it, cron is disabled while every other feature keeps working.
LeaderElector alone cannot detect a lost leadership: the returned release func is fire-and-forget, so a caller has no way to notice that the backing session died and the lock was silently released server-side (a plain process crash, a network partition, or an idle-connection kill by an intermediary all do this). A driver that can distinguish "still holding the lock" from "lock is gone" should additionally implement LeaseElector; callers that need real fencing (cron does) prefer it when available and fall back to LeaderElector's latch-forever behavior otherwise.
type LeadershipLease ¶ added in v0.0.4
type LeadershipLease interface {
// Valid reports whether this leadership is still held. A cheap,
// synchronous check (e.g. pinging the session backing the lock) meant to
// be called once per scheduler tick, not a full re-acquire attempt.
Valid(ctx context.Context) bool
// Release relinquishes the leadership. Idempotent.
Release()
}
LeadershipLease is a held leadership that can be re-verified without re-acquiring it.
type LeaseElector ¶ added in v0.0.4
type LeaseElector interface {
// AcquireLeadershipLease tries to take the named leadership. acquired=false
// means another instance leads. When acquired, the returned lease must be
// checked periodically with Valid and released with Release when done.
AcquireLeadershipLease(ctx context.Context, name string) (lease LeadershipLease, acquired bool, err error)
}
LeaseElector is the optional refinement of LeaderElector that exposes leadership as a checkable lease instead of a bare release func, so a caller can detect losing it (e.g. the backing session died) instead of believing it holds leadership forever after one successful acquire.
type Migrator ¶
type Migrator interface {
// Migrate brings the backend schema up to date.
Migrate(ctx context.Context) error
}
Migrator is the optional schema-migration capability. Core.Migrate requires it; a driver without it reports ErrNotSupported.
type Notifier ¶
type Notifier interface {
// Wake returns a channel of wakeups signaled by enqueues and publishes. The
// channel is closed when ctx ends. A nil channel with nil error means the
// backend is poll-only.
Wake(ctx context.Context) (<-chan Wake, error)
}
Notifier is the optional push-wakeup capability. Without it, runtimes fall back to polling, which is always correct.
type NukeReport ¶
NukeReport summarizes a NukeAll dev reset for one source.
type OnFailurePolicy ¶
type OnFailurePolicy string
OnFailurePolicy is a workflow's declared reaction to a dead task, applied set-based by DAGStore.ApplyFailurePolicy.
const ( // OnFailureCancel cancels the remaining tasks, inserts the compensation // chain of the succeeded tasks that declared one, and settles the workflow // to failed once the compensations finish (immediately when there are // none). OnFailureCancel OnFailurePolicy = "cancel" // OnFailureSuspend parks the workflow as suspended, leaving its tasks // untouched, so an operator (or the Manager API) decides between retry, // compensate and cancel. OnFailureSuspend OnFailurePolicy = "suspend" )
type Opener ¶
Opener constructs a Store from a DSN and resolved Config. Drivers register one under a scheme with the core's RegisterDriver, typically from an init in a blank-imported package. An Opener must redact credentials from any error it returns.
type OperationState ¶
type OperationState string
OperationState is the lifecycle of one Operation task.
const ( OperationScheduled OperationState = "scheduled" OperationActive OperationState = "active" OperationRetryWait OperationState = "retry_wait" OperationCompleted OperationState = "completed" OperationFailed OperationState = "failed" OperationCancelled OperationState = "cancelled" OperationUncertain OperationState = "uncertain" )
type OpsStats ¶
type OpsStats struct {
// Undispatched is the number of events with zero deliveries.
Undispatched int64
// Total24h is the number of events in the last 24 hours.
Total24h int64
// Types24h is the number of distinct event types in the last 24 hours.
Types24h int64
// Subscribers is the current registration count.
Subscribers int64
}
OpsStats is the event ledger admin summary.
type PublishParams ¶
type PublishParams struct {
// ID is the caller-assigned ledger primary key.
ID uuid.UUID
// Type is the event type; subscribers registered for it receive a delivery.
Type string
// AggregateType and AggregateID identify the source aggregate, if any.
AggregateType string
AggregateID string
// Version is the aggregate version this event advances to.
Version int64
// OccurredAt is the domain time the event happened.
OccurredAt time.Time
// Payload is the opaque event body, stored verbatim.
Payload json.RawMessage
// Meta carries string-valued annotations.
Meta map[string]string
}
PublishParams is the input for a single event appended to the ledger. Publish atomically writes this row and fans out one pending delivery job per matching subscriber.
type ReplayFilter ¶
type ReplayFilter struct {
Subscriber string
EventType string
EventID uuid.UUID
Since time.Time
Until time.Time
Limit int
}
ReplayFilter selects ledger events to re-fan-out into fresh deliveries. Zero fields are unbounded; Limit <= 0 means the driver's own upper bound.
type ScheduleOperationParams ¶
type ScheduleOperationParams struct {
WorkflowID uuid.UUID
// Kind is the fetch partition (e.g. "$op:name@version").
Kind string
// Payload carries name/version/input/execution key for the executor.
Payload json.RawMessage
Meta map[string]string
// ExecutionKey dedupes ScheduleOperation while a non-terminal Operation
// job with the same key already exists for the workflow (crash recovery).
ExecutionKey string
RunAt time.Time // zero = now
MaxAttempts int // 0 = driver/runtime default
}
ScheduleOperationParams inserts one leased Operation task job (source=workflow) for a workflow execution.
type SignalParams ¶
type SignalParams struct {
WorkflowID uuid.UUID
Name string
MessageID string // optional dedupe
Payload json.RawMessage
}
SignalParams appends an early signal to the inbox.
type Source ¶
type Source string
Source is the discriminator that partitions the unified job table. Every job belongs to exactly one source; runtimes operate one source in isolation (a queue never leases an event delivery and vice versa).
const ( // SourceQueue tags durable background jobs produced by Enqueue. SourceQueue Source = "queue" // SourceEvent tags event deliveries fanned out by Publish; one such job per // matching subscriber, rehydrated from the event ledger on dequeue. SourceEvent Source = "event" // SourceDAG tags DAG tasks created through the DAGStore // capability; they share the unified job table and settlement machinery. SourceDAG Source = "dag" // SourceWorkflow tags workflow-as-code tasks (workflow advances and // Operations) created through the WorkflowStore capability. SourceWorkflow Source = "workflow" )
type StalledWorkflow ¶ added in v0.0.4
StalledWorkflow identifies one execution ListStalledWorkflows found with no live task to advance it.
type Store ¶
type Store interface {
// Enqueue durably inserts one queue job (Source SourceQueue) and signals
// workers after commit. It returns inserted=false, nil error when the job was
// deduplicated by its idempotency key. run_at and the pending/scheduled split
// resolve against the backend clock.
Enqueue(ctx context.Context, p EnqueueParams) (inserted bool, err error)
// Publish atomically appends one event to the ledger and fans out one pending
// delivery job per currently registered matching subscriber, all in a single
// transaction; callers must not pre-select subscribers. It returns the number
// of deliveries created.
Publish(ctx context.Context, p PublishParams) (delivered int, err error)
// RegisterSubscriber upserts a subscriber registration, keyed by
// (Name, EventType); an existing registration's MaxAttempts is updated.
RegisterSubscriber(ctx context.Context, sub Subscriber) error
// Subscribers returns the registrations for an event type, ordered by name.
Subscribers(ctx context.Context, eventType string) ([]Subscriber, error)
// DeleteSubscriber removes the (name, eventType) registration. An empty
// eventType removes every registration of name. It returns the number of
// registrations removed; existing delivery jobs already fanned out are
// untouched (see Manager.DrainSubscriber to also clear those).
DeleteSubscriber(ctx context.Context, name, eventType string) (int64, error)
// DequeueBatch leases up to p.Limit due pending jobs of (source, p.Kind) for
// p.Lease, ordered by run_at then id. Each leased job's attempt is
// incremented, a fresh lease token minted, and its retry budget resolved
// durably (see DequeueParams.DefaultMaxAttempts). For Source SourceEvent the
// returned jobs have their Event rehydrated from the ledger.
DequeueBatch(ctx context.Context, source Source, p DequeueParams) ([]Job, error)
// Ack completes an active job, retaining it as StateSucceeded (history), not
// deleting it. Completion clears the lease and frees the live-job
// idempotency key (the unique-key check excludes succeeded/dead); a
// TTL-window key set via EnqueueParams.IdempotencyTTL deliberately survives
// finalization until it expires. It returns a not-found error (see
// IsNotFound) when the lease token no longer owns an active row.
Ack(ctx context.Context, id, leaseToken uuid.UUID) error
// Reschedule parks a failed active job as StateScheduled with run_at
// now()+delay and records the failed attempt. Fenced by lease token.
Reschedule(ctx context.Context, id, leaseToken uuid.UUID, delay time.Duration, lastError string) error
// Dead moves a failed active job to StateDead (abort or exhausted budget) and
// records the final attempt. Fenced by lease token.
Dead(ctx context.Context, id, leaseToken uuid.UUID, lastError string) error
// Skip settles an active job as StateSkipped: terminal, no result, the
// reason retained as its last error for the ops surfaces, counted as
// processed in the stats. A skipped DAG task satisfies its dependents
// like a succeeded one and never compensates. Fenced by lease token.
Skip(ctx context.Context, id, leaseToken uuid.UUID, reason string) error
// Release returns a leased job to StatePending as a safety net, decrementing
// attempt by one (floored at zero) without recording an attempt. Fenced by
// lease token.
Release(ctx context.Context, id, leaseToken uuid.UUID) error
// Snooze parks an active job as StateScheduled with run_at now()+delay,
// decrementing attempt by one (floored at zero) so the lease it hands back
// never consumes the retry budget, and without recording an attempt. It is
// the polling-wait primitive ("the resource is not ready, re-check in d"):
// a handler without a deadline can snooze indefinitely without ever
// exhausting its retries.
//
// A job carrying a snooze budget (Job.SnoozeBudget) is bounded: the FIRST
// Snooze stamps its deadline (now()+budget on the backend clock — the
// budget measures time spent waiting, not the job's age), and a Snooze
// settled past that deadline dead-letters the job atomically instead,
// recording the final attempt with deadlineError as its last error, and
// returns deadlined=true. Fenced by lease token: it returns a not-found
// error (see IsNotFound) when the token no longer owns an active row.
Snooze(ctx context.Context, id, leaseToken uuid.UUID, delay time.Duration, deadlineError string) (deadlined bool, err error)
// ExtendLease renews an active job's lease for the duration. Fenced by lease
// token: it returns a not-found error once the token no longer owns the row.
ExtendLease(ctx context.Context, id, leaseToken uuid.UUID, lease time.Duration) error
// PromoteDue moves due scheduled jobs of the given kinds to pending and
// returns the count promoted.
PromoteDue(ctx context.Context, source Source, kinds []string) (int64, error)
// ReapExpired reclaims active jobs of the given kinds whose lease expired:
// each returns to pending with reap_count incremented, or moves to StateDead
// (recording an attempt) once reap_count reaches maxReaps. It returns the
// number reaped and the subset killed.
ReapExpired(ctx context.Context, source Source, kinds []string, maxReaps int) (reaped, killed int64, err error)
// VacuumStats trims daily stat counters of the source older than retention
// and returns the rows removed. A retention <= 0 retains all and removes
// nothing.
VacuumStats(ctx context.Context, source Source, retention time.Duration) (int64, error)
// VacuumIdempotency trims expired time-window dedupe keys of the source and
// returns the rows removed.
VacuumIdempotency(ctx context.Context, source Source) (int64, error)
// VacuumCompleted trims succeeded jobs of the source completed before
// retention ago and returns the rows removed. A retention <= 0 retains
// succeeded jobs forever and removes nothing.
//
// DAG-owned jobs (DAGID != zero) are exempt regardless of retention: a
// task can be succeeded for the whole span of a long Sleep or WaitSignal
// further down its DAG, and deleting it here would blind ResultOf and
// CompleteDAGs while the DAG is still running. Their lifecycle belongs to
// the DAG — they are removed only by VacuumDAGs' terminal cascade.
//
// Workflow-as-code jobs (RunID != zero / source=workflow) are likewise
// exempt: they are removed only by WorkflowStore.VacuumWorkflows.
VacuumCompleted(ctx context.Context, source Source, retention time.Duration) (int64, error)
// ListKinds returns the distinct kinds of the source (from live jobs and stat
// history), sorted.
ListKinds(ctx context.Context, source Source) ([]string, error)
// KindDepths returns per-kind instantaneous state counters of the source.
KindDepths(ctx context.Context, source Source) (map[string]Depths, error)
// Stats returns one kind's instantaneous depths and its daily throughput
// window, oldest day first.
Stats(ctx context.Context, source Source, kind string) (Depths, []DailyCount, error)
// AllDaily returns the daily throughput window summed across every kind of
// the source, oldest day first.
AllDaily(ctx context.Context, source Source) ([]DailyCount, error)
// ListJobs lists jobs of the source matching filter, paginated, and returns
// the page and the total matching count. Ordering depends on
// filter.State:
// - StatePending or StateDead: EnqueuedAt ascending, then ID
// - StateScheduled or StatePaused: RunAt ascending, then ID
// - StateActive: LeaseUntil ascending, then ID
// - StateSucceeded: CompletedAt descending, then ID
// - no state filter: EnqueuedAt descending, then ID (newest first, for
// admin browsing)
ListJobs(ctx context.Context, source Source, filter JobFilter, offset, limit int) ([]Job, int64, error)
// GetJob returns a single job of the source by id, or a not-found error (see
// IsNotFound) when none matches.
GetJob(ctx context.Context, source Source, id uuid.UUID) (*Job, error)
// JobAttempts returns a job's failure history, oldest attempt first.
JobAttempts(ctx context.Context, source Source, id uuid.UUID) ([]AttemptError, error)
// RetryJob resets a dead job of the source to pending for immediate retry
// (attempt and reap_count cleared). It returns a not-found error when the job
// is not dead.
RetryJob(ctx context.Context, source Source, id uuid.UUID) error
// RetryAllDead resets every dead job of (source, kind) to pending and returns
// the count. An empty kind targets all kinds of the source.
RetryAllDead(ctx context.Context, source Source, kind string) (int64, error)
// RunNow expedites one scheduled job: run_at moves to now, the state to
// pending, and workers are woken — the early-wake verb for a snoozed
// poll (a webhook arrived, the operator will not wait out the re-check
// delay). Only StateScheduled qualifies; any other state — already
// runnable, leased or settled — is a not-found error (see IsNotFound).
RunNow(ctx context.Context, source Source, id uuid.UUID) error
// ArchiveJob force-fails a pending or scheduled job of the source to dead. It
// returns a not-found error when the job is not in an archivable state.
ArchiveJob(ctx context.Context, source Source, id uuid.UUID) error
// PauseJob holds a pending or scheduled job of the source out of the ready
// set (StatePaused). It returns a not-found error when the job is not
// pausable.
PauseJob(ctx context.Context, source Source, id uuid.UUID) error
// ResumeJob returns a paused job of the source to pending or scheduled per its
// run_at. It returns a not-found error when the job is not paused.
ResumeJob(ctx context.Context, source Source, id uuid.UUID) error
// DeleteJob deletes a job of the source in the given state. It returns a
// not-found error when no such job exists.
DeleteJob(ctx context.Context, source Source, id uuid.UUID, state JobState) error
// DeleteAll deletes every job of (source, kind) in the given state and
// returns the count. An empty kind targets all kinds of the source.
DeleteAll(ctx context.Context, source Source, kind string, state JobState) (int64, error)
// VacuumDead deletes dead jobs of (source, kind) enqueued before olderThan ago
// and returns the count. An empty kind targets all kinds of the source.
VacuumDead(ctx context.Context, source Source, kind string, olderThan time.Duration) (int64, error)
// NukeAll deletes all jobs, stats and idempotency keys of the source (a dev
// reset) and reports the counts. The event ledger is left intact.
NukeAll(ctx context.Context, source Source) (NukeReport, error)
// ListEvents lists ledger events matching filter, newest first, paginated,
// returning the page and total matching count.
ListEvents(ctx context.Context, filter EventFilter, offset, limit int) ([]EventAdminRow, int64, error)
// GetEvent returns a single ledger event by id, or a not-found error when
// none matches.
GetEvent(ctx context.Context, id uuid.UUID) (*EventAdminRow, error)
// ListSubscriberViews returns subscriber registrations, ordered by event type
// then name. An empty eventType returns all.
ListSubscriberViews(ctx context.Context, eventType string) ([]SubscriberView, error)
// OpsStats returns the event ledger admin summary.
OpsStats(ctx context.Context) (OpsStats, error)
// Replay re-fans-out ledger events matching filter into fresh pending
// deliveries flagged Replay, and returns the number created.
Replay(ctx context.Context, filter ReplayFilter) (int64, error)
// Retain deletes up to limit ledger events occurring before the cutoff whose
// deliveries have all reached a terminal state (StateSucceeded or
// StateDead), cascading to those terminal deliveries, and returns the
// number of events removed. Events with any non-terminal delivery job
// (pending, scheduled, active, or paused) are skipped.
Retain(ctx context.Context, before time.Time, limit int) (int64, error)
// Close releases the driver's resources. It is safe to call once; behavior of
// a Store after Close is undefined.
Close(ctx context.Context) error
}
Store is the mandatory, backend-agnostic persistence contract. All durations are resolved against the backend's own clock; all fencing is by lease token. A Store that implements nothing else is fully functional through polling.
Implementations must be safe for concurrent use.
type Subscriber ¶
Subscriber is a registration binding a named consumer to an event type with its own retry budget. Registrations are unique per (Name, EventType).
type SubscriberView ¶
type SubscriberView struct {
EventType string
Subscriber string
MaxAttempts int
CreatedAt time.Time
UpdatedAt time.Time
}
SubscriberView is one subscriber registration projected for the admin surface.
type TaskResult ¶ added in v0.0.6
type TaskResult struct {
Result json.RawMessage
Skipped bool
}
TaskResult is one settled task outcome as returned by TaskResults: either a succeeded task's persisted output (Result, possibly nil for a task that returned nothing) or a deliberate skip (Skipped=true, no result).
type TxDAGStore ¶
type TxDAGStore[TTx any] interface { // CreateDAGTx performs CreateDAG within the caller's // transaction. CreateDAGTx(ctx context.Context, tx TTx, p DAGParams) (inserted bool, existingID uuid.UUID, err error) }
TxDAGStore is the optional transactional workflow-creation capability, generic over the backend's transaction handle TTx (one concrete type per driver, e.g. pgx.Tx). It lets a caller enlist workflow creation in an existing business transaction so the workflow commits atomically with the caller's writes.
type TxStore ¶
type TxStore[TTx any] interface { // EnqueueTx performs Enqueue within the caller's transaction. EnqueueTx(ctx context.Context, tx TTx, p EnqueueParams) (inserted bool, err error) // PublishTx performs Publish within the caller's transaction. PublishTx(ctx context.Context, tx TTx, p PublishParams) (delivered int, err error) }
TxStore is the optional transactional-enqueue capability, generic over the backend's transaction handle TTx (one concrete type per driver, e.g. pgx.Tx). It lets a caller enlist an enqueue or publish in an existing business transaction so the outbox commits atomically with the caller's writes.
type UncertainDecision ¶
type UncertainDecision string
UncertainDecision is a Manager verb for ResolveUncertain.
const ( UncertainComplete UncertainDecision = "complete" UncertainFail UncertainDecision = "fail" UncertainRetry UncertainDecision = "retry" )
type UnimplementedStore ¶
type UnimplementedStore struct{}
UnimplementedStore is an embeddable no-op Store: every method returns zero values and ErrNotSupported. Embed it in a third-party driver so that adding a method to the Store contract in a later version does not break compilation — the driver keeps satisfying Store, with new methods reporting ErrNotSupported until implemented.
A partial driver overrides the methods it supports and inherits the rest.
func (UnimplementedStore) AllDaily ¶
func (UnimplementedStore) AllDaily(context.Context, Source) ([]DailyCount, error)
AllDaily reports ErrNotSupported.
func (UnimplementedStore) ArchiveJob ¶
ArchiveJob reports ErrNotSupported.
func (UnimplementedStore) Close ¶
func (UnimplementedStore) Close(context.Context) error
Close reports ErrNotSupported.
func (UnimplementedStore) DeleteSubscriber ¶ added in v0.0.4
DeleteSubscriber reports ErrNotSupported.
func (UnimplementedStore) DequeueBatch ¶
func (UnimplementedStore) DequeueBatch(context.Context, Source, DequeueParams) ([]Job, error)
DequeueBatch reports ErrNotSupported.
func (UnimplementedStore) Enqueue ¶
func (UnimplementedStore) Enqueue(context.Context, EnqueueParams) (bool, error)
Enqueue reports ErrNotSupported.
func (UnimplementedStore) ExtendLease ¶
ExtendLease reports ErrNotSupported.
func (UnimplementedStore) GetEvent ¶
func (UnimplementedStore) GetEvent(context.Context, uuid.UUID) (*EventAdminRow, error)
GetEvent reports ErrNotSupported.
func (UnimplementedStore) JobAttempts ¶
func (UnimplementedStore) JobAttempts(context.Context, Source, uuid.UUID) ([]AttemptError, error)
JobAttempts reports ErrNotSupported.
func (UnimplementedStore) KindDepths ¶
KindDepths reports ErrNotSupported.
func (UnimplementedStore) ListEvents ¶
func (UnimplementedStore) ListEvents(context.Context, EventFilter, int, int) ([]EventAdminRow, int64, error)
ListEvents reports ErrNotSupported.
func (UnimplementedStore) ListJobs ¶
func (UnimplementedStore) ListJobs(context.Context, Source, JobFilter, int, int) ([]Job, int64, error)
ListJobs reports ErrNotSupported.
func (UnimplementedStore) ListSubscriberViews ¶
func (UnimplementedStore) ListSubscriberViews(context.Context, string) ([]SubscriberView, error)
ListSubscriberViews reports ErrNotSupported.
func (UnimplementedStore) NukeAll ¶
func (UnimplementedStore) NukeAll(context.Context, Source) (NukeReport, error)
NukeAll reports ErrNotSupported.
func (UnimplementedStore) OpsStats ¶
func (UnimplementedStore) OpsStats(context.Context) (OpsStats, error)
OpsStats reports ErrNotSupported.
func (UnimplementedStore) PromoteDue ¶
PromoteDue reports ErrNotSupported.
func (UnimplementedStore) Publish ¶
func (UnimplementedStore) Publish(context.Context, PublishParams) (int, error)
Publish reports ErrNotSupported.
func (UnimplementedStore) ReapExpired ¶
ReapExpired reports ErrNotSupported.
func (UnimplementedStore) RegisterSubscriber ¶
func (UnimplementedStore) RegisterSubscriber(context.Context, Subscriber) error
RegisterSubscriber reports ErrNotSupported.
func (UnimplementedStore) Replay ¶
func (UnimplementedStore) Replay(context.Context, ReplayFilter) (int64, error)
Replay reports ErrNotSupported.
func (UnimplementedStore) Reschedule ¶
func (UnimplementedStore) Reschedule(context.Context, uuid.UUID, uuid.UUID, time.Duration, string) error
Reschedule reports ErrNotSupported.
func (UnimplementedStore) RetryAllDead ¶
RetryAllDead reports ErrNotSupported.
func (UnimplementedStore) Snooze ¶
func (UnimplementedStore) Snooze(context.Context, uuid.UUID, uuid.UUID, time.Duration, string) (bool, error)
Snooze reports ErrNotSupported.
func (UnimplementedStore) Stats ¶
func (UnimplementedStore) Stats(context.Context, Source, string) (Depths, []DailyCount, error)
Stats reports ErrNotSupported.
func (UnimplementedStore) Subscribers ¶
func (UnimplementedStore) Subscribers(context.Context, string) ([]Subscriber, error)
Subscribers reports ErrNotSupported.
func (UnimplementedStore) VacuumCompleted ¶
VacuumCompleted reports ErrNotSupported.
func (UnimplementedStore) VacuumDead ¶
VacuumDead reports ErrNotSupported.
func (UnimplementedStore) VacuumIdempotency ¶
VacuumIdempotency reports ErrNotSupported.
func (UnimplementedStore) VacuumStats ¶
VacuumStats reports ErrNotSupported.
type Wake ¶
Wake identifies the fetch loop to nudge: the (Source, Kind) partition that gained ready work.
type WorkflowExecutionView ¶
type WorkflowExecutionView struct {
ID uuid.UUID
Name string
Version string
State WorkflowState
BusinessIdempotencyKey string
TaskQueue string
Input json.RawMessage
Result json.RawMessage
FailureReason string
Meta map[string]string
CreatedAt time.Time
UpdatedAt time.Time
CompletedAt time.Time
}
WorkflowExecutionView is the admin projection of one execution.
type WorkflowStartParams ¶
type WorkflowStartParams struct {
ID uuid.UUID
Name string
Version string
Input json.RawMessage
BusinessIdempotencyKey string
TaskQueue string
Meta map[string]string
}
WorkflowStartParams creates a new workflow-as-code execution.
type WorkflowState ¶
type WorkflowState string
WorkflowState is the lifecycle of a workflow-as-code execution.
const ( WorkflowRunning WorkflowState = "running" WorkflowSuspended WorkflowState = "suspended" WorkflowSucceeded WorkflowState = "succeeded" WorkflowFailed WorkflowState = "failed" WorkflowCancelled WorkflowState = "cancelled" )
type WorkflowStore ¶
type WorkflowStore interface {
StartWorkflow(ctx context.Context, p WorkflowStartParams) (inserted bool, existingID uuid.UUID, err error)
GetWorkflowExecution(ctx context.Context, id uuid.UUID) (WorkflowExecutionView, error)
AppendHistory(ctx context.Context, workflowID uuid.UUID, typ string, payload json.RawMessage) (seq int64, err error)
ListHistory(ctx context.Context, workflowID uuid.UUID) ([]HistoryEvent, error)
SignalWorkflow(ctx context.Context, p SignalParams) (inserted bool, err error)
CompleteWorkflow(ctx context.Context, id uuid.UUID, result json.RawMessage) error
FailWorkflow(ctx context.Context, id uuid.UUID, reason string) error
CancelWorkflowExecution(ctx context.Context, id uuid.UUID) error
SuspendWorkflow(ctx context.Context, id uuid.UUID, reason string) error
ResumeWorkflow(ctx context.Context, id uuid.UUID) error
// ScheduleOperation inserts (or returns an existing) Operation task job
// for the execution. Dedupes by ExecutionKey among non-terminal jobs.
ScheduleOperation(ctx context.Context, p ScheduleOperationParams) (jobID uuid.UUID, err error)
// MarkUncertain moves an active Operation job to StateUncertain under
// lease fencing and suspends the parent execution (§8).
MarkUncertain(ctx context.Context, operationJobID, leaseToken uuid.UUID, reason string) error
// ResolveUncertain applies an audited decision to an uncertain Operation
// (complete / fail / retry), resumes the execution when appropriate, and
// leaves a wake workflow-task for the runtime to schedule when needed.
// Returns the workflow ID and workflow task kind hint from job meta
// ("workflow_name") so the caller can ScheduleTask.
ResolveUncertain(ctx context.Context, operationJobID uuid.UUID, decision string, result json.RawMessage) (workflowID uuid.UUID, workflowName string, err error)
// ScheduleTask durably inserts one workflow-task job (Source
// SourceWorkflow, RunID = workflowID) routed by kind, at runAt (zero
// means "now"). It is how the workflow runtime schedules a replay pass —
// on Start, on a delivered Signal, and on a parked Sleep's follow-up —
// reusing azync_jobs per docs/workflow-v1-spec.md §17. Once inserted, the
// job is leased and settled through the plain Store surface
// (DequeueBatch/Ack/Release/Dead/...), keyed by (Source, Kind) and
// fenced by lease token exactly like every other job.
ScheduleTask(ctx context.Context, workflowID uuid.UUID, kind string, runAt time.Time) error
// VacuumWorkflows deletes terminal workflow-as-code executions
// (succeeded/failed/cancelled) completed before retention ago, cascading
// via FKs to history, signals, timers and azync_jobs linked by run_id.
// A retention <= 0 removes nothing.
VacuumWorkflows(ctx context.Context, retention time.Duration) (int64, error)
// ListStalledWorkflows returns up to limit running executions, updated
// at least olderThan ago (inclusive, so olderThan 0 admits an execution
// updated in the same clock tick — coarse clocks would otherwise hide
// it), with no live (pending, scheduled, active or
// uncertain) source=workflow job tied to them by run_id — an execution
// that should be making progress but has nothing left to run it. This is
// defense-in-depth, not the primary correctness mechanism: StartWorkflow
// and SignalWorkflow already schedule their task atomically with their
// own effect, so a stall here means something outside that path (a
// row predating this guarantee, an operator deleting a job by hand, a
// driver bug) left an execution stranded. olderThan should be at least a
// few multiples of the worker's lease TTL, so a task that is merely
// between "scheduled" and "visible to this read" is never mistaken for
// stalled.
ListStalledWorkflows(ctx context.Context, olderThan time.Duration, limit int) ([]StalledWorkflow, error)
}
WorkflowStore is the optional driver capability for workflow-as-code. Distinct from DAGStore.
Directories
¶
| Path | Synopsis |
|---|---|
|
azyncpgx
module
|
|
|
Package drivertest provides a public conformance suite that any azync storage driver can run against its own driver.Store to prove it honors the backend-agnostic contract.
|
Package drivertest provides a public conformance suite that any azync storage driver can run against its own driver.Store to prove it honors the backend-agnostic contract. |