Repository navigation
Conversation
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.
Native Rust worker process for self-hosted "use rust" actions (opt-in, experimental)
Motivation
The wasm runtime for Rust actions is portable and sandboxed by construction. It also has limits that some
self-hosters hit:
rayon);wasm32-wasip1cannot be used;stdbehaviour that wasm lacks.This adds an optional second runtime. The same
#[action]code is built as a native shared library and run ina separate, sandboxed worker process that the backend manages. It is off by default and changes nothing while
off.
Design summary
Full document:
docs/rust-native-runtime.md(commit 3), including request-flowsketches and the safety assessment.
cdylib. A generic workerdlopens it in a separate process.__rust_native_worker), so thereis no new binary to package and no host/worker version skew.
reaper and a spawn-failure breaker.
ABI; stdout and stderr are separate pipes; a status pipe carries fatal notes.
SIGKILL. Memory is enforced by a counting allocator, an RSS watchdog and anrlimit or cgroup backstop. Error texts match the wasm runtime where one exists.
protocol is unchanged. Push-time validation parses the ELF or Mach-O (never executes it): format, dependencies,
glibc floor, exports, and manifest.
GET /api/rust_native/infolets the CLI discover whether native is enabled and which target to build for.Trust model (please read)
Native mode runs user-supplied machine code as an OS process on the backend host. It is intended only for
trusted, single-tenant, self-hosted backends where the operator controls every pushed crate. It is not a
multi-tenant isolation boundary and is never enabled on Convex Cloud.
no_new_privs, Landlock (no access to thebackend data directory), seccomp-bpf (no
execve,fork,ptrace,mount,bpf,io_uring; no socketsunless allowed), optional uid/gid drop and cgroup v2 limit. Level is
best-effortby default (applies whatthe kernel offers) or
strict(refuse to spawn without Landlock and seccomp).The backend refuses to start native mode there unless
RUST_NATIVE_ALLOW_UNSANDBOXED=trueis set..init_array) run atdlopen, after Landlock and seccomp are applied (the workerapplies the sandbox before loading the library; type-enforced). Whether
dlopensucceeds under the realfilter is not yet verified on a Linux kernel. On macOS there is no sandbox.
RUST_NATIVE_MAX_CALLS_PER_WORKER=1restoresper-call isolation.
The same exposure class as the existing self-hosted
"use node"executor, with a stronger sandbox.Upstream footprint
Almost everything is new files under
crates/isolate/src/environment/rust/native/,rust_analyze/native.rs,local_backend/src/rust_native.rsandnpm-packages/convex/src/cli/lib/rust/native.ts.Edits to files that exist in upstream
get-convex/convex-backend(commit 2 only):crates/local_backend/src/main.rsmain: hand over to the worker entry whenargv[1] == __rust_native_worker(must precede arg parsing and tracing)crates/local_backend/src/router.rscrates/local_backend/src/lib.rsmod rust_native;crates/common/src/knobs.rsRUST_NATIVE_*knob blockcrates/{isolate,application,local_backend}/Cargo.tomllibloadingin isolate;objectandsha2in application;isolatein local_backend). seccomp and Landlock are raw syscalls, no extra cratesCargo.locknpm-packages/convex/src/cli/{deploy,dev}.ts,lib/config.ts,schemas/convex.schema.json--rust-runtimeflag,rust.runtimesetting, backend capability lookup (+22, +22/-1, +7, +13)Totals for the native commit: 35 files; 12 touch files that exist upstream (+183/-1 lines, of which Rust source
lines outside
Cargo.*are 109, 104 of them the knob block); 16 are new files (+13,290 lines). The remaining 7modify files that commit 1 introduced.
Commit 1 (the wasm runtime) touches 25 upstream files (+881/-56, of which 691/29 are
Cargo.lock) and adds 25 new files (+8,152 lines). Patch sizes: wasm 365,531 bytes (340,994 withoutCargo.lock), native 579,546 bytes (578,813 without).HTTP actions on the native runtime
#[http_action]functions run on the native runtime (and on the wasm runtime in this stack), in a buffered form.The function manifest marks them
kind:"http"; the analyzer registers them as internal actions (never public,so a client cannot forge a request head), and the CLI generates
convex/_generated/rustHttpwithrustHttpAction("<module>:<fn>"), a V8httpActionthat calls the internal action with the request (head, base64body,
buffered) and builds theResponsefrom the returned{status, headers, body}. Request bodies must stayunder about 12 MiB (argument limit); a streaming response is collected in the guest and sent whole (20 MiB cap).
Streaming HTTP actions need the stock V8 host. This part (analyzer registration and
rustHttpcodegen, about 230lines including tests) is in the first commit; the native commit adds nothing to it.
Platform status
isolatecross-compiles forx86_64-unknown-linux-gnuandclippyis clean (zigcross build; this found and fixed a Linux-only build error in the sandbox module,
a5d46a5). That is a compilecheck only.
real kernel in this preparation. Its plan construction and the BPF program are unit-tested, but the end-to-end
isolation checks listed under Testing still need a run on Linux (a Docker container on a Linux host
or a VM). Please treat Linux sandbox behaviour as unverified until
that run is reported in this thread.
Testing
Commands:
cargo test -p isolate rust(wasm and native unit tests: frames, pump with a fake worker, pool with a fakespawner, worker loop, sandbox plan, crash classification).
cargo test -p application rust_analyze(push-time validation with synthetic ELF and Mach-O files).cd npm-packages/convex && npx vitest run src/cli/lib/rustandnpx tsc --noEmit --emitDeclarationOnly false.overflow, OOM, timeout,
process::exit, output floods), isolation checks and lifecycle checks as listed inthe design, on macOS arm64 (local backend) and Linux (Docker
ubuntu:noble, arm64 and x86_64).Results on this branch (macOS arm64):
cargo check -p isolate -p application -p local_backendpasses; its onlywarning is pre-existing upstream (
db_connection). The same two commits on the earlier base342530f(identical code apart from one added match arm for868554b, "Add function.runtime to log stream events"):
cargo test -p isolate -p application --lib rust128 + 32passed, CLI vitest 104 passed,
tsc --noEmitclean, and the end-to-end spec and parity suite passed 47/47 againsta backend built from that tree. Commit 1 alone: 47/47 end to end as well. Linux sandbox checks are still open
(see Platform status).
How to try it
Cross-building from macOS to a Linux backend uses
cargo-zigbuild(cargo install cargo-zigbuild,brew install zig); the CLI builds for<triple>.<backend glibc>. User documentation lives in theconvex-rsSDK repository(
docs/rust/native.md), to be published.Open questions
nowinside the worker to save a round trip, at the cost of clock parity under test runtimes?RLIMIT_DATAenough without a delegated cgroup in Docker?upstream push code, so it is deferred.
the sandbox".
ModuleEnvironment::Rusthook (about200 lines, no new dependencies) with the runtimes kept out of tree?
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
https://claude.ai/code/session_01QtCEDMqQnUwastSLyVXX3S