A dependency-free C++23 foundation for building high-performance AWS Lambda
durable executions. The API follows C++ value semantics and templates for hot
paths while retaining the replay and checkpoint behavior of
async-durable-execution.
Project status: early development. The replay/checkpoint core, durable steps, waits, retries, deterministic IDs, protocol models, and service-client boundary are implemented. The dependency-free Lambda wire codec, optional AWS SDK for C++ transport, and optional
aws-lambda-cpphandler adapter are also implemented and compile-checked against the current official headers. Durable callbacks, child contexts, chained invoke, and wait-for-callback are implemented. Parallel/map composition and contention-aware checkpoint batching are implemented with deterministic branch IDs. Wait-for-condition, whole-block retry, replay-safe values, and recursive invoke are implemented. Typed declarative flow/DAG composition is implemented with pre-checkpoint validation and durable node isolation. A deterministic local runner provides virtual time, callback controls, mocked invokes, and inspectable history. Versioned instrumentation plugins expose invocation, operation, attempt, replay, and state-change lifecycle hooks with per-plugin fault isolation. Independent reference fixtures and the complete upstream conformance handler matrix are integrated. Deployed AWS conformance runs through the repository integration-test role.
- C++23 with strong types, RAII,
std::expected,std::chrono, concepts, and zero-cost serializer customization. - Performance close to C for local hot paths. Known wire enums and history lookups do not allocate; serializers and operations are statically dispatched.
- Deterministic replay with a stable, SDK-owned BLAKE2b operation-ID scheme. The algorithm and truncation are part of this SDK's durable-history contract.
- Backward and forward compatibility built into the public model. ABI symbols
use the inline
v1namespace; wire enums retain unknown future AWS values. - AWS integration behind
service_client, keeping network and AWS SDK types out of workflow code and the core library. - A specialized wire parser skips unknown fields without constructing a general JSON DOM and decodes a minimal durable invocation in under one microsecond on the current development host.
#include <chrono>
#include <string>
#include <string_view>
#include <aws/durable_execution/durable_execution.hpp>
namespace durable = aws::durable_execution;
using namespace std::chrono_literals;
durable::invocation_output handle(
const durable::invocation_input& input,
durable::service_client& aws_client) {
return durable::run(input, aws_client, [](std::string_view event) {
const int validation = durable::step(
[event] {
// Nondeterministic I/O belongs inside a durable step.
return event.empty() ? 0 : 1;
},
durable::step_config{.name = "validate_order"});
if (validation == 0) {
return std::string{"rejected"};
}
durable::wait(5s, "await_confirmation");
return std::string{"approved"};
});
}run accepts handlers taking (durable_context&, std::string_view),
std::string_view, durable_context&, or no arguments. Primitive results use
the built-in JSON serializers. Applications provide a serializer object for
domain types; its calls are statically dispatched.
AWS-native durable primitives use typed C++ handles and std::optional for
backend results that may be absent:
auto callback = durable::create_callback(
durable::callback_config{.name = "approval", .timeout = 1h});
send_approval_request(callback.callback_id());
const auto approval = callback.result(); // suspends until completed
const auto downstream = durable::invoke<std::string>(
"worker:prod", std::string{"payload"},
durable::invoke_config{.name = "worker-call"});
const int child_result = durable::run_in_child_context(
[] { return durable::step([] { return 42; }); },
durable::child_context_config{.name = "calculation"});wait_for_callback composes callback creation, a checkpointed submitter step,
and callback suspension inside a child context. Its submitter receives the
callback ID directly:
const auto approval = durable::wait_for_callback(
[](std::string_view callback_id) {
submit_for_external_approval(callback_id);
},
durable::wait_for_callback_config{.name = "approval"});Parallel and map operations use bounded workers, checkpoint batching, and durable child contexts:
auto prices = durable::map(
[](const LineItem& item) {
return durable::step([&] { return fetch_price(item); });
},
line_items,
durable::map_config{.name = "price-items", .max_concurrency = 8});
auto first = durable::parallel(
std::tuple{query_primary, query_replica},
durable::parallel_config{
.name = "race-replicas",
.completion = durable::completion_config::first_successful()});Replay-safe helpers checkpoint nondeterministic values through normal steps:
const double sample = durable::replay_safe::random();
const auto created_at = durable::replay_safe::now();
const durable::uuid_value request_id = durable::replay_safe::uuid();Stateful polling and whole-block retry expose the attempt directly to the callable:
auto state = durable::wait_for_condition<PollState>(
poll,
PollState{},
durable::polling_strategy{.max_attempts = 20});
auto result = durable::with_retry(
[](std::uint32_t attempt) { return run_workflow_attempt(attempt); },
durable::with_retry_config{.name = "workflow-retry"});recurse records a chained self-invocation rather than growing the C++ stack:
auto child = durable::recurse_json<Result>(
R"({"remaining":9})",
durable::recurse_config{.with_recursive_level = true});Declarative graphs use typed node handles and immutable dependency expressions:
durable::flow_builder graph;
auto load = graph.node("load", load_order);
auto price = graph.node(
"price",
[load](durable::flow_node_context& context) {
return price_order(context.outcome(load));
});
graph.depends_on(price, load.succeeded());
graph.outputs(price.outcome());
auto result = durable::flow(
graph, durable::flow_config{.name = "order-flow"});
const Money total = result.output<Money>();Local tests run without AWS credentials or wall-clock waits:
auto runner = durable::make_local_runner(
order_handler,
durable::local_runner_options{
.input_json = R"({"order_id":"order-123"})"});
auto result = runner.run();
if (result.status() == durable::local_run_status::pending_external) {
const auto callback_id = result.pending_callback_ids().front();
runner.send_callback_success(callback_id, R"({"approved":true})");
result = runner.resume();
}
assert(result.status() == durable::local_run_status::succeeded);
assert(result.step("validate-order") != nullptr);See docs/local-runner.md. Reference fixtures and conformance coverage are documented in docs/conformance.md. Instrumentation plugins are documented in docs/plugins.md. Third-party durable primitives are documented in docs/custom-operations.md.
See docs/operations.md for replay and result semantics. See docs/concurrency.md for scheduling, batching, and early-completion behavior.
For a raw Lambda runtime payload, run_json performs tolerant wire decoding,
runs the durable handler, and serializes the invocation response:
auto response = durable::run_json(request.payload, service, durable_handler);The core remains dependency-free. Enable either optional integration explicitly:
cmake -S . -B build \
-DDURABLE_EXECUTION_BUILD_AWS_SDK_ADAPTER=ON \
-DDURABLE_EXECUTION_BUILD_LAMBDA_RUNTIME_ADAPTER=ONThe AWS SDK adapter uses CheckpointDurableExecution and
GetDurableExecutionState. Applications retain responsibility for
Aws::InitAPI/Aws::ShutdownAPI and the lifetime/configuration of the Lambda
client.
auto lambda_client = std::make_shared<Aws::Lambda::LambdaClient>();
durable::aws_sdk_service_client service{lambda_client};
auto handler = durable::make_lambda_handler(service, durable_function);
aws::lambda_runtime::run_handler(handler);See examples/lambda_main.cpp and docs/aws-integration.md.
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
ctest --test-dir build --output-on-failureEnable the self-contained microbenchmarks with:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \
-DDURABLE_EXECUTION_BUILD_BENCHMARKS=ON
cmake --build build --parallel
./build/durable_execution_microbenchOn the current development host, the initial core measures roughly 370 ns per operation-ID generation, 20 ns per integer serialize / deserialize round trip, 800 ns to decode a minimal invocation containing one operation, and 34 ns for a thread-safe history lookup. These figures are development baselines, not portable performance guarantees.
cmake --install build --prefix /your/prefixfind_package(aws_durable_execution 0.1 CONFIG REQUIRED)
target_link_libraries(your_target PRIVATE aws::durable_execution)The compatibility contract covers source, ABI, durable history, wire protocol, and serialized payloads. Unknown wire enum values are retained without allocation for known values, and unsafe unknown operation states fail closed instead of accidentally rerunning user code.
See docs/compatibility.md for the policy and docs/architecture.md for the layer boundaries and performance constraints.
- Run all 171 mapped requirements against deployed AWS Lambda durable functions.
- Retain replay fixtures from every released minor version and verify them in CI as the SDK evolves.
- Stable 1.0 API/ABI after every required conformance, compatibility, and performance gate passes.