Skip to content
Draft
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
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,44 @@ jobs:
run: rustup target add wasm32-wasip2
- name: Tests
run: cargo test --target wasm32-wasip2 --all-features
TestEmscripten:
runs-on: ubuntu-latest
timeout-minutes: 20
# TEMPORARY: this job builds against the guybedford/emscripten `cf` fork,
# which carries the epoll, AF_UNIX pathname sockets, and multicast
# getsockopt patches this branch depends on. Once those land upstream the
# fork checkout can be dropped and this can move to a released emsdk.
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
with:
components: rust-src
# JSPI is the return-to-host bridge; it needs a Node with JSPI on by
# default (V8 13.x+). Recorded here so the cargo runner uses it rather than
# the Node bundled with emsdk.
- uses: actions/setup-node@v4
with:
node-version: 26
- name: Record JSPI-capable node
run: echo "JSPI_NODE=$(which node)" >> "$GITHUB_ENV"
- name: Install emsdk (LLVM + binaryen toolchain)
run: |
git clone --depth 1 https://github.com/emscripten-core/emsdk.git
./emsdk/emsdk install latest
./emsdk/emsdk activate latest
- name: Clone guybedford/emscripten (cf) fork over the emsdk tree
run: |
git clone --depth 1 --branch cf https://github.com/guybedford/emscripten.git em-fork
python3 em-fork/bootstrap.py
rm -rf ./emsdk/upstream/emscripten
ln -s "$(pwd)/em-fork" ./emsdk/upstream/emscripten
- name: Tests
run: |
source ./emsdk/emsdk_env.sh
export EM_CONFIG="$EMSDK/.emscripten"
CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER="$JSPI_NODE" \
RUSTFLAGS="-A linker_messages -C target-feature=+atomics,+bulk-memory,+mutable-globals -C link-arg=-pthread -C link-arg=-sPROXY_TO_PTHREAD -C link-arg=-sNODERAWSOCKETS -C link-arg=-sNODERAWFS -C link-arg=-sALLOW_MEMORY_GROWTH=1 -C link-arg=-sEXIT_RUNTIME=1 -C link-arg=-sJSPI" \
cargo +nightly test --target wasm32-unknown-emscripten -Zbuild-std --all-features --tests --examples --lib
Nightly:
runs-on: ubuntu-latest
timeout-minutes: 10
Expand Down Expand Up @@ -190,6 +228,7 @@ jobs:
#- powerpc64-ibm-aix
- riscv32imc-esp-espidf
- sparcv9-sun-solaris
- wasm32-unknown-emscripten
- wasm32-wasip1
- x86_64-apple-darwin
- x86_64-apple-ios
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ net = []
log = { version = "0.4.8", optional = true }

[target.'cfg(any(unix, target_os = "hermit", target_os = "wasi"))'.dependencies]
libc = "0.2.183"
libc = { git = "https://github.com/guybedford/libc", branch = "emscripten" }

[target.'cfg(windows)'.dependencies.windows-sys]
version = "0.61"
Expand Down
7 changes: 5 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,11 @@
//!
//! The available features are described in the [`features`] module.

#[cfg(all(target_family = "wasm", not(target_os = "wasi")))]
compile_error!("This wasm target is unsupported by mio. If using Tokio, disable the net feature.");
#[cfg(all(
target_family = "wasm",
not(any(target_os = "wasi", target_os = "emscripten"))
))]
compile_error!("This wasm target is unsupported by mio (only the WASI and Emscripten wasm targets are supported). If using Tokio, disable the net feature.");

// macros used internally
#[macro_use]
Expand Down
5 changes: 4 additions & 1 deletion src/net/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,7 @@ pub use self::udp::UdpSocket;
#[cfg(unix)]
mod uds;
#[cfg(unix)]
pub use self::uds::{UnixDatagram, UnixListener, UnixStream};
pub use self::uds::{UnixListener, UnixStream};
// Emscripten's node-backed AF_UNIX is stream-only (no datagram primitive).
#[cfg(all(unix, not(target_os = "emscripten")))]
pub use self::uds::UnixDatagram;
2 changes: 2 additions & 0 deletions src/net/uds/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#[cfg(not(target_os = "emscripten"))]
mod datagram;
#[cfg(not(target_os = "emscripten"))]
pub use self::datagram::UnixDatagram;

mod listener;
Expand Down
2 changes: 2 additions & 0 deletions src/net/uds/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ impl UnixStream {
/// Creates an unnamed pair of connected sockets.
///
/// Returns two `UnixStream`s which are connected to each other.
// Emscripten has no `socketpair(2)` (no `uv_socketpair` exposed by node).
#[cfg(not(target_os = "emscripten"))]
pub fn pair() -> io::Result<(UnixStream, UnixStream)> {
sys::uds::stream::pair().map(|(stream1, stream2)| {
(UnixStream::from_std(stream1), UnixStream::from_std(stream2))
Expand Down
4 changes: 4 additions & 0 deletions src/sys/shell/uds.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Emscripten's node-backed AF_UNIX is stream-only (no datagram primitive).
#[cfg(not(target_os = "emscripten"))]
pub(crate) mod datagram {
use std::io;
use std::os::unix::net::{self, SocketAddr};
Expand Down Expand Up @@ -38,6 +40,8 @@ pub(crate) mod stream {
os_required!()
}

// Emscripten has no `socketpair(2)` (no `uv_socketpair` exposed by node).
#[cfg(not(target_os = "emscripten"))]
pub(crate) fn pair() -> io::Result<(net::UnixStream, net::UnixStream)> {
os_required!()
}
Expand Down
3 changes: 3 additions & 0 deletions src/sys/unix/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ cfg_os_poll! {
not(mio_unsupported_force_poll_poll),
any(
target_os = "android",
target_os = "emscripten",
target_os = "illumos",
target_os = "linux",
target_os = "redox",
Expand Down Expand Up @@ -105,6 +106,7 @@ cfg_os_poll! {
),
target_os = "aix",
target_os = "dragonfly",
target_os = "emscripten",
target_os = "haiku",
target_os = "hurd",
target_os = "netbsd",
Expand Down Expand Up @@ -158,6 +160,7 @@ cfg_os_poll! {
// NOTE: also add to the list for the `pipe` module below.
target_os = "aix",
target_os = "dragonfly",
target_os = "emscripten",
target_os = "haiku",
target_os = "hurd",
target_os = "netbsd",
Expand Down
3 changes: 3 additions & 0 deletions src/sys/unix/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,11 @@ pub(crate) fn new_socket(domain: libc::c_int, socket_type: libc::c_int) -> io::R
}

// Darwin (and others) doesn't have SOCK_NONBLOCK or SOCK_CLOEXEC.
// Emscripten's `socket(2)` silently strips both flags, so set `O_NONBLOCK`
// via `fcntl(2)` instead (`FD_CLOEXEC` is a no-op there).
#[cfg(any(
target_os = "aix",
target_os = "emscripten",
target_os = "ios",
target_os = "macos",
target_os = "tvos",
Expand Down
1 change: 1 addition & 0 deletions src/sys/unix/pipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub(crate) fn new_raw() -> io::Result<[RawFd; 2]> {
#[cfg(any(
target_os = "android",
target_os = "dragonfly",
target_os = "emscripten",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "hurd",
Expand Down
3 changes: 3 additions & 0 deletions src/sys/unix/selector/epoll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,9 @@ pub mod event {
}

// No special requirement from the implementation around waking.
// On emscripten the public `Waker` is not compiled (the runtime wakes through
// its own event loop), so this re-export is unused there.
#[cfg_attr(target_os = "emscripten", allow(unused_imports))]
pub(crate) use crate::sys::unix::waker::Waker;

cfg_io_source! {
Expand Down
1 change: 1 addition & 0 deletions src/sys/unix/tcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ pub(crate) fn accept(listener: &net::TcpListener) -> io::Result<(net::TcpStream,
// See https://github.com/tokio-rs/mio/issues/1445 for details
all(not(target_arch="x86"), target_os = "android"),
target_os = "dragonfly",
target_os = "emscripten",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "hurd",
Expand Down
7 changes: 6 additions & 1 deletion src/sys/unix/uds/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
#[cfg(not(target_os = "emscripten"))]
use std::io;
#[cfg(target_os = "android")]
use std::os::android::net::SocketAddrExt;
#[cfg(target_os = "linux")]
use std::os::linux::net::SocketAddrExt;
use std::os::unix::ffi::OsStrExt;
#[cfg(not(target_os = "emscripten"))]
use std::os::unix::io::FromRawFd;
use std::os::unix::net::SocketAddr;
use std::{io, mem, ptr};
use std::{mem, ptr};

#[cfg(not(target_os = "emscripten"))]
pub(crate) mod datagram;
pub(crate) mod listener;
pub(crate) mod stream;
Expand Down Expand Up @@ -81,6 +85,7 @@ fn unix_addr(address: &SocketAddr) -> (libc::sockaddr_un, libc::socklen_t) {
(sockaddr, addrlen as _)
}

#[cfg(not(target_os = "emscripten"))]
fn pair<T>(flags: libc::c_int) -> io::Result<(T, T)>
where
T: FromRawFd,
Expand Down
1 change: 1 addition & 0 deletions src/sys/unix/uds/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub(crate) fn connect_addr(address: &SocketAddr) -> io::Result<net::UnixStream>
Ok(socket)
}

#[cfg(not(target_os = "emscripten"))]
pub(crate) fn pair() -> io::Result<(net::UnixStream, net::UnixStream)> {
super::pair(libc::SOCK_STREAM)
}
2 changes: 1 addition & 1 deletion tests/regressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ fn issue_1205() {
}

#[test]
#[cfg(unix)]
#[cfg(all(unix, not(target_os = "emscripten")))]
#[cfg_attr(miri, ignore = "Miri doesn't support Unix domain sockets")]
fn issue_1403() {
use mio::net::UnixDatagram;
Expand Down
5 changes: 5 additions & 0 deletions tests/tcp_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,10 @@ fn shutdown_both() {
}

#[cfg(unix)]
#[cfg_attr(
target_os = "emscripten",
ignore = "REMOVE BEFORE LANDING - pending Node.js 26.6.0 in runner"
)]
#[test]
fn raw_fd() {
init();
Expand Down Expand Up @@ -636,6 +640,7 @@ fn tcp_shutdown_client_read_close_event() {
#[cfg_attr(
any(
target_os = "android",
target_os = "emscripten",
target_os = "hurd",
target_os = "illumos",
target_os = "solaris",
Expand Down
10 changes: 10 additions & 0 deletions tests/udp_socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,10 @@ fn smoke_test_connected_udp_socket(mut socket1: UdpSocket, mut socket2: UdpSocke
assert!(socket2.take_error().unwrap().is_none());
}

#[cfg_attr(
target_os = "emscripten",
ignore = "libuv does not re-associate a connected UDP socket on reconnect"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this related to libuv?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test runner here is Node.js. See libuv/libuv#5193 for the upstream PR (which it seems will likely not be approved).

)]
#[test]
fn reconnect_udp_socket_sending() {
let (mut poll, mut events) = init_with_poll();
Expand Down Expand Up @@ -470,6 +474,10 @@ fn reconnect_udp_socket_sending() {
assert!(socket3.take_error().unwrap().is_none());
}

#[cfg_attr(
target_os = "emscripten",
ignore = "libuv does not re-associate a connected UDP socket on reconnect"
)]
#[test]
fn reconnect_udp_socket_receiving() {
let (mut poll, mut events) = init_with_poll();
Expand Down Expand Up @@ -652,6 +660,7 @@ fn connected_udp_socket_unconnected_methods() {
target_os = "android",
target_os = "hurd",
target_os = "linux",
target_os = "emscripten",
target_os = "windows",
target_os = "cygwin",
target_os = "wasi",
Expand All @@ -664,6 +673,7 @@ fn connected_udp_socket_unconnected_methods() {
target_os = "android",
target_os = "hurd",
target_os = "linux",
target_os = "emscripten",
target_os = "windows",
target_os = "cygwin",
target_os = "wasi",
Expand Down
10 changes: 9 additions & 1 deletion tests/unix_datagram.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
#![cfg(all(unix, feature = "os-poll", feature = "net", not(miri)))] // Miri doesn't support Unix domain sockets.
// Emscripten's node-backed AF_UNIX is stream-only (no datagram sockets).
// Miri doesn't support Unix domain sockets.
#![cfg(all(
unix,
not(target_os = "emscripten"),
feature = "os-poll",
feature = "net",
not(miri)
))]

use mio::net::UnixDatagram;
use mio::{Interest, Token};
Expand Down
8 changes: 7 additions & 1 deletion tests/unix_pipe.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
#![cfg(all(unix, feature = "os-poll", feature = "os-ext", feature = "net"))]

use std::io::{Read, Write};
#[cfg(not(target_os = "emscripten"))]
use std::process::{Command, Stdio};
use std::sync::{Arc, Barrier};
use std::thread;
use std::time::Duration;

use mio::unix::pipe::{self, Receiver, Sender};
use mio::unix::pipe;
#[cfg(not(target_os = "emscripten"))]
use mio::unix::pipe::{Receiver, Sender};
use mio::{Events, Interest, Poll, Token};

mod util;
Expand Down Expand Up @@ -132,6 +135,8 @@ fn event_when_receiver_is_dropped() {
}

#[test]
// Emscripten/wasm has no fork/exec, so no subprocesses.
#[cfg(not(target_os = "emscripten"))]
#[cfg_attr(
any(target_os = "hurd", target_os = "nto", target_os = "cygwin"),
ignore = "Writer fd close events do not trigger POLLHUP on nto and GNU/Hurd targets"
Expand Down Expand Up @@ -184,6 +189,7 @@ fn from_child_process_io() {
}

#[test]
#[cfg(not(target_os = "emscripten"))]
#[cfg_attr(miri, ignore = "Miri doesn't support process spawning")]
fn nonblocking_child_process_io() {
// `cat` simply echo everything that we write via standard in.
Expand Down
3 changes: 3 additions & 0 deletions tests/unix_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const DATA1_LEN: usize = 16;
const DATA2_LEN: usize = 14;
const DEFAULT_BUF_SIZE: usize = 64;
const TOKEN_1: Token = Token(0);
#[cfg(not(target_os = "emscripten"))]
const TOKEN_2: Token = Token(1);

#[test]
Expand Down Expand Up @@ -145,6 +146,8 @@ fn unix_stream_from_std() {
)
}

// Emscripten has no `socketpair(2)`.
#[cfg(not(target_os = "emscripten"))]
#[test]
fn unix_stream_pair() {
let (mut poll, mut events) = init_with_poll();
Expand Down
7 changes: 6 additions & 1 deletion tests/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ pub fn assert_socket_non_blocking<S>(_: &S) {
}

/// Assert that `CLOEXEC` is set on `socket`.
#[cfg(any(unix, target_os = "wasi"))]
#[cfg(any(all(unix, not(target_os = "emscripten")), target_os = "wasi"))]
pub fn assert_socket_close_on_exec<S>(socket: &S)
where
S: AsRawFd,
Expand All @@ -242,6 +242,11 @@ where
}
}

// Emscripten is a single process with no `exec(2)`, so `FD_CLOEXEC` is a no-op
// (`F_GETFD` always returns 0); the concept doesn't apply.
#[cfg(target_os = "emscripten")]
pub fn assert_socket_close_on_exec<S>(_: &S) {}

#[cfg(windows)]
pub fn assert_socket_close_on_exec<S>(_: &S) {
// Windows doesn't have this concept.
Expand Down