From 42252ec3709627971e6ad6ecdf118f9a72e6213c Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Tue, 14 Jul 2026 14:27:40 -0700 Subject: [PATCH 01/12] feat: enable the wasm32-unknown-emscripten target --- .github/workflows/ci.yml | 41 ++++++++ spellcheck.dic | 3 + tokio-macros/src/entry.rs | 20 +++- tokio-test/src/lib.rs | 29 ++++++ tokio/Cargo.toml | 14 ++- tokio/src/lib.rs | 55 ++++++++++ tokio/src/macros/cfg.rs | 52 +++++++++- tokio/src/process/emscripten.rs | 127 ++++++++++++++++++++++++ tokio/src/process/mod.rs | 7 +- tokio/src/runtime/time/mod.rs | 12 ++- tokio/src/runtime/time/tests/mod.rs | 2 +- tokio/src/sync/tests/atomic_waker.rs | 2 +- tokio/src/sync/tests/notify.rs | 2 +- tokio/src/sync/tests/semaphore_batch.rs | 2 +- tokio/src/task/coop/mod.rs | 2 +- tokio/src/task/local.rs | 2 +- tokio/src/time/mod.rs | 14 ++- tokio/src/util/idle_notified_set.rs | 2 +- tokio/src/util/mod.rs | 18 ++-- tokio/src/util/trace.rs | 17 ++-- tokio/src/util/wake_list.rs | 5 + tokio/tests/macros_join.rs | 6 +- tokio/tests/macros_pin.rs | 4 +- tokio/tests/macros_select.rs | 4 +- tokio/tests/macros_try_join.rs | 4 +- tokio/tests/sync_barrier.rs | 2 +- tokio/tests/sync_broadcast.rs | 4 +- tokio/tests/sync_broadcast_weak.rs | 2 +- tokio/tests/sync_errors.rs | 2 +- tokio/tests/sync_mpsc.rs | 6 +- tokio/tests/sync_mpsc_weak.rs | 2 +- tokio/tests/sync_mutex.rs | 6 +- tokio/tests/sync_mutex_owned.rs | 6 +- tokio/tests/sync_notify.rs | 2 +- tokio/tests/sync_notify_owned.rs | 2 +- tokio/tests/sync_oneshot.rs | 6 +- tokio/tests/sync_rwlock.rs | 6 +- tokio/tests/sync_semaphore.rs | 2 +- tokio/tests/sync_semaphore_owned.rs | 2 +- tokio/tests/sync_watch.rs | 2 +- tokio/tests/time_wasm.rs | 6 +- 41 files changed, 431 insertions(+), 73 deletions(-) create mode 100644 tokio/src/process/emscripten.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 974a2050f24..6f5b7939d07 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1192,6 +1192,47 @@ jobs: RUSTFLAGS: --cfg tokio_unstable CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime run -Sinherit-network + wasm32-unknown-emscripten: + name: test tokio for wasm32-unknown-emscripten + needs: basics + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - name: Install Rust ${{ env.rust_stable }} + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.rust_stable }} + targets: wasm32-unknown-emscripten + + - name: Install Emscripten + uses: mymindstorm/setup-emsdk@v14 + with: + version: 'latest' + + - uses: actions/setup-node@v4 + with: + node-version: 26 + + - uses: Swatinem/rust-cache@v2 + + - name: Install cargo-hack + uses: taiki-e/install-action@v2 + with: + tool: cargo-hack + + - name: Check tokio feature matrix for emscripten + run: cargo hack check -p tokio --each-feature --exclude-features full,net,process,signal,rt-multi-thread,io-uring,taskdump,schedule-latency --target wasm32-unknown-emscripten + working-directory: tokio + env: + RUSTFLAGS: "" + + - name: Test tokio for emscripten + run: cargo test -p tokio --target wasm32-unknown-emscripten --features "rt,time,sync,macros,io-util,test-util" --tests + working-directory: tokio + env: + CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER: node + RUSTFLAGS: "-C link-args=-sALLOW_MEMORY_GROWTH=1 -C link-args=-sEXIT_RUNTIME=1 -C link-args=-sSTACK_SIZE=1048576" + check-external-types: name: check-external-types (${{ matrix.os }}) needs: basics diff --git a/spellcheck.dic b/spellcheck.dic index 9d447933443..a81a07f62b6 100644 --- a/spellcheck.dic +++ b/spellcheck.dic @@ -106,6 +106,9 @@ dns DNS DoS dwOpenMode +emscripten +Emscripten +emscripten's endian enqueue enqueued diff --git a/tokio-macros/src/entry.rs b/tokio-macros/src/entry.rs index 5ed8853a9eb..41e70f5dc93 100644 --- a/tokio-macros/src/entry.rs +++ b/tokio-macros/src/entry.rs @@ -506,7 +506,7 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt let body_ident = quote! { body }; // This explicit `return` is intentional. See tokio-rs/tokio#4636 - let last_block = quote_spanned! {last_stmt_end_span=> + let native_last_block = quote_spanned! {last_stmt_end_span=> #[allow(clippy::expect_used, clippy::diverging_sub_expression, clippy::needless_return, clippy::unwrap_in_result)] { @@ -521,6 +521,24 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt }; + // Emscripten runs the native expansion; only the `multi_thread` flavor + // diverges — it has no native threads there, so it's rejected with a + // targeted error rather than the opaque failure of the native + // multi-thread `block_on`. + let last_block = match config.flavor { + RuntimeFlavor::Threaded => quote! { + #[cfg(not(target_os = "emscripten"))] + #native_last_block + #[cfg(target_os = "emscripten")] + ::core::compile_error!( + "the `multi_thread` runtime flavor is not available on \ + wasm32-unknown-emscripten (no native threads); use \ + `flavor = \"current_thread\"`" + ); + }, + _ => native_last_block, + }; + let body = input.body(); // For test functions pin the body to the stack and use `Pin<&mut dyn diff --git a/tokio-test/src/lib.rs b/tokio-test/src/lib.rs index 87e63861210..321392bf9ff 100644 --- a/tokio-test/src/lib.rs +++ b/tokio-test/src/lib.rs @@ -24,6 +24,7 @@ pub mod task; /// [`tokio::runtime::Runtime::block_on`][runtime-block-on]. /// /// [runtime-block-on]: https://docs.rs/tokio/1.3.0/tokio/runtime/struct.Runtime.html#method.block_on +#[cfg(not(target_os = "emscripten"))] pub fn block_on(future: F) -> F::Output { use tokio::runtime; @@ -34,3 +35,31 @@ pub fn block_on(future: F) -> F::Output { rt.block_on(future) } + +/// Emscripten variant: polls once with a no-op waker. Ready futures (mocks, +/// pure computation) complete; ones that must yield (timers, I/O) panic — +/// use `#[tokio::test]` for those. +#[cfg(target_os = "emscripten")] +pub fn block_on(future: F) -> F::Output { + use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; + + const VTABLE: RawWakerVTable = RawWakerVTable::new( + |_| RawWaker::new(std::ptr::null(), &VTABLE), + |_| {}, + |_| {}, + |_| {}, + ); + // SAFETY: vtable entries are valid no-ops. + let waker = unsafe { Waker::from_raw(RawWaker::new(std::ptr::null(), &VTABLE)) }; + let mut cx = Context::from_waker(&waker); + + let mut future = Box::pin(future); + match future.as_mut().poll(&mut cx) { + Poll::Ready(output) => output, + Poll::Pending => panic!( + "tokio_test::block_on: future returned Pending on emscripten. \ + The main thread cannot block on JS event-loop wakeups; use #[tokio::test] \ + for futures that need timers/network I/O." + ), + } +} diff --git a/tokio/Cargo.toml b/tokio/Cargo.toml index e260bbc5bd9..0cd6fa059ff 100644 --- a/tokio/Cargo.toml +++ b/tokio/Cargo.toml @@ -101,7 +101,7 @@ bytes = { version = "1.2.1", optional = true } mio = { version = "1.2.0", optional = true, default-features = false } parking_lot = { version = "0.12.0", optional = true } -[target.'cfg(any(not(target_family = "wasm"), all(target_os = "wasi", not(target_env = "p1"))))'.dependencies] +[target.'cfg(any(not(target_family = "wasm"), target_os = "emscripten", all(target_os = "wasi", not(target_env = "p1"))))'.dependencies] socket2 = { version = "0.6.3", optional = true, features = ["all"] } # Currently unstable. The API exposed by these features may be broken at any time. @@ -130,6 +130,8 @@ libc = { version = "0.2.168", optional = true } [target.'cfg(unix)'.dev-dependencies] libc = { version = "0.2.168" } + +[target.'cfg(all(unix, not(target_os = "emscripten")))'.dev-dependencies] nix = { version = "0.31.0", default-features = false, features = ["aio", "fs", "socket"] } [target.'cfg(windows)'.dependencies.windows-sys] @@ -146,22 +148,26 @@ features = [ [dev-dependencies] tokio-test = "0.4.0" tokio-stream = "0.1" -tokio-util = { version = "0.7", features = ["rt"] } futures = { version = "0.3.0", features = ["async-await"] } futures-test = "0.3.31" mockall = "0.13.0" async-stream = "0.3" futures-concurrency = "7.6.3" +[target.'cfg(not(target_os = "emscripten"))'.dev-dependencies] +tokio-util = { version = "0.7", features = ["rt"] } + [target.'cfg(not(target_family = "wasm"))'.dev-dependencies] socket2 = "0.6.0" -tempfile = "3.1.0" proptest = "1" +[target.'cfg(any(not(target_family = "wasm"), target_os = "emscripten"))'.dev-dependencies] +tempfile = "3.1.0" + [target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dev-dependencies] rand = "0.9" -[target.'cfg(all(target_family = "wasm", not(target_os = "wasi")))'.dev-dependencies] +[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dev-dependencies] wasm-bindgen-test = "0.3.0" [target.'cfg(target_os = "freebsd")'.dev-dependencies] diff --git a/tokio/src/lib.rs b/tokio/src/lib.rs index efa527606af..bbe71de86b1 100644 --- a/tokio/src/lib.rs +++ b/tokio/src/lib.rs @@ -444,6 +444,53 @@ //! immediately instead of blocking forever. On platforms that don't support //! time, this means that the runtime can never be idle in any way. //! +//! ### Emscripten support +//! +//! The `wasm32-unknown-emscripten` target is supported at parity with the +//! other wasm targets. A host-event-loop execution model with a parking +//! `block_on` is a planned follow-up; until it lands, a `block_on` whose +//! future cannot resolve synchronously behaves as on other single-threaded +//! wasm targets. +//! +//! Supported features: `rt`, `time`, `sync`, `macros`, `fs`, `io-util`, +//! `io-std`, and `test-util`. `fs` and the stdio types run their `std::*` +//! calls inline, since emscripten's filesystem syscalls complete +//! synchronously. The `net` reactor (epoll-backed, over emscripten's socket +//! support) is planned as a follow-up; until then the `net` feature fails to +//! build for this target (rejected by `mio`). +//! +//! The `process`, `signal`, and `rt-multi-thread` features are rejected at +//! compile time: `process`/`signal` have no underlying primitives (`fork`/`exec`, +//! kernel signal delivery) and `rt-multi-thread` has no native threads. +//! +//! `spawn_blocking` has no threadpool to dispatch to, so the closure runs as a +//! canonical spawned task on the single thread and returns the usual +//! `JoinHandle`. `tokio::fs::*` and `tokio::io::{stdin, stdout, stderr}` +//! likewise run their `std::*` calls inline, because emscripten's libc +//! syscalls complete synchronously and don't block the cooperative scheduler. +//! +//! Panics behave as on native: `wasm32-unknown-emscripten` defaults to +//! `panic = "unwind"`, so panic recovery works, a panicking task yields +//! `Err(JoinError)`, and `JoinError::is_panic` / `JoinError::into_panic` +//! report the payload. +//! +//! `#[tokio::test]` / `#[tokio::main]` use the native macro expansion; the +//! `multi_thread` flavor is rejected (no native threads) — use +//! `flavor = "current_thread"`. +//! +//! +//! #### Linking and running on emscripten +//! +//! No js-library or other custom file is required, and plain `node` runs the +//! test binaries directly: +//! +//! ```text +//! CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER="node" +//! RUSTFLAGS="-C link-args=-sALLOW_MEMORY_GROWTH=1 \ +//! -C link-args=-sEXIT_RUNTIME=1 \ +//! -C link-args=-sSTACK_SIZE=1048576" +//! ``` +//! //! ## Unstable `WASM` support //! //! Tokio also has unstable support for some additional `WASM` features. This @@ -467,6 +514,7 @@ compile_error! { #[cfg(all( not(tokio_unstable), target_family = "wasm", + not(target_os = "emscripten"), any( feature = "fs", feature = "io-std", @@ -478,6 +526,13 @@ compile_error! { ))] compile_error!("Only features sync,macros,io-util,rt,time are supported on wasm."); +// On emscripten, `process`, `signal`, and `rt-multi-thread` compile but are +// inert, so `full` (and any dependency that enables these features) still +// builds. `process` and `signal` have no `fork`/`exec` or kernel signal +// delivery, so their modules are compiled out (see `cfg_process!` / +// `cfg_signal!`). The multi-threaded runtime compiles but only runs under a +// `PROXY_TO_PTHREAD` build; `#[tokio::main]` steers to `current_thread`. + #[cfg(all(not(tokio_unstable), feature = "io-uring"))] compile_error!("The `io-uring` feature requires `--cfg tokio_unstable`."); diff --git a/tokio/src/macros/cfg.rs b/tokio/src/macros/cfg.rs index 0a011eb630c..1db46e3505a 100644 --- a/tokio/src/macros/cfg.rs +++ b/tokio/src/macros/cfg.rs @@ -398,6 +398,11 @@ macro_rules! cfg_process { #[cfg_attr(docsrs, doc(cfg(feature = "process")))] #[cfg(not(loom))] #[cfg(not(target_os = "wasi"))] + // emscripten has no `fork`/`exec`, so the `process` module is a + // throwing stub there (see `process/emscripten.rs`); it still + // compiles so dependents that name the types build. The orphan + // reaper / signal driver it would otherwise need stays off via + // `cfg_process_driver!`. $item )* } @@ -407,6 +412,9 @@ macro_rules! cfg_process_driver { ($($item:item)*) => { #[cfg(unix)] #[cfg(not(loom))] + // The driver (orphan reaper backed by the signal handler) doesn't exist + // on emscripten; the process module there is a throwing stub. + #[cfg(not(target_os = "emscripten"))] cfg_process! { $($item)* } } } @@ -414,7 +422,10 @@ macro_rules! cfg_process_driver { macro_rules! cfg_not_process_driver { ($($item:item)*) => { $( - #[cfg(not(all(unix, not(loom), feature = "process")))] + #[cfg(any( + not(all(unix, not(loom), feature = "process")), + target_os = "emscripten", + ))] $item )* } @@ -427,6 +438,8 @@ macro_rules! cfg_signal { #[cfg_attr(docsrs, doc(cfg(feature = "signal")))] #[cfg(not(loom))] #[cfg(not(target_os = "wasi"))] + // No kernel signal delivery on emscripten; inert there. + #[cfg(not(target_os = "emscripten"))] $item )* } @@ -437,6 +450,7 @@ macro_rules! cfg_signal_internal { $( #[cfg(any(feature = "signal", all(unix, feature = "process")))] #[cfg(not(loom))] + #[cfg(not(target_os = "emscripten"))] $item )* } @@ -452,7 +466,7 @@ macro_rules! cfg_signal_internal_and_unix { macro_rules! cfg_not_signal_internal { ($($item:item)*) => { $( - #[cfg(any(loom, not(unix), not(any(feature = "signal", all(unix, feature = "process")))))] + #[cfg(any(loom, not(unix), target_os = "emscripten", not(any(feature = "signal", all(unix, feature = "process")))))] $item )* } @@ -715,7 +729,7 @@ macro_rules! cfg_not_wasip1 { macro_rules! cfg_is_wasm_not_wasi { ($($item:item)*) => { $( - #[cfg(all(target_family = "wasm", not(target_os = "wasi")))] + #[cfg(all(target_family = "wasm", target_os = "unknown"))] $item )* } @@ -768,3 +782,35 @@ macro_rules! cfg_not_schedule_latency { )* } } + +/// Enables emscripten-specific code. +macro_rules! cfg_emscripten { + ($($item:item)*) => { + $( + #[cfg(target_os = "emscripten")] + #[cfg_attr(docsrs, doc(cfg(target_os = "emscripten")))] + $item + )* + } +} + +/// Enables code for non-emscripten targets. +macro_rules! cfg_not_emscripten { + ($($item:item)*) => { + $( + #[cfg(not(target_os = "emscripten"))] + $item + )* + } +} + +/// Enables code requiring both the `rt` feature and the emscripten target. +macro_rules! cfg_rt_emscripten { + ($($item:item)*) => { + $( + #[cfg(all(feature = "rt", target_os = "emscripten"))] + #[cfg_attr(docsrs, doc(cfg(all(feature = "rt", target_os = "emscripten"))))] + $item + )* + } +} diff --git a/tokio/src/process/emscripten.rs b/tokio/src/process/emscripten.rs new file mode 100644 index 00000000000..093b3eb8aa1 --- /dev/null +++ b/tokio/src/process/emscripten.rs @@ -0,0 +1,127 @@ +//! Throwing process stub for `wasm32-unknown-emscripten`. +//! +//! emscripten has no `fork`/`exec`, so there is nothing to spawn or reap: the +//! orphan reaper / signal driver the unix backend relies on is compiled out +//! (see `cfg_process_driver!`). We still provide the `imp` surface that +//! `process::mod` is generic over so dependents that merely name these types +//! keep compiling. Spawning fails before it reaches here (`std`'s own +//! `Command::spawn` returns `Unsupported` on emscripten), and the `Child` / +//! `ChildStdio` types are uninhabited — every method is unreachable. + +use crate::io::{AsyncRead, AsyncWrite, ReadBuf}; +use crate::process::kill::Kill; +use crate::process::SpawnedChild; + +use std::fmt; +use std::future::Future; +use std::io; +use std::os::fd::{AsFd, AsRawFd, BorrowedFd, IntoRawFd, OwnedFd, RawFd}; +use std::pin::Pin; +use std::process::{Child as StdChild, ExitStatus, Stdio}; +use std::task::{Context, Poll}; + +fn unsupported() -> io::Result { + Err(io::Error::new( + io::ErrorKind::Unsupported, + "spawning processes is not supported on wasm32-unknown-emscripten", + )) +} + +pub(crate) enum Child {} + +impl Child { + pub(crate) fn id(&self) -> u32 { + match *self {} + } + + pub(crate) fn try_wait(&mut self) -> io::Result> { + match *self {} + } +} + +impl fmt::Debug for Child { + fn fmt(&self, _fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self {} + } +} + +impl Kill for Child { + fn kill(&mut self) -> io::Result<()> { + match *self {} + } +} + +impl Future for Child { + type Output = io::Result; + + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + match *self.get_mut() {} + } +} + +pub(crate) fn build_child(_child: StdChild) -> io::Result { + unsupported() +} + +pub(crate) enum ChildStdio {} + +impl ChildStdio { + pub(crate) fn into_owned_fd(self) -> io::Result { + match self {} + } +} + +impl fmt::Debug for ChildStdio { + fn fmt(&self, _fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self {} + } +} + +impl AsRawFd for ChildStdio { + fn as_raw_fd(&self) -> RawFd { + match *self {} + } +} + +impl AsFd for ChildStdio { + fn as_fd(&self) -> BorrowedFd<'_> { + match *self {} + } +} + +impl AsyncWrite for ChildStdio { + fn poll_write( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + _buf: &[u8], + ) -> Poll> { + match *self.get_mut() {} + } + + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + match *self.get_mut() {} + } + + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + match *self.get_mut() {} + } +} + +impl AsyncRead for ChildStdio { + fn poll_read( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + _buf: &mut ReadBuf<'_>, + ) -> Poll> { + match *self.get_mut() {} + } +} + +pub(crate) fn convert_to_stdio(io: ChildStdio) -> io::Result { + match io {} +} + +pub(crate) fn stdio(io: T) -> io::Result { + let _ = io.into_raw_fd(); + unsupported() +} diff --git a/tokio/src/process/mod.rs b/tokio/src/process/mod.rs index fb233cb4eb2..37914aa6465 100644 --- a/tokio/src/process/mod.rs +++ b/tokio/src/process/mod.rs @@ -227,7 +227,12 @@ //! [`Child`]: crate::process::Child #[path = "unix/mod.rs"] -#[cfg(unix)] +#[cfg(all(unix, not(target_os = "emscripten")))] +mod imp; + +// emscripten has no `fork`/`exec`: a throwing stub keeps the API present. +#[path = "emscripten.rs"] +#[cfg(target_os = "emscripten")] mod imp; #[cfg(unix)] diff --git a/tokio/src/runtime/time/mod.rs b/tokio/src/runtime/time/mod.rs index 224d2ff4696..9e7a9ac5560 100644 --- a/tokio/src/runtime/time/mod.rs +++ b/tokio/src/runtime/time/mod.rs @@ -287,12 +287,22 @@ impl Driver { } impl Handle { - pub(self) fn process(&self, clock: &Clock) { + #[cfg_attr(not(target_os = "emscripten"), allow(dead_code))] + pub(crate) fn process(&self, clock: &Clock) { let now = self.time_source().now(clock); self.process_at_time(now); } + /// Returns the absolute deadline in ticks for the soonest unexpired + /// timer, or `None` if no timers are registered. Used by the emscripten + /// schedule loop to compute its `setTimeout` delay; ticks here are the + /// time wheel's monotonic tick basis (`time_source.now()`). + #[cfg(target_os = "emscripten")] + pub(crate) fn next_expiration_tick(&self) -> Option { + self.inner.lock().wheel.next_expiration_time() + } + pub(self) fn process_at_time(&self, mut now: u64) { let mut waker_list = WakeList::new(); diff --git a/tokio/src/runtime/time/tests/mod.rs b/tokio/src/runtime/time/tests/mod.rs index 84c765af69e..0773f91c167 100644 --- a/tokio/src/runtime/time/tests/mod.rs +++ b/tokio/src/runtime/time/tests/mod.rs @@ -1,4 +1,4 @@ -#![cfg(not(target_os = "wasi"))] +#![cfg(all(not(target_os = "wasi"), not(target_os = "emscripten")))] use std::{task::Context, time::Duration}; diff --git a/tokio/src/sync/tests/atomic_waker.rs b/tokio/src/sync/tests/atomic_waker.rs index b182574f325..8c5e1a113d0 100644 --- a/tokio/src/sync/tests/atomic_waker.rs +++ b/tokio/src/sync/tests/atomic_waker.rs @@ -15,7 +15,7 @@ impl AssertSync for AtomicWaker {} impl AssertSend for Waker {} impl AssertSync for Waker {} -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; #[test] diff --git a/tokio/src/sync/tests/notify.rs b/tokio/src/sync/tests/notify.rs index 540aa22444a..bfbc6f486c8 100644 --- a/tokio/src/sync/tests/notify.rs +++ b/tokio/src/sync/tests/notify.rs @@ -3,7 +3,7 @@ use std::future::Future; use std::sync::Arc; use std::task::{Context, RawWaker, RawWakerVTable, Waker}; -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; #[test] diff --git a/tokio/src/sync/tests/semaphore_batch.rs b/tokio/src/sync/tests/semaphore_batch.rs index fb5e8fdd6f7..84f062d8f23 100644 --- a/tokio/src/sync/tests/semaphore_batch.rs +++ b/tokio/src/sync/tests/semaphore_batch.rs @@ -3,7 +3,7 @@ use tokio_test::*; const MAX_PERMITS: usize = crate::sync::Semaphore::MAX_PERMITS; -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; #[test] diff --git a/tokio/src/task/coop/mod.rs b/tokio/src/task/coop/mod.rs index 4a520e6f527..5b9ebbd9e50 100644 --- a/tokio/src/task/coop/mod.rs +++ b/tokio/src/task/coop/mod.rs @@ -496,7 +496,7 @@ cfg_coop! { mod test { use super::*; - #[cfg(all(target_family = "wasm", not(target_os = "wasi")))] + #[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; fn get() -> Budget { diff --git a/tokio/src/task/local.rs b/tokio/src/task/local.rs index 4d467d0fbf5..a447a184933 100644 --- a/tokio/src/task/local.rs +++ b/tokio/src/task/local.rs @@ -1276,7 +1276,7 @@ impl LocalState { // ensure they are on the same thread that owns the `LocalSet`. unsafe impl Send for LocalState {} -#[cfg(all(test, not(loom)))] +#[cfg(all(test, not(loom), not(target_os = "emscripten")))] mod tests { use super::*; diff --git a/tokio/src/time/mod.rs b/tokio/src/time/mod.rs index 8627a7838aa..e2621e5af9b 100644 --- a/tokio/src/time/mod.rs +++ b/tokio/src/time/mod.rs @@ -19,6 +19,9 @@ //! //! These types must be used from within the context of the [`Runtime`](crate::runtime::Runtime). //! +//! On `wasm32-unknown-emscripten` these timers are driven by JavaScript's +//! event loop via `setTimeout`. +//! //! # Examples //! //! Wait 100ms and print "100 ms have elapsed" @@ -86,11 +89,6 @@ mod clock; pub(crate) use self::clock::Clock; -cfg_test_util! { - pub use clock::{advance, pause, resume}; -} - -pub mod error; mod instant; pub use self::instant::Instant; @@ -105,6 +103,12 @@ mod timeout; #[doc(inline)] pub use timeout::{timeout, timeout_at, Timeout}; +cfg_test_util! { + pub use clock::{advance, pause, resume}; +} + +pub mod error; + // Re-export for convenience #[doc(no_inline)] pub use std::time::Duration; diff --git a/tokio/src/util/idle_notified_set.rs b/tokio/src/util/idle_notified_set.rs index 4c5177c5630..3d9e7dd2b8c 100644 --- a/tokio/src/util/idle_notified_set.rs +++ b/tokio/src/util/idle_notified_set.rs @@ -492,7 +492,7 @@ unsafe impl linked_list::Link for ListEntry { } } -#[cfg(all(test, not(loom)))] +#[cfg(all(test, not(loom), not(target_os = "emscripten")))] mod tests { use crate::runtime::Builder; use crate::task::JoinSet; diff --git a/tokio/src/util/mod.rs b/tokio/src/util/mod.rs index c671fd6a1da..d715c3ea942 100644 --- a/tokio/src/util/mod.rs +++ b/tokio/src/util/mod.rs @@ -5,14 +5,15 @@ cfg_io_driver! { #[cfg(feature = "fs")] pub(crate) mod as_ref; -#[cfg(feature = "rt")] -pub(crate) mod atomic_cell; +cfg_rt! { + pub(crate) mod atomic_cell; +} -#[cfg(feature = "net")] -mod blocking_check; -#[cfg(feature = "net")] -#[allow(unused_imports)] -pub(crate) use blocking_check::check_socket_for_blocking; +cfg_net! { + mod blocking_check; + #[allow(unused_imports)] + pub(crate) use blocking_check::check_socket_for_blocking; +} pub(crate) mod metric_atomics; @@ -40,6 +41,9 @@ mod wake_list; feature = "signal", feature = "time", ))] +// Some WakeList consumers are cfg'd out on emscripten, so the re-export may be +// unused there. +#[cfg_attr(target_os = "emscripten", allow(unused_imports))] pub(crate) use wake_list::WakeList; #[cfg(any( diff --git a/tokio/src/util/trace.rs b/tokio/src/util/trace.rs index b22c2aeb593..9de1ee0ebfa 100644 --- a/tokio/src/util/trace.rs +++ b/tokio/src/util/trace.rs @@ -1,3 +1,4 @@ +// SpawnMeta uses native runtime task infrastructure cfg_rt! { use std::marker::PhantomData; @@ -180,12 +181,12 @@ cfg_rt! { } } -cfg_time! { - #[track_caller] - pub(crate) fn caller_location() -> Option<&'static std::panic::Location<'static>> { - #[cfg(all(tokio_unstable, feature = "tracing"))] - return Some(std::panic::Location::caller()); - #[cfg(not(all(tokio_unstable, feature = "tracing")))] - None - } +#[cfg(feature = "time")] +#[cfg_attr(docsrs, doc(cfg(feature = "time")))] +#[track_caller] +pub(crate) fn caller_location() -> Option<&'static std::panic::Location<'static>> { + #[cfg(all(tokio_unstable, feature = "tracing"))] + return Some(std::panic::Location::caller()); + #[cfg(not(all(tokio_unstable, feature = "tracing")))] + None } diff --git a/tokio/src/util/wake_list.rs b/tokio/src/util/wake_list.rs index 23a559d02be..84e69fabc83 100644 --- a/tokio/src/util/wake_list.rs +++ b/tokio/src/util/wake_list.rs @@ -1,3 +1,8 @@ +// On emscripten, `WakeList` is reachable via the feature gates that pull +// it into `util::mod`, but the actual consumers (time::clock, sync::notify, +// etc.) may be cfg'd out — leaving the type used only through a re-export. +#![cfg_attr(target_os = "emscripten", allow(dead_code))] + use core::mem::MaybeUninit; use core::ptr; use std::task::Waker; diff --git a/tokio/tests/macros_join.rs b/tokio/tests/macros_join.rs index 4c6db26d8ae..8b304be8da4 100644 --- a/tokio/tests/macros_join.rs +++ b/tokio/tests/macros_join.rs @@ -2,13 +2,13 @@ #![allow(clippy::disallowed_names)] use std::sync::Arc; -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] #[cfg(target_pointer_width = "64")] use wasm_bindgen_test::wasm_bindgen_test as test; -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as maybe_tokio_test; -#[cfg(not(all(target_family = "wasm", not(target_os = "wasi"))))] +#[cfg(not(all(target_family = "wasm", target_os = "unknown")))] use tokio::test as maybe_tokio_test; use tokio::sync::{oneshot, Semaphore}; diff --git a/tokio/tests/macros_pin.rs b/tokio/tests/macros_pin.rs index 2de68ad031f..19c29d56fdb 100644 --- a/tokio/tests/macros_pin.rs +++ b/tokio/tests/macros_pin.rs @@ -1,9 +1,9 @@ #![cfg(feature = "macros")] -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as maybe_tokio_test; -#[cfg(not(all(target_family = "wasm", not(target_os = "wasi"))))] +#[cfg(not(all(target_family = "wasm", target_os = "unknown")))] use tokio::test as maybe_tokio_test; async fn one() {} diff --git a/tokio/tests/macros_select.rs b/tokio/tests/macros_select.rs index 3b403e4ce27..662645c0740 100644 --- a/tokio/tests/macros_select.rs +++ b/tokio/tests/macros_select.rs @@ -1,10 +1,10 @@ #![cfg(feature = "macros")] #![allow(clippy::disallowed_names)] -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as maybe_tokio_test; -#[cfg(not(all(target_family = "wasm", not(target_os = "wasi"))))] +#[cfg(not(all(target_family = "wasm", target_os = "unknown")))] use tokio::test as maybe_tokio_test; use tokio::sync::oneshot; diff --git a/tokio/tests/macros_try_join.rs b/tokio/tests/macros_try_join.rs index 03172ca2a2d..25f811c8e22 100644 --- a/tokio/tests/macros_try_join.rs +++ b/tokio/tests/macros_try_join.rs @@ -6,10 +6,10 @@ use std::{convert::Infallible, sync::Arc}; use tokio::sync::{oneshot, Semaphore}; use tokio_test::{assert_pending, assert_ready, task}; -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as maybe_tokio_test; -#[cfg(not(all(target_family = "wasm", not(target_os = "wasi"))))] +#[cfg(not(all(target_family = "wasm", target_os = "unknown")))] use tokio::test as maybe_tokio_test; #[maybe_tokio_test] diff --git a/tokio/tests/sync_barrier.rs b/tokio/tests/sync_barrier.rs index ac5977f24d8..a8f8e9790bc 100644 --- a/tokio/tests/sync_barrier.rs +++ b/tokio/tests/sync_barrier.rs @@ -2,7 +2,7 @@ #![warn(rust_2018_idioms)] #![cfg(feature = "sync")] -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; use tokio::sync::Barrier; diff --git a/tokio/tests/sync_broadcast.rs b/tokio/tests/sync_broadcast.rs index 2bfe235e511..94bfde6bbc2 100644 --- a/tokio/tests/sync_broadcast.rs +++ b/tokio/tests/sync_broadcast.rs @@ -2,7 +2,7 @@ #![warn(rust_2018_idioms)] #![cfg(feature = "sync")] -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; use tokio::sync::broadcast; @@ -563,7 +563,7 @@ fn sender_len() { } #[test] -#[cfg(not(all(target_family = "wasm", not(target_os = "wasi"))))] +#[cfg(not(all(target_family = "wasm", target_os = "unknown")))] fn sender_len_random() { let (tx, mut rx1) = broadcast::channel(16); let mut rx2 = tx.subscribe(); diff --git a/tokio/tests/sync_broadcast_weak.rs b/tokio/tests/sync_broadcast_weak.rs index 1e7fd6f2d67..ccca130e52e 100644 --- a/tokio/tests/sync_broadcast_weak.rs +++ b/tokio/tests/sync_broadcast_weak.rs @@ -2,7 +2,7 @@ #![warn(rust_2018_idioms)] #![cfg(feature = "sync")] -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; use tokio::sync::broadcast::{self, channel}; diff --git a/tokio/tests/sync_errors.rs b/tokio/tests/sync_errors.rs index 4e43c8f311e..2bc9b878111 100644 --- a/tokio/tests/sync_errors.rs +++ b/tokio/tests/sync_errors.rs @@ -1,7 +1,7 @@ #![warn(rust_2018_idioms)] #![cfg(feature = "sync")] -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; fn is_error() {} diff --git a/tokio/tests/sync_mpsc.rs b/tokio/tests/sync_mpsc.rs index 398ab633927..207bef40d7f 100644 --- a/tokio/tests/sync_mpsc.rs +++ b/tokio/tests/sync_mpsc.rs @@ -2,12 +2,12 @@ #![warn(rust_2018_idioms)] #![cfg(feature = "sync")] -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as maybe_tokio_test; -#[cfg(not(all(target_family = "wasm", not(target_os = "wasi"))))] +#[cfg(not(all(target_family = "wasm", target_os = "unknown")))] use tokio::test as maybe_tokio_test; use std::fmt; diff --git a/tokio/tests/sync_mpsc_weak.rs b/tokio/tests/sync_mpsc_weak.rs index fba0fe4e33a..f09e79dab9d 100644 --- a/tokio/tests/sync_mpsc_weak.rs +++ b/tokio/tests/sync_mpsc_weak.rs @@ -2,7 +2,7 @@ #![warn(rust_2018_idioms)] #![cfg(feature = "sync")] -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; use std::sync::atomic::AtomicUsize; diff --git a/tokio/tests/sync_mutex.rs b/tokio/tests/sync_mutex.rs index 8d74addad75..37ecf7a2e20 100644 --- a/tokio/tests/sync_mutex.rs +++ b/tokio/tests/sync_mutex.rs @@ -1,12 +1,12 @@ #![warn(rust_2018_idioms)] #![cfg(feature = "sync")] -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as maybe_tokio_test; -#[cfg(not(all(target_family = "wasm", not(target_os = "wasi"))))] +#[cfg(not(all(target_family = "wasm", target_os = "unknown")))] use tokio::test as maybe_tokio_test; use tokio::sync::Mutex; diff --git a/tokio/tests/sync_mutex_owned.rs b/tokio/tests/sync_mutex_owned.rs index 28b2afbf32b..95f7ba82e4b 100644 --- a/tokio/tests/sync_mutex_owned.rs +++ b/tokio/tests/sync_mutex_owned.rs @@ -1,12 +1,12 @@ #![warn(rust_2018_idioms)] #![cfg(feature = "sync")] -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as maybe_tokio_test; -#[cfg(not(all(target_family = "wasm", not(target_os = "wasi"))))] +#[cfg(not(all(target_family = "wasm", target_os = "unknown")))] use tokio::test as maybe_tokio_test; use tokio::sync::Mutex; diff --git a/tokio/tests/sync_notify.rs b/tokio/tests/sync_notify.rs index ee7a9ecf0ad..3eb92716080 100644 --- a/tokio/tests/sync_notify.rs +++ b/tokio/tests/sync_notify.rs @@ -1,7 +1,7 @@ #![warn(rust_2018_idioms)] #![cfg(feature = "sync")] -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; use tokio::sync::Notify; diff --git a/tokio/tests/sync_notify_owned.rs b/tokio/tests/sync_notify_owned.rs index 06a0f6ade57..cef574a68aa 100644 --- a/tokio/tests/sync_notify_owned.rs +++ b/tokio/tests/sync_notify_owned.rs @@ -1,7 +1,7 @@ #![warn(rust_2018_idioms)] #![cfg(feature = "sync")] -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; use std::sync::Arc; diff --git a/tokio/tests/sync_oneshot.rs b/tokio/tests/sync_oneshot.rs index 08206ea1d33..8aaddbdb39a 100644 --- a/tokio/tests/sync_oneshot.rs +++ b/tokio/tests/sync_oneshot.rs @@ -1,12 +1,12 @@ #![warn(rust_2018_idioms)] #![cfg(feature = "sync")] -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as maybe_tokio_test; -#[cfg(not(all(target_family = "wasm", not(target_os = "wasi"))))] +#[cfg(not(all(target_family = "wasm", target_os = "unknown")))] use tokio::test as maybe_tokio_test; use tokio::sync::oneshot; diff --git a/tokio/tests/sync_rwlock.rs b/tokio/tests/sync_rwlock.rs index 2dc7b0a62ac..c51f9616cc6 100644 --- a/tokio/tests/sync_rwlock.rs +++ b/tokio/tests/sync_rwlock.rs @@ -1,12 +1,12 @@ #![warn(rust_2018_idioms)] #![cfg(feature = "sync")] -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as maybe_tokio_test; -#[cfg(not(all(target_family = "wasm", not(target_os = "wasi"))))] +#[cfg(not(all(target_family = "wasm", target_os = "unknown")))] use tokio::test as maybe_tokio_test; use std::task::Poll; diff --git a/tokio/tests/sync_semaphore.rs b/tokio/tests/sync_semaphore.rs index f204f85c480..a5cf7ec091a 100644 --- a/tokio/tests/sync_semaphore.rs +++ b/tokio/tests/sync_semaphore.rs @@ -1,6 +1,6 @@ #![cfg(feature = "sync")] -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; use std::sync::Arc; diff --git a/tokio/tests/sync_semaphore_owned.rs b/tokio/tests/sync_semaphore_owned.rs index f9eeee0cfab..d0e3eb4cdf1 100644 --- a/tokio/tests/sync_semaphore_owned.rs +++ b/tokio/tests/sync_semaphore_owned.rs @@ -1,6 +1,6 @@ #![cfg(feature = "sync")] -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; use std::sync::Arc; diff --git a/tokio/tests/sync_watch.rs b/tokio/tests/sync_watch.rs index 48e4106841f..df366f4a8cb 100644 --- a/tokio/tests/sync_watch.rs +++ b/tokio/tests/sync_watch.rs @@ -2,7 +2,7 @@ #![warn(rust_2018_idioms)] #![cfg(feature = "sync")] -#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] +#[cfg(all(target_family = "wasm", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; use tokio::sync::watch; diff --git a/tokio/tests/time_wasm.rs b/tokio/tests/time_wasm.rs index 8e0483f2041..073df353de7 100644 --- a/tokio/tests/time_wasm.rs +++ b/tokio/tests/time_wasm.rs @@ -1,5 +1,9 @@ #![warn(rust_2018_idioms)] -#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] +#![cfg(all( + target_arch = "wasm32", + not(target_os = "wasi"), + not(target_os = "emscripten") +))] use wasm_bindgen_test::wasm_bindgen_test; From e8c38a8bdebe8228a0410a5a156e32ec3344d0f9 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Tue, 14 Jul 2026 16:07:16 -0700 Subject: [PATCH 02/12] spelling --- spellcheck.dic | 4 ++-- tokio/src/lib.rs | 22 ++++++++++------------ tokio/src/macros/cfg.rs | 12 ++++++------ tokio/src/process/mod.rs | 2 +- tokio/src/runtime/time/mod.rs | 2 +- tokio/src/util/mod.rs | 2 +- tokio/src/util/wake_list.rs | 2 +- 7 files changed, 22 insertions(+), 24 deletions(-) diff --git a/spellcheck.dic b/spellcheck.dic index a81a07f62b6..9bb2566b13f 100644 --- a/spellcheck.dic +++ b/spellcheck.dic @@ -106,9 +106,8 @@ dns DNS DoS dwOpenMode -emscripten Emscripten -emscripten's +Emscripten's endian enqueue enqueued @@ -326,6 +325,7 @@ Wakers wakeup wakeups WASI +Wasm watchOS workstealing ZST diff --git a/tokio/src/lib.rs b/tokio/src/lib.rs index bbe71de86b1..d98898cb6c9 100644 --- a/tokio/src/lib.rs +++ b/tokio/src/lib.rs @@ -447,15 +447,14 @@ //! ### Emscripten support //! //! The `wasm32-unknown-emscripten` target is supported at parity with the -//! other wasm targets. A host-event-loop execution model with a parking +//! other Wasm targets. A host-event-loop execution model with a parking //! `block_on` is a planned follow-up; until it lands, a `block_on` whose //! future cannot resolve synchronously behaves as on other single-threaded -//! wasm targets. +//! Wasm targets. //! //! Supported features: `rt`, `time`, `sync`, `macros`, `fs`, `io-util`, -//! `io-std`, and `test-util`. `fs` and the stdio types run their `std::*` -//! calls inline, since emscripten's filesystem syscalls complete -//! synchronously. The `net` reactor (epoll-backed, over emscripten's socket +//! `io-std`, and `test-util` — the same surface as the other single-threaded +//! Wasm targets. The `net` reactor (epoll-backed, over Emscripten's socket //! support) is planned as a follow-up; until then the `net` feature fails to //! build for this target (rejected by `mio`). //! @@ -463,11 +462,10 @@ //! compile time: `process`/`signal` have no underlying primitives (`fork`/`exec`, //! kernel signal delivery) and `rt-multi-thread` has no native threads. //! -//! `spawn_blocking` has no threadpool to dispatch to, so the closure runs as a -//! canonical spawned task on the single thread and returns the usual -//! `JoinHandle`. `tokio::fs::*` and `tokio::io::{stdin, stdout, stderr}` -//! likewise run their `std::*` calls inline, because emscripten's libc -//! syscalls complete synchronously and don't block the cooperative scheduler. +//! `spawn_blocking` dispatches to the blocking thread pool and so behaves as on +//! the other single-threaded Wasm targets. Running `spawn_blocking` closures +//! and `fs`/stdio `std::*` calls inline over Emscripten's synchronous syscalls +//! is part of the planned host-event-loop follow-up. //! //! Panics behave as on native: `wasm32-unknown-emscripten` defaults to //! `panic = "unwind"`, so panic recovery works, a panicking task yields @@ -479,7 +477,7 @@ //! `flavor = "current_thread"`. //! //! -//! #### Linking and running on emscripten +//! #### Linking and running on Emscripten //! //! No js-library or other custom file is required, and plain `node` runs the //! test binaries directly: @@ -526,7 +524,7 @@ compile_error! { ))] compile_error!("Only features sync,macros,io-util,rt,time are supported on wasm."); -// On emscripten, `process`, `signal`, and `rt-multi-thread` compile but are +// On Emscripten, `process`, `signal`, and `rt-multi-thread` compile but are // inert, so `full` (and any dependency that enables these features) still // builds. `process` and `signal` have no `fork`/`exec` or kernel signal // delivery, so their modules are compiled out (see `cfg_process!` / diff --git a/tokio/src/macros/cfg.rs b/tokio/src/macros/cfg.rs index 1db46e3505a..e9a9962ac8b 100644 --- a/tokio/src/macros/cfg.rs +++ b/tokio/src/macros/cfg.rs @@ -398,7 +398,7 @@ macro_rules! cfg_process { #[cfg_attr(docsrs, doc(cfg(feature = "process")))] #[cfg(not(loom))] #[cfg(not(target_os = "wasi"))] - // emscripten has no `fork`/`exec`, so the `process` module is a + // Emscripten has no `fork`/`exec`, so the `process` module is a // throwing stub there (see `process/emscripten.rs`); it still // compiles so dependents that name the types build. The orphan // reaper / signal driver it would otherwise need stays off via @@ -413,7 +413,7 @@ macro_rules! cfg_process_driver { #[cfg(unix)] #[cfg(not(loom))] // The driver (orphan reaper backed by the signal handler) doesn't exist - // on emscripten; the process module there is a throwing stub. + // on Emscripten; the process module there is a throwing stub. #[cfg(not(target_os = "emscripten"))] cfg_process! { $($item)* } } @@ -438,7 +438,7 @@ macro_rules! cfg_signal { #[cfg_attr(docsrs, doc(cfg(feature = "signal")))] #[cfg(not(loom))] #[cfg(not(target_os = "wasi"))] - // No kernel signal delivery on emscripten; inert there. + // No kernel signal delivery on Emscripten; inert there. #[cfg(not(target_os = "emscripten"))] $item )* @@ -783,7 +783,7 @@ macro_rules! cfg_not_schedule_latency { } } -/// Enables emscripten-specific code. +/// Enables Emscripten-specific code. macro_rules! cfg_emscripten { ($($item:item)*) => { $( @@ -794,7 +794,7 @@ macro_rules! cfg_emscripten { } } -/// Enables code for non-emscripten targets. +/// Enables code for non-Emscripten targets. macro_rules! cfg_not_emscripten { ($($item:item)*) => { $( @@ -804,7 +804,7 @@ macro_rules! cfg_not_emscripten { } } -/// Enables code requiring both the `rt` feature and the emscripten target. +/// Enables code requiring both the `rt` feature and the Emscripten target. macro_rules! cfg_rt_emscripten { ($($item:item)*) => { $( diff --git a/tokio/src/process/mod.rs b/tokio/src/process/mod.rs index 37914aa6465..6ca2e2b8f82 100644 --- a/tokio/src/process/mod.rs +++ b/tokio/src/process/mod.rs @@ -230,7 +230,7 @@ #[cfg(all(unix, not(target_os = "emscripten")))] mod imp; -// emscripten has no `fork`/`exec`: a throwing stub keeps the API present. +// Emscripten has no `fork`/`exec`: a throwing stub keeps the API present. #[path = "emscripten.rs"] #[cfg(target_os = "emscripten")] mod imp; diff --git a/tokio/src/runtime/time/mod.rs b/tokio/src/runtime/time/mod.rs index 9e7a9ac5560..3b9520534ee 100644 --- a/tokio/src/runtime/time/mod.rs +++ b/tokio/src/runtime/time/mod.rs @@ -295,7 +295,7 @@ impl Handle { } /// Returns the absolute deadline in ticks for the soonest unexpired - /// timer, or `None` if no timers are registered. Used by the emscripten + /// timer, or `None` if no timers are registered. Used by the Emscripten /// schedule loop to compute its `setTimeout` delay; ticks here are the /// time wheel's monotonic tick basis (`time_source.now()`). #[cfg(target_os = "emscripten")] diff --git a/tokio/src/util/mod.rs b/tokio/src/util/mod.rs index d715c3ea942..d4390c9b73e 100644 --- a/tokio/src/util/mod.rs +++ b/tokio/src/util/mod.rs @@ -41,7 +41,7 @@ mod wake_list; feature = "signal", feature = "time", ))] -// Some WakeList consumers are cfg'd out on emscripten, so the re-export may be +// Some WakeList consumers are cfg'd out on Emscripten, so the re-export may be // unused there. #[cfg_attr(target_os = "emscripten", allow(unused_imports))] pub(crate) use wake_list::WakeList; diff --git a/tokio/src/util/wake_list.rs b/tokio/src/util/wake_list.rs index 84e69fabc83..db077ebee17 100644 --- a/tokio/src/util/wake_list.rs +++ b/tokio/src/util/wake_list.rs @@ -1,4 +1,4 @@ -// On emscripten, `WakeList` is reachable via the feature gates that pull +// On Emscripten, `WakeList` is reachable via the feature gates that pull // it into `util::mod`, but the actual consumers (time::clock, sync::notify, // etc.) may be cfg'd out — leaving the type used only through a re-export. #![cfg_attr(target_os = "emscripten", allow(dead_code))] From 2351ee11c490e6003ee45c7ca64831deb56179e6 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Wed, 15 Jul 2026 11:46:35 -0700 Subject: [PATCH 03/12] review: remove stale setTimeout doc and unused follow-up scaffolding --- tokio/src/macros/cfg.rs | 32 -------------------------------- tokio/src/runtime/time/mod.rs | 12 +----------- tokio/src/time/mod.rs | 14 +++++--------- tokio/src/util/mod.rs | 18 +++++++----------- tokio/src/util/trace.rs | 17 ++++++++--------- tokio/src/util/wake_list.rs | 5 ----- 6 files changed, 21 insertions(+), 77 deletions(-) diff --git a/tokio/src/macros/cfg.rs b/tokio/src/macros/cfg.rs index e9a9962ac8b..86c1f199386 100644 --- a/tokio/src/macros/cfg.rs +++ b/tokio/src/macros/cfg.rs @@ -782,35 +782,3 @@ macro_rules! cfg_not_schedule_latency { )* } } - -/// Enables Emscripten-specific code. -macro_rules! cfg_emscripten { - ($($item:item)*) => { - $( - #[cfg(target_os = "emscripten")] - #[cfg_attr(docsrs, doc(cfg(target_os = "emscripten")))] - $item - )* - } -} - -/// Enables code for non-Emscripten targets. -macro_rules! cfg_not_emscripten { - ($($item:item)*) => { - $( - #[cfg(not(target_os = "emscripten"))] - $item - )* - } -} - -/// Enables code requiring both the `rt` feature and the Emscripten target. -macro_rules! cfg_rt_emscripten { - ($($item:item)*) => { - $( - #[cfg(all(feature = "rt", target_os = "emscripten"))] - #[cfg_attr(docsrs, doc(cfg(all(feature = "rt", target_os = "emscripten"))))] - $item - )* - } -} diff --git a/tokio/src/runtime/time/mod.rs b/tokio/src/runtime/time/mod.rs index 3b9520534ee..224d2ff4696 100644 --- a/tokio/src/runtime/time/mod.rs +++ b/tokio/src/runtime/time/mod.rs @@ -287,22 +287,12 @@ impl Driver { } impl Handle { - #[cfg_attr(not(target_os = "emscripten"), allow(dead_code))] - pub(crate) fn process(&self, clock: &Clock) { + pub(self) fn process(&self, clock: &Clock) { let now = self.time_source().now(clock); self.process_at_time(now); } - /// Returns the absolute deadline in ticks for the soonest unexpired - /// timer, or `None` if no timers are registered. Used by the Emscripten - /// schedule loop to compute its `setTimeout` delay; ticks here are the - /// time wheel's monotonic tick basis (`time_source.now()`). - #[cfg(target_os = "emscripten")] - pub(crate) fn next_expiration_tick(&self) -> Option { - self.inner.lock().wheel.next_expiration_time() - } - pub(self) fn process_at_time(&self, mut now: u64) { let mut waker_list = WakeList::new(); diff --git a/tokio/src/time/mod.rs b/tokio/src/time/mod.rs index e2621e5af9b..8627a7838aa 100644 --- a/tokio/src/time/mod.rs +++ b/tokio/src/time/mod.rs @@ -19,9 +19,6 @@ //! //! These types must be used from within the context of the [`Runtime`](crate::runtime::Runtime). //! -//! On `wasm32-unknown-emscripten` these timers are driven by JavaScript's -//! event loop via `setTimeout`. -//! //! # Examples //! //! Wait 100ms and print "100 ms have elapsed" @@ -89,6 +86,11 @@ mod clock; pub(crate) use self::clock::Clock; +cfg_test_util! { + pub use clock::{advance, pause, resume}; +} + +pub mod error; mod instant; pub use self::instant::Instant; @@ -103,12 +105,6 @@ mod timeout; #[doc(inline)] pub use timeout::{timeout, timeout_at, Timeout}; -cfg_test_util! { - pub use clock::{advance, pause, resume}; -} - -pub mod error; - // Re-export for convenience #[doc(no_inline)] pub use std::time::Duration; diff --git a/tokio/src/util/mod.rs b/tokio/src/util/mod.rs index d4390c9b73e..c671fd6a1da 100644 --- a/tokio/src/util/mod.rs +++ b/tokio/src/util/mod.rs @@ -5,15 +5,14 @@ cfg_io_driver! { #[cfg(feature = "fs")] pub(crate) mod as_ref; -cfg_rt! { - pub(crate) mod atomic_cell; -} +#[cfg(feature = "rt")] +pub(crate) mod atomic_cell; -cfg_net! { - mod blocking_check; - #[allow(unused_imports)] - pub(crate) use blocking_check::check_socket_for_blocking; -} +#[cfg(feature = "net")] +mod blocking_check; +#[cfg(feature = "net")] +#[allow(unused_imports)] +pub(crate) use blocking_check::check_socket_for_blocking; pub(crate) mod metric_atomics; @@ -41,9 +40,6 @@ mod wake_list; feature = "signal", feature = "time", ))] -// Some WakeList consumers are cfg'd out on Emscripten, so the re-export may be -// unused there. -#[cfg_attr(target_os = "emscripten", allow(unused_imports))] pub(crate) use wake_list::WakeList; #[cfg(any( diff --git a/tokio/src/util/trace.rs b/tokio/src/util/trace.rs index 9de1ee0ebfa..b22c2aeb593 100644 --- a/tokio/src/util/trace.rs +++ b/tokio/src/util/trace.rs @@ -1,4 +1,3 @@ -// SpawnMeta uses native runtime task infrastructure cfg_rt! { use std::marker::PhantomData; @@ -181,12 +180,12 @@ cfg_rt! { } } -#[cfg(feature = "time")] -#[cfg_attr(docsrs, doc(cfg(feature = "time")))] -#[track_caller] -pub(crate) fn caller_location() -> Option<&'static std::panic::Location<'static>> { - #[cfg(all(tokio_unstable, feature = "tracing"))] - return Some(std::panic::Location::caller()); - #[cfg(not(all(tokio_unstable, feature = "tracing")))] - None +cfg_time! { + #[track_caller] + pub(crate) fn caller_location() -> Option<&'static std::panic::Location<'static>> { + #[cfg(all(tokio_unstable, feature = "tracing"))] + return Some(std::panic::Location::caller()); + #[cfg(not(all(tokio_unstable, feature = "tracing")))] + None + } } diff --git a/tokio/src/util/wake_list.rs b/tokio/src/util/wake_list.rs index db077ebee17..23a559d02be 100644 --- a/tokio/src/util/wake_list.rs +++ b/tokio/src/util/wake_list.rs @@ -1,8 +1,3 @@ -// On Emscripten, `WakeList` is reachable via the feature gates that pull -// it into `util::mod`, but the actual consumers (time::clock, sync::notify, -// etc.) may be cfg'd out — leaving the type used only through a re-export. -#![cfg_attr(target_os = "emscripten", allow(dead_code))] - use core::mem::MaybeUninit; use core::ptr; use std::task::Waker; From f80d338dbd54b60b25e72e2afa93043bb885e521 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Wed, 5 Aug 2026 10:26:04 -0700 Subject: [PATCH 04/12] review: address emscripten feedback --- tokio-macros/src/entry.rs | 15 +--- tokio-test/src/lib.rs | 29 -------- tokio/src/lib.rs | 54 ++------------ tokio/src/macros/cfg.rs | 11 +-- tokio/src/macros/mod.rs | 20 +++++ tokio/src/process/emscripten.rs | 127 -------------------------------- tokio/src/process/mod.rs | 7 +- 7 files changed, 32 insertions(+), 231 deletions(-) delete mode 100644 tokio/src/process/emscripten.rs diff --git a/tokio-macros/src/entry.rs b/tokio-macros/src/entry.rs index 41e70f5dc93..54f29caaa02 100644 --- a/tokio-macros/src/entry.rs +++ b/tokio-macros/src/entry.rs @@ -521,20 +521,11 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt }; - // Emscripten runs the native expansion; only the `multi_thread` flavor - // diverges — it has no native threads there, so it's rejected with a - // targeted error rather than the opaque failure of the native - // multi-thread `block_on`. let last_block = match config.flavor { RuntimeFlavor::Threaded => quote! { - #[cfg(not(target_os = "emscripten"))] - #native_last_block - #[cfg(target_os = "emscripten")] - ::core::compile_error!( - "the `multi_thread` runtime flavor is not available on \ - wasm32-unknown-emscripten (no native threads); use \ - `flavor = \"current_thread\"`" - ); + #crate_path::__tokio_unsupported_multi_thread_on_emscripten! { + #native_last_block + } }, _ => native_last_block, }; diff --git a/tokio-test/src/lib.rs b/tokio-test/src/lib.rs index 321392bf9ff..87e63861210 100644 --- a/tokio-test/src/lib.rs +++ b/tokio-test/src/lib.rs @@ -24,7 +24,6 @@ pub mod task; /// [`tokio::runtime::Runtime::block_on`][runtime-block-on]. /// /// [runtime-block-on]: https://docs.rs/tokio/1.3.0/tokio/runtime/struct.Runtime.html#method.block_on -#[cfg(not(target_os = "emscripten"))] pub fn block_on(future: F) -> F::Output { use tokio::runtime; @@ -35,31 +34,3 @@ pub fn block_on(future: F) -> F::Output { rt.block_on(future) } - -/// Emscripten variant: polls once with a no-op waker. Ready futures (mocks, -/// pure computation) complete; ones that must yield (timers, I/O) panic — -/// use `#[tokio::test]` for those. -#[cfg(target_os = "emscripten")] -pub fn block_on(future: F) -> F::Output { - use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; - - const VTABLE: RawWakerVTable = RawWakerVTable::new( - |_| RawWaker::new(std::ptr::null(), &VTABLE), - |_| {}, - |_| {}, - |_| {}, - ); - // SAFETY: vtable entries are valid no-ops. - let waker = unsafe { Waker::from_raw(RawWaker::new(std::ptr::null(), &VTABLE)) }; - let mut cx = Context::from_waker(&waker); - - let mut future = Box::pin(future); - match future.as_mut().poll(&mut cx) { - Poll::Ready(output) => output, - Poll::Pending => panic!( - "tokio_test::block_on: future returned Pending on emscripten. \ - The main thread cannot block on JS event-loop wakeups; use #[tokio::test] \ - for futures that need timers/network I/O." - ), - } -} diff --git a/tokio/src/lib.rs b/tokio/src/lib.rs index d98898cb6c9..a07f683d297 100644 --- a/tokio/src/lib.rs +++ b/tokio/src/lib.rs @@ -446,48 +446,10 @@ //! //! ### Emscripten support //! -//! The `wasm32-unknown-emscripten` target is supported at parity with the -//! other Wasm targets. A host-event-loop execution model with a parking -//! `block_on` is a planned follow-up; until it lands, a `block_on` whose -//! future cannot resolve synchronously behaves as on other single-threaded -//! Wasm targets. -//! -//! Supported features: `rt`, `time`, `sync`, `macros`, `fs`, `io-util`, -//! `io-std`, and `test-util` — the same surface as the other single-threaded -//! Wasm targets. The `net` reactor (epoll-backed, over Emscripten's socket -//! support) is planned as a follow-up; until then the `net` feature fails to -//! build for this target (rejected by `mio`). -//! -//! The `process`, `signal`, and `rt-multi-thread` features are rejected at -//! compile time: `process`/`signal` have no underlying primitives (`fork`/`exec`, -//! kernel signal delivery) and `rt-multi-thread` has no native threads. -//! -//! `spawn_blocking` dispatches to the blocking thread pool and so behaves as on -//! the other single-threaded Wasm targets. Running `spawn_blocking` closures -//! and `fs`/stdio `std::*` calls inline over Emscripten's synchronous syscalls -//! is part of the planned host-event-loop follow-up. -//! -//! Panics behave as on native: `wasm32-unknown-emscripten` defaults to -//! `panic = "unwind"`, so panic recovery works, a panicking task yields -//! `Err(JoinError)`, and `JoinError::is_panic` / `JoinError::into_panic` -//! report the payload. -//! -//! `#[tokio::test]` / `#[tokio::main]` use the native macro expansion; the -//! `multi_thread` flavor is rejected (no native threads) — use -//! `flavor = "current_thread"`. -//! -//! -//! #### Linking and running on Emscripten -//! -//! No js-library or other custom file is required, and plain `node` runs the -//! test binaries directly: -//! -//! ```text -//! CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER="node" -//! RUSTFLAGS="-C link-args=-sALLOW_MEMORY_GROWTH=1 \ -//! -C link-args=-sEXIT_RUNTIME=1 \ -//! -C link-args=-sSTACK_SIZE=1048576" -//! ``` +//! The `wasm32-unknown-emscripten` target supports the single-threaded runtime +//! with the `rt`, `time`, `sync`, `macros`, `fs`, `io-util`, `io-std`, and +//! `test-util` features. The `net`, `process`, `signal`, and `rt-multi-thread` +//! features are not supported. //! //! ## Unstable `WASM` support //! @@ -524,12 +486,8 @@ compile_error! { ))] compile_error!("Only features sync,macros,io-util,rt,time are supported on wasm."); -// On Emscripten, `process`, `signal`, and `rt-multi-thread` compile but are -// inert, so `full` (and any dependency that enables these features) still -// builds. `process` and `signal` have no `fork`/`exec` or kernel signal -// delivery, so their modules are compiled out (see `cfg_process!` / -// `cfg_signal!`). The multi-threaded runtime compiles but only runs under a -// `PROXY_TO_PTHREAD` build; `#[tokio::main]` steers to `current_thread`. +#[cfg(all(target_os = "emscripten", feature = "process"))] +compile_error!("The `process` feature is not supported on wasm32-unknown-emscripten."); #[cfg(all(not(tokio_unstable), feature = "io-uring"))] compile_error!("The `io-uring` feature requires `--cfg tokio_unstable`."); diff --git a/tokio/src/macros/cfg.rs b/tokio/src/macros/cfg.rs index 86c1f199386..16ac1ffbf61 100644 --- a/tokio/src/macros/cfg.rs +++ b/tokio/src/macros/cfg.rs @@ -398,11 +398,7 @@ macro_rules! cfg_process { #[cfg_attr(docsrs, doc(cfg(feature = "process")))] #[cfg(not(loom))] #[cfg(not(target_os = "wasi"))] - // Emscripten has no `fork`/`exec`, so the `process` module is a - // throwing stub there (see `process/emscripten.rs`); it still - // compiles so dependents that name the types build. The orphan - // reaper / signal driver it would otherwise need stays off via - // `cfg_process_driver!`. + #[cfg(not(target_os = "emscripten"))] $item )* } @@ -412,9 +408,6 @@ macro_rules! cfg_process_driver { ($($item:item)*) => { #[cfg(unix)] #[cfg(not(loom))] - // The driver (orphan reaper backed by the signal handler) doesn't exist - // on Emscripten; the process module there is a throwing stub. - #[cfg(not(target_os = "emscripten"))] cfg_process! { $($item)* } } } @@ -423,8 +416,8 @@ macro_rules! cfg_not_process_driver { ($($item:item)*) => { $( #[cfg(any( - not(all(unix, not(loom), feature = "process")), target_os = "emscripten", + not(all(unix, not(loom), feature = "process")), ))] $item )* diff --git a/tokio/src/macros/mod.rs b/tokio/src/macros/mod.rs index acf3d010d69..a454e155822 100644 --- a/tokio/src/macros/mod.rs +++ b/tokio/src/macros/mod.rs @@ -34,3 +34,23 @@ cfg_macros! { // Includes re-exports needed to implement macros #[doc(hidden)] pub mod support; + +#[doc(hidden)] +#[macro_export] +#[cfg(not(target_os = "emscripten"))] +macro_rules! __tokio_unsupported_multi_thread_on_emscripten { + ($($body:tt)*) => { $($body)* }; +} + +#[doc(hidden)] +#[macro_export] +#[cfg(target_os = "emscripten")] +macro_rules! __tokio_unsupported_multi_thread_on_emscripten { + ($($body:tt)*) => { + ::core::compile_error!( + "the `multi_thread` runtime flavor is not available on \ + wasm32-unknown-emscripten (no native threads); use \ + `flavor = \"current_thread\"`" + ); + }; +} diff --git a/tokio/src/process/emscripten.rs b/tokio/src/process/emscripten.rs deleted file mode 100644 index 093b3eb8aa1..00000000000 --- a/tokio/src/process/emscripten.rs +++ /dev/null @@ -1,127 +0,0 @@ -//! Throwing process stub for `wasm32-unknown-emscripten`. -//! -//! emscripten has no `fork`/`exec`, so there is nothing to spawn or reap: the -//! orphan reaper / signal driver the unix backend relies on is compiled out -//! (see `cfg_process_driver!`). We still provide the `imp` surface that -//! `process::mod` is generic over so dependents that merely name these types -//! keep compiling. Spawning fails before it reaches here (`std`'s own -//! `Command::spawn` returns `Unsupported` on emscripten), and the `Child` / -//! `ChildStdio` types are uninhabited — every method is unreachable. - -use crate::io::{AsyncRead, AsyncWrite, ReadBuf}; -use crate::process::kill::Kill; -use crate::process::SpawnedChild; - -use std::fmt; -use std::future::Future; -use std::io; -use std::os::fd::{AsFd, AsRawFd, BorrowedFd, IntoRawFd, OwnedFd, RawFd}; -use std::pin::Pin; -use std::process::{Child as StdChild, ExitStatus, Stdio}; -use std::task::{Context, Poll}; - -fn unsupported() -> io::Result { - Err(io::Error::new( - io::ErrorKind::Unsupported, - "spawning processes is not supported on wasm32-unknown-emscripten", - )) -} - -pub(crate) enum Child {} - -impl Child { - pub(crate) fn id(&self) -> u32 { - match *self {} - } - - pub(crate) fn try_wait(&mut self) -> io::Result> { - match *self {} - } -} - -impl fmt::Debug for Child { - fn fmt(&self, _fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self {} - } -} - -impl Kill for Child { - fn kill(&mut self) -> io::Result<()> { - match *self {} - } -} - -impl Future for Child { - type Output = io::Result; - - fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { - match *self.get_mut() {} - } -} - -pub(crate) fn build_child(_child: StdChild) -> io::Result { - unsupported() -} - -pub(crate) enum ChildStdio {} - -impl ChildStdio { - pub(crate) fn into_owned_fd(self) -> io::Result { - match self {} - } -} - -impl fmt::Debug for ChildStdio { - fn fmt(&self, _fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self {} - } -} - -impl AsRawFd for ChildStdio { - fn as_raw_fd(&self) -> RawFd { - match *self {} - } -} - -impl AsFd for ChildStdio { - fn as_fd(&self) -> BorrowedFd<'_> { - match *self {} - } -} - -impl AsyncWrite for ChildStdio { - fn poll_write( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, - _buf: &[u8], - ) -> Poll> { - match *self.get_mut() {} - } - - fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { - match *self.get_mut() {} - } - - fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { - match *self.get_mut() {} - } -} - -impl AsyncRead for ChildStdio { - fn poll_read( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, - _buf: &mut ReadBuf<'_>, - ) -> Poll> { - match *self.get_mut() {} - } -} - -pub(crate) fn convert_to_stdio(io: ChildStdio) -> io::Result { - match io {} -} - -pub(crate) fn stdio(io: T) -> io::Result { - let _ = io.into_raw_fd(); - unsupported() -} diff --git a/tokio/src/process/mod.rs b/tokio/src/process/mod.rs index 6ca2e2b8f82..fb233cb4eb2 100644 --- a/tokio/src/process/mod.rs +++ b/tokio/src/process/mod.rs @@ -227,12 +227,7 @@ //! [`Child`]: crate::process::Child #[path = "unix/mod.rs"] -#[cfg(all(unix, not(target_os = "emscripten")))] -mod imp; - -// Emscripten has no `fork`/`exec`: a throwing stub keeps the API present. -#[path = "emscripten.rs"] -#[cfg(target_os = "emscripten")] +#[cfg(unix)] mod imp; #[cfg(unix)] From 37399a1f6acd5c420837e317cea5ea79fd150507 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Tue, 25 Aug 2026 09:49:18 -0700 Subject: [PATCH 05/12] review: exact cfg complements, drop macro-side multi_thread rejection, extend emscripten compile_error --- .github/workflows/ci.yml | 2 +- tokio-macros/src/entry.rs | 11 +---------- tokio/src/lib.rs | 14 ++++++++++++-- tokio/src/macros/cfg.rs | 36 +++++++++++++++++++++++++----------- tokio/src/macros/mod.rs | 20 -------------------- tokio/src/runtime/driver.rs | 2 +- 6 files changed, 40 insertions(+), 45 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f5b7939d07..2d374bc10f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1231,7 +1231,7 @@ jobs: working-directory: tokio env: CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER: node - RUSTFLAGS: "-C link-args=-sALLOW_MEMORY_GROWTH=1 -C link-args=-sEXIT_RUNTIME=1 -C link-args=-sSTACK_SIZE=1048576" + RUSTFLAGS: "-Clink-args=-sALLOW_MEMORY_GROWTH=1 -Clink-args=-sEXIT_RUNTIME=1 -Clink-args=-sSTACK_SIZE=1048576" check-external-types: name: check-external-types (${{ matrix.os }}) diff --git a/tokio-macros/src/entry.rs b/tokio-macros/src/entry.rs index 54f29caaa02..5ed8853a9eb 100644 --- a/tokio-macros/src/entry.rs +++ b/tokio-macros/src/entry.rs @@ -506,7 +506,7 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt let body_ident = quote! { body }; // This explicit `return` is intentional. See tokio-rs/tokio#4636 - let native_last_block = quote_spanned! {last_stmt_end_span=> + let last_block = quote_spanned! {last_stmt_end_span=> #[allow(clippy::expect_used, clippy::diverging_sub_expression, clippy::needless_return, clippy::unwrap_in_result)] { @@ -521,15 +521,6 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt }; - let last_block = match config.flavor { - RuntimeFlavor::Threaded => quote! { - #crate_path::__tokio_unsupported_multi_thread_on_emscripten! { - #native_last_block - } - }, - _ => native_last_block, - }; - let body = input.body(); // For test functions pin the body to the stack and use `Pin<&mut dyn diff --git a/tokio/src/lib.rs b/tokio/src/lib.rs index a07f683d297..2c88ee72f11 100644 --- a/tokio/src/lib.rs +++ b/tokio/src/lib.rs @@ -486,8 +486,18 @@ compile_error! { ))] compile_error!("Only features sync,macros,io-util,rt,time are supported on wasm."); -#[cfg(all(target_os = "emscripten", feature = "process"))] -compile_error!("The `process` feature is not supported on wasm32-unknown-emscripten."); +#[cfg(all( + target_os = "emscripten", + any( + feature = "net", + feature = "process", + feature = "rt-multi-thread", + feature = "signal" + ) +))] +compile_error!( + "Features net,process,rt-multi-thread,signal are not supported on wasm32-unknown-emscripten." +); #[cfg(all(not(tokio_unstable), feature = "io-uring"))] compile_error!("The `io-uring` feature requires `--cfg tokio_unstable`."); diff --git a/tokio/src/macros/cfg.rs b/tokio/src/macros/cfg.rs index 16ac1ffbf61..8cc99ba7e89 100644 --- a/tokio/src/macros/cfg.rs +++ b/tokio/src/macros/cfg.rs @@ -406,19 +406,29 @@ macro_rules! cfg_process { macro_rules! cfg_process_driver { ($($item:item)*) => { - #[cfg(unix)] - #[cfg(not(loom))] - cfg_process! { $($item)* } + $( + #[cfg(all( + unix, + not(loom), + feature = "process", + not(target_os = "wasi"), + not(target_os = "emscripten"), + ))] + $item + )* } } macro_rules! cfg_not_process_driver { ($($item:item)*) => { $( - #[cfg(any( - target_os = "emscripten", - not(all(unix, not(loom), feature = "process")), - ))] + #[cfg(not(all( + unix, + not(loom), + feature = "process", + not(target_os = "wasi"), + not(target_os = "emscripten"), + )))] $item )* } @@ -431,7 +441,6 @@ macro_rules! cfg_signal { #[cfg_attr(docsrs, doc(cfg(feature = "signal")))] #[cfg(not(loom))] #[cfg(not(target_os = "wasi"))] - // No kernel signal delivery on Emscripten; inert there. #[cfg(not(target_os = "emscripten"))] $item )* @@ -456,10 +465,15 @@ macro_rules! cfg_signal_internal_and_unix { } } -macro_rules! cfg_not_signal_internal { +macro_rules! cfg_not_signal_internal_and_unix { ($($item:item)*) => { $( - #[cfg(any(loom, not(unix), target_os = "emscripten", not(any(feature = "signal", all(unix, feature = "process")))))] + #[cfg(not(all( + unix, + any(feature = "signal", all(unix, feature = "process")), + not(loom), + not(target_os = "emscripten"), + )))] $item )* } @@ -722,7 +736,7 @@ macro_rules! cfg_not_wasip1 { macro_rules! cfg_is_wasm_not_wasi { ($($item:item)*) => { $( - #[cfg(all(target_family = "wasm", target_os = "unknown"))] + #[cfg(all(target_family = "wasm", not(target_os = "wasi")))] $item )* } diff --git a/tokio/src/macros/mod.rs b/tokio/src/macros/mod.rs index a454e155822..acf3d010d69 100644 --- a/tokio/src/macros/mod.rs +++ b/tokio/src/macros/mod.rs @@ -34,23 +34,3 @@ cfg_macros! { // Includes re-exports needed to implement macros #[doc(hidden)] pub mod support; - -#[doc(hidden)] -#[macro_export] -#[cfg(not(target_os = "emscripten"))] -macro_rules! __tokio_unsupported_multi_thread_on_emscripten { - ($($body:tt)*) => { $($body)* }; -} - -#[doc(hidden)] -#[macro_export] -#[cfg(target_os = "emscripten")] -macro_rules! __tokio_unsupported_multi_thread_on_emscripten { - ($($body:tt)*) => { - ::core::compile_error!( - "the `multi_thread` runtime flavor is not available on \ - wasm32-unknown-emscripten (no native threads); use \ - `flavor = \"current_thread\"`" - ); - }; -} diff --git a/tokio/src/runtime/driver.rs b/tokio/src/runtime/driver.rs index 92b2350db9d..35bfe0262d9 100644 --- a/tokio/src/runtime/driver.rs +++ b/tokio/src/runtime/driver.rs @@ -251,7 +251,7 @@ cfg_signal_internal_and_unix! { } } -cfg_not_signal_internal! { +cfg_not_signal_internal_and_unix! { pub(crate) type SignalHandle = (); cfg_io_driver! { From 0d32475452be2370797fe1c98ee2dc61208a0f24 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Tue, 25 Aug 2026 10:08:21 -0700 Subject: [PATCH 06/12] rt: test the multi-thread runtime on emscripten under pthread proxying --- .github/workflows/ci.yml | 16 ++++- tokio/src/lib.rs | 17 ++---- tokio/tests/rt_multi_thread_emscripten.rs | 72 +++++++++++++++++++++++ 3 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 tokio/tests/rt_multi_thread_emscripten.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d374bc10f0..c3f5ada8209 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1221,7 +1221,7 @@ jobs: tool: cargo-hack - name: Check tokio feature matrix for emscripten - run: cargo hack check -p tokio --each-feature --exclude-features full,net,process,signal,rt-multi-thread,io-uring,taskdump,schedule-latency --target wasm32-unknown-emscripten + run: cargo hack check -p tokio --each-feature --exclude-features full,net,process,signal,io-uring,taskdump,schedule-latency --target wasm32-unknown-emscripten working-directory: tokio env: RUSTFLAGS: "" @@ -1233,6 +1233,20 @@ jobs: CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER: node RUSTFLAGS: "-Clink-args=-sALLOW_MEMORY_GROWTH=1 -Clink-args=-sEXIT_RUNTIME=1 -Clink-args=-sSTACK_SIZE=1048576" + - name: Install Rust ${{ env.rust_nightly }} + uses: dtolnay/rust-toolchain@nightly + with: + toolchain: ${{ env.rust_nightly }} + targets: wasm32-unknown-emscripten + components: rust-src + + - name: Test tokio multi-thread runtime for emscripten (pthread proxy) + run: cargo +${{ env.rust_nightly }} test -Zbuild-std=std,panic_abort -p tokio --target wasm32-unknown-emscripten --features "rt,rt-multi-thread,time,sync,macros" --test rt_multi_thread_emscripten + working-directory: tokio + env: + CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER: node + RUSTFLAGS: "-Ctarget-feature=+atomics,+bulk-memory,+mutable-globals -Clink-args=-pthread -Clink-args=-sPTHREAD_POOL_SIZE=8 -Clink-args=-sPROXY_TO_PTHREAD -Clink-args=-sINITIAL_MEMORY=134217728 -Clink-args=-sEXIT_RUNTIME=1 -Clink-args=-sSTACK_SIZE=1048576" + check-external-types: name: check-external-types (${{ matrix.os }}) needs: basics diff --git a/tokio/src/lib.rs b/tokio/src/lib.rs index 2c88ee72f11..6a53b9da194 100644 --- a/tokio/src/lib.rs +++ b/tokio/src/lib.rs @@ -448,8 +448,10 @@ //! //! The `wasm32-unknown-emscripten` target supports the single-threaded runtime //! with the `rt`, `time`, `sync`, `macros`, `fs`, `io-util`, `io-std`, and -//! `test-util` features. The `net`, `process`, `signal`, and `rt-multi-thread` -//! features are not supported. +//! `test-util` features. The `rt-multi-thread` feature is additionally +//! supported when building with Emscripten pthreads (`-pthread`, with +//! `-sPROXY_TO_PTHREAD` so the main thread may block). The `net`, `process`, +//! and `signal` features are not supported. //! //! ## Unstable `WASM` support //! @@ -488,16 +490,9 @@ compile_error!("Only features sync,macros,io-util,rt,time are supported on wasm. #[cfg(all( target_os = "emscripten", - any( - feature = "net", - feature = "process", - feature = "rt-multi-thread", - feature = "signal" - ) + any(feature = "net", feature = "process", feature = "signal") ))] -compile_error!( - "Features net,process,rt-multi-thread,signal are not supported on wasm32-unknown-emscripten." -); +compile_error!("Features net,process,signal are not supported on wasm32-unknown-emscripten."); #[cfg(all(not(tokio_unstable), feature = "io-uring"))] compile_error!("The `io-uring` feature requires `--cfg tokio_unstable`."); diff --git a/tokio/tests/rt_multi_thread_emscripten.rs b/tokio/tests/rt_multi_thread_emscripten.rs new file mode 100644 index 00000000000..d31129d1007 --- /dev/null +++ b/tokio/tests/rt_multi_thread_emscripten.rs @@ -0,0 +1,72 @@ +#![warn(rust_2018_idioms)] +#![cfg(all( + target_os = "emscripten", + feature = "rt-multi-thread", + feature = "macros" +))] + +//! Multi-thread runtime tests for `wasm32-unknown-emscripten` built with +//! pthreads (`-pthread` and `-sPROXY_TO_PTHREAD`). + +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, Barrier}; +use std::time::Duration; + +#[test] +fn block_on_multi_thread() { + let rt = tokio::runtime::Builder::new_multi_thread() + .worker_threads(2) + .enable_time() + .build() + .unwrap(); + + let out = rt.block_on(async { + let jh = tokio::spawn(async { + tokio::time::sleep(Duration::from_millis(10)).await; + "hello" + }); + jh.await.unwrap() + }); + assert_eq!(out, "hello"); +} + +#[test] +fn spawn_blocking_runs_in_parallel() { + let rt = tokio::runtime::Builder::new_multi_thread() + .worker_threads(2) + .build() + .unwrap(); + + // Both closures must be running concurrently for the barrier to release. + let barrier = Arc::new(Barrier::new(2)); + rt.block_on(async { + let a = tokio::task::spawn_blocking({ + let barrier = barrier.clone(); + move || { + barrier.wait(); + } + }); + let b = tokio::task::spawn_blocking(move || { + barrier.wait(); + }); + a.await.unwrap(); + b.await.unwrap(); + }); +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn macro_multi_thread() { + static COUNT: AtomicUsize = AtomicUsize::new(0); + + let mut handles = Vec::new(); + for _ in 0..8 { + handles.push(tokio::spawn(async { + tokio::task::yield_now().await; + COUNT.fetch_add(1, Ordering::Relaxed); + })); + } + for handle in handles { + handle.await.unwrap(); + } + assert_eq!(COUNT.load(Ordering::Relaxed), 8); +} From 79cbe1ee66e35ee42a1d393e3565778e581f8fac Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Tue, 25 Aug 2026 10:39:14 -0700 Subject: [PATCH 07/12] spelling: fix dictionary count, add pthreads --- spellcheck.dic | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spellcheck.dic b/spellcheck.dic index 9bb2566b13f..b0d4535caad 100644 --- a/spellcheck.dic +++ b/spellcheck.dic @@ -1,4 +1,4 @@ -327 +331 & + < @@ -215,6 +215,7 @@ plaintext poller POSIX proxied +pthreads qos RAII RCU From 4024b767ed6b208d1171402e586c4d5870a6169e Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Tue, 25 Aug 2026 10:43:46 -0700 Subject: [PATCH 08/12] macros: remove unused cfg_is_wasm_not_wasi macro --- tokio/src/macros/cfg.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tokio/src/macros/cfg.rs b/tokio/src/macros/cfg.rs index 8cc99ba7e89..2860c0825e0 100644 --- a/tokio/src/macros/cfg.rs +++ b/tokio/src/macros/cfg.rs @@ -733,15 +733,6 @@ macro_rules! cfg_not_wasip1 { } } -macro_rules! cfg_is_wasm_not_wasi { - ($($item:item)*) => { - $( - #[cfg(all(target_family = "wasm", not(target_os = "wasi")))] - $item - )* - } -} - /// Use this macro to provide two different implementations of the same API — one for stable /// builds and one for unstable builds. macro_rules! cfg_metrics_variant { From ab19268f23c9ca0654d96d6dbe9e1ad10a596912 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Tue, 25 Aug 2026 10:57:33 -0700 Subject: [PATCH 09/12] fs: run emscripten fs and io-std inline in non-pthread builds --- .github/workflows/ci.yml | 2 +- tokio/src/blocking.rs | 30 ++++++++++++++++++++++++++++++ tokio/src/io/stdio_common.rs | 1 + tokio/src/runtime/blocking/pool.rs | 8 +++++--- tokio/tests/fs.rs | 14 +++++++++++++- tokio/tests/fs_canonicalize_dir.rs | 14 +++++++++++++- tokio/tests/fs_copy.rs | 14 +++++++++++++- tokio/tests/fs_dir.rs | 14 +++++++++++++- tokio/tests/fs_file.rs | 22 +++++++++++++++++++++- tokio/tests/fs_link.rs | 18 +++++++++++++++++- tokio/tests/fs_open_options.rs | 14 +++++++++++++- tokio/tests/fs_remove_dir_all.rs | 14 +++++++++++++- tokio/tests/fs_remove_file.rs | 14 +++++++++++++- tokio/tests/fs_rename.rs | 14 +++++++++++++- tokio/tests/fs_try_exists.rs | 14 +++++++++++++- tokio/tests/fs_write.rs | 14 +++++++++++++- 16 files changed, 205 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3f5ada8209..62feb81af72 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1227,7 +1227,7 @@ jobs: RUSTFLAGS: "" - name: Test tokio for emscripten - run: cargo test -p tokio --target wasm32-unknown-emscripten --features "rt,time,sync,macros,io-util,test-util" --tests + run: cargo test -p tokio --target wasm32-unknown-emscripten --features "rt,time,sync,macros,fs,io-util,io-std,test-util" --tests working-directory: tokio env: CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER: node diff --git a/tokio/src/blocking.rs b/tokio/src/blocking.rs index f172399d5ef..c02c9363f03 100644 --- a/tokio/src/blocking.rs +++ b/tokio/src/blocking.rs @@ -1,12 +1,42 @@ cfg_rt! { + #[cfg(any(not(target_os = "emscripten"), target_feature = "atomics"))] pub(crate) use crate::runtime::spawn_blocking; cfg_fs! { + #[cfg(any(not(target_os = "emscripten"), target_feature = "atomics"))] #[allow(unused_imports)] pub(crate) use crate::runtime::spawn_mandatory_blocking; } + #[cfg(any(not(target_os = "emscripten"), target_feature = "atomics"))] pub(crate) use crate::task::JoinHandle; + + // Non-pthread emscripten has no blocking pool, and the `std` calls behind + // `fs` and `io-std` complete synchronously there, so this internal shim + // runs the closure inline and hands back an already-completed future. The + // public `task::spawn_blocking` is not routed through here and keeps its + // native semantics. Pthread builds (`+atomics`) use the native pool. + #[cfg(all(target_os = "emscripten", not(target_feature = "atomics")))] + pub(crate) type JoinHandle = std::future::Ready>; + + #[cfg(all(target_os = "emscripten", not(target_feature = "atomics")))] + pub(crate) fn spawn_blocking(f: F) -> JoinHandle + where + F: FnOnce() -> R + Send + 'static, + R: Send + 'static, + { + std::future::ready(Ok(f())) + } + + #[cfg(all(target_os = "emscripten", not(target_feature = "atomics"), feature = "fs"))] + #[allow(dead_code)] // unit tests replace this with the `fs::mocks` version + pub(crate) fn spawn_mandatory_blocking(f: F) -> Option> + where + F: FnOnce() -> R + Send + 'static, + R: Send + 'static, + { + Some(spawn_blocking(f)) + } } cfg_not_rt! { diff --git a/tokio/src/io/stdio_common.rs b/tokio/src/io/stdio_common.rs index 72fd97d917d..902bf619c90 100644 --- a/tokio/src/io/stdio_common.rs +++ b/tokio/src/io/stdio_common.rs @@ -108,6 +108,7 @@ where #[cfg(test)] #[cfg(not(loom))] +#[cfg(not(target_os = "emscripten"))] mod tests { use crate::io::blocking::DEFAULT_MAX_BUF_SIZE; use crate::io::AsyncWriteExt; diff --git a/tokio/src/runtime/blocking/pool.rs b/tokio/src/runtime/blocking/pool.rs index 5507bf136ed..8dac3feef19 100644 --- a/tokio/src/runtime/blocking/pool.rs +++ b/tokio/src/runtime/blocking/pool.rs @@ -182,7 +182,7 @@ pub(crate) struct Task { #[derive(PartialEq, Eq)] pub(crate) enum Mandatory { - #[cfg_attr(not(feature = "fs"), allow(dead_code))] + #[cfg_attr(any(not(feature = "fs"), all(target_os = "emscripten", not(target_feature = "atomics"))), allow(dead_code))] Mandatory, NonMandatory, } @@ -246,7 +246,8 @@ where cfg_fs! { #[cfg_attr(any( all(loom, not(test)), // the function is covered by loom tests - test + test, + all(target_os = "emscripten", not(target_feature = "atomics")), // fs uses the inline shim ), allow(dead_code))] /// Runs the provided function on an executor dedicated to blocking /// operations. Tasks will be scheduled as mandatory, meaning they are @@ -385,7 +386,8 @@ impl Spawner { #[track_caller] #[cfg_attr(any( all(loom, not(test)), // the function is covered by loom tests - test + test, + all(target_os = "emscripten", not(target_feature = "atomics")), // fs uses the inline shim ), allow(dead_code))] pub(crate) fn spawn_mandatory_blocking(&self, rt: &Handle, func: F) -> Option> where diff --git a/tokio/tests/fs.rs b/tokio/tests/fs.rs index f5fb193f4f9..e8b327580f9 100644 --- a/tokio/tests/fs.rs +++ b/tokio/tests/fs.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi")))] // Wasi does not support file operations +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "fs" + ) +))] use tokio::fs; use tokio_test::assert_ok; @@ -16,6 +24,10 @@ async fn path_read_write() { } #[tokio::test] +#[cfg_attr( + target_os = "emscripten", + ignore = "emscripten libc does not implement dup()/try_clone()" +)] async fn try_clone_should_preserve_max_buf_size() { let buf_size = 128; let temp = tempdir(); diff --git a/tokio/tests/fs_canonicalize_dir.rs b/tokio/tests/fs_canonicalize_dir.rs index e7f6c68ea91..9f4d6d94a30 100644 --- a/tokio/tests/fs_canonicalize_dir.rs +++ b/tokio/tests/fs_canonicalize_dir.rs @@ -1,5 +1,17 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi")))] // WASI does not support all fs operations +#![cfg(all( + any( + feature = "full", + all( + target_os = "emscripten", + feature = "fs", + feature = "macros", + feature = "rt", + feature = "io-util" + ) + ), + not(target_os = "wasi") +))] // WASI does not support all fs operations use tokio::fs; diff --git a/tokio/tests/fs_copy.rs b/tokio/tests/fs_copy.rs index fac64dccddc..ddf4c2bc586 100644 --- a/tokio/tests/fs_copy.rs +++ b/tokio/tests/fs_copy.rs @@ -1,5 +1,17 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi")))] // WASI does not support all fs operations +#![cfg(all( + any( + feature = "full", + all( + target_os = "emscripten", + feature = "fs", + feature = "macros", + feature = "rt", + feature = "io-util" + ) + ), + not(target_os = "wasi") +))] // WASI does not support all fs operations use tempfile::tempdir; use tokio::fs; diff --git a/tokio/tests/fs_dir.rs b/tokio/tests/fs_dir.rs index 3f28c1a07b3..f11ef03643d 100644 --- a/tokio/tests/fs_dir.rs +++ b/tokio/tests/fs_dir.rs @@ -1,5 +1,17 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi")))] // WASI does not support all fs operations +#![cfg(all( + any( + feature = "full", + all( + target_os = "emscripten", + feature = "fs", + feature = "macros", + feature = "rt", + feature = "io-util" + ) + ), + not(target_os = "wasi") +))] // WASI does not support all fs operations use tokio::fs; use tokio_test::{assert_err, assert_ok}; diff --git a/tokio/tests/fs_file.rs b/tokio/tests/fs_file.rs index e5bc0bd87ff..ea4b104a593 100644 --- a/tokio/tests/fs_file.rs +++ b/tokio/tests/fs_file.rs @@ -1,5 +1,17 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi")))] // WASI does not support all fs operations +#![cfg(all( + any( + feature = "full", + all( + target_os = "emscripten", + feature = "fs", + feature = "macros", + feature = "rt", + feature = "io-util" + ) + ), + not(target_os = "wasi") +))] // WASI does not support all fs operations use futures::future::FutureExt; use std::io::prelude::*; @@ -99,6 +111,10 @@ async fn rewind_seek_position() { } #[tokio::test] +#[cfg_attr( + target_os = "emscripten", + ignore = "inline-fs shim does not insert cooperative yield points" +)] async fn coop() { let mut tempfile = tempfile(); tempfile.write_all(HELLO).unwrap(); @@ -124,6 +140,10 @@ async fn coop() { } #[tokio::test] +#[cfg_attr( + target_os = "emscripten", + ignore = "emscripten libc does not implement dup()/try_clone()" +)] async fn write_to_clone() { let tempfile = tempfile(); diff --git a/tokio/tests/fs_link.rs b/tokio/tests/fs_link.rs index ef143678bcf..2652d394f2f 100644 --- a/tokio/tests/fs_link.rs +++ b/tokio/tests/fs_link.rs @@ -1,5 +1,17 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi")))] // WASI does not support all fs operations +#![cfg(all( + any( + feature = "full", + all( + target_os = "emscripten", + feature = "fs", + feature = "macros", + feature = "rt", + feature = "io-util" + ) + ), + not(target_os = "wasi") +))] // WASI does not support all fs operations use tokio::fs; @@ -8,6 +20,7 @@ use tempfile::tempdir; #[tokio::test] #[cfg_attr(miri, ignore)] // No `linkat` in miri. +#[cfg_attr(target_os = "emscripten", ignore = "MEMFS rejects link() with EMLINK")] async fn test_hard_link() { let dir = tempdir().unwrap(); let src = dir.path().join("src.txt"); @@ -63,6 +76,7 @@ async fn test_symlink() { #[tokio::test] #[cfg_attr(miri, ignore)] // No `linkat` in miri. +#[cfg_attr(target_os = "emscripten", ignore = "MEMFS rejects link() with EMLINK")] async fn test_hard_link_error_source_not_found() { let dir = tempdir().unwrap(); let src = dir.path().join("nonexistent.txt"); @@ -74,6 +88,7 @@ async fn test_hard_link_error_source_not_found() { #[tokio::test] #[cfg_attr(miri, ignore)] // No `linkat` in miri. +#[cfg_attr(target_os = "emscripten", ignore = "MEMFS rejects link() with EMLINK")] async fn test_hard_link_error_destination_already_exists() { let dir = tempdir().unwrap(); let src = dir.path().join("src.txt"); @@ -92,6 +107,7 @@ async fn test_hard_link_error_destination_already_exists() { #[tokio::test] #[cfg_attr(miri, ignore)] // No `linkat` in miri. +#[cfg_attr(target_os = "emscripten", ignore = "MEMFS rejects link() with EMLINK")] async fn test_hard_link_error_source_is_directory() { let dir = tempdir().unwrap(); let src_dir = dir.path().join("src_directory"); diff --git a/tokio/tests/fs_open_options.rs b/tokio/tests/fs_open_options.rs index 58982d679df..957cdd33379 100644 --- a/tokio/tests/fs_open_options.rs +++ b/tokio/tests/fs_open_options.rs @@ -1,5 +1,17 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi")))] // WASI does not support all fs operations +#![cfg(all( + any( + feature = "full", + all( + target_os = "emscripten", + feature = "fs", + feature = "macros", + feature = "rt", + feature = "io-util" + ) + ), + not(target_os = "wasi") +))] // WASI does not support all fs operations use std::io::Write; use tempfile::NamedTempFile; diff --git a/tokio/tests/fs_remove_dir_all.rs b/tokio/tests/fs_remove_dir_all.rs index 5c71bdfda63..996b6ee5e94 100644 --- a/tokio/tests/fs_remove_dir_all.rs +++ b/tokio/tests/fs_remove_dir_all.rs @@ -1,5 +1,17 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi")))] // WASI does not support all fs operations +#![cfg(all( + any( + feature = "full", + all( + target_os = "emscripten", + feature = "fs", + feature = "macros", + feature = "rt", + feature = "io-util" + ) + ), + not(target_os = "wasi") +))] // WASI does not support all fs operations use tempfile::tempdir; use tokio::fs; diff --git a/tokio/tests/fs_remove_file.rs b/tokio/tests/fs_remove_file.rs index ea477213988..9d584532e10 100644 --- a/tokio/tests/fs_remove_file.rs +++ b/tokio/tests/fs_remove_file.rs @@ -1,5 +1,17 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi")))] // WASI does not support all fs operations +#![cfg(all( + any( + feature = "full", + all( + target_os = "emscripten", + feature = "fs", + feature = "macros", + feature = "rt", + feature = "io-util" + ) + ), + not(target_os = "wasi") +))] // WASI does not support all fs operations use tempfile::tempdir; use tokio::fs; diff --git a/tokio/tests/fs_rename.rs b/tokio/tests/fs_rename.rs index 91bb39ee359..01041c191fa 100644 --- a/tokio/tests/fs_rename.rs +++ b/tokio/tests/fs_rename.rs @@ -1,5 +1,17 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi")))] // WASI does not support all fs operations +#![cfg(all( + any( + feature = "full", + all( + target_os = "emscripten", + feature = "fs", + feature = "macros", + feature = "rt", + feature = "io-util" + ) + ), + not(target_os = "wasi") +))] // WASI does not support all fs operations use tempfile::tempdir; use tokio::fs; diff --git a/tokio/tests/fs_try_exists.rs b/tokio/tests/fs_try_exists.rs index 5e698cf1886..9e11bb96307 100644 --- a/tokio/tests/fs_try_exists.rs +++ b/tokio/tests/fs_try_exists.rs @@ -1,5 +1,17 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi")))] // WASI does not support all fs operations +#![cfg(all( + any( + feature = "full", + all( + target_os = "emscripten", + feature = "fs", + feature = "macros", + feature = "rt", + feature = "io-util" + ) + ), + not(target_os = "wasi") +))] // WASI does not support all fs operations use tempfile::tempdir; use tokio::fs; diff --git a/tokio/tests/fs_write.rs b/tokio/tests/fs_write.rs index a125e040875..dae2d7ba8a4 100644 --- a/tokio/tests/fs_write.rs +++ b/tokio/tests/fs_write.rs @@ -1,5 +1,17 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi")))] // WASI does not support all fs operations +#![cfg(all( + any( + feature = "full", + all( + target_os = "emscripten", + feature = "fs", + feature = "macros", + feature = "rt", + feature = "io-util" + ) + ), + not(target_os = "wasi") +))] // WASI does not support all fs operations use tempfile::tempdir; use tokio::fs; From 6165a6f5260841b76b91230e7b2c5f35aafbf21c Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Tue, 25 Aug 2026 11:02:44 -0700 Subject: [PATCH 10/12] chore: rustfmt --- tokio/src/runtime/blocking/pool.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tokio/src/runtime/blocking/pool.rs b/tokio/src/runtime/blocking/pool.rs index 8dac3feef19..eb18dc6e57e 100644 --- a/tokio/src/runtime/blocking/pool.rs +++ b/tokio/src/runtime/blocking/pool.rs @@ -182,7 +182,13 @@ pub(crate) struct Task { #[derive(PartialEq, Eq)] pub(crate) enum Mandatory { - #[cfg_attr(any(not(feature = "fs"), all(target_os = "emscripten", not(target_feature = "atomics"))), allow(dead_code))] + #[cfg_attr( + any( + not(feature = "fs"), + all(target_os = "emscripten", not(target_feature = "atomics")) + ), + allow(dead_code) + )] Mandatory, NonMandatory, } From 86a39c0a886f1d2fb8c836ba17cd08e1dd930bf5 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Tue, 25 Aug 2026 11:06:36 -0700 Subject: [PATCH 11/12] tests: enable the io, task, sync, rt and time test gates on emscripten --- tokio/tests/io_async_read.rs | 10 ++++- tokio/tests/io_buf_reader.rs | 10 ++++- tokio/tests/io_buf_writer.rs | 10 ++++- tokio/tests/io_chain.rs | 10 ++++- tokio/tests/io_copy.rs | 10 ++++- tokio/tests/io_emscripten.rs | 59 +++++++++++++++++++++++++++++ tokio/tests/io_fill_buf.rs | 11 +++++- tokio/tests/io_join.rs | 10 ++++- tokio/tests/io_lines.rs | 10 ++++- tokio/tests/io_mem_stream.rs | 10 ++++- tokio/tests/io_panic.rs | 15 +++++--- tokio/tests/io_read.rs | 10 ++++- tokio/tests/io_read_buf.rs | 10 ++++- tokio/tests/io_read_exact.rs | 10 ++++- tokio/tests/io_read_line.rs | 10 ++++- tokio/tests/io_read_to_end.rs | 10 ++++- tokio/tests/io_read_to_string.rs | 10 ++++- tokio/tests/io_read_until.rs | 10 ++++- tokio/tests/io_repeat.rs | 10 ++++- tokio/tests/io_sink.rs | 10 ++++- tokio/tests/io_split.rs | 10 ++++- tokio/tests/io_take.rs | 10 ++++- tokio/tests/io_util_empty.rs | 10 ++++- tokio/tests/io_write.rs | 10 ++++- tokio/tests/io_write_all.rs | 10 ++++- tokio/tests/io_write_all_buf.rs | 10 ++++- tokio/tests/io_write_buf.rs | 10 ++++- tokio/tests/io_write_int.rs | 10 ++++- tokio/tests/join_handle_panic.rs | 5 ++- tokio/tests/rt_basic.rs | 24 ++++++++++-- tokio/tests/rt_handle.rs | 5 ++- tokio/tests/rt_panic.rs | 14 ++++++- tokio/tests/rt_shutdown_err.rs | 4 +- tokio/tests/rt_time_start_paused.rs | 10 ++++- tokio/tests/sync_once_cell.rs | 10 ++++- tokio/tests/sync_panic.rs | 5 ++- tokio/tests/sync_set_once.rs | 10 ++++- tokio/tests/task_abort.rs | 20 +++++++--- tokio/tests/task_emscripten.rs | 12 ++++++ tokio/tests/task_id.rs | 28 ++++++++++---- tokio/tests/task_join_set.rs | 12 +++++- tokio/tests/task_local.rs | 8 +++- tokio/tests/task_panic.rs | 10 ++++- tokio/tests/task_yield_now.rs | 10 ++++- tokio/tests/test_clock.rs | 10 ++++- tokio/tests/time_interval.rs | 10 ++++- tokio/tests/time_panic.rs | 34 ++++++++++------- tokio/tests/time_pause.rs | 19 +++++++++- tokio/tests/time_sleep.rs | 10 ++++- tokio/tests/time_timeout.rs | 10 ++++- tokio/tests/unwindsafe.rs | 10 ++++- 51 files changed, 539 insertions(+), 86 deletions(-) create mode 100644 tokio/tests/io_emscripten.rs create mode 100644 tokio/tests/task_emscripten.rs diff --git a/tokio/tests/io_async_read.rs b/tokio/tests/io_async_read.rs index aaeadfa4c11..8fe8ca0139c 100644 --- a/tokio/tests/io_async_read.rs +++ b/tokio/tests/io_async_read.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] use tokio::io::AsyncRead; diff --git a/tokio/tests/io_buf_reader.rs b/tokio/tests/io_buf_reader.rs index 0d3f6bafc20..2e69b1ce2d7 100644 --- a/tokio/tests/io_buf_reader.rs +++ b/tokio/tests/io_buf_reader.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] // https://github.com/rust-lang/futures-rs/blob/1803948ff091b4eabf7f3bf39e16bbbdefca5cc8/futures/tests/io_buf_reader.rs diff --git a/tokio/tests/io_buf_writer.rs b/tokio/tests/io_buf_writer.rs index d3acf62c784..8b4491ac05e 100644 --- a/tokio/tests/io_buf_writer.rs +++ b/tokio/tests/io_buf_writer.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] // https://github.com/rust-lang/futures-rs/blob/1803948ff091b4eabf7f3bf39e16bbbdefca5cc8/futures/tests/io_buf_writer.rs diff --git a/tokio/tests/io_chain.rs b/tokio/tests/io_chain.rs index 70398295be9..23574fb0005 100644 --- a/tokio/tests/io_chain.rs +++ b/tokio/tests/io_chain.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] use tokio::io::AsyncReadExt; use tokio_test::assert_ok; diff --git a/tokio/tests/io_copy.rs b/tokio/tests/io_copy.rs index 3bde8e7fa69..931d7edfa2c 100644 --- a/tokio/tests/io_copy.rs +++ b/tokio/tests/io_copy.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] use bytes::BytesMut; use tokio::io::{self, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf}; diff --git a/tokio/tests/io_emscripten.rs b/tokio/tests/io_emscripten.rs new file mode 100644 index 00000000000..a4971d418e4 --- /dev/null +++ b/tokio/tests/io_emscripten.rs @@ -0,0 +1,59 @@ +//! Standard I/O tests for emscripten. +//! +//! `tokio::io::{stdout, stderr}` round through emscripten's libc to the JS +//! `print`/`printErr` hooks; these tests mostly check that writes don't fail — +//! observable output verification is left to manual `--nocapture` runs. +//! +//! `tokio::io::stdin` reads fd 0 synchronously in emscripten, so this only +//! completes when stdin is non-interactive (EOF), as under CI where +//! the runner's stdin is `/dev/null`. The contract worth pinning is "a stdin +//! read returns rather than deadlocking", not a specific errno. + +#![cfg(all(target_os = "emscripten", feature = "io-std"))] + +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +#[tokio::test] +async fn stdout_write_completes() { + let mut out = tokio::io::stdout(); + out.write_all(b"hello from stdout\n").await.unwrap(); + out.flush().await.unwrap(); +} + +#[tokio::test] +async fn stderr_write_completes() { + let mut err = tokio::io::stderr(); + err.write_all(b"hello from stderr\n").await.unwrap(); + err.flush().await.unwrap(); +} + +#[tokio::test] +async fn stdout_large_multichunk_write_completes() { + // Exercises the BufWriter chunking inside `Stdout` (writes larger than + // the internal buffer force multiple underlying writes). + let mut out = tokio::io::stdout(); + let data = vec![b'x'; 64 * 1024]; + out.write_all(&data).await.unwrap(); + out.flush().await.unwrap(); +} + +#[tokio::test] +async fn stdout_interleaved_writes_complete() { + let mut out = tokio::io::stdout(); + for i in 0..16 { + out.write_all(format!("line {i}\n").as_bytes()) + .await + .unwrap(); + } + out.flush().await.unwrap(); +} + +#[tokio::test] +async fn stdin_read_does_not_hang() { + // The runner detaches stdin onto the null device, so a read must return + // promptly (Ok(0) EOF or an I/O error) rather than blocking the host + // loop. The runner's watchdog fails the test if this ever deadlocks. + let mut stdin = tokio::io::stdin(); + let mut buf = [0u8; 32]; + let _ = stdin.read(&mut buf).await; +} diff --git a/tokio/tests/io_fill_buf.rs b/tokio/tests/io_fill_buf.rs index 534417c855d..7cf44cdcc1c 100644 --- a/tokio/tests/io_fill_buf.rs +++ b/tokio/tests/io_fill_buf.rs @@ -1,5 +1,14 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi")))] // Wasi does not support file operations +#![cfg(any( + all(feature = "full", not(target_os = "wasi")), + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util", + feature = "fs" + ) +))] use tempfile::NamedTempFile; use tokio::fs::File; diff --git a/tokio/tests/io_join.rs b/tokio/tests/io_join.rs index 9b9f1e5ae5b..7f753986330 100644 --- a/tokio/tests/io_join.rs +++ b/tokio/tests/io_join.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] use tokio::io::{join, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, Join, ReadBuf}; diff --git a/tokio/tests/io_lines.rs b/tokio/tests/io_lines.rs index 9996d81ca74..61ae6dfc290 100644 --- a/tokio/tests/io_lines.rs +++ b/tokio/tests/io_lines.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] use tokio::io::AsyncBufReadExt; use tokio_test::assert_ok; diff --git a/tokio/tests/io_mem_stream.rs b/tokio/tests/io_mem_stream.rs index 9c4304203c0..b5965697c89 100644 --- a/tokio/tests/io_mem_stream.rs +++ b/tokio/tests/io_mem_stream.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] use futures::FutureExt; use std::io::IoSlice; diff --git a/tokio/tests/io_panic.rs b/tokio/tests/io_panic.rs index 048244d8a93..9c179ed9011 100644 --- a/tokio/tests/io_panic.rs +++ b/tokio/tests/io_panic.rs @@ -1,5 +1,8 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi")))] // Wasi does not support panic recovery +#![cfg(any( + all(feature = "full", not(target_os = "wasi")), // Wasi does not support panic recovery + all(target_os = "emscripten", feature = "io-util") +))] #![cfg(panic = "unwind")] use std::task::{Context, Poll}; @@ -42,7 +45,7 @@ impl AsyncWrite for RW { } } -#[cfg(unix)] +#[cfg(all(unix, not(target_os = "emscripten")))] mod unix { use std::os::unix::prelude::{AsRawFd, RawFd}; @@ -132,7 +135,7 @@ fn unsplit_panic_caller() -> Result<(), Box> { } #[test] -#[cfg(unix)] +#[cfg(all(unix, not(target_os = "emscripten")))] fn async_fd_new_panic_caller() -> Result<(), Box> { use tokio::io::unix::AsyncFd; use tokio::runtime::Builder; @@ -154,7 +157,7 @@ fn async_fd_new_panic_caller() -> Result<(), Box> { } #[test] -#[cfg(unix)] +#[cfg(all(unix, not(target_os = "emscripten")))] fn async_fd_with_interest_panic_caller() -> Result<(), Box> { use tokio::io::unix::AsyncFd; use tokio::io::Interest; @@ -177,7 +180,7 @@ fn async_fd_with_interest_panic_caller() -> Result<(), Box> { } #[test] -#[cfg(unix)] +#[cfg(all(unix, not(target_os = "emscripten")))] fn async_fd_try_new_panic_caller() -> Result<(), Box> { use tokio::io::unix::AsyncFd; use tokio::runtime::Builder; @@ -199,7 +202,7 @@ fn async_fd_try_new_panic_caller() -> Result<(), Box> { } #[test] -#[cfg(unix)] +#[cfg(all(unix, not(target_os = "emscripten")))] fn async_fd_try_with_interest_panic_caller() -> Result<(), Box> { use tokio::io::unix::AsyncFd; use tokio::io::Interest; diff --git a/tokio/tests/io_read.rs b/tokio/tests/io_read.rs index 6bea0ac865e..2e6fd6f470e 100644 --- a/tokio/tests/io_read.rs +++ b/tokio/tests/io_read.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi")))] // Wasi does not support panic recovery +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] use tokio::io::{AsyncRead, AsyncReadExt, ReadBuf}; use tokio_test::assert_ok; diff --git a/tokio/tests/io_read_buf.rs b/tokio/tests/io_read_buf.rs index 49a4f86f8ad..33db330608a 100644 --- a/tokio/tests/io_read_buf.rs +++ b/tokio/tests/io_read_buf.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] use tokio::io::{AsyncRead, AsyncReadExt, ReadBuf}; use tokio_test::assert_ok; diff --git a/tokio/tests/io_read_exact.rs b/tokio/tests/io_read_exact.rs index d0e659bd339..670fa33befb 100644 --- a/tokio/tests/io_read_exact.rs +++ b/tokio/tests/io_read_exact.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] use tokio::io::AsyncReadExt; use tokio_test::assert_ok; diff --git a/tokio/tests/io_read_line.rs b/tokio/tests/io_read_line.rs index 15841c9b49d..eab23fbff26 100644 --- a/tokio/tests/io_read_line.rs +++ b/tokio/tests/io_read_line.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] use std::io::ErrorKind; use tokio::io::{AsyncBufReadExt, BufReader, Error}; diff --git a/tokio/tests/io_read_to_end.rs b/tokio/tests/io_read_to_end.rs index 6573f8b68b2..08e5c23ed02 100644 --- a/tokio/tests/io_read_to_end.rs +++ b/tokio/tests/io_read_to_end.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] use std::pin::Pin; use std::task::{Context, Poll}; diff --git a/tokio/tests/io_read_to_string.rs b/tokio/tests/io_read_to_string.rs index 80649353b28..1a4753dc419 100644 --- a/tokio/tests/io_read_to_string.rs +++ b/tokio/tests/io_read_to_string.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] use std::io; use tokio::io::AsyncReadExt; diff --git a/tokio/tests/io_read_until.rs b/tokio/tests/io_read_until.rs index 61800a0d9c1..f8b73eff6e5 100644 --- a/tokio/tests/io_read_until.rs +++ b/tokio/tests/io_read_until.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] use std::io::ErrorKind; use tokio::io::{AsyncBufReadExt, BufReader, Error}; diff --git a/tokio/tests/io_repeat.rs b/tokio/tests/io_repeat.rs index 5a48817b1a0..f9ad4db4a28 100644 --- a/tokio/tests/io_repeat.rs +++ b/tokio/tests/io_repeat.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(miri)))] +#![cfg(any( + all(feature = "full", not(miri)), + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] use tokio::io::AsyncReadExt; diff --git a/tokio/tests/io_sink.rs b/tokio/tests/io_sink.rs index fb085c51561..749a7b260b3 100644 --- a/tokio/tests/io_sink.rs +++ b/tokio/tests/io_sink.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] use tokio::io::AsyncWriteExt; diff --git a/tokio/tests/io_split.rs b/tokio/tests/io_split.rs index 983982ccaf9..76ad42bf8d8 100644 --- a/tokio/tests/io_split.rs +++ b/tokio/tests/io_split.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi")))] // Wasi does not support panic recovery +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] use tokio::io::{ split, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf, ReadHalf, WriteHalf, diff --git a/tokio/tests/io_take.rs b/tokio/tests/io_take.rs index 1ae5f6908f5..94982f3fc10 100644 --- a/tokio/tests/io_take.rs +++ b/tokio/tests/io_take.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi")))] // Wasi does not support panic recovery +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] use std::pin::Pin; use std::task::{Context, Poll}; diff --git a/tokio/tests/io_util_empty.rs b/tokio/tests/io_util_empty.rs index 7a4b8c6a575..72fac1b140f 100644 --- a/tokio/tests/io_util_empty.rs +++ b/tokio/tests/io_util_empty.rs @@ -1,4 +1,12 @@ -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncSeekExt}; #[tokio::test] diff --git a/tokio/tests/io_write.rs b/tokio/tests/io_write.rs index 96cebc3313b..21bdcbe6e3b 100644 --- a/tokio/tests/io_write.rs +++ b/tokio/tests/io_write.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] use tokio::io::{AsyncWrite, AsyncWriteExt}; use tokio_test::assert_ok; diff --git a/tokio/tests/io_write_all.rs b/tokio/tests/io_write_all.rs index 7ca02228a3c..1ac9b61f3a4 100644 --- a/tokio/tests/io_write_all.rs +++ b/tokio/tests/io_write_all.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] use tokio::io::{AsyncWrite, AsyncWriteExt}; use tokio_test::assert_ok; diff --git a/tokio/tests/io_write_all_buf.rs b/tokio/tests/io_write_all_buf.rs index 52ad5965c09..c5e736255d5 100644 --- a/tokio/tests/io_write_all_buf.rs +++ b/tokio/tests/io_write_all_buf.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] use tokio::io::{AsyncWrite, AsyncWriteExt}; use tokio_test::{assert_err, assert_ok}; diff --git a/tokio/tests/io_write_buf.rs b/tokio/tests/io_write_buf.rs index 8bd09ad62c9..dd74c73132f 100644 --- a/tokio/tests/io_write_buf.rs +++ b/tokio/tests/io_write_buf.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] use tokio::io::{AsyncWrite, AsyncWriteExt}; use tokio_test::assert_ok; diff --git a/tokio/tests/io_write_int.rs b/tokio/tests/io_write_int.rs index 48a583d8c3f..c9c58fa8892 100644 --- a/tokio/tests/io_write_int.rs +++ b/tokio/tests/io_write_int.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "io-util" + ) +))] use tokio::io::{AsyncWrite, AsyncWriteExt}; diff --git a/tokio/tests/join_handle_panic.rs b/tokio/tests/join_handle_panic.rs index 248d5702f68..de505c36f1b 100644 --- a/tokio/tests/join_handle_panic.rs +++ b/tokio/tests/join_handle_panic.rs @@ -1,5 +1,8 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi")))] // Wasi doesn't support panic recovery +#![cfg(any( + all(feature = "full", not(target_os = "wasi")), // Wasi doesn't support panic recovery + all(target_os = "emscripten", feature = "rt", feature = "macros", feature = "time") +))] #![cfg(panic = "unwind")] struct PanicsOnDrop; diff --git a/tokio/tests/rt_basic.rs b/tokio/tests/rt_basic.rs index 3fab2649a12..af4552af298 100644 --- a/tokio/tests/rt_basic.rs +++ b/tokio/tests/rt_basic.rs @@ -1,5 +1,8 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all(target_os = "emscripten", feature = "rt", feature = "macros") +))] use tokio::runtime::{self, Runtime}; use tokio::sync::oneshot; @@ -249,7 +252,10 @@ fn spawn_two() { } } -#[cfg_attr(target_os = "wasi", ignore = "WASI: std::thread::spawn not supported")] +#[cfg_attr( + target_family = "wasm", + ignore = "wasm targets do not support std::thread::spawn" +)] #[test] fn spawn_remote() { let rt = rt(); @@ -346,7 +352,10 @@ mod unstable { } #[test] - #[cfg_attr(target_os = "wasi", ignore = "Wasi does not support panic recovery")] + #[cfg_attr( + target_family = "wasm", + ignore = "wasm targets do not support std::thread::spawn" + )] fn spawns_do_nothing() { use std::sync::Arc; @@ -375,7 +384,10 @@ mod unstable { } #[test] - #[cfg_attr(target_os = "wasi", ignore = "Wasi does not support panic recovery")] + #[cfg_attr( + target_family = "wasm", + ignore = "wasm targets do not support std::thread::spawn" + )] fn shutdown_all_concurrent_block_on() { const N: usize = 2; use std::sync::{mpsc, Arc}; @@ -479,6 +491,10 @@ fn rt() -> Runtime { } #[test] +#[cfg_attr( + target_os = "emscripten", + ignore = "on_thread_park never fires on emscripten (kernel suspends to the JS event loop instead of parking)" +)] fn before_park_yields() { use futures::task::ArcWake; use std::sync::Arc; diff --git a/tokio/tests/rt_handle.rs b/tokio/tests/rt_handle.rs index 8feb7207d0b..db1ee6f7124 100644 --- a/tokio/tests/rt_handle.rs +++ b/tokio/tests/rt_handle.rs @@ -1,5 +1,8 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all(target_os = "emscripten", feature = "rt", feature = "macros") +))] use std::sync::Arc; use tokio::runtime::Runtime; diff --git a/tokio/tests/rt_panic.rs b/tokio/tests/rt_panic.rs index 1bf05580ab6..96105f664da 100644 --- a/tokio/tests/rt_panic.rs +++ b/tokio/tests/rt_panic.rs @@ -1,11 +1,16 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all(target_os = "emscripten", feature = "rt", feature = "macros") +))] #![cfg(not(target_os = "wasi"))] // Wasi doesn't support panic recovery #![cfg(panic = "unwind")] use futures::future; use std::error::Error; -use tokio::runtime::{Builder, Handle, Runtime}; +#[cfg(not(target_os = "emscripten"))] +use tokio::runtime::Builder; +use tokio::runtime::{Handle, Runtime}; mod support { pub mod panic; @@ -47,6 +52,7 @@ fn into_panic_panic_caller() -> Result<(), Box> { } #[test] +#[cfg(not(target_os = "emscripten"))] // no rt-multi-thread fn builder_worker_threads_panic_caller() -> Result<(), Box> { let panic_location_file = test_panic(|| { let _ = Builder::new_multi_thread().worker_threads(0).build(); @@ -59,6 +65,7 @@ fn builder_worker_threads_panic_caller() -> Result<(), Box> { } #[test] +#[cfg(not(target_os = "emscripten"))] // no rt-multi-thread fn builder_max_blocking_threads_panic_caller() -> Result<(), Box> { let panic_location_file = test_panic(|| { let _ = Builder::new_multi_thread().max_blocking_threads(0).build(); @@ -71,6 +78,7 @@ fn builder_max_blocking_threads_panic_caller() -> Result<(), Box> { } #[test] +#[cfg(not(target_os = "emscripten"))] // no rt-multi-thread fn builder_global_queue_interval_panic_caller() -> Result<(), Box> { let panic_location_file = test_panic(|| { let _ = Builder::new_multi_thread().global_queue_interval(0).build(); @@ -83,6 +91,7 @@ fn builder_global_queue_interval_panic_caller() -> Result<(), Box> { } #[test] +#[cfg(not(target_os = "emscripten"))] // no rt-multi-thread fn builder_event_interval_interval_panic_caller() -> Result<(), Box> { let panic_location_file = test_panic(|| { let _ = Builder::new_multi_thread().event_interval(0).build(); @@ -95,6 +104,7 @@ fn builder_event_interval_interval_panic_caller() -> Result<(), Box> } #[test] +#[cfg(not(target_os = "emscripten"))] // no rt-multi-thread fn builder_name_panic_caller() -> Result<(), Box> { let panic_location_file = test_panic(|| { let _ = Builder::new_multi_thread().name(" ").build(); diff --git a/tokio/tests/rt_shutdown_err.rs b/tokio/tests/rt_shutdown_err.rs index b92d8eb24f1..cf03debed25 100644 --- a/tokio/tests/rt_shutdown_err.rs +++ b/tokio/tests/rt_shutdown_err.rs @@ -1,8 +1,9 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any(feature = "full", all(target_os = "emscripten", feature = "rt")))] #![cfg(not(miri))] // No socket in miri. use std::io; +#[cfg(not(target_family = "wasm"))] use tokio::net::TcpListener; use tokio::runtime::Builder; @@ -10,6 +11,7 @@ fn rt() -> tokio::runtime::Runtime { Builder::new_current_thread().enable_all().build().unwrap() } +#[cfg(not(target_family = "wasm"))] // needs net (TcpListener) #[test] fn test_is_rt_shutdown_err() { let rt1 = rt(); diff --git a/tokio/tests/rt_time_start_paused.rs b/tokio/tests/rt_time_start_paused.rs index 1765d625e19..6ec020f7944 100644 --- a/tokio/tests/rt_time_start_paused.rs +++ b/tokio/tests/rt_time_start_paused.rs @@ -1,4 +1,12 @@ -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "test-util" + ) +))] use tokio::time::{Duration, Instant}; diff --git a/tokio/tests/sync_once_cell.rs b/tokio/tests/sync_once_cell.rs index a05438f24c8..acc2a1f427c 100644 --- a/tokio/tests/sync_once_cell.rs +++ b/tokio/tests/sync_once_cell.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "test-util" + ) +))] use std::mem; use std::sync::atomic::{AtomicU32, Ordering}; diff --git a/tokio/tests/sync_panic.rs b/tokio/tests/sync_panic.rs index c781c846bf5..f2f79e0f308 100644 --- a/tokio/tests/sync_panic.rs +++ b/tokio/tests/sync_panic.rs @@ -1,5 +1,8 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi")))] +#![cfg(any( + all(feature = "full", not(target_os = "wasi")), + all(target_os = "emscripten", feature = "rt", feature = "sync") +))] #![cfg(panic = "unwind")] use std::{error::Error, sync::Arc}; diff --git a/tokio/tests/sync_set_once.rs b/tokio/tests/sync_set_once.rs index 5b6d88a9f51..99c0e6255cf 100644 --- a/tokio/tests/sync_set_once.rs +++ b/tokio/tests/sync_set_once.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "sync", + feature = "macros" + ) +))] use std::sync::{ atomic::{AtomicU32, Ordering}, diff --git a/tokio/tests/task_abort.rs b/tokio/tests/task_abort.rs index 8de366454e0..101daad58fc 100644 --- a/tokio/tests/task_abort.rs +++ b/tokio/tests/task_abort.rs @@ -1,16 +1,22 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi")))] // Wasi doesn't support panic recovery +#![cfg(any( + all(feature = "full", not(target_os = "wasi")), // Wasi doesn't support panic recovery + all(target_os = "emscripten", feature = "rt", feature = "time") +))] +#[cfg(not(target_family = "wasm"))] use std::sync::Arc; +#[cfg(not(target_family = "wasm"))] use std::thread::sleep; +#[cfg(not(target_family = "wasm"))] use tokio::time::Duration; use tokio::runtime::Builder; -#[cfg(panic = "unwind")] +#[cfg(all(panic = "unwind", not(target_family = "wasm")))] struct PanicOnDrop; -#[cfg(panic = "unwind")] +#[cfg(all(panic = "unwind", not(target_family = "wasm")))] impl Drop for PanicOnDrop { fn drop(&mut self) { panic!("Well what did you expect would happen..."); @@ -19,6 +25,7 @@ impl Drop for PanicOnDrop { /// Checks that a suspended task can be aborted without panicking as reported in /// issue #3157: . +#[cfg(not(target_family = "wasm"))] #[test] fn test_abort_without_panic_3157() { let rt = Builder::new_multi_thread() @@ -41,6 +48,7 @@ fn test_abort_without_panic_3157() { /// Checks that a suspended task can be aborted inside of a current_thread /// executor without panicking as reported in issue #3662: /// . +#[cfg(not(target_family = "wasm"))] #[test] fn test_abort_without_panic_3662() { use std::sync::atomic::{AtomicBool, Ordering}; @@ -104,6 +112,7 @@ fn test_abort_without_panic_3662() { /// Checks that a suspended LocalSet task can be aborted from a remote thread /// without panicking and without running the tasks destructor on the wrong thread. /// +#[cfg(not(target_family = "wasm"))] #[test] fn remote_abort_local_set_3929() { struct DropCheck { @@ -147,6 +156,7 @@ fn remote_abort_local_set_3929() { /// Checks that a suspended task can be aborted even if the `JoinHandle` is immediately dropped. /// issue #3964: . +#[cfg(not(target_family = "wasm"))] #[test] fn test_abort_wakes_task_3964() { let rt = Builder::new_current_thread().enable_time().build().unwrap(); @@ -177,8 +187,8 @@ fn test_abort_wakes_task_3964() { /// Checks that aborting a task whose destructor panics does not allow the /// panic to escape the task. +#[cfg(all(panic = "unwind", not(target_family = "wasm")))] #[test] -#[cfg(panic = "unwind")] fn test_abort_task_that_panics_on_drop_contained() { let rt = Builder::new_current_thread().enable_time().build().unwrap(); @@ -201,8 +211,8 @@ fn test_abort_task_that_panics_on_drop_contained() { } /// Checks that aborting a task whose destructor panics has the expected result. +#[cfg(all(panic = "unwind", not(target_family = "wasm")))] #[test] -#[cfg(panic = "unwind")] fn test_abort_task_that_panics_on_drop_returned() { let rt = Builder::new_current_thread().enable_time().build().unwrap(); diff --git a/tokio/tests/task_emscripten.rs b/tokio/tests/task_emscripten.rs new file mode 100644 index 00000000000..cb72517dd03 --- /dev/null +++ b/tokio/tests/task_emscripten.rs @@ -0,0 +1,12 @@ +#![cfg(all(target_os = "emscripten", not(target_feature = "atomics")))] + +/// There is no threadpool on a single-threaded JS worker: the public +/// `spawn_blocking` is unsupported on non-pthread emscripten, as on the other +/// single-threaded wasm targets. `tokio::fs` and +/// `tokio::io::{stdin, stdout, stderr}` do not rely on it there — their +/// syscalls complete synchronously. +#[tokio::test] +#[should_panic = "OS can't spawn worker thread"] +async fn spawn_blocking_is_unsupported() { + let _ = tokio::task::spawn_blocking(|| 42).await; +} diff --git a/tokio/tests/task_id.rs b/tokio/tests/task_id.rs index 0cbf80d5ace..1c54773af07 100644 --- a/tokio/tests/task_id.rs +++ b/tokio/tests/task_id.rs @@ -1,10 +1,19 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "sync", + feature = "macros" + ) +))] use std::error::Error; use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; +#[cfg(not(target_family = "wasm"))] use tokio::runtime::Runtime; use tokio::sync::oneshot; use tokio::task::{self, Id, LocalSet}; @@ -21,7 +30,7 @@ async fn task_id_spawn() { .unwrap(); } -#[cfg(not(target_os = "wasi"))] +#[cfg(not(any(target_os = "wasi", target_os = "emscripten")))] #[tokio::test(flavor = "current_thread")] async fn task_id_spawn_blocking() { task::spawn_blocking(|| println!("task id: {}", task::id())) @@ -38,7 +47,7 @@ async fn task_id_collision_current_thread() { assert_ne!(id1.unwrap(), id2.unwrap()); } -#[cfg(not(target_os = "wasi"))] +#[cfg(not(target_family = "wasm"))] #[tokio::test(flavor = "multi_thread")] async fn task_id_collision_multi_thread() { let handle1 = tokio::spawn(async { task::id() }); @@ -59,7 +68,7 @@ async fn task_ids_match_current_thread() { handle.await.unwrap(); } -#[cfg(not(target_os = "wasi"))] +#[cfg(not(target_family = "wasm"))] #[tokio::test(flavor = "multi_thread")] async fn task_ids_match_multi_thread() { let (tx, rx) = oneshot::channel(); @@ -72,7 +81,8 @@ async fn task_ids_match_multi_thread() { } #[cfg(not(target_os = "wasi"))] -#[tokio::test(flavor = "multi_thread")] +#[cfg_attr(target_os = "emscripten", tokio::test(flavor = "current_thread"))] +#[cfg_attr(not(target_os = "emscripten"), tokio::test(flavor = "multi_thread"))] async fn task_id_future_destructor_completion() { struct MyFuture { tx: Option>, @@ -100,7 +110,8 @@ async fn task_id_future_destructor_completion() { } #[cfg(not(target_os = "wasi"))] -#[tokio::test(flavor = "multi_thread")] +#[cfg_attr(target_os = "emscripten", tokio::test(flavor = "current_thread"))] +#[cfg_attr(not(target_os = "emscripten"), tokio::test(flavor = "multi_thread"))] async fn task_id_future_destructor_abort() { struct MyFuture { tx: Option>, @@ -205,7 +216,7 @@ fn task_try_id_outside_task() { assert_eq!(None, task::try_id()); } -#[cfg(not(target_os = "wasi"))] +#[cfg(not(target_family = "wasm"))] #[test] fn task_try_id_inside_block_on() { let rt = Runtime::new().unwrap(); @@ -248,7 +259,7 @@ async fn task_id_nested_spawn_local() { .await; } -#[cfg(not(target_os = "wasi"))] +#[cfg(not(target_family = "wasm"))] #[tokio::test(flavor = "multi_thread")] async fn task_id_block_in_place_block_on_spawn() { use tokio::runtime::Builder; @@ -283,6 +294,7 @@ fn task_id_outside_task_panic_caller() -> Result<(), Box> { Ok(()) } +#[cfg(not(target_family = "wasm"))] #[test] #[cfg_attr(not(panic = "unwind"), ignore)] fn task_id_inside_block_on_panic_caller() -> Result<(), Box> { diff --git a/tokio/tests/task_join_set.rs b/tokio/tests/task_join_set.rs index 38534d1b734..6d6f2b3159c 100644 --- a/tokio/tests/task_join_set.rs +++ b/tokio/tests/task_join_set.rs @@ -1,5 +1,14 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "sync", + feature = "macros", + feature = "time" + ) +))] use futures::future::{pending, FutureExt}; use std::panic; @@ -433,6 +442,7 @@ mod spawn_local { set.spawn_local(async {}); } + #[cfg(not(target_family = "wasm"))] #[tokio::test(flavor = "multi_thread")] #[should_panic( expected = "`spawn_local` called from outside of a `task::LocalSet` or `runtime::LocalRuntime`" diff --git a/tokio/tests/task_local.rs b/tokio/tests/task_local.rs index be9de724163..aabef53c00b 100644 --- a/tokio/tests/task_local.rs +++ b/tokio/tests/task_local.rs @@ -1,11 +1,15 @@ -#![cfg(all(feature = "full", not(target_os = "wasi")))] // Wasi doesn't support threads +#![cfg(any( + feature = "full", + all(target_os = "emscripten", feature = "rt", feature = "macros") +))] use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; use tokio::sync::oneshot; -#[tokio::test(flavor = "multi_thread")] +#[cfg_attr(not(target_family = "wasm"), tokio::test(flavor = "multi_thread"))] +#[cfg_attr(target_family = "wasm", tokio::test)] async fn local() { tokio::task_local! { static REQ_ID: u32; diff --git a/tokio/tests/task_panic.rs b/tokio/tests/task_panic.rs index 8b4de2ada54..03f67f1f96a 100644 --- a/tokio/tests/task_panic.rs +++ b/tokio/tests/task_panic.rs @@ -1,11 +1,16 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi")))] +#![cfg(any( + all(feature = "full", not(target_os = "wasi")), + all(target_os = "emscripten", feature = "rt", feature = "macros") +))] #![cfg(panic = "unwind")] use futures::future; use std::error::Error; use tokio::runtime::Builder; -use tokio::task::{self, block_in_place}; +use tokio::task; +#[cfg(not(target_os = "emscripten"))] +use tokio::task::block_in_place; mod support { pub mod panic; @@ -13,6 +18,7 @@ mod support { use support::panic::test_panic; #[test] +#[cfg(not(target_os = "emscripten"))] // no rt-multi-thread fn block_in_place_panic_caller() -> Result<(), Box> { let panic_location_file = test_panic(|| { let rt = Builder::new_current_thread().enable_all().build().unwrap(); diff --git a/tokio/tests/task_yield_now.rs b/tokio/tests/task_yield_now.rs index e6fe5d2009a..343b9418125 100644 --- a/tokio/tests/task_yield_now.rs +++ b/tokio/tests/task_yield_now.rs @@ -1,4 +1,11 @@ -#![cfg(all(feature = "full", not(target_os = "wasi"), tokio_unstable))] +#![cfg(all( + any( + feature = "full", + all(target_os = "emscripten", feature = "rt", feature = "macros") + ), + not(target_os = "wasi"), + tokio_unstable +))] use tokio::task; use tokio_test::task::spawn; @@ -15,6 +22,7 @@ fn yield_now_outside_of_runtime() { assert!(task.poll().is_ready()); } +#[cfg(not(target_family = "wasm"))] #[tokio::test(flavor = "multi_thread")] async fn yield_now_external_executor_and_block_in_place() { let j = tokio::spawn(async { diff --git a/tokio/tests/test_clock.rs b/tokio/tests/test_clock.rs index 891636fdb28..f5f643a7b59 100644 --- a/tokio/tests/test_clock.rs +++ b/tokio/tests/test_clock.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "test-util" + ) +))] use tokio::time::{self, Duration, Instant}; diff --git a/tokio/tests/time_interval.rs b/tokio/tests/time_interval.rs index 7472a37123c..c33ca177c01 100644 --- a/tokio/tests/time_interval.rs +++ b/tokio/tests/time_interval.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "test-util" + ) +))] use std::pin::Pin; use std::task::{Context, Poll}; diff --git a/tokio/tests/time_panic.rs b/tokio/tests/time_panic.rs index 918d02a416e..5aa651f35c3 100644 --- a/tokio/tests/time_panic.rs +++ b/tokio/tests/time_panic.rs @@ -1,5 +1,8 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi")))] // Wasi doesn't support panic recovery +#![cfg(any( + all(feature = "full", not(target_os = "wasi")), // Wasi doesn't support panic recovery + all(target_os = "emscripten", feature = "rt", feature = "time") +))] #![cfg(panic = "unwind")] use futures::future; @@ -22,21 +25,24 @@ fn rt_combinations() -> Vec { .unwrap(); rts.push(rt); - let rt = tokio::runtime::Builder::new_multi_thread() - .worker_threads(1) - .enable_all() - .build() - .unwrap(); - rts.push(rt); + #[cfg(not(target_os = "emscripten"))] + { + let rt = tokio::runtime::Builder::new_multi_thread() + .worker_threads(1) + .enable_all() + .build() + .unwrap(); + rts.push(rt); - let rt = tokio::runtime::Builder::new_multi_thread() - .worker_threads(4) - .enable_all() - .build() - .unwrap(); - rts.push(rt); + let rt = tokio::runtime::Builder::new_multi_thread() + .worker_threads(4) + .enable_all() + .build() + .unwrap(); + rts.push(rt); + } - #[cfg(tokio_unstable)] + #[cfg(all(tokio_unstable, not(target_os = "emscripten")))] { let rt = tokio::runtime::Builder::new_multi_thread() .worker_threads(1) diff --git a/tokio/tests/time_pause.rs b/tokio/tests/time_pause.rs index be993b4c8dd..ae79dfebe5e 100644 --- a/tokio/tests/time_pause.rs +++ b/tokio/tests/time_pause.rs @@ -1,13 +1,23 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "test-util" + ) +))] #![cfg(not(miri))] // Too slow on miri. +#[cfg(not(target_os = "emscripten"))] use rand::SeedableRng; +#[cfg(not(target_os = "emscripten"))] use rand::{rngs::StdRng, Rng}; use tokio::time::{self, Duration, Instant, Sleep}; use tokio_test::{assert_elapsed, assert_pending, assert_ready, assert_ready_eq, task}; -#[cfg(not(target_os = "wasi"))] +#[cfg(all(feature = "full", not(target_os = "wasi")))] use tokio_test::assert_err; use std::{ @@ -48,6 +58,10 @@ async fn pause_time_in_spawn_threads() { assert_err!(t.await); } +// `#[tokio::main]` returning a value isn't supported on emscripten: the +// worker entry can only signal completion, not marshal a return value back +// across the worker boundary. +#[cfg(not(target_os = "emscripten"))] #[test] fn paused_time_is_deterministic() { let run_1 = paused_time_stress_run(); @@ -56,6 +70,7 @@ fn paused_time_is_deterministic() { assert_eq!(run_1, run_2); } +#[cfg(not(target_os = "emscripten"))] #[tokio::main(flavor = "current_thread", start_paused = true)] async fn paused_time_stress_run() -> Vec { let mut rng = StdRng::seed_from_u64(1); diff --git a/tokio/tests/time_sleep.rs b/tokio/tests/time_sleep.rs index b82b1cc6ae4..ff35170799b 100644 --- a/tokio/tests/time_sleep.rs +++ b/tokio/tests/time_sleep.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "test-util" + ) +))] #![cfg(not(miri))] // Too slow on Miri. use std::future::Future; diff --git a/tokio/tests/time_timeout.rs b/tokio/tests/time_timeout.rs index ec871cf62fe..e54e470d5f4 100644 --- a/tokio/tests/time_timeout.rs +++ b/tokio/tests/time_timeout.rs @@ -1,5 +1,13 @@ #![warn(rust_2018_idioms)] -#![cfg(feature = "full")] +#![cfg(any( + feature = "full", + all( + target_os = "emscripten", + feature = "rt", + feature = "macros", + feature = "test-util" + ) +))] use tokio::sync::oneshot; use tokio::time::{self, timeout, timeout_at, Instant}; diff --git a/tokio/tests/unwindsafe.rs b/tokio/tests/unwindsafe.rs index 8ab6654295b..a07bc904dd9 100644 --- a/tokio/tests/unwindsafe.rs +++ b/tokio/tests/unwindsafe.rs @@ -1,5 +1,8 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi")))] // Wasi does not support panic recovery +#![cfg(any( + all(feature = "full", not(target_os = "wasi")), // Wasi does not support panic recovery + all(target_os = "emscripten", feature = "rt", feature = "sync") +))] use std::panic::{RefUnwindSafe, UnwindSafe}; @@ -14,6 +17,7 @@ fn join_handle_is_unwind_safe() { } #[test] +#[cfg(any(not(target_os = "emscripten"), feature = "net"))] fn net_types_are_unwind_safe() { is_unwind_safe::(); is_unwind_safe::(); @@ -22,8 +26,10 @@ fn net_types_are_unwind_safe() { } #[test] -#[cfg(unix)] +#[cfg(all(unix, any(not(target_os = "emscripten"), feature = "net")))] fn unix_net_types_are_unwind_safe() { + // No datagram `AF_UNIX` on emscripten's node backend. + #[cfg(not(target_os = "emscripten"))] is_unwind_safe::(); is_unwind_safe::(); is_unwind_safe::(); From fab0ff8f56e9ee5ee247f180ffb05912279c67bf Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Tue, 25 Aug 2026 11:37:30 -0700 Subject: [PATCH 12/12] ci: pin the emscripten pthread test toolchains --- .github/workflows/ci.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 62feb81af72..69d0a9a4b53 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,8 @@ env: rust_nightly: nightly-2025-10-12 # Pin a specific miri version rust_miri_nightly: nightly-2026-06-29 + rust_emscripten_nightly: nightly-2026-08-17 + emsdk_version: '6.0.5' rust_clippy: '1.88' # When updating this, also update: # - README.md @@ -1207,7 +1209,7 @@ jobs: - name: Install Emscripten uses: mymindstorm/setup-emsdk@v14 with: - version: 'latest' + version: ${{ env.emsdk_version }} - uses: actions/setup-node@v4 with: @@ -1233,15 +1235,15 @@ jobs: CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER: node RUSTFLAGS: "-Clink-args=-sALLOW_MEMORY_GROWTH=1 -Clink-args=-sEXIT_RUNTIME=1 -Clink-args=-sSTACK_SIZE=1048576" - - name: Install Rust ${{ env.rust_nightly }} + - name: Install Rust ${{ env.rust_emscripten_nightly }} uses: dtolnay/rust-toolchain@nightly with: - toolchain: ${{ env.rust_nightly }} + toolchain: ${{ env.rust_emscripten_nightly }} targets: wasm32-unknown-emscripten components: rust-src - name: Test tokio multi-thread runtime for emscripten (pthread proxy) - run: cargo +${{ env.rust_nightly }} test -Zbuild-std=std,panic_abort -p tokio --target wasm32-unknown-emscripten --features "rt,rt-multi-thread,time,sync,macros" --test rt_multi_thread_emscripten + run: cargo +${{ env.rust_emscripten_nightly }} test -Zbuild-std=std,panic_abort -p tokio --target wasm32-unknown-emscripten --features "rt,rt-multi-thread,time,sync,macros" --test rt_multi_thread_emscripten working-directory: tokio env: CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER: node