Cloudflare Workers request.signal: Test What Actually Stops

Cloudflare Workers request.signal testing should answer a precise question: did downstream work stop, or did your code merely hear that the visitor left? Our original 18-run local simulation shows why those outcomes need separate evidence. The version that received an abort notification without forwarding the signal kept producing all three mock chunks.

With explicit forwarding, the mock stopped before producing a chunk in the early-abort cases and after one chunk in the during-stream cases. These are executed Node.js teaching simulations, not hosted Cloudflare integration results. No production Worker, paid API, real network stream or persistent business write was involved.

The compatibility change is dated 17 November

Cloudflare’s 2 November announcement says incoming request.signal becomes enabled by default for compatibility dates of 17 November 2026 or later. Existing Workers retain their behaviour until their dates or flags change. The feature itself was introduced in May 2025; the November news concerns its default.

The announcement also distinguishes this from request_signal_passthrough. Code should explicitly pass the signal to APIs that accept it. A notification on the incoming request does not establish that a separate downstream operation received a cancellation request.

As of our 7 November research date, the announced default date is still ahead. Check the current compatibility reference when planning an implementation. For earlier dates it documents enable_request_signal; it also documents disable_request_signal for opting out. We changed no compatibility configuration for this study.

A small cancellation experiment with known conditions

We created a fictional slow catalogue lookup using local timers and an AbortController. It imitates a headers event at 100 milliseconds and three chunk events at 150, 200 and 250 milliseconds. Those are chosen fixture settings, not measurements of a Nigerian network or Cloudflare response time.

Two implementations use the same mock. One listens for the simulated incoming abort but does not forward its signal. The other explicitly supplies that signal to the mock operation, which is written to accept cancellation. Therefore, the test examines an explicit control path with a known implementation.

Client conditionChosen local triggerRepeats per implementation
Stay connectedNo abort3
Abort before mock headersTimer scheduled at 20 ms3
Abort during mock streamTimer scheduled at 160 ms3

Three conditions times two implementations times three repeats give 18 executed runs. We ran them sequentially in Node v22.23.1 on one local macOS computer. They use no network requests, account credentials or customer records. No Cloudflare runtime was started, so Workers compatibility flags were not applied by this harness.

Cloudflare Workers request.signal testing teaching matrix: in 18 local Node runs, unforwarded signals complete the mock in every condition; forwarded signals cancel the two abort conditions, with three repeats per cell.
Original local simulation, 7 November 2026. The six cancelled runs and 12 completed runs describe this chosen timer mock. They are not a hosted Workers reliability estimate or a production cancellation success rate.

What stopped, and what merely received a notification

All nine no-forward runs completed the downstream mock and produced three chunks. In its six abort-condition runs, the harness recorded an abort notification first. That notification did not affect the operation because the downstream mock had not received the signal.

The explicitly forwarded version also completed all three stay-connected runs. In its three early-abort runs, cancellation stopped the mock before headers or chunks appeared. In its three during-stream runs, headers and the first chunk appeared before cancellation stopped the remaining work.

The result is inspectable in the event ledger. We retain request start, abort notification, downstream cancellation request, downstream stop or completion, and invocation completion. “Unobserved” is used when an event did not occur. It is never silently converted to a zero-millisecond response.

Read relative timings as local evidence

The no-forward early-abort cases completed roughly 230–231 milliseconds after the abort notification. Its during-stream cases completed roughly 90–92 milliseconds after notification. These intervals follow the deliberately selected timers and the local event loop.

In the forwarded version, the recorded downstream stop followed notification within about 0.033–0.116 milliseconds in this run. The same-process callback sequence explains that small interval. It is not an edge-network latency target, an operating guarantee or a measure of a real service’s cancellation speed.

Twelve local abort-condition runs show the elapsed time from notification to downstream stop or completion: unforwarded work continues toward the chosen timer completion; explicitly forwarded mock work stops in the same local callback sequence.
Actual monotonic relative timings from the mock. Stay-connected runs are excluded because they have no abort event. Every point represents one declared local run; missing events remain unobserved in the downloadable ledger.

Use a monotonic clock for intervals within a run. Keep wall-clock timestamps for correlating separate systems, and document their synchronization limits. A timer scheduled for 20 milliseconds can fire later; preserve the observed time instead of replacing it with the intended setting.

Our harness records invocation completion after the mock settles. That is a local function boundary, not an observation of Cloudflare isolate termination. The distinction matters when comparing a teaching trace with real runtime logs.

Unlock Your Unique Tech Path

Get expert tech consulting tailored just for you. Receive personalized advice and solutions within 1-3 business days.

Get Started

Cancellation cannot erase a chunk that already exists

The during-stream condition is useful because some work has already happened. In our forwarded cases, one chunk existed before the abort. Cancelling the remaining timers prevented the next two mock chunks; it did not retract the first one.

Chunks produced per local run: unforwarded implementation produces three in all conditions; explicit forwarding produces three when connected, zero for early abort and one for during-stream abort. All three repeats agree within each pairing.
Original timer simulation, not real HTTP streaming. The during-stream abort is scheduled after the first chunk. These chosen counts do not establish database rollback, production savings or behaviour of a different downstream API.

The same reasoning prevents a more consequential mistake: treating a browser abort as proof that a database write was rolled back. A completed effect needs its own transaction and application evidence. This study performs no such writes, so it offers no rollback result.

The infographic keeps four events separate. A client can leave; code can request cancellation; a downstream operation can actually stop; and a persistent effect can still require an independent check. A useful log names the event it observed rather than collapsing all four into “cancelled.”

Lifecycle infographic distinguishes client disconnection, an explicit cancellation request, downstream stop and a separate check of persistent effects. A completed write is not undone by abort alone.
An editorial sequence framework for interpreting cancellation evidence. Whether a particular operation stops depends on its API and implementation. The final persistent-effect check was not simulated or measured here.

Place background work in the right lifecycle

Cloudflare’s context documentation, checked 7 November, says HTTP-triggered waitUntil work can continue for up to 30 seconds after the response is sent or the client disconnects. That allowance is shared across the request’s waitUntil calls. It is not 30 seconds per call or a total HTTP-request duration cap.

The documentation points to a queue and separate consumer for work that cannot finish within that allowance. Our local mock does not test waitUntil, queue delivery or retries. Those require their own lifecycle design and verification.

A disposable catalogue read may be suitable for explicit cancellation. Best-effort logging needs a clear loss policy. A job that must survive the visitor leaving needs a durable design, with retry and idempotency decisions appropriate to its real effects. These categories are a planning aid, not interchangeable guarantees.

Run the mock and collect your own evidence

The runnable simulation pack includes the original Node script, raw run receipts, environment record, four CSVs and README. From the extracted root, run node scripts/run_simulation.mjs. It creates local output files and makes no external calls.

For a later authorized hosted test, retain the exact runtime, compatibility date, flags and downstream API. First use a read-only operation and identify which events your logs can observe. Keep that integration session separate from these local results. Do not attach the exercise to a payment or personal-data workflow merely to make the example look realistic.

Our CI checker experiment applies the same principle to badges: verify what the signal actually establishes. Our rendered-page testing fixture likewise separates measurements taken from measurements still missing.

Method and limits: Executed 7 November 2026; 18 selected timer runs on one machine, with three repeats per pairing. The code demonstrates a cancellation path it explicitly implements. It cannot establish production cost savings, Nigerian disconnection prevalence, every race condition or the behaviour of a downstream service that ignores cancellation.