Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1202,12 +1202,19 @@ jobs:
env:
RUSTFLAGS: ""

- name: Test tokio for emscripten
- name: Test tokio for emscripten (JSPI)
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"
RUSTFLAGS: "-C link-args=-sALLOW_MEMORY_GROWTH=1 -C link-args=-sEXIT_RUNTIME=1 -C link-args=-sJSPI -C link-args=-sSTACK_SIZE=1048576"

- name: Test tokio for emscripten (non-JSPI)
run: cargo test -p tokio --target wasm32-unknown-emscripten --features "rt,time,sync,macros,io-util,test-util" --test rt_emscripten_block_on
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"

check-external-types:
name: check-external-types (${{ matrix.os }})
Expand Down
5 changes: 4 additions & 1 deletion spellcheck.dic
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
327
330
&
+
<
Expand Down Expand Up @@ -162,6 +162,8 @@ IP
IPv4
IPv6
iteratively
JS
JSPI
Kotlin's
latencies
Lauck
Expand Down Expand Up @@ -266,6 +268,7 @@ subfield
suboptimal
subprocess
superset
suspendable
symlink
symlinks
sys
Expand Down
66 changes: 54 additions & 12 deletions tokio-macros/src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -521,10 +521,49 @@ 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 output_type = match &input.sig.output {
// For functions with no return value syn doesn't print anything,
// but that doesn't work as `Output` for our boxed `Future`, so
// default to `()` (the same type as the function output).
syn::ReturnType::Default => quote! { () },
syn::ReturnType::Type(_, ret_type) => quote! { #ret_type },
};
let raw_body = input.body();
// On Emscripten `#[tokio::test]` claims exclusive JSPI suspension for the
// promising activation libtest runs it under (Emscripten auto-promising-
// wraps `main` under `-sJSPI`), so the body's `block_on` can suspend on
// the host event loop; without `-sJSPI` no guard is created (waits
// panic).
let emscripten_test_block = quote_spanned! {last_stmt_end_span=>
#[allow(clippy::expect_used, clippy::diverging_sub_expression, clippy::needless_return, clippy::unwrap_in_result)]
{
let _suspend_guard = if #crate_path::runtime::jspi_enabled() {
::core::option::Option::Some(#crate_path::runtime::SuspendGuard::new())
} else {
::core::option::Option::None
};

let body = async #raw_body;
#crate_path::pin!(body);
let body: ::core::pin::Pin<&mut dyn ::core::future::Future<Output = #output_type>> = body;

// Inner block: keep the `use` from shadowing names in the
// user body, which shares this scope.
let rt = {
#use_builder

#rt
.enable_all()
.#build
.expect("Failed building the Runtime")
};
return rt.block_on(body);
}
};

// The `multi_thread` flavor has no native threads on Emscripten, so it's
// rejected with a targeted error (pending a `PROXY_TO_PTHREAD` runtime)
// 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"))]
Expand All @@ -536,6 +575,12 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
`flavor = \"current_thread\"`"
);
},
_ if is_test => quote! {
#[cfg(not(target_os = "emscripten"))]
#native_last_block
#[cfg(target_os = "emscripten")]
#emscripten_test_block
},
_ => native_last_block,
};

Expand All @@ -550,18 +595,15 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
//
// We don't do this for the main function as it should only be used once so
// there will be no benefit.
let output_type = match &input.sig.output {
// For functions with no return value syn doesn't print anything,
// but that doesn't work as `Output` for our boxed `Future`, so
// default to `()` (the same type as the function output).
syn::ReturnType::Default => quote! { () },
syn::ReturnType::Type(_, ret_type) => quote! { #ret_type },
};

let body = if is_test {
// On Emscripten the body construction lives inside the capture-free
// closure (see `emscripten_test_block`).
quote! {
#[cfg(not(target_os = "emscripten"))]
let body = async #body;
#[cfg(not(target_os = "emscripten"))]
#crate_path::pin!(body);
#[cfg(not(target_os = "emscripten"))]
let body: ::core::pin::Pin<&mut dyn ::core::future::Future<Output = #output_type>> = body;
}
} else {
Expand Down
67 changes: 41 additions & 26 deletions tokio/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,46 +446,52 @@
//!
//! ### 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 `wasm32-unknown-emscripten` target runs Tokio's canonical
//! single-threaded (`current_thread`) scheduler, time driver, and task system
//! unchanged. `Runtime::block_on` drives the scheduler to a synchronous fixed
//! point; a future that would have to suspend (await a timer or an external
//! wake) panics with a targeted error, since the host event loop cannot run
//! while Wasm blocks the stack.
//!
//! [JSPI] (-sJSPI in Emscripten) implements an exception to this behavior:
//! a drive that would block instead suspends the whole calling stack on a
//! host timer — the host loop keeps running and wakes it. Emscripten
//! auto-promising-wraps `main` under `-sJSPI`, so `#[tokio::main]` and
//! `#[tokio::test]` suspend as-is; calling `block_on` from a host callback
//! outside a promising activation traps at the engine. The drive is
//! non-reentrant, like `block_on` everywhere else.
//!
//! [JSPI]: https://github.com/WebAssembly/js-promise-integration
//!
//! Supported features: `rt`, `time`, `sync`, `macros`, `io-util`, and
//! `test-util`. The `net` reactor (epoll-backed, over Emscripten's socket
//! support) and the inline `fs`/`io-std` shims are planned as follow-ups.
//!
//! 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.
//! `spawn_blocking` is unchanged: with no threadpool to dispatch to it fails
//! at runtime, as on the other single-threaded Wasm targets.
//!
//! 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"`.
//!
//! `#[tokio::main]` still defaults to the unsupported `multi_thread` flavor,
//! `#[tokio::main(flavor = "current_thread")]` is currently required.
//!
//! #### Linking and running on Emscripten
//!
//! No js-library or other custom file is required, and plain `node` runs the
//! test binaries directly:
//! No js-library or custom file is required (Tokio embeds its JS glue in
//! the objects); 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=-sJSPI \
//! -C link-args=-sSTACK_SIZE=1048576"
//! ```
//!
Expand Down Expand Up @@ -524,12 +530,21 @@ 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
// On Emscripten, `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_signal!`) exactly as on wasi; naming those APIs is a compile error.
// `rt-multi-thread` compiles but only runs under a `PROXY_TO_PTHREAD` build;
// `#[tokio::main]` steers to `current_thread`.

// We currently only support the single-threaded Emscripten runtime, with
// support for JSPI.
// A `-pthread` (atomics) build breaks its assumptions, until the kernel is made
// thread-aware.
#[cfg(all(target_os = "emscripten", feature = "rt", target_feature = "atomics"))]
compile_error!(
"Tokio's `wasm32-unknown-emscripten` runtime is single-threaded and does \
not support `-pthread` (atomics) builds."
);

#[cfg(all(not(tokio_unstable), feature = "io-uring"))]
compile_error!("The `io-uring` feature requires `--cfg tokio_unstable`.");
Expand Down
11 changes: 3 additions & 8 deletions tokio/src/macros/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,11 +398,9 @@ 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!`.
// Emscripten has no `fork`/`exec`, so the `process` module is not
// available there.
#[cfg(not(target_os = "emscripten"))]
$item
)*
}
Expand All @@ -412,9 +410,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)* }
}
}
Expand Down
127 changes: 0 additions & 127 deletions tokio/src/process/emscripten.rs

This file was deleted.

Loading