Documentation
¶
Index ¶
- Constants
- Variables
- func ExecuteInlineActivity[I, O proto.Message, N ActivityName[I, O]](ctx WorkflowContext, name N, input I, opts ...ExecuteInlineActivityOption) (O, error)
- func Metadata[M proto.Message](ctx WorkflowContext) (M, error)
- func New(ctx context.Context, cfg *Config) (*Engine, Client, error)
- func RegisterActivity[I, O proto.Message, N ActivityName[I, O]](r *Registry, name N, fn func(ctx ActivityContext, input I) (O, error), ...)
- func RegisterWorkflow[I, O proto.Message, N WorkflowName[I, O]](r *Registry, name N, fn func(ctx WorkflowContext, input I) (O, error), ...)
- type AbortCause
- type AbortError
- type ActivityContext
- type ActivityExecutionDisposition
- type ActivityExecutionResult
- type ActivityName
- type ActivityPollPredicate
- type CancelWorkflowOptions
- type CentralDB
- type Channel
- type ChildFuture
- type ChildWorkflowOptions
- type Client
- type Config
- type Dropper
- type Engine
- type ExecuteActivityOption
- type ExecuteInlineActivityOption
- type ExecuteWorkflowOptions
- type ExecuteWorkflowResult
- type ExternalNamespaceID
- type Future
- type MembershipView
- type ReadinessAnnouncer
- type ReadinessTracker
- type ReadinessTrackerOption
- type RegisterActivityOption
- type RegisterWorkflowOption
- type Registry
- type RetryPolicy
- type Selectable
- type Selector
- type SendToWorkflowOptions
- type SequenceID
- type ShardID
- type SignalID
- type SignalPromotionContext
- type TerminalObserver
- type TimerFuture
- type TypedWorkflowInfo
- type WorkerManager
- func (wm *WorkerManager) AddShard(ctx context.Context, shardID persistence.ShardID, fenceID persistence.FenceID) bool
- func (wm *WorkerManager) AddShards(ctx context.Context, claimed []persistence.ClaimedShard) []persistence.ShardID
- func (wm *WorkerManager) DrainShard(shardID persistence.ShardID)
- func (wm *WorkerManager) InFlightByShard() map[persistence.ShardID]int
- func (wm *WorkerManager) NotifySignalReady(shardID persistence.ShardID) bool
- func (wm *WorkerManager) NotifyWorkflowTaskReady(shardID persistence.ShardID) bool
- func (wm *WorkerManager) RemoveShard(ctx context.Context, shardID persistence.ShardID, fenceID persistence.FenceID, ...) bool
- func (wm *WorkerManager) Run(ctx context.Context)
- type WorkerManagerOption
- func WithWMActivityPollInterval(d time.Duration) WorkerManagerOption
- func WithWMMaxChildWorkflowDepth(n int) WorkerManagerOption
- func WithWMMaxChildrenPerParent(n int) WorkerManagerOption
- func WithWMMinimumTick(d time.Duration) WorkerManagerOption
- func WithWMOrphanSweepInterval(d time.Duration) WorkerManagerOption
- func WithWMRecoveryBatchSize(n int64) WorkerManagerOption
- func WithWMSignalScanInterval(d time.Duration) WorkerManagerOption
- func WithWMSuspensionTTL(d time.Duration) WorkerManagerOption
- func WithWMTimerHintThreshold(d time.Duration) WorkerManagerOption
- func WithWMTimerScanInterval(d time.Duration) WorkerManagerOption
- func WithWMWorkflowPollInterval(d time.Duration) WorkerManagerOption
- type WorkflowContext
- type WorkflowDB
- type WorkflowDBs
- type WorkflowExecutionResult
- type WorkflowID
- type WorkflowInfo
- type WorkflowKey
- type WorkflowName
- type WorkflowState
Constants ¶
const ( WorkflowStateUnknown = persistence.WorkflowExecutionStateUnknown WorkflowStateRunning = persistence.WorkflowExecutionStateRunning WorkflowStateCompleted = persistence.WorkflowExecutionStateCompleted WorkflowStateFailed = persistence.WorkflowExecutionStateFailed WorkflowStateCanceled = persistence.WorkflowExecutionStateCanceled WorkflowStateTimedOut = persistence.WorkflowExecutionStateTimedOut WorkflowStateSystemFailed = persistence.WorkflowExecutionStateSystemFailed )
const ( // DefaultScheduleToCompleteTimeout is applied to every workflow whose // ExecuteWorkflowOptions.ScheduleToCompleteTimeout is zero. The deadline // cannot be disabled per workflow. DefaultScheduleToCompleteTimeout = 30 * 24 * time.Hour // MaxScheduleToCompleteTimeout is the hard upper bound on any per-workflow // schedule-to-complete deadline (the default or a per-call override). MaxScheduleToCompleteTimeout = 60 * 24 * time.Hour )
const MaxHistoryResultSize = 262144 // 256 KiB
MaxHistoryResultSize is the marshaled size a result carried by a history event accepts.
const ReservedPrefix = persistence.ReservedPrefix
ReservedPrefix is the namespace autocore keeps for the names it generates itself. A caller may not supply an idempotency key or a channel name that starts with it.
Variables ¶
var ( ErrCanceled = errors.New("workflow canceled") // ErrTimedOut marks a workflow terminated because its schedule-to-complete // deadline elapsed. Surfaced to pending futures like ErrCanceled, but maps // to the TimedOut terminal rather than Canceled. ErrTimedOut = errors.New("workflow timed out") // ErrSystemFailure marks a failure the system caused (e.g. an activity whose // abort retry budget was exhausted or that hit a deterministic internal // error) rather than a deliberate error return. A workflow that propagates // it unhandled terminates as SystemFailed instead of Failed. ErrSystemFailure = errors.New("system failure") // ErrNonRetryable marks an activity failure that re-running cannot change. An // activity wraps its error with it when every attempt fails identically, so the // failure is terminal at once instead of spending the retry budget re-running // the body, side effects included, on the way to the same outcome. It says // nothing about the workflow's terminal state: the failure reaches the workflow // as any other activity failure does. ErrNonRetryable = errors.New("non-retryable") // ErrFutureDropped is what a dropped future's Get returns. Deliberately not // ErrCanceled, which means the workflow itself was canceled and maps to the // Canceled terminal: a workflow that drops one of its own operations and then // propagates the error unhandled has failed, not been canceled. ErrFutureDropped = errors.New("future dropped") ErrReservedIdempotencyKeyPrefix = fmt.Errorf("idempotency_key starting with %q is reserved for engine-internal use", persistence.ReservedPrefix) ErrChildFanOutExceeded = errors.New("child workflow fan-out cap exceeded") ErrChildDepthExceeded = errors.New("child workflow depth cap exceeded") // ErrMetadataTooLarge is returned when workflow metadata marshals to more than // maxWorkflowMetadataSize bytes. ErrMetadataTooLarge = errors.New("workflow metadata too large") // ErrInputTooLarge is returned by ExecuteWorkflow when the input marshals to // more than maxWorkflowInputSize bytes. A caller error: no retry of the same // input can succeed. ErrInputTooLarge = errors.New("workflow input too large") ErrWorkflowNotFound = persistence.ErrWorkflowNotFound // ErrWorkflowNotRunning is returned by SendToWorkflow when the target workflow // has already reached a terminal state and can never observe the signal. ErrWorkflowNotRunning = persistence.ErrWorkflowNotRunning // ErrSignalLimitExceeded is returned by SendToWorkflow when the target workflow // has spent its signal history budget. Permanent for that workflow: a retry // cannot succeed. ErrSignalLimitExceeded = persistence.ErrSignalLimitExceeded // ErrInvalidWorkflowKey is returned by ParseWorkflowKey when the input is not a // well-formed workflow key. Callers can map it to a permanent client error. ErrInvalidWorkflowKey = errors.New("invalid workflow key") // ErrIdempotencyKeyRetired is returned when the central directory pins a key to // a workflow id no partition covers, and only for an id adopted from the // directory: an id the call minted itself is a gap in the partition runway and // stays retryable. Usually the entry outlived its workflow, meaning the retention // sweep has not run for long enough, and no retry can succeed until the sweep // drops the entry on its own horizon. A key whose first submission fell in a // runway gap reports this too, and does succeed once the runway heals. ErrIdempotencyKeyRetired = errors.New("idempotency key names a workflow whose data has been retired") )
var ErrPollExhausted = errors.New("poll exhausted without satisfying predicate")
ErrPollExhausted is the error a poll activity's future resolves to when its poll deadline passes without the predicate ever passing.
Functions ¶
func ExecuteInlineActivity ¶
func ExecuteInlineActivity[I, O proto.Message, N ActivityName[I, O]](ctx WorkflowContext, name N, input I, opts ...ExecuteInlineActivityOption) (O, error)
ExecuteInlineActivity invokes a registered activity synchronously inside the workflow goroutine: no activity_task row, no separate worker. Its outcome is recorded as a single InlineActivity{Completed,Failed} event in a dedicated fenced tx committed before this returns, and replay reads that outcome without re-running the body. An input that violates its own proto rules is returned as an error before the body runs, so nothing is recorded.
There are no retries; WithInlineTimeout bounds execution time, and a caller wanting retries builds its own (a NewTimer backoff loop, or a fall back to ExecuteActivity).
The body must be idempotent at its destination: a worker crash between the body and the history-commit tx re-invokes it on round retry. The result is recorded at most once, but the body can run more than once.
func Metadata ¶ added in v19.3.0
func Metadata[M proto.Message](ctx WorkflowContext) (M, error)
Metadata returns the metadata the running workflow was created with, decoded as M. A workflow created without metadata yields the zero M, so a caller that requires it checks the decoded message rather than an error.
The metadata comes from the workflow's execution row, which is the only place it lives, so what a workflow reads here is what a signal promotion interceptor is handed for the same workflow.
func RegisterActivity ¶
func RegisterActivity[I, O proto.Message, N ActivityName[I, O]](r *Registry, name N, fn func(ctx ActivityContext, input I) (O, error), opts ...RegisterActivityOption[O])
RegisterActivity registers a typed activity function. Options attach optional per-activity capabilities such as a poll predicate (see WithPollPredicate).
func RegisterWorkflow ¶
func RegisterWorkflow[I, O proto.Message, N WorkflowName[I, O]](r *Registry, name N, fn func(ctx WorkflowContext, input I) (O, error), opts ...RegisterWorkflowOption)
RegisterWorkflow registers a typed workflow function.
Types ¶
type AbortCause ¶
type AbortCause string
AbortCause categorizes why an in-flight task run produced no persisted result. Aborted runs do not consume the application retry budget; the row stays in 'running' until the orphan sweep, or the recovery on the next shard claim, re-enqueues it. The exception is a transaction whose COMMIT was reported as failed after the server had made it durable: the row moved, and the abort handler detects that as a lost transition.
const ( // AbortCauseFenced: this instance lost the shard to another instance // mid-run (errFencedOut), or a fenced transaction returned ErrFenced. AbortCauseFenced AbortCause = "fenced" // AbortCauseShardReleased: this instance voluntarily released the shard // mid-run (errShardReleased). AbortCauseShardReleased AbortCause = "released" // AbortCauseShutdown: parent context canceled (typically pool shutdown) // without a more specific cause. AbortCauseShutdown AbortCause = "shutdown" // AbortCausePersistFailed: a fenced transaction returned a non-fence // error before a result was committed. AbortCausePersistFailed AbortCause = "persist_failed" // AbortCauseInternalError: a pre-execute setup failure (proto decode, // workflow or activity not registered, build-context failure). Indicates // a bug or configuration drift, not infrastructure flakiness. AbortCauseInternalError AbortCause = "internal_error" // AbortCauseUnknown: cause could not be classified -- e.g. parent // context canceled with a cause we don't recognize, or shard ownership // lost without a cancellation cause having propagated yet (race with // RemoveShard). Indicates a gap in classification rather than a known // failure mode; investigate if it stops being rare. AbortCauseUnknown AbortCause = "unknown" // AbortCauseRecovered: the recovery sweep on a shard handover re-pended the // row and bumped its abort_retry_attempt; the claim enforces the budget. The // cause of the abort that left the row running is not recorded on the row. AbortCauseRecovered AbortCause = "recovered" )
type AbortError ¶
type AbortError struct {
Cause AbortCause
Err error
}
AbortError pairs an AbortCause with the underlying error (if any).
func (*AbortError) Error ¶
func (e *AbortError) Error() string
func (*AbortError) LogAttrs ¶
func (e *AbortError) LogAttrs() slog.Attr
LogAttrs returns the abort cause and underlying error (if any) as a single slog.Attr. The empty group name flattens both fields to the top level of the log record so they appear as plain abort_cause and error keys, not nested under a group.
func (*AbortError) Unwrap ¶
func (e *AbortError) Unwrap() error
type ActivityContext ¶
type ActivityContext interface {
context.Context
Logger() *slog.Logger
WorkflowKey() WorkflowKey
IdempotencyKey() string
ExternalNamespaceID() ExternalNamespaceID
}
type ActivityExecutionDisposition ¶
type ActivityExecutionDisposition string
ActivityExecutionDisposition is the second dimension of the activity outcome (for non-aborted runs only): whether the persisted attempt was the final outcome or a retry was scheduled. Only meaningful when ActivityExecutionResult is not Aborted.
const ( // ActivityExecutionDispositionSucceeded: result was Completed; no further // attempts. ActivityExecutionDispositionSucceeded ActivityExecutionDisposition = "succeeded" // ActivityExecutionDispositionRetried: result was Failed or TimedOut and the // activity has remaining attempts; a retry was scheduled. ActivityExecutionDispositionRetried ActivityExecutionDisposition = "retried" // ActivityExecutionDispositionExhausted: result was Failed or TimedOut and no // attempts remain; the activity is terminally failed. ActivityExecutionDispositionExhausted ActivityExecutionDisposition = "exhausted" )
type ActivityExecutionResult ¶
type ActivityExecutionResult string
ActivityExecutionResult is the typed outcome of a single activity execution. Aligned across metrics, traces, and logs via pre-computed attribute KVs.
const ( ActivityExecutionResultCompleted ActivityExecutionResult = "completed" ActivityExecutionResultFailed ActivityExecutionResult = "failed" ActivityExecutionResultTimedOut ActivityExecutionResult = "timed_out" // ActivityExecutionResultAborted: execution produced no persisted // result (shard fenced, shard released, shutdown, persist failure, // internal error). Recorded via the activity aborts counter, not the // execution duration histogram. An abort never consumes the app RetryPolicy // budget. On shard loss the activity_task row stays 'running' for the // recovery sweep, which bumps abort_retry_attempt; on a persist failure the // abort handler bumps it and re-pends the row through the retry path, or // terminal-fails it once the abort budget is spent. ActivityExecutionResultAborted ActivityExecutionResult = "aborted" // ActivityExecutionResultNotReady: a poll activity completed but its // predicate was not satisfied. It re-polls on the poll interval without // consuming the failure RetryPolicy attempt budget, until the predicate // passes or the poll deadline is reached (then it exhausts). ActivityExecutionResultNotReady ActivityExecutionResult = "not_ready" // ActivityExecutionResultInvalid: a failure re-running cannot change. Either // the activity produced an output the engine refuses, typically one that does // not satisfy the proto rules of the type it was registered with, or it // returned an error wrapping ErrNonRetryable. Every attempt reaches the same // outcome, so it exhausts immediately rather than consuming the failure // RetryPolicy budget and re-running the body, side effects included, on the // way there. ActivityExecutionResultInvalid ActivityExecutionResult = "invalid" )
type ActivityName ¶
type ActivityPollPredicate ¶ added in v19.2.0
type ActivityPollPredicate[O proto.Message] func(ctx ActivityContext, output O, config []byte) (bool, error)
ActivityPollPredicate decides, from a typed activity output and the per-call opaque config, whether a poll is satisfied. It runs on the activity worker at completion, before any history is written.
(true, nil) -> satisfied; record terminal success (false, nil) -> not satisfied; re-poll via the activity retry path (_, err) -> terminal failure
Register one per pollable activity via RegisterActivity + WithPollPredicate.
type CancelWorkflowOptions ¶
type CancelWorkflowOptions struct {
WorkflowKey WorkflowKey
}
type CentralDB ¶ added in v19.4.0
type CentralDB interface {
OpenPool(ctx context.Context, opts ...pgtool.PoolOption) (*pgxpool.Pool, error)
OpenMigrationDB() (*sql.DB, error)
}
CentralDB opens handles on the central database, with the same roles as a workflow database. It has no DDL pool because only workflow databases are partitioned.
type Channel ¶ added in v19.2.0
type Channel[O proto.Message] interface { Selectable Receive() (O, error) Name() string }
Channel yields successive values over time, like a Go channel: Receive may be called repeatedly.
func NewChannel ¶ added in v19.2.0
func NewChannel[O proto.Message](ctx WorkflowContext) Channel[O]
NewChannel constructs a typed channel sourcing values from the underlying channel on the workflow context.
func NewNamedChannel ¶ added in v19.3.0
func NewNamedChannel[O proto.Message](ctx WorkflowContext, name string) Channel[O]
NewNamedChannel constructs a typed channel for a caller-chosen channel name, letting a workflow receive values that a sender addresses by that name. The name must be 1 to 255 bytes, the range a send accepts, and must not use persistence.ReservedPrefix, which autocore keeps for the channels it names itself. The name alone identifies the channel: the type parameter is not part of its identity, so two differently typed handles for one name share the underlying channel.
type ChildFuture ¶
type ChildFuture[O proto.Message] interface { Future[O] WorkflowID() WorkflowID }
ChildFuture is a Future plus the deterministic child workflow ID, available synchronously (before persistence).
func ExecuteChildWorkflow ¶
func ExecuteChildWorkflow[I, O proto.Message, N WorkflowName[I, O]](ctx WorkflowContext, name N, input I, opts *ChildWorkflowOptions) ChildFuture[O]
ExecuteChildWorkflow schedules an awaitable child workflow on the same shard as the parent. The returned ChildFuture exposes the deterministic child WorkflowID synchronously and resolves once the child reaches a terminal state.
Parent termination cancels pending awaitable children via the signal inbox: each child observes the cancellation on its next yield and unwinds.
type ChildWorkflowOptions ¶ added in v19.3.0
type ChildWorkflowOptions struct {
// Metadata is extra information about the child workflow execution, available
// to interceptors. It is not inherited from the parent: a child that needs the
// parent's metadata is given it explicitly here. Mirrors
// ExecuteWorkflowOptions.Metadata.
Metadata proto.Message
}
ChildWorkflowOptions are the per-child options of ExecuteChildWorkflow and StartChildWorkflow. The zero value is all defaults.
type Client ¶
type Client interface {
// ExecuteWorkflow executes the named workflow.
// Do not use this method directly, use the ExecuteWorkflow function.
ExecuteWorkflow(ctx context.Context, name string, input proto.Message, opts *ExecuteWorkflowOptions) (*ExecuteWorkflowResult, error)
CancelWorkflow(ctx context.Context, opts *CancelWorkflowOptions) error
// SendToWorkflow puts a value on one of the target workflow's channels.
// Returns ErrWorkflowNotFound if no such workflow exists, ErrWorkflowNotRunning
// if it is already terminal, and ErrSignalLimitExceeded once it has spent its
// signal history budget. The latter two are permanent for that workflow.
//
// TODO: I think we want to align the name here with the workflow context.
// Maybe SendMessageToWorkflow and ReceiveMessage (instead of ReceiveChannel)?
// Not sure yet.
SendToWorkflow(ctx context.Context, opts *SendToWorkflowOptions) error
// GetWorkflowInfo returns a read-only snapshot of a workflow's execution
// state and terminal result. Returns ErrWorkflowNotFound if no workflow
// exists for the key.
GetWorkflowInfo(ctx context.Context, key WorkflowKey) (*WorkflowInfo, error)
// GetWorkflowInfoWithMetadata is GetWorkflowInfo and GetWorkflowMetadata in one
// read of one row, for a caller that needs both. A caller that only needs the
// status should use GetWorkflowInfo, which does not touch the payload column.
// Returns ErrWorkflowNotFound if no workflow exists for the key.
GetWorkflowInfoWithMetadata(ctx context.Context, key WorkflowKey) (*WorkflowInfo, []byte, error)
// GetWorkflowMetadata returns the marshaled opaque metadata supplied via
// ExecuteWorkflowOptions.Metadata when the workflow was created, or nil when
// none was supplied. Returns ErrWorkflowNotFound if no workflow exists for
// the key.
GetWorkflowMetadata(ctx context.Context, key WorkflowKey) ([]byte, error)
}
type Config ¶
type Config struct {
Log *slog.Logger
MeterProvider metric.MeterProvider
TracerProvider trace.TracerProvider
ErrCapturer errz.ErrCapturer
Validator protovalidate.Validator
RedisClient rueidis.Client
RedisKeyPrefix string
InstanceID int64
CentralDB CentralDB
WorkflowDBs WorkflowDBs
Registry *Registry
// OnReady is called once the central database is migrated, before any work
// starts.
OnReady func()
// If WorkflowPoolSize and ActivityPoolSize are 0, this Engine instance doesn't perform any work.
// Only the client is functional.
WorkflowPoolSize int
MaxSuspendedWorkflows int
ActivityPoolSize int
}
type Dropper ¶ added in v19.3.0
type Dropper interface {
Drop()
}
Dropper is implemented by futures the workflow can stop tracking. Drop reclaims whatever durable state backs the operation, so nothing wakes the workflow for a result it will never read. It is idempotent, and a no-op once the operation has completed.
Drop records nothing in history and needs no event to be replay safe: the decision belongs to the workflow's own code, so a replay re-runs the same Drop call and reaches the same resolution without reading anything back. Get on a dropped future returns ErrFutureDropped, and keeps doing so even if the operation had in fact completed first, so the resolution does not depend on that race.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
type ExecuteActivityOption ¶
type ExecuteActivityOption func(*executeActivityOptions)
func WithClaimToCompleteTimeout ¶
func WithClaimToCompleteTimeout(d time.Duration) ExecuteActivityOption
func WithInlineFirst ¶ added in v19.3.0
func WithInlineFirst(timeout time.Duration) ExecuteActivityOption
WithInlineFirst attempts the activity in the calling workflow's goroutine, bounded by timeout, before queueing it as a task. On success nothing is queued and the returned future is already resolved, so the activity costs one history event and no activity_task row. On failure the activity is queued as usual and retried under the configured policy, and the attempt is not recorded: the queued execution takes over its replay key, so history holds one event either way.
The body must therefore be idempotent at its destination, since a failed attempt may have got far enough to have an effect. Both executions present the same idempotency key so a destination deduping on it sees one logical invocation.
Only for bodies that are quick and in-process: the workflow's round stalls for the attempt's duration. Exceeding timeout costs nothing beyond queueing the activity, but the bound is cooperative -- it cancels the body's context, and a body that ignores it stalls the round for as long as it runs.
func WithPollConfig ¶ added in v19.2.0
func WithPollConfig(config []byte, interval time.Duration, deadline time.Time) ExecuteActivityOption
WithPollConfig marks the activity invocation as a poll. config is the opaque config handed to the activity's registered poll predicate after each successful execution. A not-ready result re-polls after interval until deadline, at which point the poll is exhausted. Polling is bounded by deadline, independently of the failure RetryPolicy (which still governs genuine activity failures).
func WithRetryPolicy ¶
func WithRetryPolicy(p *RetryPolicy) ExecuteActivityOption
WithRetryPolicy overrides the default retry policy entirely. The caller must set all fields - zero-valued fields are not merged with defaults.
type ExecuteInlineActivityOption ¶
type ExecuteInlineActivityOption func(*executeInlineActivityOptions)
func WithInlineTimeout ¶
func WithInlineTimeout(d time.Duration) ExecuteInlineActivityOption
WithInlineTimeout overrides the default execution timeout for an inline activity. On expiry the activity's context.Context is canceled; a well- behaved body returns (typically context.DeadlineExceeded). The error is recorded in history and replay returns the same recorded error.
type ExecuteWorkflowOptions ¶
type ExecuteWorkflowOptions struct {
// ExternalNamespaceID is the caller's id of the namespace the workflow runs
// in. Required, must be positive.
ExternalNamespaceID ExternalNamespaceID
// IdempotencyKey deduplicates workflow creation within the namespace. Required,
// 1-255 bytes; the engine's reserved prefix is rejected. A user that wants a
// workflow per call must mint a key that cannot collide, rather than leaving
// this empty.
IdempotencyKey string
// ScheduleToCompleteTimeout overrides the engine's default per-workflow
// schedule-to-complete deadline. Zero uses the engine default; a positive
// value overrides it. The deadline cannot be disabled per workflow.
ScheduleToCompleteTimeout time.Duration
// Metadata is extra information about the workflow execution, available to interceptors.
Metadata proto.Message
}
type ExecuteWorkflowResult ¶
type ExecuteWorkflowResult struct {
WorkflowKey WorkflowKey
AlreadyExists bool
}
func ExecuteWorkflow ¶
func ExecuteWorkflow[I, O proto.Message, N WorkflowName[I, O]](ctx context.Context, client Client, name N, input I, opts *ExecuteWorkflowOptions) (*ExecuteWorkflowResult, error)
type ExternalNamespaceID ¶
type ExternalNamespaceID = persistence.ExternalNamespaceID
ExternalNamespaceID is the namespace id supplied by the caller when starting a workflow; autocore maps it to an internal namespace surrogate.
type Future ¶
type Future[O proto.Message] interface { Selectable Get() (O, error) }
Future represents the result of an asynchronous operation (activity or timer). It is safe to call Get() multiple times.
func ExecuteActivity ¶
func ExecuteActivity[I, O proto.Message, N ActivityName[I, O]](ctx WorkflowContext, name N, input I, opts ...ExecuteActivityOption) Future[O]
ExecuteActivity schedules the named activity and returns a future for its result. An input that violates its own proto rules is a caller error and is reported through the returned future, so the workflow handles it like any other activity failure.
type MembershipView ¶ added in v19.4.0
type MembershipView struct {
// Index is this instance's position among the ready instances, ordered by
// instance ID. It is in [0, Count).
Index int
// Count is the number of ready instances, at least 1.
Count int
// Observed reports whether a fleet has ever been seen. Without one there is
// no evidence that any peer exists, let alone that one died.
Observed bool
// Fresh reports whether the shared store still shows the fleet this view
// describes. A view goes stale while the store is unreachable and while a
// changed fleet has yet to hold still long enough to be accepted.
Fresh bool
// StaleFor is how long the view has described a fleet the store no longer
// shows. Zero while Fresh.
StaleFor time.Duration
// Evicted reports that the store lists other instances but not this one, for
// long enough that they have resized without it. Its leases are then the only
// thing keeping them from covering its shards.
Evicted bool
}
MembershipView is the fleet an instance sizes itself against.
type ReadinessAnnouncer ¶
type ReadinessAnnouncer interface {
// Announce registers this instance as ready in the shared store.
Announce(ctx context.Context) error
// Membership returns the fleet view with hysteresis applied.
Membership(ctx context.Context) MembershipView
// Deregister removes this instance from the ready set.
Deregister(ctx context.Context) error
// GC removes expired entries from the backing store.
GC(ctx context.Context)
}
ReadinessAnnouncer handles instance readiness announcements and peer discovery.
type ReadinessTracker ¶
type ReadinessTracker struct {
// contains filtered or unexported fields
}
ReadinessTracker implements ReadinessAnnouncer using a Redis hash via the ExpiringHashAPI abstraction. The tracker applies hysteresis to the observed member set to dampen transient fleet-size fluctuations.
func NewReadinessTracker ¶
func NewReadinessTracker( log *slog.Logger, client rueidis.Client, redisKeyPrefix string, instanceID int64, meter metric.Meter, errCapturer errz.ErrCapturer, opts ...ReadinessTrackerOption, ) (*ReadinessTracker, error)
func (*ReadinessTracker) Deregister ¶
func (r *ReadinessTracker) Deregister(ctx context.Context) error
func (*ReadinessTracker) GC ¶
func (r *ReadinessTracker) GC(ctx context.Context)
func (*ReadinessTracker) Membership ¶ added in v19.4.0
func (r *ReadinessTracker) Membership(ctx context.Context) MembershipView
type ReadinessTrackerOption ¶
type ReadinessTrackerOption func(*ReadinessTracker)
func WithMaxAcceptedAge ¶ added in v19.4.0
func WithMaxAcceptedAge(d time.Duration) ReadinessTrackerOption
func WithReadinessTTL ¶
func WithReadinessTTL(d time.Duration) ReadinessTrackerOption
func WithStabilityThreshold ¶
func WithStabilityThreshold(n int) ReadinessTrackerOption
type RegisterActivityOption ¶ added in v19.2.0
RegisterActivityOption configures an activity registration. O is the activity's output type, so options can be typed against it.
func WithPollPredicate ¶ added in v19.2.0
func WithPollPredicate[O proto.Message](predicate ActivityPollPredicate[O]) RegisterActivityOption[O]
WithPollPredicate registers a typed poll predicate for the activity. When the activity is invoked as a poll (ExecuteActivity with WithPollConfig), the worker hands its output to predicate, which decides whether the poll is satisfied. The output is unmarshaled into O here, so the predicate is fully typed.
type RegisterWorkflowOption ¶ added in v19.2.0
func WithSignalPromotionInterceptor ¶ added in v19.2.0
func WithSignalPromotionInterceptor[M proto.Message](f func(log *slog.Logger, sCtx *SignalPromotionContext, metadata M) (bool, error)) RegisterWorkflowOption
WithSignalPromotionInterceptor sets an interceptor called for each channel signal being promoted, so the workflow can authorize or drop it. An interceptor may rewrite sCtx.Payload in place, and the rewritten bytes are what the history event carries. Engine-internal signals (cancellation, child terminal) never reach an interceptor: they are one-shot, their idempotency key is retained after consumption, and dropping one would defeat cancellation or strand a parent waiting on a child.
The interceptor runs inside the promotion transaction, which holds row locks and pins the vacuum horizon while it runs, and its decision is final: a false return and an error alike consume the signal for good. It must therefore be CPU-only and deterministic. No I/O, no blocking, no dependence on the wall clock: an error is a permanent drop, not a retry. Anything time-dependent judges by sCtx.CreatedAt, the signal's creation time, so that a rolled back promotion re-running later reaches the same verdict. A panic is recovered and treated as a drop.
func WithTerminalObserver ¶ added in v19.4.0
func WithTerminalObserver(observe TerminalObserver) RegisterWorkflowOption
WithTerminalObserver sets the observer told of every terminal the workflow's own return commits. It runs after the commit, outside any transaction, and must not block.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry is not thread-safe. All registrations must happen before the registry is used by workers.
func NewRegistry ¶
func NewRegistry(validator protovalidate.Validator) *Registry
type RetryPolicy ¶
type RetryPolicy = persistence.RetryPolicy
type Selectable ¶ added in v19.3.0
type Selectable interface {
// ReadyAt reports whether the operation can be consumed without blocking
// and, when it can, the sequence id of the history event that made it ready,
// or 0 when no event did.
//
// Ids are comparable across operations and stable across a replay: an
// operation that is not ready can only become ready at a higher id than one
// that already is. Among several ready operations the lowest id is therefore
// the one that became ready first, and a replay picks the same operation as
// the live execution did even though it has seen more of the history.
//
// While a workflow is terminating, an operation that had not completed before
// the cancellation reports the cancellation event's id.
//
// Workflow code must not call this and must never branch on readiness:
// readiness is the one piece of state that differs between live execution and
// replay, because replay loads the whole history up front. The method is
// exported only so that a Selectable implemented in another package can
// forward readiness to a Selector. Blocking on the operation, or letting a
// Selector rank it, are the replay-safe ways to consume it.
ReadyAt() (SequenceID, bool)
}
Selectable is an operation whose readiness a Selector can rank. Futures and channels are selectable, but most are consumed directly with Get or Receive and never reach a Selector.
type Selector ¶
type Selector interface {
AddCase(source Selectable, fn func()) Selector
Select()
HasPending() bool
}
type SendToWorkflowOptions ¶
type SendToWorkflowOptions struct {
WorkflowKey WorkflowKey
IdempotencyKey string // user-supplied dedup key; required, 1-255 bytes, reserved prefix rejected
ChannelName string
Payload proto.Message
}
type SequenceID ¶ added in v19.3.0
type SequenceID = persistence.SequenceID
type ShardID ¶ added in v19.2.0
type ShardID = persistence.ShardID
type SignalID ¶ added in v19.2.0
type SignalID = persistence.SignalID
type SignalPromotionContext ¶ added in v19.2.0
type SignalPromotionContext struct {
ShardID ShardID
WorkflowID WorkflowID
SignalID SignalID
SignalName string
Payload []byte
// CreatedAt is when the signal was accepted into the inbox, read off the
// database clock at insert and never zero. It is the clock an interceptor
// judges by.
CreatedAt time.Time
}
type TerminalObserver ¶ added in v19.4.0
type TerminalObserver func(result WorkflowExecutionResult, err error)
TerminalObserver is told the terminal a workflow's own return was committed under, once per committed terminal, with the error the return carried; the error includes an output the registry refused. A terminal the engine writes without the workflow returning, such as an exhausted abort budget, is not observed.
type TypedWorkflowInfo ¶
type TypedWorkflowInfo[O proto.Message] struct { WorkflowKey WorkflowKey Name string IdempotencyKey string State WorkflowState CreatedAt time.Time UpdatedAt time.Time Output O Error string }
TypedWorkflowInfo is the generic counterpart of WorkflowInfo returned by the GetWorkflowInfoWithMetadata helper. Output is the workflow's output decoded into O, set only when State is Completed.
func GetWorkflowInfoWithMetadata ¶ added in v19.4.0
func GetWorkflowInfoWithMetadata[I, O proto.Message, N WorkflowName[I, O]](ctx context.Context, client Client, name N, key WorkflowKey) (*TypedWorkflowInfo[O], []byte, error)
GetWorkflowInfoWithMetadata returns a snapshot of the workflow addressed by key, with its output decoded into O and its opaque metadata read from the same row in one round trip. The name binds the output type and is verified against the stored workflow so the bytes are never decoded into the wrong type. A caller that only needs the status should use Client.GetWorkflowInfo, which leaves the payload column alone. Returns ErrWorkflowNotFound if no such workflow exists.
type WorkerManager ¶
type WorkerManager struct {
// contains filtered or unexported fields
}
func NewWorkerManager ¶
func NewWorkerManager( log *slog.Logger, validator protovalidate.Validator, meter metric.Meter, tracer trace.Tracer, errCapturer errz.ErrCapturer, store *persistence.WorkloadStore, registry *Registry, workflowPool *workflowWorkerPool, activityPool *activityWorkerPool, opts ...WorkerManagerOption, ) (*WorkerManager, error)
func (*WorkerManager) AddShard ¶
func (wm *WorkerManager) AddShard(ctx context.Context, shardID persistence.ShardID, fenceID persistence.FenceID) bool
AddShard recovers the shard's running tasks and publishes the fence, reporting whether it did. A fence RemoveShard has dropped, before or during the recovery, is not published: the lease manager no longer tracks it and would never remove it.
func (*WorkerManager) AddShards ¶ added in v19.4.0
func (wm *WorkerManager) AddShards(ctx context.Context, claimed []persistence.ClaimedShard) []persistence.ShardID
AddShards publishes every shard of one claim batch. Each shard recovers the previous owner's orphaned tasks before it becomes visible to dispatch. It returns the shards it did not publish, whose fence RemoveShard dropped before or during their recovery.
func (*WorkerManager) DrainShard ¶
func (wm *WorkerManager) DrainShard(shardID persistence.ShardID)
DrainShard marks an owned shard as draining: it is dropped from the claim snapshot and skipped by the scanners so no new work is dispatched onto it, while it stays in the fence map with its fence_id so in-flight fenced work keeps committing. No in-flight goroutine is canceled. A shard that is not owned cannot be drained.
func (*WorkerManager) InFlightByShard ¶
func (wm *WorkerManager) InFlightByShard() map[persistence.ShardID]int
InFlightByShard returns the number of active in-flight tasks per shard across both worker pools, computed in a single pass per pool. Only active goroutines are counted: suspended workflows hold no running row and are evicted when the shard is released or re-claimed, so the pools' active-only counts already exclude them. The drain machinery uses this to pick the least-loaded shard to drain and to tell when a draining shard has no in-flight work left (count zero / absent).
func (*WorkerManager) NotifySignalReady ¶
func (wm *WorkerManager) NotifySignalReady(shardID persistence.ShardID) bool
NotifySignalReady wakes the signal scanner for a shard this instance owns and reports whether it did. See NotifyWorkflowTaskReady for why ownership decides.
func (*WorkerManager) NotifyWorkflowTaskReady ¶
func (wm *WorkerManager) NotifyWorkflowTaskReady(shardID persistence.ShardID) bool
NotifyWorkflowTaskReady wakes the workflow poller for a shard this instance owns and reports whether it did. The poller only claims tasks on owned shards, so a notification for a peer-owned shard must be answered with false to let the caller route it to the owner instead.
A draining shard still counts as owned: the lease and the fence are still ours, so there is no peer subscribed to route the notification to. The shard's work waits for the drain to finish and the next owner to claim it.
func (*WorkerManager) RemoveShard ¶
func (wm *WorkerManager) RemoveShard(ctx context.Context, shardID persistence.ShardID, fenceID persistence.FenceID, cause error) bool
RemoveShard drops the shard if it is still held under fenceID and cancels the work of that epoch, reporting whether it did. A newer fence means the shard was re-claimed in between and its publish already retired this epoch, so a late removal must leave the new epoch alone. A fence that is not published yet is recorded so that AddShard drops it instead of publishing it; that is not a removal, since nothing runs under an unpublished fence.
func (*WorkerManager) Run ¶
func (wm *WorkerManager) Run(ctx context.Context)
Run starts all goroutines and blocks until ctx is canceled.
Goroutine layout:
- 2 claim-and-dispatch loops (1 per task type), each issues an unfenced probe and then one fenced cross-shard claim per poll tick, and spawns worker goroutines
- 0 to N workflow workers + 0 to M activity workers (spawned on demand)
- 2 scanners (timers, signals), each issues one cross-shard transaction per chunk of at most maxScanShardsPerTx shards, and up to maxScanRoundsPerChunk of those per chunk while batches come back full
Fixed goroutines: 4. Dynamic goroutines: up to N + M.
type WorkerManagerOption ¶
type WorkerManagerOption func(*WorkerManager)
func WithWMActivityPollInterval ¶
func WithWMActivityPollInterval(d time.Duration) WorkerManagerOption
func WithWMMaxChildWorkflowDepth ¶ added in v19.3.0
func WithWMMaxChildWorkflowDepth(n int) WorkerManagerOption
WithWMMaxChildWorkflowDepth caps how deep a chain of child workflows may go. A top-level workflow has depth 0 and each child is one deeper. Calls to ExecuteChildWorkflow / StartChildWorkflow from a workflow already at the cap surface ErrChildDepthExceeded via the returned future / error.
func WithWMMaxChildrenPerParent ¶
func WithWMMaxChildrenPerParent(n int) WorkerManagerOption
WithWMMaxChildrenPerParent caps the lifetime number of children (every ChildWorkflowScheduled event in the parent's history plus the current yield round's child schedules) a single parent may schedule. Calls to ExecuteChildWorkflow / StartChildWorkflow above the cap surface ErrChildFanOutExceeded via the returned future / error.
func WithWMMinimumTick ¶
func WithWMMinimumTick(d time.Duration) WorkerManagerOption
func WithWMOrphanSweepInterval ¶ added in v19.4.0
func WithWMOrphanSweepInterval(d time.Duration) WorkerManagerOption
WithWMOrphanSweepInterval sets how often each claim loop re-enqueues the running tasks on its owned shards that no goroutine of this instance is processing.
func WithWMRecoveryBatchSize ¶
func WithWMRecoveryBatchSize(n int64) WorkerManagerOption
func WithWMSignalScanInterval ¶
func WithWMSignalScanInterval(d time.Duration) WorkerManagerOption
func WithWMSuspensionTTL ¶
func WithWMSuspensionTTL(d time.Duration) WorkerManagerOption
func WithWMTimerHintThreshold ¶ added in v19.3.0
func WithWMTimerHintThreshold(d time.Duration) WorkerManagerOption
WithWMTimerHintThreshold caps how far ahead an in-memory timer hint is armed. A scheduled_task whose remaining duration exceeds d gets no hint and is served by the periodic timer scan instead, which keeps hint state proportional to the soon-to-fire timers rather than to every pending timer on owned shards.
func WithWMTimerScanInterval ¶
func WithWMTimerScanInterval(d time.Duration) WorkerManagerOption
func WithWMWorkflowPollInterval ¶
func WithWMWorkflowPollInterval(d time.Duration) WorkerManagerOption
type WorkflowContext ¶
type WorkflowContext interface {
Logger() *slog.Logger
Replaying() bool
Now() time.Time
NewSelector() Selector
// Sleep blocks the workflow until the duration elapses. Returns ErrCanceled
// if the workflow is canceled while sleeping. Cancellation is cooperative:
// the caller chooses whether to propagate the error or ignore it. If the
// underlying context is canceled, Sleep panics with
// the classified abort, so the round ends without persisting completion and
// the workflow_task is retried within the abort budget.
Sleep(d time.Duration) error
NewTimer(d time.Duration) TimerFuture
WorkflowKey() WorkflowKey
// Deadline is when the workflow's schedule-to-complete timeout elapses: the
// instant the engine times it out. Read from the creation event, so it is the
// same value on every replay round. A child workflow reports its parent's
// deadline, so a whole spawn tree shares the root's.
Deadline() time.Time
// Context is canceled, with the abort as its cause, when the round is
// aborting: the shard fence was lost or the shard released, the engine is
// shutting down, or the suspended goroutine outlived its TTL. Nothing the
// workflow does after that is persisted, so observing it cannot change
// history. Workflow code that computes for long without awaiting must return
// once it is canceled; what it returns is discarded and the task is retried.
// The context carries no deadline and no values.
Context() context.Context
// contains filtered or unexported methods
}
type WorkflowDB ¶ added in v19.4.0
type WorkflowDB interface {
// OpenDDLPool opens the pool partition DDL runs on, under the role owning the
// tables. DDL needs that ownership and OpenPool's role does not have it, so
// the two cannot share a pool.
OpenDDLPool(ctx context.Context, opts ...pgtool.PoolOption) (*pgxpool.Pool, error)
// OpenPool opens a pool for reading and writing rows, under a role entitled to
// no more than that.
OpenPool(ctx context.Context, opts ...pgtool.PoolOption) (*pgxpool.Pool, error)
// OpenMigrationDB opens the handle goose runs on. It is a *sql.DB rather than
// a pool because goose coordinates with a session-level advisory lock.
OpenMigrationDB() (*sql.DB, error)
}
WorkflowDB opens handles on one workflow database. Each method says what the handle is for, and the implementation decides which credentials that takes.
type WorkflowDBs ¶ added in v19.4.0
type WorkflowDBs interface {
// Resolve is called once per worker start, so a credential that changed since
// the last start takes effect there.
Resolve(ctx context.Context, dbID persistence.DBID) (WorkflowDB, error)
}
WorkflowDBs makes a workflow database's connections available.
type WorkflowExecutionResult ¶
type WorkflowExecutionResult string
WorkflowExecutionResult is the typed outcome of a single workflow round. Two values (Yield, Aborted) are autocore-only. The others (Completed, Failed, Canceled, TimedOut, SystemFailed) spell the persisted persistence.WorkflowExecutionState the same way, which a test pins, so the observability and persisted-state vocabularies stay in lockstep. All are constants so a switch over the type can be checked for exhaustiveness.
const ( WorkflowExecutionResultYield WorkflowExecutionResult = "yield" // WorkflowExecutionResultAborted: round produced no persisted outcome. // Recorded via the workflow aborts counter, not the round duration // histogram. WorkflowExecutionResultAborted WorkflowExecutionResult = "aborted" WorkflowExecutionResultCompleted WorkflowExecutionResult = "completed" WorkflowExecutionResultFailed WorkflowExecutionResult = "failed" WorkflowExecutionResultCanceled WorkflowExecutionResult = "canceled" WorkflowExecutionResultTimedOut WorkflowExecutionResult = "timed_out" WorkflowExecutionResultSystemFailed WorkflowExecutionResult = "system_failed" )
type WorkflowID ¶
type WorkflowID = persistence.WorkflowID
func NewWorkflowID ¶ added in v19.2.0
func NewWorkflowID() WorkflowID
NewWorkflowID generates a new unique WorkflowID.
func StartChildWorkflow ¶
func StartChildWorkflow[I, O proto.Message, N WorkflowName[I, O]](ctx WorkflowContext, name N, input I, opts *ChildWorkflowOptions) (WorkflowID, error)
StartChildWorkflow schedules a fire-and-forget child workflow on the parent's shard. The child runs independently; parent terminal state has no effect, and no completion event is ever delivered to the parent. Returns the deterministic child WorkflowID immediately.
type WorkflowInfo ¶
type WorkflowInfo struct {
WorkflowKey WorkflowKey
Name string
IdempotencyKey string
State WorkflowState
CreatedAt time.Time
UpdatedAt time.Time
Output []byte
Error string
}
WorkflowInfo is a read-only snapshot of a workflow's execution state and terminal result, returned by Client.GetWorkflowInfo.
type WorkflowKey ¶
type WorkflowKey struct {
ShardID ShardID
WorkflowID WorkflowID
}
WorkflowKey uniquely addresses a workflow across all databases. The ShardID provides the routing key (shard_id → db_id via central DB).
func ParseWorkflowKey ¶
func ParseWorkflowKey(s string) (WorkflowKey, error)
ParseWorkflowKey is the inverse of WorkflowKey.String: it accepts strings of the form "wk:<shard_id>/<workflow_id>" and returns ErrInvalidWorkflowKey for anything else.
func (WorkflowKey) String ¶ added in v19.2.0
func (wk WorkflowKey) String() string
type WorkflowName ¶
type WorkflowState ¶
type WorkflowState = persistence.WorkflowExecutionState
WorkflowState is the lifecycle state of a workflow execution.
Source Files
¶
- abort_cause.go
- activity_context.go
- activity_execution_result.go
- activity_worker_pool.go
- api.go
- api_activity.go
- api_workflow.go
- channel.go
- client.go
- db_manager.go
- db_manager_with_workers.go
- db_manager_without_workers.go
- directory_manager.go
- engine.go
- future.go
- local_notifier.go
- otel.go
- partition_manager.go
- poll.go
- readiness_tracker.go
- redis_notifier.go
- redis_shard_subscriber.go
- registry.go
- replay_aware_log_handler.go
- selector.go
- timer_hint_tracker.go
- worker_manager.go
- worker_manager_children.go
- worker_manager_noop.go
- worker_manager_scan_round_report.go
- worker_manager_tasks.go
- workflow_context.go
- workflow_db_manager.go
- workflow_db_worker.go
- workflow_db_worker_wrapper.go
- workflow_execution_result.go
- workflow_worker_pool.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package dbsource obtains autocore's database connections from the connection files a deployment mounts.
|
Package dbsource obtains autocore's database connections from the connection files a deployment mounts. |