Documentation
¶
Overview ¶
Package durable runs persisted linear workflows using PostgreSQL and application-owned Go workers and a scheduler.
Declare typed steps with DefineActivity, build a deterministic plan with Define, and Register it before calling Start. Start commits the complete plan before any step becomes claimable. New uses an application-owned pgx pool; Open owns its pool. Apply Migrate explicitly before running workers.
A Worker executes one handler at a time and heartbeats its expiring lease. A Scheduler recovers abandoned attempts and promotes persisted retry waits. Production ownership uses PostgreSQL time through SystemClock. ManualClock controls persisted time and runtime tickers in deterministic tests.
Delivery is at least once. A handler can perform an external effect and crash before acknowledgement. Use Metadata's stable effect key with application-owned idempotency. Cancellation fences engine progress and requests cooperative handler shutdown. Compensation runs explicitly declared cleanup for completed steps; neither operation promises to undo arbitrary external effects.
Keep workflow and handler name/version/schema identities immutable. Existing executions continue their stored plans. ListWorkflows and the other List methods provide bounded, redacted live pages. Raw result/query methods can expose payloads. PurgeTerminal removes terminal details while preserving keyed-start deduplication and its original input indefinitely.
See the repository's integration and operations guides for a complete application, supported environments, deployment, and recovery procedures.
Index ¶
- Constants
- Variables
- func Migrate(ctx context.Context, pool *pgxpool.Pool) error
- func Permanent(err error) error
- func Register[Input any](registry *Registry, definition Definition[Input]) error
- func ValidateActivityTransition(from, to ActivityState) error
- func ValidateAttemptTransition(from, to AttemptState) error
- func ValidateLeaseTransition(from, to LeaseState) error
- func ValidateWorkflowTransition(from, to WorkflowState) error
- type Activity
- type ActivityDefinition
- func (a ActivityDefinition[Input, Output]) Handle(handler func(context.Context, Input) (Output, error)) HandlerRegistration
- func (a ActivityDefinition[Input, Output]) Result(ctx context.Context, key string) (Output, error)
- func (a ActivityDefinition[Input, Output]) Step(key string, input Input, options ...StepOption) (ActivitySpec, error)
- func (a ActivityDefinition[Input, Output]) WithVersion(version, inputSchema, outputSchema string) ActivityDefinition[Input, Output]
- func (a ActivityDefinition[Input, Output]) WorkflowResult(ctx context.Context, engine *Engine, workflowID string) (Output, error)
- type ActivityKind
- type ActivityMetadata
- type ActivitySpec
- type ActivityState
- type Attempt
- type AttemptState
- type Claim
- type Clock
- type CompensationPlan
- type CompensationState
- type Definition
- type Engine
- func (e *Engine) Activities(ctx context.Context, workflowID string) ([]Activity, error)
- func (e *Engine) Attempts(ctx context.Context, activityID string) ([]Attempt, error)
- func (e *Engine) Cancel(ctx context.Context, id, actor, reason string) (WorkflowExecution, error)
- func (e *Engine) CheckSchema(ctx context.Context) error
- func (e *Engine) ClaimActivity(ctx context.Context, workerID string, leaseDuration time.Duration) (Claim, error)
- func (e *Engine) Close()
- func (e *Engine) CompleteActivity(ctx context.Context, claim Claim, output json.RawMessage) error
- func (e *Engine) FailActivity(ctx context.Context, claim Claim, activityErr error) error
- func (e *Engine) Heartbeat(ctx context.Context, claim Claim, extension time.Duration) (time.Time, error)
- func (e *Engine) ListActivities(ctx context.Context, workflowID string, request PageRequest) (Page[Activity], error)
- func (e *Engine) ListAttempts(ctx context.Context, activityID string, request PageRequest) (Page[Attempt], error)
- func (e *Engine) ListLeases(ctx context.Context, activityID string, request PageRequest) (Page[Lease], error)
- func (e *Engine) ListTimeline(ctx context.Context, workflowID string, request PageRequest) (Page[TimelineEvent], error)
- func (e *Engine) ListWorkflows(ctx context.Context, filter WorkflowFilter, request PageRequest) (Page[WorkflowSummary], error)
- func (e *Engine) Migrate(ctx context.Context) error
- func (e *Engine) PurgeTerminal(ctx context.Context, policy RetentionPolicy) (RetentionResult, error)
- func (e *Engine) RecoverAbandoned(ctx context.Context, limit int) (int, error)
- func (e *Engine) Timeline(ctx context.Context, workflowID string) ([]TimelineEvent, error)
- func (e *Engine) Workflow(ctx context.Context, workflowID string) (WorkflowExecution, error)
- type EngineOption
- type ExecutionExpiredError
- type Handler
- type HandlerPanicError
- type HandlerRegistration
- type Lease
- type LeaseState
- type ManualClock
- type Observation
- type Observer
- type ObserverFunc
- type Page
- type PageRequest
- type PendingActivity
- type Registry
- type RetentionPolicy
- type RetentionResult
- type RetryPolicy
- type RuntimeHealth
- type Scheduler
- type StartOption
- type StepOption
- type SystemClock
- type TaskError
- type Ticker
- type TickerClock
- type TimelineEvent
- type UnsupportedActivity
- type Worker
- type WorkerConfig
- type WorkflowExecution
- type WorkflowFilter
- type WorkflowState
- type WorkflowSummary
Examples ¶
Constants ¶
const MaxPayloadBytes = 1 << 20
MaxPayloadBytes limits each workflow input, step input, and step output before persistence. JSON uses encoding/json and PostgreSQL jsonb semantics.
Variables ¶
var ( ErrNoActivity = errors.New("no activity is ready") ErrLeaseLost = errors.New("activity lease is no longer valid") ErrUnknownWorkflow = errors.New("workflow definition is not registered") ErrNotFound = errors.New("execution or step not found") ErrInvalidInput = errors.New("invalid input") ErrPayloadTooLarge = errors.New("JSON payload exceeds limit") ErrTypeMismatch = errors.New("activity type does not match persisted contract") ErrStartConflict = errors.New("start key already used with different input") ErrWorkerBusy = errors.New("worker instance is already running") ErrHandlerUnresponsive = errors.New("activity handler did not stop within shutdown grace period") ErrExecutionDeadline = errors.New("execution deadline exceeded") ErrIncompatibleSchema = errors.New("database schema is incompatible with this binary") ErrExecutionExpired = errors.New("execution was removed by retention") )
Functions ¶
func Migrate ¶
Migrate checks the immutable migration catalog and atomically applies pending SQL under an exclusive transaction lock. The pool's role must have DDL rights.
func Register ¶
func Register[Input any](registry *Registry, definition Definition[Input]) error
Register adds an immutable workflow name/version and rejects duplicate entries.
func ValidateActivityTransition ¶
func ValidateActivityTransition(from, to ActivityState) error
ValidateActivityTransition checks scheduling, retry, and terminal step transitions.
func ValidateAttemptTransition ¶
func ValidateAttemptTransition(from, to AttemptState) error
ValidateAttemptTransition allows one running attempt to reach one terminal outcome.
func ValidateLeaseTransition ¶
func ValidateLeaseTransition(from, to LeaseState) error
ValidateLeaseTransition permits active ownership to be released or expired once.
func ValidateWorkflowTransition ¶
func ValidateWorkflowTransition(from, to WorkflowState) error
ValidateWorkflowTransition rejects illegal transitions, including reopening terminal work.
Types ¶
type Activity ¶
type Activity struct {
Contract string
Version string
InputSchema string
OutputSchema string
Kind ActivityKind
CompensatesActivityID *string
Compensation *CompensationPlan
ID string
WorkflowID string
Position int
StepKey string
Name string
Input json.RawMessage
Output json.RawMessage
State ActivityState
AttemptCount int
CreatedAt time.Time
UpdatedAt time.Time
CompletedAt *time.Time
RetryPolicy RetryPolicy
AvailableAt time.Time
AttemptTimeout time.Duration
LastError string
}
Activity is a persisted forward or cleanup step. ListActivities omits payloads.
type ActivityDefinition ¶
type ActivityDefinition[Input, Output any] struct { // contains filtered or unexported fields }
ActivityDefinition ties one durable name to its Go input and output types. Keep names and types compatible while executions using them are in progress.
func DefineActivity ¶
func DefineActivity[Input, Output any](name string) ActivityDefinition[Input, Output]
DefineActivity declares typed step inputs, handler outputs, and result reads.
Example ¶
package main
import (
"fmt"
"log"
"time"
durable "github.com/KleitonBarone/durable-task-runner"
)
func main() {
double := durable.DefineActivity[int, int]("double").
WithVersion("1", "integer/1", "integer/1")
step, err := double.Step("double-input", 21,
durable.WithAttemptTimeout(time.Minute),
durable.WithRetry(durable.RetryPolicy{MaxAttempts: 3, RetryErrors: true,
InitialInterval: time.Second, MaximumInterval: time.Minute}))
if err != nil {
log.Fatal(err)
}
fmt.Println(step.Name, step.Key, string(step.Input))
}
Output: double double-input 21
func (ActivityDefinition[Input, Output]) Handle ¶
func (a ActivityDefinition[Input, Output]) Handle(handler func(context.Context, Input) (Output, error)) HandlerRegistration
Handle binds the declaration's types and compatibility identities to a handler.
func (ActivityDefinition[Input, Output]) Result ¶
func (a ActivityDefinition[Input, Output]) Result(ctx context.Context, key string) (Output, error)
Result reads a completed earlier step in the current handler's workflow. The handler context carries execution identity; callers cannot read a future step or a result with a different activity type.
func (ActivityDefinition[Input, Output]) Step ¶
func (a ActivityDefinition[Input, Output]) Step(key string, input Input, options ...StepOption) (ActivitySpec, error)
Step binds input to a stable key within a workflow. Keys are independent of activity names, so one activity definition can appear more than once.
func (ActivityDefinition[Input, Output]) WithVersion ¶
func (a ActivityDefinition[Input, Output]) WithVersion(version, inputSchema, outputSchema string) ActivityDefinition[Input, Output]
WithVersion declares handler and payload compatibility explicitly. Keep the previous version registered until its forward and cleanup work has drained.
func (ActivityDefinition[Input, Output]) WorkflowResult ¶
func (a ActivityDefinition[Input, Output]) WorkflowResult(ctx context.Context, engine *Engine, workflowID string) (Output, error)
WorkflowResult reads the last step's output after successful completion.
type ActivityKind ¶
type ActivityKind string
ActivityKind distinguishes a forward step from its explicitly declared cleanup.
const ( ActivityForward ActivityKind = "forward" ActivityCompensation ActivityKind = "compensation" )
type ActivityMetadata ¶
type ActivityMetadata struct {
Kind ActivityKind
CompensatesActivityID *string
WorkflowID string
ActivityID string
ActivityName string
ActivityVersion string
InputSchema string
OutputSchema string
StepKey string
WorkerID string
AttemptID string
AttemptNumber int
EffectKey string
AttemptDeadline time.Time
}
ActivityMetadata identifies this delivery and its logical effect. EffectKey stays stable across replacement attempts; AttemptID and AttemptNumber change.
type ActivitySpec ¶
type ActivitySpec struct {
Name string
Version string
InputSchema string
OutputSchema string
Key string
Input json.RawMessage
// contains filtered or unexported fields
}
ActivitySpec is a serialized step declaration persisted as part of a plan.
func NewActivity ¶
func NewActivity[Input any](name string, input Input, options ...StepOption) (ActivitySpec, error)
NewActivity constructs an untyped-output step. Prefer DefineActivity for typed handlers and results. Omitted keys receive stable positional keys at start.
type ActivityState ¶
type ActivityState string
ActivityState describes one persisted step's scheduling and execution state.
const ( ActivityBlocked ActivityState = "blocked" ActivityReady ActivityState = "ready" ActivityRunning ActivityState = "running" ActivityWaiting ActivityState = "retry_wait" ActivityCompleted ActivityState = "completed" ActivityFailed ActivityState = "failed" ActivityTimedOut ActivityState = "timed_out" ActivityCancelled ActivityState = "cancelled" )
type Attempt ¶
type Attempt struct {
ID string
ActivityID string
Number int
WorkerID string
State AttemptState
Error string
StartedAt time.Time
EndedAt *time.Time
RetryDecision string
RetryDueAt *time.Time
DeadlineAt time.Time
}
Attempt records a delivery's outcome, deadline, and retry decision.
type AttemptState ¶
type AttemptState string
AttemptState describes one delivery; every claim consumes the activity's budget.
const ( AttemptRunning AttemptState = "running" AttemptCompleted AttemptState = "completed" AttemptFailed AttemptState = "failed" AttemptAbandoned AttemptState = "abandoned" AttemptTimedOut AttemptState = "timed_out" AttemptCancelled AttemptState = "cancelled" )
type Claim ¶
type Claim struct {
ActivityVersion string
InputSchema string
OutputSchema string
Kind ActivityKind
CompensatesActivityID *string
WorkflowID string
ActivityID string
ActivityName string
StepKey string
Position int
ActivityInput json.RawMessage
AttemptID string
AttemptNumber int
LeaseID string
LeaseToken string
WorkerID string
LeaseExpiresAt time.Time
ClaimedAt time.Time
AttemptDeadline time.Time
}
Claim carries low-level delivery ownership. Keep LeaseToken private; it is a fencing capability, not a stable identity for deduplicating external effects.
type Clock ¶
Clock controls runtime timers and injected test time. SystemClock uses PostgreSQL time for durable mutations; ManualClock controls exact boundaries.
type CompensationPlan ¶
type CompensationPlan struct {
Name string `json:"name"`
Version string `json:"version"`
InputSchema string `json:"input_schema"`
OutputSchema string `json:"output_schema"`
Contract string `json:"contract"`
Input json.RawMessage `json:"input"`
RetryPolicy RetryPolicy `json:"retry_policy"`
AttemptTimeout time.Duration `json:"attempt_timeout_ns"`
}
CompensationPlan is the immutable cleanup declaration stored on a forward activity. Cleanup handlers can also read committed forward step results.
type CompensationState ¶
type CompensationState string
CompensationState is independent of the original workflow failure or cancellation.
const ( CompensationNone CompensationState = "none" CompensationRunning CompensationState = "running" CompensationCompleted CompensationState = "completed" CompensationFailed CompensationState = "failed" )
type Definition ¶
type Definition[Input any] struct { Name string Version string InputSchema string Plan func(Input) ([]ActivitySpec, error) // contains filtered or unexported fields }
Definition turns typed workflow input into a deterministic linear activity plan. A name and version must retain the same meaning while executions using them are in progress. Existing executions continue their stored plan.
func Define ¶
func Define[Input any](name string, plan func(Input) ([]ActivitySpec, error)) Definition[Input]
Define declares a workflow plan. The plan must be deterministic and free of side effects; duplicate keyed starts can evaluate it again before deduplication.
func (Definition[Input]) WithVersion ¶
func (d Definition[Input]) WithVersion(version, inputSchema string) Definition[Input]
WithVersion declares an immutable workflow version and input schema identity. Register old and new versions together while old executions drain.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine coordinates persisted executions. It is safe for concurrent operations.
func New ¶
func New(pool *pgxpool.Pool, registry *Registry, clock Clock, options ...EngineOption) (*Engine, error)
New uses an application-owned pool without doing I/O. Use SystemClock in production. The first operation checks schema compatibility.
func Open ¶
func Open(ctx context.Context, databaseURL string, registry *Registry, clock Clock, options ...EngineOption) (*Engine, error)
Open creates and owns a pool, verifies connectivity, and accepts a known schema prefix so the caller can apply pending migrations. It never migrates implicitly.
func (*Engine) Activities ¶
func (*Engine) Cancel ¶
Cancel durably prevents further forward claims and fences active ownership. The scheduler settles the request without waiting for handler cooperation. Repeated and terminal requests preserve the original outcome and metadata.
func (*Engine) CheckSchema ¶
CheckSchema rejects missing, altered, and unknown migrations. Opening an engine allows a known older schema so its owner can call Migrate first.
func (*Engine) ClaimActivity ¶
func (e *Engine) ClaimActivity(ctx context.Context, workerID string, leaseDuration time.Duration) (Claim, error)
ClaimActivity is a low-level claim without handler capability filtering. Prefer Worker, which claims only compatible registered handlers.
func (*Engine) Close ¶
func (e *Engine) Close()
Close closes only a pool created by Open. Stop workers and the scheduler first.
func (*Engine) CompleteActivity ¶
CompleteActivity atomically acknowledges current ownership, stores JSON output, and advances the stored plan. A duplicate or stale acknowledgement is fenced.
func (*Engine) FailActivity ¶
FailActivity records a non-nil task failure and its persisted retry decision.
func (*Engine) Heartbeat ¶
func (e *Engine) Heartbeat(ctx context.Context, claim Claim, extension time.Duration) (time.Time, error)
Heartbeat extends current ownership up to the attempt and phase deadlines. An expired or fenced token returns ErrLeaseLost, including at exact expiry.
func (*Engine) ListActivities ¶
func (e *Engine) ListActivities(ctx context.Context, workflowID string, request PageRequest) (Page[Activity], error)
ListActivities omits inputs, outputs, cleanup payloads, and diagnostic text by default. Contract identities and due times explain compatible waiting work.
func (*Engine) ListAttempts ¶
func (*Engine) ListLeases ¶
func (e *Engine) ListLeases(ctx context.Context, activityID string, request PageRequest) (Page[Lease], error)
ListLeases never returns ownership tokens, even for sensitive inspection.
func (*Engine) ListTimeline ¶
func (e *Engine) ListTimeline(ctx context.Context, workflowID string, request PageRequest) (Page[TimelineEvent], error)
ListTimeline excludes event details unless sensitive inspection is requested.
func (*Engine) ListWorkflows ¶
func (e *Engine) ListWorkflows(ctx context.Context, filter WorkflowFilter, request PageRequest) (Page[WorkflowSummary], error)
ListWorkflows returns bounded live pages ordered by stable database ordinal. Start a new scan to observe new inserts and concurrent status changes.
func (*Engine) Migrate ¶
Migrate atomically applies pending embedded migrations under an exclusive lock.
func (*Engine) PurgeTerminal ¶
func (e *Engine) PurgeTerminal(ctx context.Context, policy RetentionPolicy) (RetentionResult, error)
PurgeTerminal deliberately deletes terminal execution details and history in bounded workflow batches. It is never called automatically by the scheduler. Keyed input moves to the deduplication record and remains retained indefinitely.
func (*Engine) RecoverAbandoned ¶
RecoverAbandoned reconciles at most limit workflows with expired ownership, deadlines, cancellation requests, or due retries. It returns reconciled work items, which need not equal the workflow count. Prefer Scheduler for a run loop.
type EngineOption ¶
type EngineOption struct {
// contains filtered or unexported fields
}
EngineOption configures optional engine diagnostics.
func WithObserver ¶
func WithObserver(observer Observer) EngineOption
WithObserver adds a non-nil diagnostic observer. Multiple observers are allowed.
type ExecutionExpiredError ¶
type ExecutionExpiredError struct{ ID string }
ExecutionExpiredError identifies a keyed execution whose details were purged. The retained start key still prevents another execution from using that key.
func (*ExecutionExpiredError) Error ¶
func (e *ExecutionExpiredError) Error() string
func (*ExecutionExpiredError) Unwrap ¶
func (e *ExecutionExpiredError) Unwrap() error
type Handler ¶
type Handler func(context.Context, json.RawMessage) (json.RawMessage, error)
Handler is the low-level JSON handler form. Handlers must tolerate redelivery.
type HandlerPanicError ¶
type HandlerPanicError struct{ Value any }
HandlerPanicError records an abnormal handler exit as a task failure. Panics in goroutines created by application handlers remain the application's concern.
func (*HandlerPanicError) Error ¶
func (e *HandlerPanicError) Error() string
func (*HandlerPanicError) Unwrap ¶
func (e *HandlerPanicError) Unwrap() error
type HandlerRegistration ¶
type HandlerRegistration struct {
Name string
Version string
InputSchema string
OutputSchema string
Handler Handler
// contains filtered or unexported fields
}
HandlerRegistration binds a handler to its durable compatibility identities.
type Lease ¶
type Lease struct {
ID string
ActivityID string
AttemptID string
Token string
WorkerID string
State LeaseState
AcquiredAt time.Time
HeartbeatAt time.Time
ExpiresAt time.Time
EndedAt *time.Time
}
Lease records ownership timing. ListLeases never returns its Token.
type LeaseState ¶
type LeaseState string
LeaseState records ownership release or expiry. Only active, unexpired leases acknowledge work.
const ( LeaseActive LeaseState = "active" LeaseReleased LeaseState = "released" LeaseExpired LeaseState = "expired" )
type ManualClock ¶
type ManualClock struct {
// contains filtered or unexported fields
}
ManualClock controls durable timestamps and ticker wakeups in deterministic tests.
func NewManualClock ¶
func NewManualClock(now time.Time) *ManualClock
func (*ManualClock) Advance ¶
func (c *ManualClock) Advance(d time.Duration)
func (*ManualClock) NewTicker ¶
func (c *ManualClock) NewTicker(interval time.Duration) Ticker
NewTicker follows time.Ticker's buffered, coalescing behavior. Advancing time makes due ticks available without waiting for their consumers to run.
func (*ManualClock) Now ¶
func (c *ManualClock) Now() time.Time
type Observation ¶
type Observation struct {
Type string
At time.Time
WorkflowID string
ActivityID string
AttemptID string
WorkerID string
StepKey string
Version string
Kind ActivityKind
Outcome string
Reason string
Duration time.Duration
ReadyAge time.Duration
}
Observation contains diagnostics, never payloads, effect keys, ownership tokens, or arbitrary error text. Committed events are best effort notifications; the execution timeline remains the durable audit record.
type Observer ¶
type Observer interface {
Observe(context.Context, Observation)
}
Observer must be safe for concurrent calls and return promptly without network waits. Transition callbacks run after commit, outside locks. Timing and runtime notifications are local observations. Panics cannot change committed outcomes.
func SlogObserver ¶
SlogObserver emits correlated structured diagnostics with no sensitive text.
type ObserverFunc ¶
type ObserverFunc func(context.Context, Observation)
ObserverFunc adapts a concurrent, prompt callback to Observer.
func (ObserverFunc) Observe ¶
func (f ObserverFunc) Observe(ctx context.Context, event Observation)
type Page ¶
type Page[T any] struct { Items []T `json:"items"` NextCursor string `json:"next_cursor,omitempty"` }
Page is one bounded live read. Continue with NextCursor and identical filters; concurrent commits can change or remove rows between requests.
type PageRequest ¶
PageRequest bounds a live scan. Sensitive exposes diagnostic text and history details, never lease tokens. Application result APIs remain the payload path.
type PendingActivity ¶
type PendingActivity struct {
Name string
Version string
State ActivityState
Kind ActivityKind
AvailableAt time.Time
}
PendingActivity identifies the first unfinished step, including version and retry due time.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry stores process-local definitions and compatible activity contracts.
func NewRegistry ¶
func NewRegistry() *Registry
NewRegistry creates an empty concurrency-safe registry.
type RetentionPolicy ¶
RetentionPolicy selects terminal executions strictly older than CompletedBefore. The cutoff is required and cannot be in the future. Limit defaults to 100, maximum 500.
type RetentionResult ¶
RetentionResult identifies executions whose details were deleted atomically.
type RetryPolicy ¶
type RetryPolicy struct {
MaxAttempts int `json:"max_attempts"`
RetryErrors bool `json:"retry_errors"`
InitialInterval time.Duration `json:"initial_interval"`
MaximumInterval time.Duration `json:"maximum_interval"`
JitterFraction float64 `json:"jitter_fraction"`
}
RetryPolicy is persisted per step. MaxAttempts includes crashes and defaults to three. RetryErrors defaults to false; abandonment can still redeliver.
type RuntimeHealth ¶
type RuntimeHealth struct {
Running bool
Healthy bool
ConsecutiveFailures int
LastError error
LastSuccessAt time.Time
}
RuntimeHealth describes the local Run loop, not fleet-wide readiness. LastError contains runtime errors, never a recorded handler failure.
type Scheduler ¶
type Scheduler struct {
// contains filtered or unexported fields
}
Scheduler recovers expired ownership and drives persisted control transitions.
func NewScheduler ¶
NewScheduler defaults to a one-second poll and batches of 100 workflows.
func (*Scheduler) Health ¶
func (s *Scheduler) Health() RuntimeHealth
Health describes this instance's Run loop, not fleet-wide scheduler availability.
type StartOption ¶
type StartOption struct {
// contains filtered or unexported fields
}
StartOption configures start deduplication, phase deadlines, or reconciliation linkage.
func WithCompensationTimeout ¶
func WithCompensationTimeout(timeout time.Duration) StartOption
WithCompensationTimeout bounds cleanup from its activation, independently of the forward workflow deadline. The default is 24 hours.
func WithPredecessor ¶
func WithPredecessor(id string) StartOption
WithPredecessor links a new reconciliation execution to a terminal execution. The identifier remains recorded if retention later removes the predecessor.
func WithStartKey ¶
func WithStartKey(key string) StartOption
WithStartKey deduplicates starts within a workflow name/version and database. Reusing a key with JSON-equivalent input returns the original execution, including terminal executions. A conflicting input returns ErrStartConflict. After retention, matching input returns ExecutionExpiredError with the original ID. Retention never releases a start key for reuse.
func WithWorkflowTimeout ¶
func WithWorkflowTimeout(timeout time.Duration) StartOption
WithWorkflowTimeout bounds forward execution from its durable start. The default is 24 hours.
type StepOption ¶
type StepOption struct {
// contains filtered or unexported fields
}
StepOption configures persisted retry, timeout, or cleanup behavior.
func WithAttemptTimeout ¶
func WithAttemptTimeout(timeout time.Duration) StepOption
WithAttemptTimeout bounds each delivery, including heartbeats. The default is five minutes.
func WithCompensation ¶
func WithCompensation(cleanup ActivitySpec) StepOption
WithCompensation declares cleanup for this step if it completes before the workflow stops. Cleanup input and policy are persisted with the forward plan.
func WithRetry ¶
func WithRetry(policy RetryPolicy) StepOption
WithRetry sets the step's finite claim budget and optional handler-error retries.
type SystemClock ¶
type SystemClock struct{}
SystemClock uses host time for local diagnostics and PostgreSQL time for durable ownership.
func (SystemClock) Now ¶
func (SystemClock) Now() time.Time
type TaskError ¶
TaskError means a handler failure was successfully recorded. RunOnce exposes it to callers; Run keeps processing other executions. Unwrap preserves causes.
type TickerClock ¶
TickerClock also controls runtime waiting. Clocks that only supply Now keep using real tickers; ManualClock supplies both for deterministic loop tests.
type TimelineEvent ¶
type TimelineEvent struct {
Sequence int64
WorkflowID string
ActivityID *string
AttemptID *string
Type string
Details json.RawMessage
RecordedAt time.Time
}
TimelineEvent records a committed transition in increasing sequence order.
type UnsupportedActivity ¶
type Worker ¶
type Worker struct {
// contains filtered or unexported fields
}
Worker runs one compatible handler at a time. Use multiple instances for concurrency.
func NewWorker ¶
func NewWorker(engine *Engine, config WorkerConfig, registrations ...HandlerRegistration) (*Worker, error)
NewWorker requires a nonblank ID and at least one handler. Defaults are a 30 s lease, heartbeat at one third of it, 100 ms idle poll, and 5 s shutdown grace.
func (*Worker) Health ¶
func (w *Worker) Health() RuntimeHealth
Health describes this instance's Run loop. RunOnce does not maintain loop health.
func (*Worker) Run ¶
Run continues after recorded task failures and cooperative lease loss. Runtime failures use bounded reconnects. Cancellation requests handler shutdown within the grace period; an unresponsive handler prevents another invocation.
func (*Worker) RunOnce ¶
RunOnce claims at most one activity and waits for its handler to finish. It heartbeats while the handler runs and fences completion if ownership is lost.
func (*Worker) UnsupportedActivities ¶
func (w *Worker) UnsupportedActivities(ctx context.Context) ([]UnsupportedActivity, error)
UnsupportedActivities reports up to 100 ready activity types this worker cannot claim. It does not classify blocked steps or claim global worker health.
type WorkerConfig ¶
type WorkerConfig struct {
ID string
LeaseDuration time.Duration
HeartbeatInterval time.Duration
PollInterval time.Duration
DatabaseTimeout time.Duration
ShutdownGracePeriod time.Duration
}
WorkerConfig controls local ownership and shutdown. Zero durations select defaults; DatabaseTimeout plus HeartbeatInterval must fit inside LeaseDuration.
type WorkflowExecution ¶
type WorkflowExecution struct {
DefinitionVersion string
InputSchema string
ID string
DefinitionName string
StartKey *string
Input json.RawMessage
Output json.RawMessage
State WorkflowState
CreatedAt time.Time
UpdatedAt time.Time
CompletedAt *time.Time
TerminalReason string
LastError string
DeadlineAt time.Time
Timeout time.Duration
PreviousExecutionID *string
CancelActor string
CancelReason string
CancelRequestedAt *time.Time
CancelSettledAt *time.Time
OriginalState WorkflowState
CompensationState CompensationState
CompensationError string
CompensationTimeout time.Duration
CompensationDeadlineAt *time.Time
}
WorkflowExecution is a persisted execution snapshot, including application data. Use ListWorkflows for redacted operator summaries.
func Start ¶
func Start[Input any](ctx context.Context, engine *Engine, definition Definition[Input], input Input, options ...StartOption) (WorkflowExecution, error)
Start persists typed input, the full plan, and initial history atomically. The definition must be registered. Use WithStartKey to resolve ambiguous commits.
Example ¶
This example needs PostgreSQL. The repository's cmd/example also runs worker and scheduler processes, reads typed results, and demonstrates deduplication.
package main
import (
"context"
"fmt"
"log"
"os"
"time"
durable "github.com/KleitonBarone/durable-task-runner"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
double := durable.DefineActivity[int, int]("double")
definition := durable.Define("calculate", func(input int) ([]durable.ActivitySpec, error) {
step, err := double.Step("double-input", input)
return []durable.ActivitySpec{step}, err
})
registry := durable.NewRegistry()
if err := durable.Register(registry, definition); err != nil {
log.Fatal(err)
}
engine, err := durable.Open(ctx, os.Getenv("DATABASE_URL"), registry, durable.SystemClock{})
if err != nil {
log.Fatal(err)
}
defer engine.Close()
if err := engine.Migrate(ctx); err != nil {
log.Fatal(err)
}
execution, err := durable.Start(ctx, engine, definition, 21,
durable.WithStartKey("calculation/42"))
if err != nil {
log.Fatal(err)
}
fmt.Println(execution.ID)
// Application workers register double.Handle and run alongside one scheduler.
}
Output:
type WorkflowFilter ¶
type WorkflowFilter struct {
ID string
State WorkflowState
DefinitionName string
DefinitionVersion string
TerminalReason string
CompensationState CompensationState
CreatedBefore *time.Time
}
WorkflowFilter combines optional exact matches and an exclusive creation cutoff.
type WorkflowState ¶
type WorkflowState string
WorkflowState describes forward execution, cancellation, cleanup, or a terminal outcome.
const ( WorkflowRunning WorkflowState = "running" WorkflowCompleted WorkflowState = "completed" WorkflowFailed WorkflowState = "failed" WorkflowTimedOut WorkflowState = "timed_out" WorkflowCancelRequested WorkflowState = "cancel_requested" WorkflowCancelled WorkflowState = "cancelled" WorkflowCompensating WorkflowState = "compensating" WorkflowCompensationFailed WorkflowState = "compensation_failed" )
type WorkflowSummary ¶
type WorkflowSummary struct {
ID string
DefinitionName string
DefinitionVersion string
State WorkflowState
CreatedAt time.Time
UpdatedAt time.Time
CompletedAt *time.Time
Age time.Duration
DeadlineAt time.Time
TerminalReason string
HasError bool // Failure outcome or retained diagnostic text, even if redacted.
LastError string `json:",omitempty"`
CancelActor string `json:",omitempty"`
CancelReason string `json:",omitempty"`
CancelRequestedAt *time.Time
CancelSettledAt *time.Time
OriginalState WorkflowState
CompensationState CompensationState
CompensationError string `json:",omitempty"`
NextActivity *PendingActivity
}
WorkflowSummary omits payloads and keys. Diagnostic text is opt-in through PageRequest.Sensitive. Age is measured using the engine's time authority.
Source Files
¶
- activity.go
- cancellation.go
- capabilities.go
- clock.go
- compensation.go
- deadline.go
- doc.go
- engine.go
- errors.go
- id.go
- inspection.go
- metadata.go
- migrations.go
- model.go
- observation.go
- ownership.go
- pagination.go
- payload.go
- query.go
- recovery.go
- registry.go
- results.go
- retention.go
- retry.go
- runtime.go
- scheduler.go
- start.go
- state.go
- version.go
- worker.go
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
dev
command
Command dev provides the repository's local development and verification loop.
|
Command dev provides the repository's local development and verification loop. |
|
durable
command
Command durable migrates and inspects an application-owned engine database.
|
Command durable migrates and inspects an application-owned engine database. |
|
example
command
Command example demonstrates application-owned startup, workers, and recovery.
|
Command example demonstrates application-owned startup, workers, and recovery. |
|
internal
|
|
|
testdb
Package testdb owns isolated PostgreSQL schemas for integration and process tests.
|
Package testdb owns isolated PostgreSQL schemas for integration and process tests. |
|
Package telemetry optionally adapts durable runtime observations to application-owned OpenTelemetry providers.
|
Package telemetry optionally adapts durable runtime observations to application-owned OpenTelemetry providers. |