From f56f99c9f5fa02adb5989aba258ac28eb9fbdb74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Fri, 3 Jan 2025 12:14:46 +0100 Subject: [PATCH 1/3] feat(quinn,quinn-udp): Disable `socket2` and `std::net::UdpSocket` dependencies in wasm/browser targets --- .github/workflows/rust.yml | 17 +++++++++-------- Cargo.toml | 1 + quinn-udp/Cargo.toml | 6 ++++-- quinn-udp/build.rs | 1 + quinn-udp/src/lib.rs | 16 ++++++++++++---- quinn/Cargo.toml | 13 +++++++++++-- quinn/build.rs | 9 +++++++++ quinn/src/connection.rs | 3 +-- quinn/src/endpoint.rs | 13 +++++++------ quinn/src/lib.rs | 7 ++++++- quinn/src/mutex.rs | 6 ++---- quinn/src/runtime.rs | 4 +++- quinn/src/tests.rs | 6 ++---- quinn/src/work_limiter.rs | 2 +- 14 files changed, 69 insertions(+), 35 deletions(-) create mode 100644 quinn/build.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index f984280b8a..150a147e73 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -164,20 +164,21 @@ jobs: - name: Install cargo binstall uses: cargo-bins/cargo-binstall@main - - name: build wasm32 tests (quinn-proto) - run: cargo test -p quinn-proto --target wasm32-unknown-unknown --no-run + - run: cargo test -p quinn-proto --target wasm32-unknown-unknown --no-run + - run: cargo check -p quinn-udp --target wasm32-unknown-unknown --no-default-features --features=tracing,log + - run: cargo rustc -p quinn --target wasm32-unknown-unknown --no-default-features --features=log,platform-verifier,rustls-ring --crate-type=cdylib # If the Wasm file contains any 'import "env"' declarations, then # some non-Wasm-compatible code made it into the final code. - - name: Check for 'import "env"' in Wasm + - name: Ensure no 'import "env"' in quinn_proto Wasm run: | ! wasm-tools print --skeleton target/wasm32-unknown-unknown/debug/deps/quinn_proto-*.wasm | grep 'import "env"' + - name: Ensure no 'import "env"' in quinn Wasm + run: | + ! wasm-tools print --skeleton target/wasm32-unknown-unknown/debug/quinn.wasm | grep 'import "env"' - - name: Install wasm-bindgen-test-runner - run: cargo binstall wasm-bindgen-cli --locked --no-confirm - - - name: wasm32 test (quinn-proto) - run: cargo test -p quinn-proto --target wasm32-unknown-unknown + - run: cargo binstall wasm-bindgen-cli --locked --no-confirm + - run: cargo test -p quinn-proto --target wasm32-unknown-unknown msrv: runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index 9a1452a75b..5370dbbb26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,7 @@ url = "2" wasm-bindgen-test = { version = "0.3.45" } web-time = "1" windows-sys = { version = ">=0.52, <=0.59", features = ["Win32_Foundation", "Win32_System_IO", "Win32_Networking_WinSock"] } +cfg_aliases = "0.2" [profile.bench] debug = true diff --git a/quinn-udp/Cargo.toml b/quinn-udp/Cargo.toml index 9064b1e028..5a5ca87642 100644 --- a/quinn-udp/Cargo.toml +++ b/quinn-udp/Cargo.toml @@ -21,9 +21,11 @@ fast-apple-datapath = [] [dependencies] libc = "0.2.158" log = { workspace = true, optional = true } -socket2 = { workspace = true } tracing = { workspace = true, optional = true } +[target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dependencies] +socket2 = { workspace = true } + [target.'cfg(windows)'.dependencies] once_cell = { workspace = true } windows-sys = { workspace = true } @@ -33,7 +35,7 @@ criterion = { version = "0.5", default-features = false, features = ["async_toki tokio = { workspace = true, features = ["rt", "rt-multi-thread", "net"] } [build-dependencies] -cfg_aliases = "0.2" +cfg_aliases = { workspace = true } [lib] # See https://github.com/bheisler/criterion.rs/blob/master/book/src/faq.md#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options diff --git a/quinn-udp/build.rs b/quinn-udp/build.rs index d9893ed948..c43c0aa342 100644 --- a/quinn-udp/build.rs +++ b/quinn-udp/build.rs @@ -28,5 +28,6 @@ fn main() { // Convenience aliases apple_fast: { all(apple, feature = "fast-apple-datapath") }, apple_slow: { all(apple, not(feature = "fast-apple-datapath")) }, + wasm_browser: { all(target_family = "wasm", target_os = "unknown") }, } } diff --git a/quinn-udp/src/lib.rs b/quinn-udp/src/lib.rs index f8115ddd66..a8a0243cb4 100644 --- a/quinn-udp/src/lib.rs +++ b/quinn-udp/src/lib.rs @@ -27,12 +27,13 @@ #![warn(unreachable_pub)] #![warn(clippy::use_self)] +use std::net::{IpAddr, Ipv6Addr, SocketAddr}; #[cfg(unix)] use std::os::unix::io::AsFd; #[cfg(windows)] use std::os::windows::io::AsSocket; +#[cfg(not(wasm_browser))] use std::{ - net::{IpAddr, Ipv6Addr, SocketAddr}, sync::Mutex, time::{Duration, Instant}, }; @@ -49,7 +50,7 @@ mod imp; mod imp; // No ECN support -#[cfg(not(any(unix, windows)))] +#[cfg(not(any(wasm_browser, unix, windows)))] #[path = "fallback.rs"] mod imp; @@ -76,10 +77,15 @@ mod log { pub(crate) use no_op::*; } +#[cfg(not(wasm_browser))] pub use imp::UdpSocketState; /// Number of UDP packets to send/receive at a time +#[cfg(not(wasm_browser))] pub const BATCH_SIZE: usize = imp::BATCH_SIZE; +/// Number of UDP packets to send/receive at a time +#[cfg(wasm_browser)] +pub const BATCH_SIZE: usize = 1; /// Metadata for a single buffer filled with bytes received from the network /// @@ -141,13 +147,14 @@ pub struct Transmit<'a> { } /// Log at most 1 IO error per minute +#[cfg(not(wasm_browser))] const IO_ERROR_LOG_INTERVAL: Duration = std::time::Duration::from_secs(60); /// Logs a warning message when sendmsg fails /// /// Logging will only be performed if at least [`IO_ERROR_LOG_INTERVAL`] /// has elapsed since the last error was logged. -#[cfg(any(feature = "tracing", feature = "direct-log"))] +#[cfg(all(not(wasm_browser), any(feature = "tracing", feature = "direct-log")))] fn log_sendmsg_error( last_send_error: &Mutex, err: impl core::fmt::Debug, @@ -164,7 +171,7 @@ fn log_sendmsg_error( } // No-op -#[cfg(not(any(feature = "tracing", feature = "direct-log")))] +#[cfg(not(any(wasm_browser, feature = "tracing", feature = "direct-log")))] fn log_sendmsg_error(_: &Mutex, _: impl core::fmt::Debug, _: &Transmit) {} /// A borrowed UDP socket @@ -172,6 +179,7 @@ fn log_sendmsg_error(_: &Mutex, _: impl core::fmt::Debug, _: &Transmit) /// On Unix, constructible via `From`. On Windows, constructible via `From`. // Wrapper around socket2 to avoid making it a public dependency and incurring stability risk +#[cfg(not(wasm_browser))] pub struct UdpSockRef<'a>(socket2::SockRef<'a>); #[cfg(unix)] diff --git a/quinn/Cargo.toml b/quinn/Cargo.toml index a061520d7c..a4cba2bba9 100644 --- a/quinn/Cargo.toml +++ b/quinn/Cargo.toml @@ -27,7 +27,8 @@ rustls-aws-lc-rs = ["dep:rustls", "aws-lc-rs", "proto/rustls-aws-lc-rs", "proto/ rustls-aws-lc-rs-fips = ["dep:rustls", "aws-lc-rs-fips", "proto/rustls-aws-lc-rs-fips", "proto/aws-lc-rs-fips"] # Enable rustls with the `ring` crypto provider rustls-ring = ["dep:rustls", "ring", "proto/rustls-ring", "proto/ring"] -# Enables `Endpoint::client` and `Endpoint::server` conveniences +# Enable the `ring` crypto provider. +# Outside wasm*-unknown-unknown targets, this enables `Endpoint::client` and `Endpoint::server` conveniences. ring = ["proto/ring"] runtime-tokio = ["tokio/time", "tokio/rt", "tokio/net"] runtime-async-std = ["async-io", "async-std"] @@ -49,12 +50,17 @@ pin-project-lite = { workspace = true } proto = { package = "quinn-proto", path = "../quinn-proto", version = "0.11.7", default-features = false } rustls = { workspace = true, optional = true } smol = { workspace = true, optional = true } -socket2 = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } tokio = { workspace = true } udp = { package = "quinn-udp", path = "../quinn-udp", version = "0.5", default-features = false, features = ["tracing"] } +[target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dependencies] +socket2 = { workspace = true } + +[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies] +web-time = { workspace = true } + [dev-dependencies] anyhow = { workspace = true } crc = { workspace = true } @@ -69,6 +75,9 @@ tracing-subscriber = { workspace = true } tracing-futures = { workspace = true } url = { workspace = true } +[build-dependencies] +cfg_aliases = { workspace = true } + [[example]] name = "server" required-features = ["rustls-ring"] diff --git a/quinn/build.rs b/quinn/build.rs new file mode 100644 index 0000000000..7aae56820c --- /dev/null +++ b/quinn/build.rs @@ -0,0 +1,9 @@ +use cfg_aliases::cfg_aliases; + +fn main() { + // Setup cfg aliases + cfg_aliases! { + // Convenience aliases + wasm_browser: { all(target_family = "wasm", target_os = "unknown") }, + } +} diff --git a/quinn/src/connection.rs b/quinn/src/connection.rs index e08a4fdc0c..3e13d24477 100644 --- a/quinn/src/connection.rs +++ b/quinn/src/connection.rs @@ -7,7 +7,6 @@ use std::{ pin::Pin, sync::Arc, task::{Context, Poll, Waker}, - time::{Duration, Instant}, }; use bytes::Bytes; @@ -22,7 +21,7 @@ use crate::{ recv_stream::RecvStream, runtime::{AsyncTimer, AsyncUdpSocket, Runtime, UdpPoller}, send_stream::SendStream, - udp_transmit, ConnectionEvent, VarInt, + udp_transmit, ConnectionEvent, Duration, Instant, VarInt, }; use proto::{ congestion::Controller, ConnectionError, ConnectionHandle, ConnectionStats, Dir, EndpointEvent, diff --git a/quinn/src/endpoint.rs b/quinn/src/endpoint.rs index 509d5abd6d..9f4cc78a0f 100644 --- a/quinn/src/endpoint.rs +++ b/quinn/src/endpoint.rs @@ -10,14 +10,13 @@ use std::{ str, sync::{Arc, Mutex}, task::{Context, Poll, Waker}, - time::Instant, }; -#[cfg(any(feature = "aws-lc-rs", feature = "ring"))] +#[cfg(all(not(wasm_browser), any(feature = "aws-lc-rs", feature = "ring")))] use crate::runtime::default_runtime; use crate::{ runtime::{AsyncUdpSocket, Runtime}, - udp_transmit, + udp_transmit, Instant, }; use bytes::{Bytes, BytesMut}; use pin_project_lite::pin_project; @@ -26,7 +25,7 @@ use proto::{ EndpointEvent, ServerConfig, }; use rustc_hash::FxHashMap; -#[cfg(any(feature = "aws-lc-rs", feature = "ring"))] +#[cfg(all(not(wasm_browser), any(feature = "aws-lc-rs", feature = "ring"),))] use socket2::{Domain, Protocol, Socket, Type}; use tokio::sync::{futures::Notified, mpsc, Notify}; use tracing::{Instrument, Span}; @@ -68,7 +67,7 @@ impl Endpoint { /// /// Some environments may not allow creation of dual-stack sockets, in which case an IPv6 /// client will only be able to connect to IPv6 servers. An IPv4 client is never dual-stack. - #[cfg(any(feature = "aws-lc-rs", feature = "ring"))] // `EndpointConfig::default()` is only available with these + #[cfg(all(not(wasm_browser), any(feature = "aws-lc-rs", feature = "ring")))] // `EndpointConfig::default()` is only available with these pub fn client(addr: SocketAddr) -> io::Result { let socket = Socket::new(Domain::for_address(addr), Type::DGRAM, Some(Protocol::UDP))?; if addr.is_ipv6() { @@ -98,7 +97,7 @@ impl Endpoint { /// IPv6 address on Windows will not by default be able to communicate with IPv4 /// addresses. Portable applications should bind an address that matches the family they wish to /// communicate within. - #[cfg(any(feature = "aws-lc-rs", feature = "ring"))] // `EndpointConfig::default()` is only available with these + #[cfg(all(not(wasm_browser), any(feature = "aws-lc-rs", feature = "ring")))] // `EndpointConfig::default()` is only available with these pub fn server(config: ServerConfig, addr: SocketAddr) -> io::Result { let socket = std::net::UdpSocket::bind(addr)?; let runtime = default_runtime() @@ -112,6 +111,7 @@ impl Endpoint { } /// Construct an endpoint with arbitrary configuration and socket + #[cfg(not(wasm_browser))] pub fn new( config: EndpointConfig, server_config: Option, @@ -235,6 +235,7 @@ impl Endpoint { /// Switch to a new UDP socket /// /// See [`Endpoint::rebind_abstract()`] for details. + #[cfg(not(wasm_browser))] pub fn rebind(&self, socket: std::net::UdpSocket) -> io::Result<()> { self.rebind_abstract(self.runtime.wrap_udp_socket(socket)?) } diff --git a/quinn/src/lib.rs b/quinn/src/lib.rs index 4f6e6ea738..fe24aa70f6 100644 --- a/quinn/src/lib.rs +++ b/quinn/src/lib.rs @@ -41,7 +41,7 @@ #![warn(unreachable_pub)] #![warn(clippy::use_self)] -use std::{sync::Arc, time::Duration}; +use std::sync::Arc; macro_rules! ready { ($e:expr $(,)?) => { @@ -61,6 +61,11 @@ mod runtime; mod send_stream; mod work_limiter; +#[cfg(not(wasm_browser))] +pub(crate) use std::time::{Duration, Instant}; +#[cfg(wasm_browser)] +pub(crate) use web_time::{Duration, Instant}; + pub use proto::{ congestion, crypto, AckFrequencyConfig, ApplicationClose, Chunk, ClientConfig, ClosedStream, ConfigError, ConnectError, ConnectionClose, ConnectionError, ConnectionId, diff --git a/quinn/src/mutex.rs b/quinn/src/mutex.rs index a2a1fe2773..913566922c 100644 --- a/quinn/src/mutex.rs +++ b/quinn/src/mutex.rs @@ -6,10 +6,8 @@ use std::{ #[cfg(feature = "lock_tracking")] mod tracking { use super::*; - use std::{ - collections::VecDeque, - time::{Duration, Instant}, - }; + use crate::{Duration, Instant}; + use std::collections::VecDeque; use tracing::warn; #[derive(Debug)] diff --git a/quinn/src/runtime.rs b/quinn/src/runtime.rs index 40a363297d..9471c1834e 100644 --- a/quinn/src/runtime.rs +++ b/quinn/src/runtime.rs @@ -6,11 +6,12 @@ use std::{ pin::Pin, sync::Arc, task::{Context, Poll}, - time::Instant, }; use udp::{RecvMeta, Transmit}; +use crate::Instant; + /// Abstracts I/O and timer operations for runtime independence pub trait Runtime: Send + Sync + Debug + 'static { /// Construct a timer that will expire at `i` @@ -18,6 +19,7 @@ pub trait Runtime: Send + Sync + Debug + 'static { /// Drive `future` to completion in the background fn spawn(&self, future: Pin + Send>>); /// Convert `t` into the socket type used by this runtime + #[cfg(not(wasm_browser))] fn wrap_udp_socket(&self, t: std::net::UdpSocket) -> io::Result>; /// Look up the current time /// diff --git a/quinn/src/tests.rs b/quinn/src/tests.rs index fb4fc1bd2f..1b628b8150 100755 --- a/quinn/src/tests.rs +++ b/quinn/src/tests.rs @@ -14,6 +14,7 @@ use std::{ }; use crate::runtime::TokioRuntime; +use crate::{Duration, Instant}; use bytes::Bytes; use proto::{crypto::rustls::QuicClientConfig, RandomConnectionIdGenerator}; use rand::{rngs::StdRng, RngCore, SeedableRng}; @@ -21,10 +22,7 @@ use rustls::{ pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}, RootCertStore, }; -use tokio::{ - runtime::{Builder, Runtime}, - time::{Duration, Instant}, -}; +use tokio::runtime::{Builder, Runtime}; use tracing::{error_span, info}; use tracing_futures::Instrument as _; use tracing_subscriber::EnvFilter; diff --git a/quinn/src/work_limiter.rs b/quinn/src/work_limiter.rs index efffd2cca0..c3c3d3551d 100644 --- a/quinn/src/work_limiter.rs +++ b/quinn/src/work_limiter.rs @@ -1,4 +1,4 @@ -use std::time::{Duration, Instant}; +use crate::{Duration, Instant}; /// Limits the amount of time spent on a certain type of work in a cycle /// From b1a9b52aa993da40164a72a1bdfcaa06a15045b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Tue, 12 Nov 2024 13:59:02 +0100 Subject: [PATCH 2/3] Remove redundant step names --- .github/workflows/rust.yml | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 150a147e73..1d34529654 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -144,25 +144,14 @@ jobs: name: test wasm32-unknown-unknown runs-on: ubuntu-latest steps: - - name: Checkout sources - uses: actions/checkout@v4 - - - name: Install stable toolchain - uses: dtolnay/rust-toolchain@stable - - - name: Add wasm target - run: rustup target add wasm32-unknown-unknown - - - name: Install nodejs v20 - uses: actions/setup-node@v4 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: rustup target add wasm32-unknown-unknown + - uses: actions/setup-node@v4 with: node-version: 20 - - - name: Setup `wasm-tools` - uses: bytecodealliance/actions/wasm-tools/setup@v1 - - - name: Install cargo binstall - uses: cargo-bins/cargo-binstall@main + - uses: bytecodealliance/actions/wasm-tools/setup@v1 + - uses: cargo-bins/cargo-binstall@main - run: cargo test -p quinn-proto --target wasm32-unknown-unknown --no-run - run: cargo check -p quinn-udp --target wasm32-unknown-unknown --no-default-features --features=tracing,log From a9a01285514b499f177a453d936b589a2e53579c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Tue, 12 Nov 2024 13:58:26 +0100 Subject: [PATCH 3/3] Prefer `sleep` instead of `sleep_until` with `Instant::now` --- quinn/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quinn/src/tests.rs b/quinn/src/tests.rs index 1b628b8150..571f558d2c 100755 --- a/quinn/src/tests.rs +++ b/quinn/src/tests.rs @@ -158,7 +158,7 @@ fn read_after_close() { .unwrap() .await .expect("connect"); - tokio::time::sleep_until(Instant::now() + Duration::from_millis(100)).await; + tokio::time::sleep(Duration::from_millis(100)).await; let mut stream = new_conn.accept_uni().await.expect("incoming streams"); let msg = stream.read_to_end(usize::MAX).await.expect("read_to_end"); assert_eq!(msg, MSG);