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 c6a31c2ab1ee8cc..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,9 +277,11 @@ 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. -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. +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 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). ```bash diff --git a/src/content/docs/workers/observability/errors.mdx b/src/content/docs/workers/observability/errors.mdx index d45ef218a482019..05a941bdc89a6ed 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. 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 56385a01059ffa5..6c8a36c123e8e03 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,7 +151,7 @@ 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 | | ----------------------------------------------------------------- | -------------- | @@ -155,7 +160,7 @@ Duration measures wall-clock time from start to end of a Worker invocation. Ther | [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. +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 @@ -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. 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 970f182ae859f3a..b8b842b5721aadd 100644 --- a/src/content/docs/workers/runtime-apis/context.mdx +++ b/src/content/docs/workers/runtime-apis/context.mdx @@ -188,16 +188,18 @@ 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 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: - 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] -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, `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. ::: @@ -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 0cf1342d6bb2643..a6424e47073ccb9 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(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 + 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..65e696fc2c262ef 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 | 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) | 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). |