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 @@ -99,6 +99,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 callback, 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
# A recent Node for the cargo runner (rather than the Node bundled with
# emsdk). No JSPI needed: blocking test-harness socket ops block on their
# pthread via the sync-proxy (_emscripten_fd_wait), not stack suspension.
- uses: actions/setup-node@v4
with:
node-version: 26
- name: Record runner node
run: echo "RUNNER_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="$RUNNER_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" \
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 @@ -170,6 +208,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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ Currently supported platforms:
* iOS
* macOS
* Solaris
* Emscripten

Mio can handle interfacing with each of the event systems of the aforementioned
platforms. The details of their implementation are further discussed in the
Expand Down
5 changes: 4 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@
//!
//! The available features are described in the [`features`] module.

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

// macros used internally
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")))]
fn issue_1403() {
use mio::net::UnixDatagram;
use util::temp_file;
Expand Down
5 changes: 5 additions & 0 deletions tests/tcp_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,10 @@ fn shutdown_both() {
}

#[cfg(unix)]
#[cfg_attr(
target_os = "emscripten",
ignore = "local_addr not preserved across from_raw_fd on emscripten"
)]
#[test]
fn raw_fd() {
init();
Expand Down Expand Up @@ -624,6 +628,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 @@ -453,6 +453,10 @@ fn smoke_test_connected_udp_socket(mut socket1: UdpSocket, mut socket2: UdpSocke
target_os = "wasi",
ignore = "temporarily disabled for WASI pending https://github.com/bytecodealliance/wasmtime/pull/12595"
)]
#[cfg_attr(
target_os = "emscripten",
ignore = "libuv does not re-associate a connected UDP socket on reconnect"
)]
#[test]
fn reconnect_udp_socket_sending() {
let (mut poll, mut events) = init_with_poll();
Expand Down Expand Up @@ -520,6 +524,10 @@ fn reconnect_udp_socket_sending() {
target_os = "wasi",
ignore = "temporarily disabled for WASI pending https://github.com/bytecodealliance/wasmtime/pull/12595"
)]
#[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 @@ -708,6 +716,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 @@ -720,6 +729,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
8 changes: 7 additions & 1 deletion tests/unix_datagram.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
#![cfg(all(unix, feature = "os-poll", feature = "net"))]
// Emscripten's node-backed AF_UNIX is stream-only (no datagram sockets).
#![cfg(all(
unix,
not(target_os = "emscripten"),
feature = "os-poll",
feature = "net"
))]

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 @@ -183,6 +188,7 @@ fn from_child_process_io() {
}

#[test]
#[cfg(not(target_os = "emscripten"))]
fn nonblocking_child_process_io() {
// `cat` simply echo everything that we write via standard in.
let mut child = Command::new("cat")
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 @@ -226,7 +226,7 @@ pub fn assert_socket_non_blocking<S>(_: &S) {
}

/// Assert that `CLOEXEC` is set on `socket`.
#[cfg(unix)]
#[cfg(all(unix, not(target_os = "emscripten")))]
pub fn assert_socket_close_on_exec<S>(socket: &S)
where
S: AsRawFd,
Expand All @@ -235,6 +235,11 @@ where
assert!(flags & libc::FD_CLOEXEC != 0, "socket flag CLOEXEC not set");
}

// 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
Loading