Skip to content

Latest commit

 

History

History
193 lines (139 loc) · 7.38 KB

File metadata and controls

193 lines (139 loc) · 7.38 KB

Durable operations

All operations reserve deterministic IDs in call order. Replaying the same history with a changed type, subtype, name, or parent fails before user code is executed.

Step

step isolates nondeterministic work. A successful checkpoint returns the deserialized historical result without rerunning the callable.

  • at_least_once_per_retry may rerun an interrupted attempt.
  • at_most_once_per_retry schedules a new retry or fails rather than rerunning an interrupted attempt.

Wait

wait writes a timer checkpoint and suspends. It returns only after replay observes SUCCEEDED.

Callback

create_callback<Result> writes a callback checkpoint and returns callback_handle<Result>. Creation only validates that callback details and an ID exist. Terminal callback errors are intentionally deferred until callback_handle::result() so code between creation and result retrieval runs on both original execution and replay.

result() returns std::optional<Result>:

  • a payload produces a value through the selected serializer;
  • successful completion without a payload produces std::nullopt;
  • incomplete callbacks suspend;
  • failed, cancelled, timed-out, or stopped callbacks throw callback_error.

The default string callback serializer is pass-through because callback success APIs already provide a serialized string payload. Supply a serializer for typed results.

Wait for callback

wait_for_callback creates a non-virtual child context containing:

  1. a callback operation;
  2. a durable submitter step;
  3. callback result retrieval.

The submitter accepts std::string_view callback_id or no arguments. Passing the ID directly is explicit, testable, and avoids an additional thread-local context API.

Chained invoke

invoke<Result> serializes its payload, starts a CHAINED_INVOKE, and suspends. On replay it returns std::optional<Result>, or throws callable_error for a terminal failure. A function name or ARN and optional tenant ID are recorded in the start checkpoint.

Child context

run_in_child_context creates a CONTEXT operation and executes nested durable operations in a namespace prefixed by the child operation ID. This prevents collisions between identical child workflows.

Results up to 256 KiB are checkpointed normally. Larger results set ReplayChildren; an optional summary generator supplies the compact checkpoint payload, and replay re-executes the child operations to reconstruct the full result.

Virtual contexts use the same prefixed ID namespace but omit context lifecycle checkpoints. Their child operations are flattened under the current parent. Changing an observable context between virtual and non-virtual mode fails closed when prior history makes the mismatch detectable.

Parallel

parallel accepts either a random-access range of homogeneous callables or a tuple of heterogeneous callable types with a common result type. Branch IDs are reserved on the caller thread before any worker starts, so scheduling order cannot change durable history identities.

Each nested branch has subtype ParallelBranch. Flat nesting omits branch context lifecycle checkpoints while retaining its prefixed ID namespace. batch_result records successful, failed, cancelled, and still-started items.

Map

map copies the input range into stable storage, reserves one deterministic MapIteration branch per item, and invokes the item function through the same parallel executor. max_concurrency limits worker slots.

Completion policies

completion_config supports:

  • minimum successful count;
  • tolerated failure count;
  • first successful;
  • all successful/all completed;
  • a deterministic custom decision callback.

Early completion prevents unscheduled branches from starting and records them as cancelled. Synchronous C++ code already running on another worker cannot be forcibly interrupted safely, so workers already executing are joined before the aggregate result is returned.

Wait for condition

wait_for_condition<State> stores the latest state in the STEP retry payload. The check receives optional current state and optionally the one-based attempt. A polling strategy returns the next delay or std::nullopt to complete.

Pending checkpoints suspend without running the check. READY checkpoints restore state, increment the attempt, and execute the next check. Exhaustion and checker exceptions are checkpointed as terminal failures.

With retry

with_retry wraps an entire durable block in a child context. The body accepts the one-based attempt or no arguments. A failed attempt creates a named durable wait before reconstructing the next attempt during replay.

Durable suspension, checkpoint failures, state-fetch failures, serialization errors, and replay-identity errors are control failures and are not treated as retryable body errors. Applications can supply retry_decider to filter other exceptions.

Replay-safe values

The replay_safe namespace provides checkpointed:

  • uniform random doubles;
  • millisecond-normalized UTC timestamp values;
  • Unix timestamps in seconds;
  • RFC 4122 version-4 uuid_value values.

Each helper is a normal durable step, so replay returns exactly the checkpointed value without calling the clock or entropy source again.

Recursive invoke

recurse and recurse_json build on chained invoke. The target is resolved from an explicit function name or Lambda invocation metadata. Unqualified runtime names are combined with the function version when available.

Recursive payloads must differ from the current execution input. with_recursive_level requires a JSON object and replaces __recursive_level with the current level plus one. Tenant metadata is propagated unless explicitly overridden.

Declarative flow

flow_builder creates typed node handles without executing user code. Calling flow freezes and validates the definition before writing the top-level checkpoint.

Validation rejects:

  • blank or duplicate node names;
  • self and duplicate dependencies;
  • nodes or outputs from another builder;
  • cycles;
  • mutation after freeze.

Only nodes reachable by reverse dependency traversal from selected outputs are executed. Other nodes appear as skipped without consuming operation IDs.

Dependencies are immutable expressions:

  • node.succeeded() requires a successful result;
  • node.failed() routes failure and marks it handled;
  • node.completed() matches every settled logical status without handling a failure;
  • && requires every child expression;
  • || requires one child expression.

ANY expressions choose the first matching child in expression order from the currently settled durable results. This deterministic rule avoids completion-order drift between native threads and replay.

Ready nodes execute concurrently in pre-reserved child contexts. Node callables receive flow_node_context& or no arguments. The context provides typed, freshly deserialized snapshots of direct dependency outcomes, results, and errors, preventing mutable values from leaking between consumers.

Logical node exceptions become flow_node_status::failed records and can activate failure routes. Durable suspension and SDK control failures propagate without being misclassified as logical failures.

flow_result supports typed node lookup and selected outcome, error, or full result projections. Unhandled failures and unavailable outcome projections raise flow_execution_error only after the complete flow result has been checkpointed.