UNPKG

workflow

Version:

Workflow SDK - Build durable, resilient, and observable workflows

85 lines (63 loc) • 4.11 kB
--- title: abort-signal-timeout-in-workflow description: AbortSignal.timeout() cannot be used inside workflow functions because it relies on real timers which break deterministic replay. type: troubleshooting summary: Use sleep() with AbortController instead of AbortSignal.timeout() in workflow functions. prerequisites: - /docs/foundations/workflows-and-steps related: - /docs/foundations/cancellation - /docs/api-reference/workflow/sleep - /docs/errors/timeout-in-workflow --- <CopyPrompt text="Replace `AbortSignal.timeout()` inside workflow functions. Find the failing `AbortSignal.timeout(ms)` call in a `&quot;use workflow&quot;` function. Import `sleep` from `workflow`. Create `const controller = new AbortController()`, pass `controller.signal` into the async work that supports cancellation, and start a deterministic timeout with `void sleep(&quot;10s&quot;).then(() => controller.abort())` or a `Promise.race` between the work and `sleep(...)`. Keep actual fetch/SDK side effects inside `&quot;use step&quot;` helpers when they need Node.js APIs. Treat intentional aborts as non-retryable if appropriate by throwing `FatalError`. Verify the operation succeeds before the timeout, aborts after the timeout, and replays without abort-signal-timeout-in-workflow." /> ## Error ```text AbortSignal.timeout() is not supported in workflow functions. Use sleep() with an AbortController instead. ``` ## Why this happens `AbortSignal.timeout()` creates a signal that aborts after a real-time delay using an internal timer. Workflow functions must be [deterministic](/docs/foundations/workflows-and-steps) to support replay: they run the same code multiple times during the workflow's lifecycle, using the [event log](/docs/how-it-works/event-sourcing) to resume execution to the correct point. Real-time timers break this determinism because: - On the first execution, the timer might fire after 10 seconds. - On replay, the timer would fire again, but the event log may have already advanced past that point. - The timer's behavior depends on wall-clock time, which varies between executions. ## How to fix Use [`sleep()`](/docs/api-reference/workflow/sleep) with an `AbortController` to create a deterministic timeout that cancels in-flight work: **Before (incorrect):** {/* @skip-typecheck: intentionally incorrect example */} ```typescript lineNumbers export async function workflow() { "use workflow"; // This will throw an error const signal = AbortSignal.timeout(10_000); // [!code highlight] const result = await fetchData(signal); return result; } ``` **After (correct):** ```typescript lineNumbers import { sleep } from "workflow"; export async function workflow() { "use workflow"; const controller = new AbortController(); // [!code highlight] void sleep("10s").then(() => controller.abort()); // [!code highlight] return await fetchData(controller.signal); } async function fetchData(signal: AbortSignal) { "use step"; const response = await fetch("https://api.example.com/data", { signal }); return response.json(); } ``` The `sleep()` and `AbortController` pattern is the durable equivalent of `AbortSignal.timeout()`. The sleep is recorded in the event log, so it replays deterministically. If `fetchData` finishes within 10 seconds, you get the response. Otherwise, the timer calls `controller.abort()`, `fetch` rejects with an `AbortError`, and the step's failure propagates to the workflow as a `FatalError` (no retries because the abort is intentional cancellation). <Callout type="info"> `AbortSignal.timeout()` works normally inside step functions, since steps have full Node.js runtime access and are not replayed. </Callout> ## Related - [Cancellation](/docs/foundations/cancellation): Patterns for canceling in-flight work - [`sleep()` API Reference](/docs/api-reference/workflow/sleep): Durable sleep primitive - [Workflows and Steps](/docs/foundations/workflows-and-steps): Why workflow functions must be deterministic - [`setTimeout` in Workflow](/docs/errors/timeout-in-workflow): Similar restriction on `setTimeout`