Repository navigation
computer: Keep long-running isolate JavaScript alive - #237
Merged
Merged
Conversation
The doc comment on WorkerJavaScriptBackendOptions.retentionMs said completed executions are kept for five minutes. The backend keeps them for 60 minutes.
WorkerJavaScriptBackend passed each execution's timeout to the Worker Loader as its CPU limit, so a run allowed to last two hours also asked for two hours of CPU. A long run that mostly waits on I/O needs a long timeout but little CPU. Add a maxCpuMs option. Each execution's CPU limit is the smaller of its timeout and maxCpuMs, which defaults to maxTimeoutMs so existing configurations behave as before. CPU time adds up over the whole Dynamic Worker call, and Cloudflare currently stops a call at about five minutes of CPU whatever the limit, so values above 300000 have no effect. The option comment says so.
From compatibility date 2026-10-01, pending work such as a call into a Dynamic Worker keeps a Durable Object in memory for at most 15 minutes from when it starts. An execution is one such call. If it makes no host calls and prints nothing for that long, for example while it waits on a slow fetch, the object is evicted and the execution dies with it. Before that date, pending work does not keep the object in memory at all. While any execution is running, the backend now schedules an interval timer every keepAliveIntervalMs, 60 seconds by default. Each timer counts as new pending work, so the object stays in memory for as long as an execution runs. The timer is cleared when the last execution settles, so an idle Workspace can still be evicted or hibernate. Set keepAliveIntervalMs to 0 to turn it off, for example when the host keeps the object in memory itself.
Add a section on running isolate JavaScript for longer than the default three minutes: which limits to raise, how the compatibility date decides whether the Durable Object stays in memory, how CPU time is limited separately from wall-clock time, and that nothing carries a run across a deploy or a crash. Replace the paragraph that said pending work keeps the Durable Object resident and that reading the event stream keeps a run alive. Neither holds: before compatibility date 2026-10-01 pending work does not keep the object in memory, and from that date it does so for at most 15 minutes per piece of work.
馃 Changeset detectedLatest commit: d2d5328 The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
When an execution times out or is killed, the backend waits for host calls it already accepted before it records the execution as finished. A host module that ignores its abort signal and never settles leaves the execution finalizing forever, and the keep-alive timer kept the Durable Object in memory, and billed, for as long as that lasted. Without the timer the object would eventually have been evicted. Give each running execution a keep-alive deadline of its timeout plus maxHostCallMs, the longest a well-behaved host call accepted at the last moment can take. The timer drops executions past their deadline when it fires and stops once none are left, so the platform's own eviction rules apply again.
Pending work protects a Durable Object for at most 15 minutes from when it starts, so a keep-alive timer with a longer interval leaves gaps in which the object can be evicted mid-execution. The option accepted any finite value. Refuse 15 minutes or more when the backend is constructed.
The long-running section only mentioned maxCapabilityCalls. A run also reaches the cumulative capability request and response byte limits, the shared standard output and error limit, and the platform's subrequest limit, all counted over the whole execution. List them with their defaults, and note that the keep-alive timer stops counting a run past its timeout plus maxHostCallMs, that the object is billed and can't hibernate while it runs, and that a long run holds one of the ten Dynamic Workers a Durable Object can have in flight.
Replace the two changesets for maxCpuMs and the keep-alive timer with one short entry covering both.
scuffi
force-pushed
the
computer/long-running-js-exec
branch
from
October 14, 2026 09:39
df853a1 to
123b64d
Compare
scuffi
marked this pull request as ready for review
October 14, 2026 14:24
Pending work keeps a Durable Object in memory only when the host Worker's compatibility date is 2026-10-01 or later, or it sets durable_object_io_tasks_prevent_eviction. On earlier dates the keep-alive timer did nothing but fire every minute while a run was in flight. The backend now reads the host's flags from Cloudflare.compatibilityFlags when it connects and schedules the timer only when that flag is on. The Dynamic Worker's own compatibility date plays no part. Outside the Workers runtime, where the flags can't be read, the timer is scheduled as before, which is harmless. A host that moves to a newer date gets the timer without changing any configuration.
Killing the last running execution and closing the Workspace with executions running both clear the keep-alive timer. Only a run settling on its own was tested before.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Isolate JavaScript can't run in the background for long yet.
runtime.exec()returns straight away, but the run only lives as long as its Durable Object stays in memory:The backend also used each run's timeout as its CPU limit, so a two-hour timeout asked for two hours of CPU.
Changes
keepAliveIntervalMs(60 seconds by default,0turns it off, 15 minutes or more is rejected). Each timer counts as new pending work, so the object stays in memory for the whole run. The timer stops when the last run settles. The backend reads the host Worker's compatibility flags and only schedules the timer where pending work can keep the object in memory, so earlier dates get no timer at all. A run also stops counting once its timeout plusmaxHostCallMshas passed, so a host module that never settles can't pin the object forever.maxCpuMs. Caps a run's CPU limit below its timeout. It defaults tomaxTimeoutMs, so nothing changes unless you set it.docs/17_isolate_javascript.mdgets a "Long-running executions" section, including the per-run limits a long run hits first. It also drops a paragraph claiming that pending work keeps the object resident and that reading the event stream keeps a run alive, neither of which is true.Earlier compatibility dates behave exactly as before. A deploy or crash still ends a run, and the run is marked as failed the next time the backend connects.
Trying it
Use compatibility date 2026-10-01 or later, and raise the limits that grow with run length:
Then start a long run from a Durable Object method that returns without awaiting it, and check the result with
getExeclater.Testing
Unit tests cover the timer (including kill, close, and the compatibility-flag gate) and the CPU limit. In production,
Cloudflare.compatibilityFlags.durable_object_io_tasks_prevent_evictionreadfalseon a Worker dated 2026-06-01 andtrueon one dated 2026-10-12. I also ran this branch in production on compatibility date 2026-10-12, starting every run from a method that returned straight away:cpuMs400000maxCpuMswith a 2 hour timeout (cpuMs7200000)Caveats
maxCapabilityCalls(256), capability request and response bytes (8 MiB each), output (1 MiB), and the platform's subrequest limit.Follow-ups
maxCapabilityCallsfirst.