Repository navigation
Enable getrandom support for Emscripten - #2877
guybedford wants to merge 1 commit into
Conversation
Moves the branch off every local checkout and onto published toolchains:
* [patch.crates-io] now carries the workers-rs emscripten tokio patchset
verbatim from cloudflare/workers-rs examples/emscripten-tokio (guybedford
tokio/mio tags + rust-lang/libc), and the whole wasm-bindgen family comes
from crates.io 0.2.129 — the sibling ../workers-rs forks are gone, as are
the 3 tokio methods that fork needed.
* `worker-build --emscripten --release` (workers-rs 0.8.7) is the build
driver, via api/build-worker.sh and wrangler's [build] command. It
provisions emsdk 6.0.10 and applies the emscripten frontend patches itself,
so the hand-patched emscripten checkout and the import-source workaround
are no longer needed. It also requires the `worker` SDK crate in the graph,
which is what makes it pass the tokio/wasm-bindgen unstable cfgs.
* The fetch export is `#[wasm_bindgen(experimental_tokio = "isolated")]`
(upstream's name) and drops `js_namespace = ["default"]`: worker-build's
generated shim provides the default export as a WorkerEntrypoint subclass.
* tokio can no longer use `features = ["full"]` on wasm — the patched tokio
hard-errors on process/signal/rt-multi-thread for emscripten — so the
shared dep lists a wasm-buildable set and native re-adds `full`.
* Release profile for the worker: opt-level = "s" + fat LTO, 36.6MB -> 24.2MB
wasm. opt-level = "z" reaches 22.0MB but triples latency and starves the
per-request pool under load; opt-level = 3 + LTO does not link (wasm-bindgen
cannot parse the exnref output). See EMSCRIPTEN_EXPERIMENT.md#size.
* Four patches retired by upgrading instead of patching (libc from the git
patchset, and socket2 0.6 / deno_doc / tar from the vendored set), leaving the
tokio+mio git patchset and six vendored cfg fixes:
- libc: the emscripten declarations shipped in 0.2.190, so it comes from
crates.io now. The release alone was not enough - the mio fork hardcodes
`libc = { git = "rust-lang/libc", branch = "libc-0.2" }`, so a
[patch."https://github.com/rust-lang/libc"] entry redirects that source at the
published crate, otherwise the graph carries two libc copies.
- socket2 0.6: 0.6.5 has the emscripten cfg upstream (tokio uses it unpatched);
socket2 0.5 refreshed 0.5.7 -> 0.5.10.
- deno_doc: 0.208.0 ships denoland/deno_doc#857. Needs deno_graph =0.109.0 ->
=0.111.0 (two deno_graph versions in one graph are different types), and with
it: BuildOptions::unstable_text_imports is gone (0.111 allows
`with { type: "text" }` unconditionally - jsr had it false, and there is no
flag left to keep it off), BuildOptions gained unstable_config_imports and
prefer_cached_jsr_versions (both false = previous behaviour), GenerateOptions
gained symbol_listing_limit (None = render everything, as before).
- tar: 0.4.46 builds for emscripten unpatched. The blocker was jsr's own
fixture - big_file/big.txt is 21MB of zeros stored sparsely, and tar >= 0.4.46
writes a sparse file as a GNUSparse entry, which the reader rejects as
invalidEntryType before the size check the test is about. tar.sparse(false) in
the test helpers fixes it and stops the fixture depending on the checkout's
filesystem.
The six fixes that remain are deliberately NOT in this commit. jsr does not
carry local patches of third-party crates, so the committed [patch] table is
just the git tags above, and as a consequence the wasm32-unknown-emscripten
link does not succeed from a clean checkout until those six land upstream.
Native builds are unaffected: `cargo test` is 173/173 with no patch directory
present at all, and Cargo.lock resolves those crates from crates.io at main's
versions (reqwest 0.12.12 and astral-tokio-tar 0.6.3 pinned back so re-resolving
does not drag in unrelated drift).
EMSCRIPTEN_EXPERIMENT.md records what each fix is - 2 lines for ring, 3 for
slug, 4 for socket2, ~10 for astral-tokio-tar, 110 mechanical ones for reqwest -
and the blocker for each: ring
needs the tree-sitter grammar bump (tree-sitter-javascript 0.21.4 is the last
crate pinning cc ~1.0.90) before briansmith/ring#2877 can be used; socket2 0.5
is pulled only by routerify's hyper "tcp" feature; reqwest is stuck in two
different ways (below); slug is a mandatory comrak 0.29 dependency that
deno_doc pins; astral-tokio-tar has no upstream fix in 0.7.0.
* .gitignore picks up .wrangler/, api/build/ and .emscripten-patches/, and
.licenserc.json / deno.json exclude .emscripten-patches/ as well, so a locally
reconstructed copy of those crates cannot be committed by accident and does not
trip the license-header check (it globs **/*.rs and flagged 418 files).
Also fixes a regression from the first experiment commit: the native-only
rust-s3 dep had lost `default-features = false`, which re-enabled rust-s3's
`fail-on-err` and turned every missing-object 404 into a 500 (3 failing tests).
Upstream re-checked 2026-10-08: mio#1969 is merged but unreleased (mio 1.2.4
predates it), tokio#8484 and ring#2877 are still open, rust-s3 0.38.0 is still on
reqwest 0.12, and comrak 0.56 drops slug to a dev-dependency but deno_doc still
pins comrak 0.29.
reqwest specifically (2026-10-10): the emscripten gate only ever landed in 0.13 -
0.12.28, the latest 0.12, still has the bare cfg(target_arch = "wasm32") - and
neither copy can reach it from jsr's side. The 0.12 copy belongs to rust-s3 and
opentelemetry (opentelemetry-http is still ^0.12 even at 0.31), and 0.13 cannot
satisfy ^0.12. The 0.11 copy is capped at 0.12 by oauth2 5.0.0, so the only
reachable step is consolidating 0.11 -> 0.12: it retires one of the two patches
and costs oauth2 4->5 plus postmark 0.10->2.0 in the auth/email path.
Verified: 173/173 + 8 native tests pass; the worker serves on stock
`wrangler dev` (no forked workerd) with 64/64 concurrent requests returning
200 and no cross-request-I/O errors.
Bumps tree-sitter-highlight 0.22.6 -> 0.26.13 and the eleven grammars to their current releases. They no longer have to move in lockstep with the core: the grammar crates went over to tree-sitter-language 0.1 and don't depend on tree-sitter itself any more. The reason to do it now is the cc pin. tree-sitter-javascript 0.21.4 was the last crate in the graph asking for cc ~1.0.90, which held cc at 1.0.106, and that in turn held ring at 0.17.9 since 0.17.10 and up need cc ^1.2. Current grammars ask for cc ^1.1/^1.2, so cc resolves to 1.7.0 and ring moves to 0.17.14. That also removes the last blocker on the emscripten branch's ring patch, though the emscripten fix itself is still only in an open PR (briansmith/ring#2877). Highlight stays on 0.26.13 rather than 0.27.1, and tree-sitter-language is pinned to 0.1.7, because 0.27/0.1.8 raised their MSRV to 1.90 and we pin 1.89.0. Two API changes, both in api/src/tree_sitter.rs: grammars export a LanguageFn constant (LANGUAGE, LANGUAGE_TYPESCRIPT, LANGUAGE_XML and so on) instead of a language() function, and HtmlRenderer::render's attribute callback writes into a buffer now instead of returning a slice. Highlighting had no test coverage, so I compared the rendered output for ts, jsx, rust, bash, json, css, xml, md, regex and html before and after the bump: byte-identical. Added a test asserting a token gets a class in each language, because nothing fails loudly when a grammar and CAPTURE_NAMES stop lining up - the code just comes back as plain text.
The tree-sitter bump took tree-sitter-javascript off `cc ~1.0.90`, so cc is at 1.7.0 and ring at 0.17.14. What's left for ring is the emscripten fix itself, briansmith/ring#2877, which is open and lives on `guybedford:emscripten` rather than in briansmith/ring, so using it would mean a git dep on a fork.
Two of the emscripten patches disappeared in the merge, without any work on this branch: main moved jsr's own HTTP client to reqwest 0.13, which has the emscripten cfg upstream, so only rust-s3's 0.12 copy is left; and main did the tree-sitter bump itself, which lifts the `cc ~1.0.90` pin that was holding ring at 0.17.9. ring now comes from the branch briansmith/ring#2877 is opened from rather than a local patch, so the remaining three (socket2 0.5, reqwest 0.12, astral-tokio-tar) are the only ones left. How the conflicts were resolved: * api/Cargo.toml: main's dependency set, with the experiment's gating re-applied on top - tokio trimmed to a wasm-buildable feature set (native re-adds `full`), the OTLP stack and jemalloc moved into the native-only target block, rust-s3 split so wasm skips `tokio-rustls-tls`, async-tar swapped for astral-tokio-tar, and askalono's `gzip` codec. deno_doc stays at =0.209.0 and comrak at 0.56 (the slug fix); everything else takes main's version. * jsonwebtoken is now declared per target: main's `aws_lc_rs` backend for native, the pure-Rust `rust_crypto` one for wasm, because aws-lc-sys does not build for emscripten and the two backends do not feature-unify. jsonwebkey is gone with main's move to jsonwebtoken 11. * api/src/tracing.rs and api/src/tree_sitter.rs: main's rewrites taken whole - the opentelemetry 0.32 API, and the hand-rolled line renderer with source links - then re-gated/ported. tracing.rs keeps the wasm32 cfgs around the exporter stack and the tolerant `set_global_default`/`DISPATCH` (the worker rebuilds its router per request); tree_sitter.rs moves to comrak 0.56's `fmt::Write` adapters. * api/src/analysis.rs and api/src/api/package.rs: main had already added the deno_graph 0.111 `BuildOptions` fields, so our copies of them are dropped. package.rs keeps main's specifier links and collects into a String, which is what comrak's `fmt::Write` adapters need. * api/src/api/mod.rs: main's `/hooks` scope, then the cfg-gated jemalloc routes. * api/src/docs.rs: main's `symbol_listing_limit: Some(SYMBOL_LISTING_LIMIT)`. * api/src/publish.rs: main's wording for the `tar.sparse(false)` comment - it fixed the same sparse-fixture problem independently. * Cargo.lock: regenerated from main's rather than hand-merged. Native verified after the merge: cargo test 267/267, clippy and fmt clean.
|
This is the same Additional coverage was Emscripten 4.0.10, Rust The focused Rust calls are: use ring::rand::{SecureRandom, SystemRandom};
let mut bytes = [0u8; 32];
SystemRandom::new().fill(&mut bytes).unwrap();
let provider = rustls::crypto::ring::default_provider();
provider.secure_random.fill(&mut bytes).unwrap();Our synthetic host also checks entropy failure propagation. It proves the exercised RNG route, not a TLS handshake or a complete application. No duplicate PR is needed for this fix. |
This enables
SystemRandomonwasm32-unknown-emscripten.getrandom0.4 implements Emscripten via libcgetentropy, which Emscripten's runtime backs withcrypto.getRandomValues, so this is the environment's CSPRNG rather than a custom orrdrandimplementation, matching the criterion for the existingtarget_oslist.target_os = "emscripten"to theSystemRandomcfg insrc/rand.rs.No
build.rschanges were needed; the existing wasm32 C build works as-is withCC=emcc AR=emar.Tested locally with Emscripten 6.0.10 and Node 22:
cargo test --target wasm32-unknown-emscriptenwithCARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER=nodepasses the full suite includingrand_testsand doctests.