Skip to content

Add durable-execution support to Lambda Test Tool v2 (preview)#2495

Closed
GarrettBeatty wants to merge 6 commits into
fix/testtool-net10-static-assetsfrom
feature/testtool-durable-execution
Closed

Add durable-execution support to Lambda Test Tool v2 (preview)#2495
GarrettBeatty wants to merge 6 commits into
fix/testtool-net10-static-assetsfrom
feature/testtool-durable-execution

Conversation

@GarrettBeatty

Copy link
Copy Markdown
Contributor

Draft. Targets fix/testtool-net10-static-assets (PR #2492) rather than dev, since this work is built directly on top of that fix. Rebase onto dev once #2492 merges.

What this adds

Durable-execution support in the Lambda Test Tool v2 — run a durable Lambda workflow locally, end to end, with a web UI.

Enable with --durable-execution. The tool emulates the durable-execution service: it stores checkpoints, drives the workflow across its replay cycle, resolves timers, and manages callbacks. Point the function's AWS_ENDPOINT_URL_LAMBDA at the tool to use it.

Commits (each independently reviewable)

  • Phase 1-2 — data plane (CheckpointDurableExecution / GetDurableExecutionState endpoints) + the re-invocation driver that starts an execution from the X-Amz-Durable-Execution-Name header and drives it across the replay cycle (steps, waits, retries, child contexts, parallel/map).
  • Phase 3 — external callbacks (SendDurableExecutionCallback{Success,Failure,Heartbeat}) with park/resume, plus a Durable Execution web page (execution list, operation timeline, Send Callback).
  • Phase 4 — chained durable invokes (nested executions), StopDurableExecution, payload-size caps.
  • UI — a "Start as durable execution" checkbox on the Function Tester page, Re-Invoke re-running a durable execution from the original payload, a live time-skip toggle, sample request, README + in-app docs.
  • Fixes--durable-time-skip default (was effectively false), completed WAITs showing SUCCEEDED, Send Callback interactivity.

Design

See Libraries/src/Amazon.Lambda.DurableExecution/docs/design/testtool-integration-plan.md.

Verification

Backend covered by the Test Tool xUnit suite (data plane + full start/drive/callback/chained-invoke cycle). The UI flows (checkbox start, Re-Invoke, callbacks, time-skip toggle) were verified end to end in a real browser against the sample durable function.

Not yet

  • No xUnit coverage for the UI-layer additions (verified via browser only).
  • Uses a hand-written wire-protocol fake in the automated E2E tests, not the real DurableExecution SDK engine (that engine is covered in its own package); a real out-of-process-executable integration test is the recommended follow-up.

Adds a local durable-execution service emulator to the Test Tool, enabled
with --durable-execution. A durable function whose AWS_ENDPOINT_URL_LAMBDA
is pointed at the Test Tool can now run a full workflow locally.

Phase 1 (data plane): CheckpointDurableExecution and GetDurableExecutionState
endpoints hosted alongside the Runtime API, backed by the operation store +
checkpoint processor ported from Amazon.Lambda.DurableExecution.Testing and
retyped to plain System.Text.Json request DTOs (the AWSSDK model types use
ConstantClass and can't be STJ-deserialized). Response timestamps are emitted
as unix seconds to match the AWSSDK rest-json unmarshaller.

Phase 2 (control plane): invoking with X-Amz-Durable-Execution-Name starts an
execution that DurableExecutionDriver drives to completion, re-invoking the
function over the Runtime API across the replay cycle. Timers are time-skipped
by default (--durable-time-skip). Chained durable invokes and external
callbacks are not yet supported and fail fast with a clear message.

Includes end-to-end tests exercising both the data plane and the full
start-hook/drive-loop cycle through a real AmazonLambdaClient and Runtime API.
Callbacks: SendDurableExecutionCallback{Success,Failure,Heartbeat} endpoints
resolve a pending CALLBACK operation by its minted id and wake the parked
drive loop. The driver now parks on a pending callback (instead of failing)
and resumes when the callback is resolved; park/resume is lock-guarded so a
resolve that races ahead of the park is not lost.

Web UI: a new Durable Execution page lists local executions, renders each
one's operation timeline (id/type/status/detail), and can send a callback
from the browser. The page shows a friendly hint when --durable-execution is
not enabled.

Includes an end-to-end test covering the full start -> park-on-callback ->
SendDurableExecutionCallbackSuccess -> resume -> Succeeded cycle.
…ator (Phase 4)

Chained durable invokes: a CHAINED_INVOKE operation is now resolved by
starting the target function as a nested durable execution and stamping its
result (or error) back onto the parent's operation, so ctx.InvokeAsync works
locally. The sibling runs the same drive loop under its own function name.

StopDurableExecution: the /stop endpoint cancels the execution's drive loop
and marks the EXECUTION operation STOPPED, returning a StopTimestamp.

Payload caps: checkpoint updates whose operation payload exceeds the service
limits (256 KB general, 1 MB for chained invokes) are rejected with 413.

Also fixes a latent bug: the drive loop awaited EventContainer.WaitForCompletion
on a threadpool thread, a blocking 15-minute wait that ignored cancellation, so
Stop could not interrupt an in-flight invocation. Replaced with a
cancellation-aware status poll. The durable web-host tests now run in a
non-parallel xUnit collection to avoid starving their Runtime-API poll loops
under CPU pressure.

Full Test Tool unit suite passes (207 passed, 1 pre-existing skip).
The callback-id and result inputs now bind on oninput (not the default onchange)
so a value is committed as the user types, and the Send Success button is no
longer gated on a disabled attribute that consumed the first click to commit the
field. Verified end to end in a real browser (Playwright): starting a durable
workflow, parking on a callback, clicking Send Callback to prefill the id, and
clicking Send Success resumes the execution to Succeeded.
Adds a 'Start as durable execution' checkbox (with optional execution name) to
the Function Tester page so users can start a durable workflow from the UI
without manually setting the X-Amz-Durable-Execution-Name header. The checkbox
only appears when the tool is started with --durable-execution.

Fixes Re-Invoke for durable functions: the queued/executed events for a durable
function are internal replay envelopes, so re-posting one was a no-op replay.
Re-Invoke now detects a durable envelope, extracts the original user payload
from the EXECUTION operation, and starts a fresh durable execution.

Adds a durable-execution sample request and documents the feature in the
README (Durable Execution Mode) and the in-app Documentation page.

Verified end to end in a browser (Playwright): the checkbox starts an execution
that appears on the Durable Execution page, and Re-Invoke creates a new
execution.
Fixes --durable-time-skip effectively defaulting to false: as a plain bool
Spectre.Console.Cli option it bound presence->true/absence->false regardless of
the C# field initializer, so timers were never skipped even though the default
was documented as true. Now uses [DefaultValue(true)] and accepts an explicit
value (--durable-time-skip false).

Adds a live 'Time-skip timers' toggle to the Durable Execution page. Time-skip
is now read on each checkpoint (via a provider) rather than captured at store
construction, so it can be flipped at runtime and applies to subsequent
checkpoints across all executions.

Fixes the timeline showing a completed WAIT as STARTED: when time-skip is off
and a timer elapses, the SDK proceeds without emitting a SUCCEEDED checkpoint,
leaving the op STARTED forever. The driver now stamps elapsed WAITs as SUCCEEDED
before re-invoking (mirroring the service firing the timer), so the store and UI
reflect that the wait completed.

Verified end to end in a browser (Playwright): time-skip defaults on (execution
parks in ~1s), the WAIT shows SUCCEEDED, the UI toggle flips interactively, and
the checkbox-start and callback flows still work.
@GarrettBeatty

Copy link
Copy Markdown
Contributor Author

Superseded by the stacked #2496 (shared local-emulation kernel) + #2497 (durable Test Tool on the kernel). All durable commits here are re-homed on top of the kernel in #2497.

@GarrettBeatty
GarrettBeatty deleted the feature/testtool-durable-execution branch July 23, 2026 19:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant