From 30ab98a433f53d30e3611d16bfaf7bc0ef4afd27 Mon Sep 17 00:00:00 2001 From: "ask-bonk[bot]" Date: Sun, 17 May 2026 23:31:41 +0000 Subject: [PATCH 1/6] Fixed Workers duration limits in 6 files. Co-authored-by: irvinebroque --- .../best-practices/workers-best-practices.mdx | 2 +- .../docs/workers/observability/errors.mdx | 60 +++++++++---------- src/content/docs/workers/platform/limits.mdx | 21 ++++--- .../docs/workers/runtime-apis/context.mdx | 2 +- .../runtime-apis/handlers/scheduled.mdx | 37 ++++++++---- .../partials/workers/wall-time-limits.mdx | 16 ++--- 6 files changed, 77 insertions(+), 61 deletions(-) diff --git a/src/content/docs/workers/best-practices/workers-best-practices.mdx b/src/content/docs/workers/best-practices/workers-best-practices.mdx index c6a31c2ab1ee8cc..9dfe8a0f779ad4f 100644 --- a/src/content/docs/workers/best-practices/workers-best-practices.mdx +++ b/src/content/docs/workers/best-practices/workers-best-practices.mdx @@ -279,7 +279,7 @@ For more information, refer to [Streams](/workers/runtime-apis/streams/). [`ctx.waitUntil()`](/workers/runtime-apis/context/) lets you perform work after the response is sent to the client, such as analytics, cache writes, non-critical logging, or webhook notifications. This keeps your response fast while still completing background tasks. -There are two common pitfalls: destructuring `ctx` (which loses the `this` binding and throws "Illegal invocation"), and exceeding the 30-second `waitUntil` time limit after the response is sent. +There are two common pitfalls: destructuring `ctx` (which loses the `this` binding and throws "Illegal invocation"), and exceeding the 30-second total invocation wall time limit. diff --git a/src/content/docs/workers/observability/errors.mdx b/src/content/docs/workers/observability/errors.mdx index d45ef218a482019..7c3f97286532cf1 100644 --- a/src/content/docs/workers/observability/errors.mdx +++ b/src/content/docs/workers/observability/errors.mdx @@ -15,22 +15,21 @@ Review Workers errors and exceptions. When a Worker running in production has an error that prevents it from returning a response, the client will receive an error page with an error code, defined as follows: -| Error code | Meaning | -| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -| `1101` | Worker threw a JavaScript exception. | -| `1102` | Worker exceeded [CPU time limit](/workers/platform/limits/#cpu-time). | -| `1103` | The owner of this worker needs to contact [Cloudflare Support](/support/contacting-cloudflare-support/) | -| `1019` | Worker hit [loop limit](#loop-limit). | -| `1021` | Worker has requested a host it cannot access. | -| `1022` | Cloudflare has failed to route the request to the Worker. | -| `1024` | Worker cannot make a subrequest to a Cloudflare-owned IP address. | -| `1027` | Worker exceeded free tier [daily request limit](/workers/platform/limits/#daily-requests). | +| Error code | Meaning | +| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `1101` | Worker threw a JavaScript exception. | +| `1102` | Worker exceeded [CPU time limit](/workers/platform/limits/#cpu-time). | +| `1103` | The owner of this worker needs to contact [Cloudflare Support](/support/contacting-cloudflare-support/) | +| `1019` | Worker hit [loop limit](#loop-limit). | +| `1021` | Worker has requested a host it cannot access. | +| `1022` | Cloudflare has failed to route the request to the Worker. | +| `1024` | Worker cannot make a subrequest to a Cloudflare-owned IP address. | +| `1027` | Worker exceeded free tier [daily request limit](/workers/platform/limits/#daily-requests). | | `1042` | Worker tried to fetch from another Worker on the same zone, which is only [supported](/workers/runtime-apis/fetch/) when the [`global_fetch_strictly_public` compatibility flag](/workers/configuration/compatibility-flags/#global-fetch-strictly-public) is used. | -| `10162` | Module has an unsupported Content-Type. | +| `10162` | Module has an unsupported Content-Type. | Other `11xx` errors generally indicate a problem with the Workers runtime itself. Refer to the [status page](https://www.cloudflarestatus.com) if you are experiencing an error. - ### Loop limit A Worker cannot call itself or another Worker more than 16 times. In order to prevent infinite loops between Workers, the [`CF-EW-Via`](/fundamentals/reference/http-headers/#cf-ew-via) header's value is an integer that indicates how many invocations are left. Every time a Worker is invoked, the integer will decrement by 1. If the count reaches zero, a [`1019`](#error-pages-generated-by-workers) error is returned. @@ -191,23 +190,23 @@ If you need to share state across requests, consider using [Durable Objects](/du These errors occur when a Worker is uploaded or modified. -| Error code | Meaning | -| ---------- | ------------------------------------------------------------------------------------------------------------------------------- | -| `10006` | Could not parse your Worker's code. | -| `10007` | Worker or [workers.dev subdomain](/workers/configuration/routing/workers-dev/) not found. | -| `10015` | Account is not entitled to use Workers. | -| `10016` | Invalid Worker name. | -| `10021` | Validation Error. Refer to [Validation Errors](/workers/observability/errors/#validation-errors-10021) for details. | -| `10026` | Could not parse request body. | -| `10027` | The uploaded Worker exceeded the [Worker size limits](/workers/platform/limits/#worker-size). | -| `10035` | Multiple attempts to modify a resource at the same time | -| `10037` | An account has exceeded the number of [Workers allowed](/workers/platform/limits/#number-of-workers). | -| `10052` | A [binding](/workers/runtime-apis/bindings/) is uploaded without a name. | -| `10054` | A environment variable or secret exceeds the [size limit](/workers/platform/limits/#environment-variables). | -| `10055` | The number of environment variables or secrets exceeds the [limit/Worker](/workers/platform/limits/#environment-variables). | -| `10056` | [Binding](/workers/runtime-apis/bindings/) not found. | -| `10068` | The uploaded Worker has no registered [event handlers](/workers/runtime-apis/handlers/). | -| `10069` | The uploaded Worker contains [event handlers](/workers/runtime-apis/handlers/) unsupported by the Workers runtime. | +| Error code | Meaning | +| ---------- | --------------------------------------------------------------------------------------------------------------------------- | +| `10006` | Could not parse your Worker's code. | +| `10007` | Worker or [workers.dev subdomain](/workers/configuration/routing/workers-dev/) not found. | +| `10015` | Account is not entitled to use Workers. | +| `10016` | Invalid Worker name. | +| `10021` | Validation Error. Refer to [Validation Errors](/workers/observability/errors/#validation-errors-10021) for details. | +| `10026` | Could not parse request body. | +| `10027` | The uploaded Worker exceeded the [Worker size limits](/workers/platform/limits/#worker-size). | +| `10035` | Multiple attempts to modify a resource at the same time | +| `10037` | An account has exceeded the number of [Workers allowed](/workers/platform/limits/#number-of-workers). | +| `10052` | A [binding](/workers/runtime-apis/bindings/) is uploaded without a name. | +| `10054` | A environment variable or secret exceeds the [size limit](/workers/platform/limits/#environment-variables). | +| `10055` | The number of environment variables or secrets exceeds the [limit/Worker](/workers/platform/limits/#environment-variables). | +| `10056` | [Binding](/workers/runtime-apis/bindings/) not found. | +| `10068` | The uploaded Worker has no registered [event handlers](/workers/runtime-apis/handlers/). | +| `10069` | The uploaded Worker contains [event handlers](/workers/runtime-apis/handlers/) unsupported by the Workers runtime. | ### Validation Errors (10021) @@ -236,6 +235,7 @@ Runtime errors will occur within the runtime, do not throw up an error page, and ## Identify errors: Workers Metrics To review whether your application is experiencing any downtime or returning any errors: + 1. In the Cloudflare dashboard, go to the **Workers & Pages** page. @@ -278,7 +278,7 @@ Exceptions will show up under the `exceptions` field in the JSON returned by `wr A Worker can make HTTP requests to any HTTP service on the public Internet. You can use a service like [Sentry](https://sentry.io) to collect error logs from your Worker, by making an HTTP request to the service to report the error. Refer to your service’s API documentation for details on what kind of request to make. -When using an external logging strategy, remember that outstanding asynchronous tasks are canceled as soon as a Worker finishes sending its main response body to the client. To ensure that a logging subrequest completes, pass the request promise to [`event.waitUntil()`](https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent/waitUntil). For example: +When using an external logging strategy, remember that floating promises (promises that are neither `await`ed, `return`ed, nor passed to `ctx.waitUntil()`) may be canceled when the Worker invocation completes. To ensure that a logging subrequest completes, pass the request promise to [`ctx.waitUntil()`](/workers/runtime-apis/context/#waituntil). For example: diff --git a/src/content/docs/workers/platform/limits.mdx b/src/content/docs/workers/platform/limits.mdx index 56385a01059ffa5..089fef5e2117d14 100644 --- a/src/content/docs/workers/platform/limits.mdx +++ b/src/content/docs/workers/platform/limits.mdx @@ -9,7 +9,12 @@ products: - workers --- -import { Render, WranglerConfig, DashButton, TypeScriptExample } from "~/components"; +import { + Render, + WranglerConfig, + DashButton, + TypeScriptExample, +} from "~/components"; ## Account plan limits @@ -146,16 +151,16 @@ To view memory errors in the dashboard: ## Duration -Duration measures wall-clock time from start to end of a Worker invocation. There is no hard limit on duration for HTTP-triggered Workers. As long as the client remains connected, the Worker can continue processing, making subrequests, and setting timeouts. +Duration measures wall-clock time from start to end of a Worker invocation. | Trigger type | Duration limit | | ----------------------------------------------------------------- | -------------- | -| HTTP request | No limit | +| HTTP request | 30 seconds | | [Cron Trigger](/workers/configuration/cron-triggers/) | 15 min | | [Durable Object Alarm](/durable-objects/api/alarms/) | 15 min | | [Queue Consumer](/queues/configuration/javascript-apis/#consumer) | 15 min | -When the client disconnects, all tasks associated with that request are canceled. Use [`event.waitUntil()`](/workers/runtime-apis/handlers/fetch/) to delay cancellation for another 30 seconds or until the promise you pass to `waitUntil()` completes. +For HTTP-triggered Workers, the total invocation wall time is 30 seconds. The response must be returned within this limit. Use [`ctx.waitUntil()`](/workers/runtime-apis/handlers/fetch/) to perform work after returning a response. Any tasks passed to `waitUntil()` must also complete within the same 30-second total wall time. :::note @@ -183,14 +188,14 @@ You can configure the fail mode by toggling the corresponding [route](/workers/c A subrequest is any request a Worker makes using the [Fetch API](/workers/runtime-apis/fetch/) or to Cloudflare services like [R2](/r2/), [KV](/kv/), or [D1](/d1/). -| Limit | Workers Free | Workers Paid | -| -------------------------------- | ------------ | ---------------------- | -| Subrequests per invocation | 50 | 10,000 (up to 10M) | +| Limit | Workers Free | Workers Paid | +| -------------------------------- | ------------ | ----------------------------------------- | +| Subrequests per invocation | 50 | 10,000 (up to 10M) | | Subrequests to internal services | 1,000 | Matches configured limit (default 10,000) | Each subrequest in a redirect chain counts against this limit. The total number of subrequests may exceed the number of `fetch()` calls in your code. You can change the subrequest limit per Worker using the [`limits` configuration](/workers/wrangler/configuration/#limits) in your Wrangler configuration file. -There is no set time limit on individual subrequests. As long as the client remains connected, the Worker can continue making subrequests. When the client disconnects, all tasks are canceled. Use [`event.waitUntil()`](/workers/runtime-apis/handlers/fetch/) to delay cancellation for up to 30 seconds. +There is no set time limit on individual subrequests, but all subrequests must complete within the 30-second total wall time for the invocation. When the invocation ends, all tasks are canceled. ### Worker-to-Worker subrequests diff --git a/src/content/docs/workers/runtime-apis/context.mdx b/src/content/docs/workers/runtime-apis/context.mdx index 970f182ae859f3a..562b38b1429ed82 100644 --- a/src/content/docs/workers/runtime-apis/context.mdx +++ b/src/content/docs/workers/runtime-apis/context.mdx @@ -195,7 +195,7 @@ When declaring an entrypoint class that accepts `props`, make sure to declare it :::caution[`waitUntil` has a 30-second time limit] -The Worker's lifetime is extended for up to 30 seconds after the response is sent or the client disconnects. This time limit is shared across all `waitUntil()` calls within the same request — if any Promises have not settled after 30 seconds, they are cancelled. When `waitUntil` tasks are cancelled, the following warning will be logged to [Workers Logs](/workers/observability/logs/workers-logs/) and any attached [Tail Workers](/workers/observability/logs/tail-workers/): `waitUntil() tasks did not complete within the allowed time after invocation end and have been cancelled.` +For HTTP-triggered Workers, the total invocation wall time is 30 seconds. `ctx.waitUntil()` allows work to continue after the response is sent, but all tasks must complete within the same 30-second total limit. This time limit is shared across all `waitUntil()` calls within the same request — if any Promises have not settled before the 30-second limit is reached, they are cancelled. When `waitUntil` tasks are cancelled, the following warning will be logged to [Workers Logs](/workers/observability/logs/workers-logs/) and any attached [Tail Workers](/workers/observability/logs/tail-workers/): `waitUntil() tasks did not complete within the allowed time after invocation end and have been cancelled.` If you need to guarantee that work completes successfully, you should send messages to a [Queue](/queues/) and process them in a separate consumer Worker. Queues provide reliable delivery and automatic retries, ensuring your work is not lost. diff --git a/src/content/docs/workers/runtime-apis/handlers/scheduled.mdx b/src/content/docs/workers/runtime-apis/handlers/scheduled.mdx index 0cf1342d6bb2643..3199c1bdac3a983 100644 --- a/src/content/docs/workers/runtime-apis/handlers/scheduled.mdx +++ b/src/content/docs/workers/runtime-apis/handlers/scheduled.mdx @@ -37,7 +37,7 @@ curl "http://localhost:8787/cdn-cgi/handler/scheduled?cron=*+*+*+*+*" # Python W ```js export default { async scheduled(controller, env, ctx) { - ctx.waitUntil(doSomeTaskOnASchedule()); + await doSomeTaskOnASchedule(); }, }; ``` @@ -52,7 +52,7 @@ export default { env: Env, ctx: ExecutionContext, ) { - ctx.waitUntil(doSomeTaskOnASchedule()); + await doSomeTaskOnASchedule(); }, }; ``` @@ -64,7 +64,7 @@ from workers import WorkerEntrypoint, Response, fetch class Default(WorkerEntrypoint): async def scheduled(self, controller, env, ctx): - ctx.waitUntil(doSomeTaskOnASchedule()) + await doSomeTaskOnASchedule() ``` @@ -109,10 +109,10 @@ export default { async scheduled(controller, env, ctx) { switch (controller.cron) { case "*/5 * * * *": - ctx.waitUntil(fetch("https://example.com/api/sync")); + await fetch("https://example.com/api/sync"); break; case "0 0 * * *": - ctx.waitUntil(env.MY_KV.put("last-cleanup", new Date().toISOString())); + await env.MY_KV.put("last-cleanup", new Date().toISOString()); break; } }, @@ -130,10 +130,10 @@ export default { ) { switch (controller.cron) { case "*/5 * * * *": - ctx.waitUntil(fetch("https://example.com/api/sync")); + await fetch("https://example.com/api/sync"); break; case "0 0 * * *": - ctx.waitUntil(env.MY_KV.put("last-cleanup", new Date().toISOString())); + await env.MY_KV.put("last-cleanup", new Date().toISOString()); break; } }, @@ -148,9 +148,20 @@ The value of `controller.cron` is the exact cron expression string from your con When a Workers script is invoked by a [Cron Trigger](/workers/configuration/cron-triggers/), the Workers runtime starts a `ScheduledEvent` which will be handled by the `scheduled` function in your Workers Module class. The `ctx` argument represents the context your function runs in, and contains the following methods to control what happens next: -- ctx.waitUntil(promisePromise) : void - Use this method to notify - the runtime to wait for asynchronous tasks (for example, logging, analytics to - third-party services, streaming and caching). The first `ctx.waitUntil` to - fail will be observed and recorded as the status in the [Cron - Trigger](/workers/configuration/cron-triggers/) Past Events table. Otherwise, - it will be reported as a success. +- ctx.waitUntil(promisePromise) : void - Use this method to + register asynchronous tasks (for example, logging, analytics to third-party + services, streaming and caching) that should settle before the invocation + completes. The first `ctx.waitUntil` to fail will be observed and recorded as + the status in the [Cron Trigger](/workers/configuration/cron-triggers/) Past + Events table. Otherwise, it will be reported as a success. + +:::note + +The runtime waits for the promise returned by the `scheduled()` handler to +resolve (up to the 15-minute duration limit). You do not need to use +`waitUntil()` for the runtime to wait for a single asynchronous task. +`waitUntil()` is most useful when you need to run multiple concurrent tasks, or +when you want the outcome of a specific promise to be recorded as the Cron +Trigger invocation status. + +::: diff --git a/src/content/partials/workers/wall-time-limits.mdx b/src/content/partials/workers/wall-time-limits.mdx index 434cd7c3588ed1d..a5958eae050c420 100644 --- a/src/content/partials/workers/wall-time-limits.mdx +++ b/src/content/partials/workers/wall-time-limits.mdx @@ -8,11 +8,11 @@ Wall time (also called wall-clock time) is the total elapsed time from the start The following table summarizes the wall time limits for different types of Worker invocations across the developer platform: -| Invocation type | Wall time limit | Details | -| ------------------------------------------------------------------ | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Incoming HTTP request | Unlimited | No hard limit while the client remains connected. When the client disconnects, tasks are canceled unless you call [`waitUntil()`](/workers/runtime-apis/handlers/fetch/) to extend execution by up to 30 seconds. | -| [Cron Triggers](/workers/configuration/cron-triggers/) | 15 minutes | Scheduled Workers have a maximum wall time of 15 minutes per invocation. | -| [Queue consumers](/queues/configuration/javascript-apis/#consumer) | 15 minutes | Each consumer invocation has a maximum wall time of 15 minutes. | -| [Durable Object alarm handlers](/durable-objects/api/alarms/) | 15 minutes | Alarm handler invocations have a maximum wall time of 15 minutes. | -| [Durable Objects](/durable-objects/) (RPC / HTTP) | Unlimited | No hard limit while the caller stays connected to the Durable Object. | -| [Workflows](/workflows/) (per step) | Unlimited | Each step can run for an unlimited wall time. Individual steps are subject to the configured [CPU time limit](/workers/platform/limits/#cpu-time). | +| Invocation type | Wall time limit | Details | +| ------------------------------------------------------------------ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Incoming HTTP request | 30 seconds | The response must be returned within 30 seconds. [`waitUntil()`](/workers/runtime-apis/handlers/fetch/) allows work to continue after the response is sent, but all tasks must complete within the same 30-second total limit. | +| [Cron Triggers](/workers/configuration/cron-triggers/) | 15 minutes | Scheduled Workers have a maximum wall time of 15 minutes per invocation. | +| [Queue consumers](/queues/configuration/javascript-apis/#consumer) | 15 minutes | Each consumer invocation has a maximum wall time of 15 minutes. | +| [Durable Object alarm handlers](/durable-objects/api/alarms/) | 15 minutes | Alarm handler invocations have a maximum wall time of 15 minutes. | +| [Durable Objects](/durable-objects/) (RPC / HTTP) | Unlimited | No hard limit while the caller stays connected to the Durable Object. | +| [Workflows](/workflows/) (per step) | Unlimited | Each step can run for an unlimited wall time. Individual steps are subject to the configured [CPU time limit](/workers/platform/limits/#cpu-time). | From c4d854ab1d5c1741ccda8fb8e8959f8d89bd56c5 Mon Sep 17 00:00:00 2001 From: Brendan Irvine-Broque Date: Sun, 17 May 2026 17:00:23 -0700 Subject: [PATCH 2/6] Clarify Workers waitUntil duration semantics --- src/content/docs/durable-objects/api/state.mdx | 4 ++-- .../best-practices/workers-best-practices.mdx | 6 +++++- .../docs/workers/observability/errors.mdx | 2 +- src/content/docs/workers/platform/limits.mdx | 6 +++--- .../docs/workers/runtime-apis/context.mdx | 12 ++++++++++-- .../workers/runtime-apis/handlers/scheduled.mdx | 9 +++++---- .../partials/workers/wall-time-limits.mdx | 16 ++++++++-------- 7 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/content/docs/durable-objects/api/state.mdx b/src/content/docs/durable-objects/api/state.mdx index d4614780245c8f5..2c9675b343bfab3 100644 --- a/src/content/docs/durable-objects/api/state.mdx +++ b/src/content/docs/durable-objects/api/state.mdx @@ -77,11 +77,11 @@ Contains loopback bindings to the Worker's own top-level exports. This has exact ### `waitUntil` -`waitUntil` waits until the promise which is passed as a parameter resolves, and can extend a request context even after the last client disconnects. Refer to [Lifecycle of a Durable Object](/durable-objects/concepts/durable-object-lifecycle/) for more information. +`waitUntil` is available on `DurableObjectState` for API compatibility with [Workers Runtime APIs](/workers/runtime-apis/context/#waituntil). :::note[`waitUntil` has no effect in Durable Objects] -Unlike in Workers, `waitUntil` has no effect in Durable Objects. It exists only for API compatibility with the [Workers Runtime APIs](/workers/runtime-apis/context/#waituntil). +Unlike in Workers, `waitUntil` has no effect in Durable Objects. It does not extend the lifetime of a Durable Object or affect when a request or RPC completes. Durable Objects automatically remain active as long as there is ongoing work or pending I/O, so `waitUntil` is not needed. Refer to [Lifecycle of a Durable Object](/durable-objects/concepts/durable-object-lifecycle/) for more information. ::: diff --git a/src/content/docs/workers/best-practices/workers-best-practices.mdx b/src/content/docs/workers/best-practices/workers-best-practices.mdx index 9dfe8a0f779ad4f..5a2b0626be086bb 100644 --- a/src/content/docs/workers/best-practices/workers-best-practices.mdx +++ b/src/content/docs/workers/best-practices/workers-best-practices.mdx @@ -279,7 +279,9 @@ For more information, refer to [Streams](/workers/runtime-apis/streams/). [`ctx.waitUntil()`](/workers/runtime-apis/context/) lets you perform work after the response is sent to the client, such as analytics, cache writes, non-critical logging, or webhook notifications. This keeps your response fast while still completing background tasks. -There are two common pitfalls: destructuring `ctx` (which loses the `this` binding and throws "Illegal invocation"), and exceeding the 30-second total invocation wall time limit. +Use `ctx.waitUntil()` only for work that does not affect the response. If the response depends on the work, `await` it before returning the response or stream the response as the work completes. A Worker that is still streaming a response body remains active without `ctx.waitUntil()`. + +There are two common pitfalls: destructuring `ctx` (which loses the `this` binding and throws "Illegal invocation"), and exceeding the 30-second `waitUntil()` time limit after the response is sent or the client disconnects. @@ -747,6 +749,8 @@ For more information, refer to [Workers errors](/workers/observability/errors/#c A `Promise` that is not `await`ed, `return`ed, or passed to `ctx.waitUntil()` is a floating promise. Floating promises cause silent bugs: dropped results, swallowed errors, and unfinished work. The Workers runtime may terminate your isolate before a floating promise completes. +Choose based on whether the work is required. Use `await` or `return` for work that must complete before the response is correct. Use `ctx.waitUntil()` for best-effort work that may run after the response is sent and can finish within the `waitUntil()` time limit. For work that must complete reliably after the response, use [Queues](/queues/) or [Workflows](/workflows/). + Enable the `no-floating-promises` lint rule to catch these at development time. If you use ESLint, enable [`@typescript-eslint/no-floating-promises`](https://typescript-eslint.io/rules/no-floating-promises/). If you use oxlint, enable [`typescript/no-floating-promises`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-floating-promises.html). ```bash diff --git a/src/content/docs/workers/observability/errors.mdx b/src/content/docs/workers/observability/errors.mdx index 7c3f97286532cf1..05a941bdc89a6ed 100644 --- a/src/content/docs/workers/observability/errors.mdx +++ b/src/content/docs/workers/observability/errors.mdx @@ -278,7 +278,7 @@ Exceptions will show up under the `exceptions` field in the JSON returned by `wr A Worker can make HTTP requests to any HTTP service on the public Internet. You can use a service like [Sentry](https://sentry.io) to collect error logs from your Worker, by making an HTTP request to the service to report the error. Refer to your service’s API documentation for details on what kind of request to make. -When using an external logging strategy, remember that floating promises (promises that are neither `await`ed, `return`ed, nor passed to `ctx.waitUntil()`) may be canceled when the Worker invocation completes. To ensure that a logging subrequest completes, pass the request promise to [`ctx.waitUntil()`](/workers/runtime-apis/context/#waituntil). For example: +When using an external logging strategy, remember that floating promises (promises that are neither `await`ed, `return`ed, nor passed to `ctx.waitUntil()`) may be canceled when the Worker invocation completes. A Worker invocation has not completed while it is still streaming a response body to the client. To run logging after the response is complete, pass the request promise to [`ctx.waitUntil()`](/workers/runtime-apis/context/#waituntil). For example: diff --git a/src/content/docs/workers/platform/limits.mdx b/src/content/docs/workers/platform/limits.mdx index 089fef5e2117d14..6c8a36c123e8e03 100644 --- a/src/content/docs/workers/platform/limits.mdx +++ b/src/content/docs/workers/platform/limits.mdx @@ -155,12 +155,12 @@ Duration measures wall-clock time from start to end of a Worker invocation. | Trigger type | Duration limit | | ----------------------------------------------------------------- | -------------- | -| HTTP request | 30 seconds | +| HTTP request | No limit | | [Cron Trigger](/workers/configuration/cron-triggers/) | 15 min | | [Durable Object Alarm](/durable-objects/api/alarms/) | 15 min | | [Queue Consumer](/queues/configuration/javascript-apis/#consumer) | 15 min | -For HTTP-triggered Workers, the total invocation wall time is 30 seconds. The response must be returned within this limit. Use [`ctx.waitUntil()`](/workers/runtime-apis/handlers/fetch/) to perform work after returning a response. Any tasks passed to `waitUntil()` must also complete within the same 30-second total wall time. +There is no hard limit on duration for HTTP-triggered Workers. As long as the client remains connected, the Worker can continue processing, making subrequests, and streaming a response body. When the client disconnects or the response is complete, tasks associated with that request may be canceled. Use [`ctx.waitUntil()`](/workers/runtime-apis/context/#waituntil) to perform work after returning a response. `waitUntil()` can extend execution for up to 30 seconds after the response is sent or the client disconnects. :::note @@ -195,7 +195,7 @@ A subrequest is any request a Worker makes using the [Fetch API](/workers/runtim Each subrequest in a redirect chain counts against this limit. The total number of subrequests may exceed the number of `fetch()` calls in your code. You can change the subrequest limit per Worker using the [`limits` configuration](/workers/wrangler/configuration/#limits) in your Wrangler configuration file. -There is no set time limit on individual subrequests, but all subrequests must complete within the 30-second total wall time for the invocation. When the invocation ends, all tasks are canceled. +There is no set time limit on individual subrequests. As long as the client remains connected, the Worker can continue making subrequests. When the client disconnects or the response is complete, outstanding work may be canceled unless it is passed to [`ctx.waitUntil()`](/workers/runtime-apis/context/#waituntil), which can extend execution for up to 30 seconds. ### Worker-to-Worker subrequests diff --git a/src/content/docs/workers/runtime-apis/context.mdx b/src/content/docs/workers/runtime-apis/context.mdx index 562b38b1429ed82..e54689f65222c61 100644 --- a/src/content/docs/workers/runtime-apis/context.mdx +++ b/src/content/docs/workers/runtime-apis/context.mdx @@ -188,14 +188,16 @@ When declaring an entrypoint class that accepts `props`, make sure to declare it `ctx.waitUntil()` extends the lifetime of your Worker, allowing you to perform work without blocking returning a response, and that may continue after a response is returned. It accepts a `Promise`, which the Workers runtime will continue executing, even after a response has been returned by the Worker's [handler](/workers/runtime-apis/handlers/). +Use `ctx.waitUntil()` for work that can run after the response is sent, such as non-critical logging, analytics, or cache writes. If the client is still receiving the response, including a streamed response body, the Worker invocation remains active without `ctx.waitUntil()`. If your response depends on the work, `await` the work before returning the response or stream the response as the work completes. + `waitUntil` is commonly used to: - Fire off events to external analytics providers. (note that when you use [Workers Analytics Engine](/analytics/analytics-engine/), you do not need to use `waitUntil`) - Put items into cache using the [Cache API](/workers/runtime-apis/cache/) -:::caution[`waitUntil` has a 30-second time limit] +:::caution[`waitUntil` has a 30-second time limit after invocation end] -For HTTP-triggered Workers, the total invocation wall time is 30 seconds. `ctx.waitUntil()` allows work to continue after the response is sent, but all tasks must complete within the same 30-second total limit. This time limit is shared across all `waitUntil()` calls within the same request — if any Promises have not settled before the 30-second limit is reached, they are cancelled. When `waitUntil` tasks are cancelled, the following warning will be logged to [Workers Logs](/workers/observability/logs/workers-logs/) and any attached [Tail Workers](/workers/observability/logs/tail-workers/): `waitUntil() tasks did not complete within the allowed time after invocation end and have been cancelled.` +For HTTP-triggered Workers, `ctx.waitUntil()` can extend execution for up to 30 seconds after the response is sent or the client disconnects. This is not a limit on the total wall time of an HTTP request. This time limit is shared across all `waitUntil()` calls within the same request. If any Promises have not settled after 30 seconds, they are canceled. When `waitUntil` tasks are canceled, the following warning will be logged to [Workers Logs](/workers/observability/logs/workers-logs/) and any attached [Tail Workers](/workers/observability/logs/tail-workers/): `waitUntil() tasks did not complete within the allowed time after invocation end and have been cancelled.` If you need to guarantee that work completes successfully, you should send messages to a [Queue](/queues/) and process them in a separate consumer Worker. Queues provide reliable delivery and automatic retries, ensuring your work is not lost. @@ -208,6 +210,12 @@ If you are using `waitUntil()` to emit logs or exceptions, we recommend using [T [Cloudflare Queues](/queues/) is purpose-built for performing work out-of-band, without blocking returning a response back to the client Worker. ::: +:::note[`waitUntil` in Durable Objects] + +Do not use `waitUntil()` to keep a Durable Object alive during normal request or RPC handling. Durable Objects remain active while they are handling requests, RPC calls, response streams, WebSockets, or pending I/O. [`DurableObjectState.waitUntil()`](/durable-objects/api/state/#waituntil) exists for API compatibility and is not needed for this behavior. + +::: + You can call `waitUntil()` multiple times. Similar to `Promise.allSettled`, even if a promise passed to one `waitUntil` call is rejected, promises passed to other `waitUntil()` calls will still continue to execute. For example: diff --git a/src/content/docs/workers/runtime-apis/handlers/scheduled.mdx b/src/content/docs/workers/runtime-apis/handlers/scheduled.mdx index 3199c1bdac3a983..b588f8583ca1bca 100644 --- a/src/content/docs/workers/runtime-apis/handlers/scheduled.mdx +++ b/src/content/docs/workers/runtime-apis/handlers/scheduled.mdx @@ -148,7 +148,7 @@ The value of `controller.cron` is the exact cron expression string from your con When a Workers script is invoked by a [Cron Trigger](/workers/configuration/cron-triggers/), the Workers runtime starts a `ScheduledEvent` which will be handled by the `scheduled` function in your Workers Module class. The `ctx` argument represents the context your function runs in, and contains the following methods to control what happens next: -- ctx.waitUntil(promisePromise) : void - Use this method to +- ctx.waitUntil(promise) : void - Use this method to register asynchronous tasks (for example, logging, analytics to third-party services, streaming and caching) that should settle before the invocation completes. The first `ctx.waitUntil` to fail will be observed and recorded as @@ -157,9 +157,10 @@ When a Workers script is invoked by a [Cron Trigger](/workers/configuration/cron :::note -The runtime waits for the promise returned by the `scheduled()` handler to -resolve (up to the 15-minute duration limit). You do not need to use -`waitUntil()` for the runtime to wait for a single asynchronous task. +In module Workers, the runtime waits for the promise returned by the +`scheduled()` handler to resolve (up to the 15-minute duration limit). You do +not need to use `waitUntil()` for the runtime to wait for a single asynchronous +task. `waitUntil()` is most useful when you need to run multiple concurrent tasks, or when you want the outcome of a specific promise to be recorded as the Cron Trigger invocation status. diff --git a/src/content/partials/workers/wall-time-limits.mdx b/src/content/partials/workers/wall-time-limits.mdx index a5958eae050c420..0aba8aed7d05c70 100644 --- a/src/content/partials/workers/wall-time-limits.mdx +++ b/src/content/partials/workers/wall-time-limits.mdx @@ -8,11 +8,11 @@ Wall time (also called wall-clock time) is the total elapsed time from the start The following table summarizes the wall time limits for different types of Worker invocations across the developer platform: -| Invocation type | Wall time limit | Details | -| ------------------------------------------------------------------ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Incoming HTTP request | 30 seconds | The response must be returned within 30 seconds. [`waitUntil()`](/workers/runtime-apis/handlers/fetch/) allows work to continue after the response is sent, but all tasks must complete within the same 30-second total limit. | -| [Cron Triggers](/workers/configuration/cron-triggers/) | 15 minutes | Scheduled Workers have a maximum wall time of 15 minutes per invocation. | -| [Queue consumers](/queues/configuration/javascript-apis/#consumer) | 15 minutes | Each consumer invocation has a maximum wall time of 15 minutes. | -| [Durable Object alarm handlers](/durable-objects/api/alarms/) | 15 minutes | Alarm handler invocations have a maximum wall time of 15 minutes. | -| [Durable Objects](/durable-objects/) (RPC / HTTP) | Unlimited | No hard limit while the caller stays connected to the Durable Object. | -| [Workflows](/workflows/) (per step) | Unlimited | Each step can run for an unlimited wall time. Individual steps are subject to the configured [CPU time limit](/workers/platform/limits/#cpu-time). | +| Invocation type | Wall time limit | Details | +| ------------------------------------------------------------------ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Incoming HTTP request | No hard limit | No hard limit while the client remains connected. A Worker that is still streaming a response body remains active. [`waitUntil()`](/workers/runtime-apis/context/#waituntil) extends execution for up to 30 seconds after the response or disconnect. | +| [Cron Triggers](/workers/configuration/cron-triggers/) | 15 minutes | Scheduled Workers have a maximum wall time of 15 minutes per invocation. | +| [Queue consumers](/queues/configuration/javascript-apis/#consumer) | 15 minutes | Each consumer invocation has a maximum wall time of 15 minutes. | +| [Durable Object alarm handlers](/durable-objects/api/alarms/) | 15 minutes | Alarm handler invocations have a maximum wall time of 15 minutes. | +| [Durable Objects](/durable-objects/) (RPC / HTTP) | No hard limit | No hard limit while the caller stays connected to the Durable Object. Durable Objects remain active while a request, RPC call, response stream, WebSocket, or pending I/O is in flight. | +| [Workflows](/workflows/) (per step) | Unlimited | Each step can run for an unlimited wall time. Individual steps are subject to the configured [CPU time limit](/workers/platform/limits/#cpu-time). | From 66b597a1c31fac7e2e6a8a5f6429841129fc2c6f Mon Sep 17 00:00:00 2001 From: Brendan Irvine-Broque Date: Sun, 17 May 2026 17:09:20 -0700 Subject: [PATCH 3/6] Clarify waitUntil best practice guidance --- .../docs/workers/best-practices/workers-best-practices.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/workers/best-practices/workers-best-practices.mdx b/src/content/docs/workers/best-practices/workers-best-practices.mdx index 5a2b0626be086bb..48b198ade917c72 100644 --- a/src/content/docs/workers/best-practices/workers-best-practices.mdx +++ b/src/content/docs/workers/best-practices/workers-best-practices.mdx @@ -749,7 +749,7 @@ For more information, refer to [Workers errors](/workers/observability/errors/#c A `Promise` that is not `await`ed, `return`ed, or passed to `ctx.waitUntil()` is a floating promise. Floating promises cause silent bugs: dropped results, swallowed errors, and unfinished work. The Workers runtime may terminate your isolate before a floating promise completes. -Choose based on whether the work is required. Use `await` or `return` for work that must complete before the response is correct. Use `ctx.waitUntil()` for best-effort work that may run after the response is sent and can finish within the `waitUntil()` time limit. For work that must complete reliably after the response, use [Queues](/queues/) or [Workflows](/workflows/). +Choose based on whether the response depends on the work. Use `await` or `return` for work that must complete before the response is correct. Use `ctx.waitUntil()` for work that can run after the response is sent and can finish within the `waitUntil()` time limit. Enable the `no-floating-promises` lint rule to catch these at development time. If you use ESLint, enable [`@typescript-eslint/no-floating-promises`](https://typescript-eslint.io/rules/no-floating-promises/). If you use oxlint, enable [`typescript/no-floating-promises`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-floating-promises.html). From 574b9585f00c1196680397baed6595b96f9d0d5e Mon Sep 17 00:00:00 2001 From: Brendan Irvine-Broque Date: Sun, 17 May 2026 17:10:34 -0700 Subject: [PATCH 4/6] Refine scheduled waitUntil note --- .../docs/workers/runtime-apis/handlers/scheduled.mdx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/content/docs/workers/runtime-apis/handlers/scheduled.mdx b/src/content/docs/workers/runtime-apis/handlers/scheduled.mdx index b588f8583ca1bca..a6424e47073ccb9 100644 --- a/src/content/docs/workers/runtime-apis/handlers/scheduled.mdx +++ b/src/content/docs/workers/runtime-apis/handlers/scheduled.mdx @@ -157,10 +157,9 @@ When a Workers script is invoked by a [Cron Trigger](/workers/configuration/cron :::note -In module Workers, the runtime waits for the promise returned by the -`scheduled()` handler to resolve (up to the 15-minute duration limit). You do -not need to use `waitUntil()` for the runtime to wait for a single asynchronous -task. +The runtime waits for the promise returned by the `scheduled()` handler to +resolve (up to the 15-minute duration limit). You do not need to use +`waitUntil()` for the runtime to wait for a single asynchronous task. `waitUntil()` is most useful when you need to run multiple concurrent tasks, or when you want the outcome of a specific promise to be recorded as the Cron Trigger invocation status. From 267a9d95b52df5e90a172f6b83a13b3cb6568a60 Mon Sep 17 00:00:00 2001 From: Brendan Irvine-Broque Date: Sun, 17 May 2026 17:11:40 -0700 Subject: [PATCH 5/6] Refine waitUntil post-response guidance --- .../docs/workers/best-practices/workers-best-practices.mdx | 2 +- src/content/docs/workers/runtime-apis/context.mdx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/content/docs/workers/best-practices/workers-best-practices.mdx b/src/content/docs/workers/best-practices/workers-best-practices.mdx index 48b198ade917c72..0b73861952a3b01 100644 --- a/src/content/docs/workers/best-practices/workers-best-practices.mdx +++ b/src/content/docs/workers/best-practices/workers-best-practices.mdx @@ -277,7 +277,7 @@ For more information, refer to [Streams](/workers/runtime-apis/streams/). ### Use waitUntil for work after the response -[`ctx.waitUntil()`](/workers/runtime-apis/context/) lets you perform work after the response is sent to the client, such as analytics, cache writes, non-critical logging, or webhook notifications. This keeps your response fast while still completing background tasks. +[`ctx.waitUntil()`](/workers/runtime-apis/context/) lets you perform work after the response is sent to the client, such as analytics, cache writes, logging, or webhook notifications. This keeps your response fast while still completing background tasks. Use `ctx.waitUntil()` only for work that does not affect the response. If the response depends on the work, `await` it before returning the response or stream the response as the work completes. A Worker that is still streaming a response body remains active without `ctx.waitUntil()`. diff --git a/src/content/docs/workers/runtime-apis/context.mdx b/src/content/docs/workers/runtime-apis/context.mdx index e54689f65222c61..b8b842b5721aadd 100644 --- a/src/content/docs/workers/runtime-apis/context.mdx +++ b/src/content/docs/workers/runtime-apis/context.mdx @@ -188,7 +188,7 @@ When declaring an entrypoint class that accepts `props`, make sure to declare it `ctx.waitUntil()` extends the lifetime of your Worker, allowing you to perform work without blocking returning a response, and that may continue after a response is returned. It accepts a `Promise`, which the Workers runtime will continue executing, even after a response has been returned by the Worker's [handler](/workers/runtime-apis/handlers/). -Use `ctx.waitUntil()` for work that can run after the response is sent, such as non-critical logging, analytics, or cache writes. If the client is still receiving the response, including a streamed response body, the Worker invocation remains active without `ctx.waitUntil()`. If your response depends on the work, `await` the work before returning the response or stream the response as the work completes. +Use `ctx.waitUntil()` for work that can run after the response is sent, such as logging, analytics, or cache writes, as long as the work can finish within the `waitUntil()` time limit. If the client is still receiving the response, including a streamed response body, the Worker invocation remains active without `ctx.waitUntil()`. If your response depends on the work, `await` the work before returning the response or stream the response as the work completes. `waitUntil` is commonly used to: @@ -199,7 +199,7 @@ Use `ctx.waitUntil()` for work that can run after the response is sent, such as For HTTP-triggered Workers, `ctx.waitUntil()` can extend execution for up to 30 seconds after the response is sent or the client disconnects. This is not a limit on the total wall time of an HTTP request. This time limit is shared across all `waitUntil()` calls within the same request. If any Promises have not settled after 30 seconds, they are canceled. When `waitUntil` tasks are canceled, the following warning will be logged to [Workers Logs](/workers/observability/logs/workers-logs/) and any attached [Tail Workers](/workers/observability/logs/tail-workers/): `waitUntil() tasks did not complete within the allowed time after invocation end and have been cancelled.` -If you need to guarantee that work completes successfully, you should send messages to a [Queue](/queues/) and process them in a separate consumer Worker. Queues provide reliable delivery and automatic retries, ensuring your work is not lost. +If the work cannot finish within the `waitUntil()` time limit, send messages to a [Queue](/queues/) and process them in a separate consumer Worker. Queues provide reliable delivery and automatic retries. ::: From 998533e5d8e8938df4163a1f2741bcf48a94eeb2 Mon Sep 17 00:00:00 2001 From: Brendan Irvine-Broque Date: Sun, 17 May 2026 17:12:33 -0700 Subject: [PATCH 6/6] Restore unlimited wall time labels --- src/content/partials/workers/wall-time-limits.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/content/partials/workers/wall-time-limits.mdx b/src/content/partials/workers/wall-time-limits.mdx index 0aba8aed7d05c70..65e696fc2c262ef 100644 --- a/src/content/partials/workers/wall-time-limits.mdx +++ b/src/content/partials/workers/wall-time-limits.mdx @@ -10,9 +10,9 @@ The following table summarizes the wall time limits for different types of Worke | Invocation type | Wall time limit | Details | | ------------------------------------------------------------------ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Incoming HTTP request | No hard limit | No hard limit while the client remains connected. A Worker that is still streaming a response body remains active. [`waitUntil()`](/workers/runtime-apis/context/#waituntil) extends execution for up to 30 seconds after the response or disconnect. | +| Incoming HTTP request | Unlimited | No hard limit while the client remains connected. A Worker that is still streaming a response body remains active. [`waitUntil()`](/workers/runtime-apis/context/#waituntil) extends execution for up to 30 seconds after the response or disconnect. | | [Cron Triggers](/workers/configuration/cron-triggers/) | 15 minutes | Scheduled Workers have a maximum wall time of 15 minutes per invocation. | | [Queue consumers](/queues/configuration/javascript-apis/#consumer) | 15 minutes | Each consumer invocation has a maximum wall time of 15 minutes. | | [Durable Object alarm handlers](/durable-objects/api/alarms/) | 15 minutes | Alarm handler invocations have a maximum wall time of 15 minutes. | -| [Durable Objects](/durable-objects/) (RPC / HTTP) | No hard limit | No hard limit while the caller stays connected to the Durable Object. Durable Objects remain active while a request, RPC call, response stream, WebSocket, or pending I/O is in flight. | +| [Durable Objects](/durable-objects/) (RPC / HTTP) | Unlimited | No hard limit while the caller stays connected to the Durable Object. Durable Objects remain active while a request, RPC call, response stream, WebSocket, or pending I/O is in flight. | | [Workflows](/workflows/) (per step) | Unlimited | Each step can run for an unlimited wall time. Individual steps are subject to the configured [CPU time limit](/workers/platform/limits/#cpu-time). |