Repository navigation
Conversation
…c builds
Getting remote-exec into a Worker meant downloading it,
checking it, and either importing it, which holds it in memory, or
uploading it with wrangler.jsonc rules, which the Cloudflare Vite
plugin ignores.
remoteExec() from @cloudflare/computer/vite does it. It downloads
each build from the @cloudflare/computer release matching the
installed package, checks it against the release's SHA256SUMS, and
caches it in Vite's cache directory. It serves them as
`computer:remote-exec`:
import binaries from "computer:remote-exec";
createExecModule({ binaries, ... });
vite build emits each build as remote-exec-linux-<arch>.bin beside
the Worker. Nothing imports it. The output wrangler.json has
no_bundle, so wrangler deploy uploads every file its default module
rules match, and .bin files go up as Data modules, which workerd
shows under /bundle. The module streams them from there with
fileRemoteExec(), so the Worker never holds a build in memory. vite
dev puts nothing but its entry under /bundle, so there the module
imports the builds instead.
It takes `arch` (x86_64 by default), `version` and `builds`, a
directory of local builds. The tests serve a release locally, and
run the result in vite preview and vite dev.
…build The example now builds with Vite, and remoteExec() gets the remote-exec build: from the release by default, or from artifacts/remote-exec when the repository has its own, as in CI. scripts/fetch-remote-exec.sh, bin/ and the wrangler.jsonc rules go. The tests start the example with Vite's createServer() rather than wrangler dev. It needs @cloudflare/vite-plugin 1.68.0. 1.63's workerd drops a container's exec() streams sent over RPC before they finish, which hung every command; that's why wrangler moved to 4.158.0. Every workspace now asks for 1.68.0. It also emits each chunk's source map, so computerBundle()'s build test counts chunks without them.
|
| Name | Type |
|---|---|
| @cloudflare/computer | Minor |
| @cloudflare/dofs | Minor |
| @cloudflare/computer-rpc | Minor |
| @cloudflare/computerd | Minor |
| @cloudflare/remote-exec | Minor |
Click here to learn what changesets are, and how to add one.
Click here if you're a maintainer who wants to add a changeset to this PR
commit: |
| async generateBundle() { | ||
| if (!carriers.has(this.environment.name)) return; | ||
| for (const { arch, path } of await prepare()) { | ||
| this.emitFile({ type: "asset", fileName: carried(arch), source: await readFile(path) }); | ||
| } |
There was a problem hiding this comment.
🔴 Deployed Worker lacks its remote-exec build
On deployment, generateBundle emits an unreferenced binary that Wrangler does not upload without additional-module discovery. fileRemoteExec then cannot open its /bundle path, so remote commands fail.
Learn more
Wrangler uploads an additional module only when it is imported or its path matches an enabled additional-module rule. The build branch creates a virtual module containing only a path string, so it does not import the emitted binary. The example removes find_additional_modules, and its emitted configuration has no rule for .bin files; preview can see Vite output assets without proving Wrangler deploy uploads them. The runtime then passes /bundle/remote-exec-linux-x86_64.bin to fileRemoteExec, which opens that path on the first command.
Example: Build and deploy the example Worker, then call /s/demo/run with a program invoking exec(["true"]). The binary exists under dist but is absent from the deployed module bundle, so its file read fails instead of running true.
Recommended fix: Arrange for the built Wrangler upload to include the emitted binary as a named Data module under its fixed path, without importing its bytes into the Worker. Add a deployment-level test that inspects uploaded modules or runs an actual deployed Worker, rather than testing only vite preview.
Was this helpful? React with 👍 or 👎 to provide feedback.
| const partial = `${path}.${process.pid}.partial`; | ||
| await writeFile(partial, bytes, { mode: 0o755 }); | ||
| await rename(partial, path); |
There was a problem hiding this comment.
🟡 Concurrent builds collide on cached binary
When two builds share a cache and process, writeAtomically gives both downloads the same partial filename. One rename can remove the other's file or publish incomplete bytes, failing a build or corrupting its cached binary.
Learn more
Each plugin instance memoizes its own prepare() call, but two Vite builders can create separate plugin instances while sharing a cache directory. Both can see that the same release binary is missing, download it, and write to the identical ${path}.${process.pid}.partial name. writeFile truncates the shared partial file, and the first rename removes it from the path before the second rename. The second build then fails; overlapping writes can also leave a cached binary that does not match the validated bytes.
Example: In one Node process, start two Vite builds of the same project with an empty cache and the same release. Both target remote-exec-linux-x86_64.bin.<pid>.partial; if the first renames before the second, the second gets ENOENT.
Recommended fix: Use a unique partial filename per write, such as a random suffix, and clean it up on failure. Keep the final rename atomic and consider how concurrent writers to the same release path interact.
Was this helpful? React with 👍 or 👎 to provide feedback.
codemode:execcopies theremote-execbinary into the container on first use, so the Worker has to carry it. Getting it there meant downloading the build, checking its hash, and then either importing it, which holds 1.5 MB in memory, or uploading it withwrangler.jsoncmodule rules, which the Cloudflare Vite plugin ignores. The example did the first half with a shell script and the second with those rules.remoteExec()from@cloudflare/computer/vitedoes all of it. It downloads each build from the@cloudflare/computerGitHub release matching the installed package, checks it against the release'sSHA256SUMS, caches it in Vite's cache directory, and serves it as a virtual module:vite buildemits each build asremote-exec-linux-<arch>.binnext to the Worker without importing it. Wrangler uploads.binfiles as data modules, which workerd exposes under/bundle, and the module streams them from there withfileRemoteExec(), so a deployed Worker never holds the build in memory.vite devonly puts the entry under/bundle, so in development the module imports the build instead. The plugin takesarch(x86_64by default, which is what Cloudflare runs),version, andbuilds, a directory of local builds to use instead of downloading.The example now runs under
vite dev, withremoteExec()taking the repository's own builds fromartifacts/remote-execwhen they exist, as they do in CI. The fetch script,bin/and thewrangler.jsoncrules are gone, and the end-to-end tests start the example with Vite'screateServer()instead of spawningwrangler dev. This needs@cloudflare/vite-plugin1.68: the workerd in 1.63 drops a container'sexec()streams sent over RPC before they finish, which hangs every command. Every workspace now asks for 1.68, which also emits source maps, so thecomputerBundle()build test now ignores.mapfiles.The plugin's tests serve a fake release locally, then check the download, the checksum and cache handling, and run the result in both
vite previewandvite dev. To run the example end to end with Docker:docs/20_remote_exec.md, the package README and the example README describe the plugin, and the unreleased remote-exec changeset now mentions it.Until a release ships with the remote-exec binaries,
remoteExec()with no options fails, because the 0.10.0 release doesn't have them. Its error says to passversionorbuilds. The example works from a checkout onceremote-exechas been built locally.