diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1362609..30e5735 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -49,7 +49,9 @@ jobs: role-session-name: github-ci - name: check - run: cargo check --workspace --all --bins --examples + run: | + cargo check -p wstd -p wstd-axum --target wasm32-wasip2 --all-targets + cargo check -p test-programs - name: wstd tests run: cargo test -p wstd -p wstd-axum --target wasm32-wasip2 -- --nocapture @@ -73,10 +75,12 @@ jobs: run: cargo fmt --all -- --check - name: Docs - run: cargo doc + run: cargo doc --target wasm32-wasip2 - name: Clippy - run: cargo clippy --all + run: | + cargo clippy -p wstd -p wstd-axum --target wasm32-wasip2 --all-targets + cargo clippy -p test-programs verify-publish: name: Verify publish diff --git a/Cargo.toml b/Cargo.toml index 130fe56..9de9890 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ json = ["dep:serde", "dep:serde_json"] anyhow.workspace = true async-task.workspace = true bytes.workspace = true +cfg-if.workspace = true futures-lite.workspace = true http-body-util.workspace = true http-body.workspace = true @@ -71,6 +72,7 @@ anyhow = "1" async-task = "4.7" axum = { version = "0.8.6", default-features = false } bytes = "1.10.1" +cfg-if = "1" cargo_metadata = "0.22" clap = { version = "4.5.26", features = ["derive"] } futures-core = "0.3.19" diff --git a/ci/publish.rs b/ci/publish.rs index 66867ce..bf9ba79 100644 --- a/ci/publish.rs +++ b/ci/publish.rs @@ -365,6 +365,10 @@ fn verify(crates: &[Crate]) { .arg("--manifest-path") .arg(&krate.manifest) .env("CARGO_TARGET_DIR", "./target"); + // wstd and wstd-axum only compile for wasm32-wasip2 + if krate.name == "wstd" || krate.name == "wstd-axum" { + cmd.arg("--target").arg("wasm32-wasip2"); + } let status = cmd.status().unwrap(); assert!(status.success(), "failed to verify {:?}", &krate.manifest); let tar = Command::new("tar") diff --git a/src/http.rs b/src/http.rs new file mode 100644 index 0000000..12309be --- /dev/null +++ b/src/http.rs @@ -0,0 +1,69 @@ +//! HTTP networking support + +pub use http::status::StatusCode; +pub use http::uri::{Authority, PathAndQuery, Uri}; + +pub use crate::sys::http::client::Client; +pub use crate::sys::http::fields::{HeaderMap, HeaderName, HeaderValue}; +pub use crate::sys::http::method::Method; +pub use crate::sys::http::scheme::{InvalidUri, Scheme}; +#[doc(inline)] +pub use body::{Body, util::BodyExt}; +pub use error::{Error, ErrorCode, Result}; +pub use request::Request; +pub use response::Response; + +pub mod body { + //! HTTP body types. + pub use crate::sys::http::body::*; +} + +pub mod error { + //! The http portion of wstd uses `anyhow::Error` as its `Error` type. + //! + //! There are various concrete error types + + pub use crate::http::body::InvalidContentLength; + pub use crate::sys::http::{ErrorCode, HeaderError}; + pub use anyhow::Context; + pub use http::header::{InvalidHeaderName, InvalidHeaderValue}; + pub use http::method::InvalidMethod; + + pub type Error = anyhow::Error; + /// The `http` result type. + pub type Result = std::result::Result; +} + +pub mod request { + //! HTTP request types. + pub use crate::sys::http::request::*; +} + +pub mod response { + //! HTTP response types. + pub use crate::sys::http::response::*; +} + +pub mod server { + //! HTTP servers + //! + //! The WASI HTTP server uses the [typed main] idiom, with a `main` function + //! that takes a [`Request`] and succeeds with a [`Response`], using the + //! [`http_server`] macro: + //! + //! ```no_run + //! use wstd::http::{Request, Response, Body, Error}; + //! #[wstd::http_server] + //! async fn main(_request: Request) -> Result, Error> { + //! Ok(Response::new("Hello!\n".into())) + //! } + //! ``` + //! + //! [typed main]: https://sunfishcode.github.io/typed-main-wasi-presentation/chapter_1.html + //! [`Request`]: crate::http::Request + //! [`Responder`]: crate::http::server::Responder + //! [`Response`]: crate::http::Response + //! [`http_server`]: crate::http_server + + pub use crate::sys::http::server::*; +} diff --git a/src/http/error.rs b/src/http/error.rs deleted file mode 100644 index a4f22b0..0000000 --- a/src/http/error.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! The http portion of wstd uses `anyhow::Error` as its `Error` type. -//! -//! There are various concrete error types - -pub use crate::http::body::InvalidContentLength; -pub use anyhow::Context; -pub use http::header::{InvalidHeaderName, InvalidHeaderValue}; -pub use http::method::InvalidMethod; -pub use wasip2::http::types::{ErrorCode, HeaderError}; - -pub type Error = anyhow::Error; -/// The `http` result type. -pub type Result = std::result::Result; diff --git a/src/http/mod.rs b/src/http/mod.rs deleted file mode 100644 index 39f0a40..0000000 --- a/src/http/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! HTTP networking support -//! -pub use http::status::StatusCode; -pub use http::uri::{Authority, PathAndQuery, Uri}; - -#[doc(inline)] -pub use body::{Body, util::BodyExt}; -pub use client::Client; -pub use error::{Error, ErrorCode, Result}; -pub use fields::{HeaderMap, HeaderName, HeaderValue}; -pub use method::Method; -pub use request::Request; -pub use response::Response; -pub use scheme::{InvalidUri, Scheme}; - -pub mod body; - -mod client; -pub mod error; -mod fields; -mod method; -pub mod request; -pub mod response; -mod scheme; -pub mod server; diff --git a/src/io/mod.rs b/src/io/mod.rs index 0f34b1b..1f360ac 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -5,18 +5,15 @@ mod cursor; mod empty; mod read; mod seek; -mod stdio; -mod streams; mod write; pub use crate::runtime::AsyncPollable; +pub use crate::sys::io::*; pub use copy::*; pub use cursor::*; pub use empty::*; pub use read::*; pub use seek::*; -pub use stdio::*; -pub use streams::*; pub use write::*; /// The error type for I/O operations. diff --git a/src/lib.rs b/src/lib.rs index ebc673d..e4edc55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,6 +55,9 @@ //! These are unique capabilities provided by WASI 0.2, and because this library //! is specific to that are exposed from here. +#[allow(unreachable_pub)] +mod sys; + pub mod future; #[macro_use] pub mod http; diff --git a/src/net.rs b/src/net.rs new file mode 100644 index 0000000..37b9371 --- /dev/null +++ b/src/net.rs @@ -0,0 +1,206 @@ +//! Async network abstractions. +//! +//! The types here are the crate's public network surface. They are thin +//! facades that delegate to the selected backend's implementation under +//! [`crate::sys::net`]; the portable address-resolution loop in +//! [`TcpStream::connect`] is the one piece written once here rather than per +//! backend. A second backend only has to supply the `sys::net` primitives. + +use crate::io::{self, AsyncInputStream, AsyncOutputStream, AsyncRead, AsyncWrite}; +use crate::iter::AsyncIterator; +use std::io::ErrorKind; +use std::net::{SocketAddr, ToSocketAddrs}; + +/// A TCP stream between a local and a remote socket. +pub struct TcpStream { + inner: crate::sys::net::TcpStream, +} + +impl TcpStream { + /// Opens a TCP connection to a remote host. + /// + /// `addr` is an address of the remote host. Anything which implements the + /// [`ToSocketAddrs`] trait can be supplied as the address. If `addr` + /// yields multiple addresses, connect will be attempted with each of the + /// addresses until a connection is successful. If none of the addresses + /// result in a successful connection, the error returned from the last + /// connection attempt (the last address) is returned. + pub async fn connect(addr: impl ToSocketAddrs) -> io::Result { + let addrs = addr.to_socket_addrs()?; + let mut last_err = None; + for addr in addrs { + match TcpStream::connect_addr(addr).await { + Ok(stream) => return Ok(stream), + Err(e) => last_err = Some(e), + } + } + + Err(last_err.unwrap_or_else(|| { + io::Error::new(ErrorKind::InvalidInput, "could not resolve to any address") + })) + } + + /// Establishes a connection to the specified `addr`. + pub async fn connect_addr(addr: SocketAddr) -> io::Result { + Ok(Self { + inner: crate::sys::net::TcpStream::connect_addr(addr).await?, + }) + } + + /// Returns the socket address of the remote peer of this TCP connection. + pub fn peer_addr(&self) -> io::Result { + self.inner.peer_addr() + } + + /// Splits this stream into a read half and a write half, which can be used + /// to read and write concurrently. + pub fn split(&self) -> (ReadHalf<'_>, WriteHalf<'_>) { + let (read, write) = self.inner.split(); + (ReadHalf(read), WriteHalf(write)) + } +} + +impl AsyncRead for TcpStream { + async fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.inner.read(buf).await + } + + fn as_async_input_stream(&self) -> Option<&AsyncInputStream> { + self.inner.as_async_input_stream() + } +} + +impl AsyncRead for &TcpStream { + async fn read(&mut self, buf: &mut [u8]) -> io::Result { + let mut inner = &self.inner; + inner.read(buf).await + } + + fn as_async_input_stream(&self) -> Option<&AsyncInputStream> { + self.inner.as_async_input_stream() + } +} + +impl AsyncWrite for TcpStream { + async fn write(&mut self, buf: &[u8]) -> io::Result { + self.inner.write(buf).await + } + + async fn flush(&mut self) -> io::Result<()> { + self.inner.flush().await + } + + fn as_async_output_stream(&self) -> Option<&AsyncOutputStream> { + self.inner.as_async_output_stream() + } +} + +impl AsyncWrite for &TcpStream { + async fn write(&mut self, buf: &[u8]) -> io::Result { + let mut inner = &self.inner; + inner.write(buf).await + } + + async fn flush(&mut self) -> io::Result<()> { + let mut inner = &self.inner; + inner.flush().await + } + + fn as_async_output_stream(&self) -> Option<&AsyncOutputStream> { + self.inner.as_async_output_stream() + } +} + +impl Drop for TcpStream { + fn drop(&mut self) { + // The socket shutdown runs when the inner backend handle is dropped. + } +} + +/// The read half of a [`TcpStream`], created by [`TcpStream::split`]. +pub struct ReadHalf<'a>(crate::sys::net::ReadHalf<'a>); + +impl<'a> AsyncRead for ReadHalf<'a> { + async fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.0.read(buf).await + } + + fn as_async_input_stream(&self) -> Option<&AsyncInputStream> { + self.0.as_async_input_stream() + } +} + +impl<'a> Drop for ReadHalf<'a> { + fn drop(&mut self) { + // The receive-side shutdown runs when the inner backend handle is dropped. + } +} + +/// The write half of a [`TcpStream`], created by [`TcpStream::split`]. +pub struct WriteHalf<'a>(crate::sys::net::WriteHalf<'a>); + +impl<'a> AsyncWrite for WriteHalf<'a> { + async fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.write(buf).await + } + + async fn flush(&mut self) -> io::Result<()> { + self.0.flush().await + } + + fn as_async_output_stream(&self) -> Option<&AsyncOutputStream> { + self.0.as_async_output_stream() + } +} + +impl<'a> Drop for WriteHalf<'a> { + fn drop(&mut self) { + // The send-side shutdown runs when the inner backend handle is dropped. + } +} + +/// A TCP socket server, listening for connections. +#[derive(Debug)] +pub struct TcpListener { + inner: crate::sys::net::TcpListener, +} + +impl TcpListener { + /// Creates a new TcpListener which will be bound to the specified address. + /// + /// The returned listener is ready for accepting connections. + pub async fn bind(addr: &str) -> io::Result { + Ok(Self { + inner: crate::sys::net::TcpListener::bind(addr).await?, + }) + } + + /// Returns the local socket address of this listener. + pub fn local_addr(&self) -> io::Result { + self.inner.local_addr() + } + + /// Returns an iterator over the connections being received on this listener. + pub fn incoming(&self) -> Incoming<'_> { + Incoming { + inner: self.inner.incoming(), + } + } +} + +/// An iterator that infinitely accepts connections on a [`TcpListener`]. +#[derive(Debug)] +pub struct Incoming<'a> { + inner: crate::sys::net::Incoming<'a>, +} + +impl<'a> AsyncIterator for Incoming<'a> { + type Item = io::Result; + + async fn next(&mut self) -> Option { + self.inner + .next() + .await + .map(|result| result.map(|inner| TcpStream { inner })) + } +} diff --git a/src/rand.rs b/src/rand.rs new file mode 100644 index 0000000..cdad313 --- /dev/null +++ b/src/rand.rs @@ -0,0 +1,19 @@ +//! Random number generation. + +/// Fill the slice with cryptographically secure random bytes. +pub fn get_random_bytes(buf: &mut [u8]) { + if buf.is_empty() { + return; + } + let output = crate::sys::rand::random_bytes(buf.len() as u64); + buf.copy_from_slice(&output); +} + +/// Fill the slice with insecure random bytes. +pub fn get_insecure_random_bytes(buf: &mut [u8]) { + if buf.is_empty() { + return; + } + let output = crate::sys::rand::insecure_random_bytes(buf.len() as u64); + buf.copy_from_slice(&output); +} diff --git a/src/rand/mod.rs b/src/rand/mod.rs deleted file mode 100644 index 8474b80..0000000 --- a/src/rand/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Random number generation. - -use wasip2::random; - -/// Fill the slice with cryptographically secure random bytes. -pub fn get_random_bytes(buf: &mut [u8]) { - match buf.len() { - 0 => {} - _ => { - let output = random::random::get_random_bytes(buf.len() as u64); - buf.copy_from_slice(&output[..]); - } - } -} - -/// Fill the slice with insecure random bytes. -pub fn get_insecure_random_bytes(buf: &mut [u8]) { - match buf.len() { - 0 => {} - _ => { - let output = random::insecure::get_insecure_random_bytes(buf.len() as u64); - buf.copy_from_slice(&output[..]); - } - } -} diff --git a/src/runtime.rs b/src/runtime.rs new file mode 100644 index 0000000..4aeb1e4 --- /dev/null +++ b/src/runtime.rs @@ -0,0 +1,23 @@ +//! Async event loop support. +//! +//! The way to use this is to call [`block_on()`]. Inside the future, [`Reactor::current`] +//! will give an instance of the [`Reactor`] running the event loop, which can be +//! to [`AsyncPollable::wait_for`] instances of +//! [`wasip2::Pollable`](https://docs.rs/wasi/latest/wasi/io/poll/struct.Pollable.html). +//! This will automatically wait for the futures to resolve, and call the +//! necessary wakers to work. + +pub use ::async_task::Task; + +pub use crate::sys::runtime::{AsyncPollable, Reactor, WaitFor, block_on}; + +/// Spawn a `Future` as a `Task` on the current `Reactor`. +/// +/// Panics if called from outside `block_on`. +pub fn spawn(fut: F) -> Task +where + F: std::future::Future + 'static, + T: 'static, +{ + Reactor::current().spawn(fut) +} diff --git a/src/sys/mod.rs b/src/sys/mod.rs new file mode 100644 index 0000000..cbb4385 --- /dev/null +++ b/src/sys/mod.rs @@ -0,0 +1,40 @@ +//! Platform-specific backends. +//! +//! Each supported target provides an implementation under `src/sys/`, selected +//! here by a single `cfg-if`. The crate-root modules (`crate::time`, +//! `crate::io`, ...) are target-agnostic facades, free of `#[cfg]`, written +//! against the `crate::sys::*` items the selected backend provides. There is no +//! shared `trait`: backends are duck-typed in the `std::sys` style, and the +//! `const _` assertions below check the shapes the facades depend on. +//! +//! Backend modules: `time`, `io`, `net`, `http`, `rand`, `runtime`. The reified +//! pollable types (`AsyncPollable`, `WaitFor`) are p2-only and intentionally +//! left out of the common contract; once a second backend lands they become a +//! localized escape hatch rather than facade `#[cfg]`s. + +cfg_if::cfg_if! { + if #[cfg(all(target_os = "wasi", target_env = "p2"))] { + mod p2; + use p2 as backend; + } else { + compile_error!("unsupported target: wstd only compiles on `wasm32-wasip2`"); + } +} + +pub use backend::*; + +// Check the selected backend provides the shapes the facades rely on, so drift +// fails here instead of deep inside a facade. +const _: fn() = || { + fn assert_async_read() {} + fn assert_async_write() {} + fn assert_sleep_future>() {} + + assert_async_read::(); + assert_async_write::(); + assert_sleep_future::(); + + let _: fn() -> crate::sys::time::MonotonicInstant = crate::sys::time::now; + let _: fn(crate::sys::time::MonotonicInstant) -> crate::sys::time::Sleep = + crate::sys::time::sleep_until; +}; diff --git a/src/http/body.rs b/src/sys/p2/http/body.rs similarity index 99% rename from src/http/body.rs rename to src/sys/p2/http/body.rs index 95e8a2e..d0381d2 100644 --- a/src/http/body.rs +++ b/src/sys/p2/http/body.rs @@ -1,8 +1,5 @@ -use crate::http::{ - Error, HeaderMap, - error::Context as _, - fields::{header_map_from_wasi, header_map_to_wasi}, -}; +use super::fields::{header_map_from_wasi, header_map_to_wasi}; +use crate::http::{Error, HeaderMap, error::Context as _}; use crate::io::{AsyncInputStream, AsyncOutputStream}; use crate::runtime::{AsyncPollable, Reactor, WaitFor}; diff --git a/src/http/client.rs b/src/sys/p2/http/client.rs similarity index 98% rename from src/http/client.rs rename to src/sys/p2/http/client.rs index 3676fa8..6f7c47b 100644 --- a/src/http/client.rs +++ b/src/sys/p2/http/client.rs @@ -1,6 +1,6 @@ -use super::{Body, Error, Request, Response}; use crate::http::request::try_into_outgoing; use crate::http::response::try_from_incoming; +use crate::http::{Body, Error, Request, Response}; use crate::io::AsyncPollable; use crate::time::Duration; use wasip2::http::types::RequestOptions as WasiRequestOptions; diff --git a/src/http/fields.rs b/src/sys/p2/http/fields.rs similarity index 95% rename from src/http/fields.rs rename to src/sys/p2/http/fields.rs index de6df16..76162ef 100644 --- a/src/http/fields.rs +++ b/src/sys/p2/http/fields.rs @@ -1,6 +1,6 @@ pub use http::header::{HeaderMap, HeaderName, HeaderValue}; -use super::{Error, error::Context}; +use crate::http::{Error, error::Context}; use wasip2::http::types::Fields; pub(crate) fn header_map_from_wasi(wasi_fields: Fields) -> Result { diff --git a/src/http/method.rs b/src/sys/p2/http/method.rs similarity index 100% rename from src/http/method.rs rename to src/sys/p2/http/method.rs diff --git a/src/sys/p2/http/mod.rs b/src/sys/p2/http/mod.rs new file mode 100644 index 0000000..3ae5a9f --- /dev/null +++ b/src/sys/p2/http/mod.rs @@ -0,0 +1,12 @@ +//! HTTP networking support (wasip2 backend). + +pub mod body; +pub(crate) mod client; +pub(crate) mod fields; +pub(crate) mod method; +pub mod request; +pub mod response; +pub(crate) mod scheme; +pub mod server; + +pub use wasip2::http::types::{ErrorCode, HeaderError}; diff --git a/src/http/request.rs b/src/sys/p2/http/request.rs similarity index 95% rename from src/http/request.rs rename to src/sys/p2/http/request.rs index 6694d03..fd65cff 100644 --- a/src/http/request.rs +++ b/src/sys/p2/http/request.rs @@ -1,10 +1,10 @@ -use super::{ +use super::fields::{header_map_from_wasi, header_map_to_wasi}; +use super::method::{from_wasi_method, to_wasi_method}; +use super::scheme::{from_wasi_scheme, to_wasi_scheme}; +use crate::http::{ Authority, HeaderMap, PathAndQuery, Uri, body::{Body, BodyHint}, error::{Context, Error, ErrorCode}, - fields::{header_map_from_wasi, header_map_to_wasi}, - method::{from_wasi_method, to_wasi_method}, - scheme::{from_wasi_scheme, to_wasi_scheme}, }; use wasip2::http::outgoing_handler::OutgoingRequest; use wasip2::http::types::IncomingRequest; diff --git a/src/http/response.rs b/src/sys/p2/http/response.rs similarity index 96% rename from src/http/response.rs rename to src/sys/p2/http/response.rs index 2ab8d87..44f264b 100644 --- a/src/http/response.rs +++ b/src/sys/p2/http/response.rs @@ -1,9 +1,10 @@ use http::StatusCode; use wasip2::http::types::IncomingResponse; +use super::fields::header_map_from_wasi; +use crate::http::HeaderMap; use crate::http::body::{Body, BodyHint}; use crate::http::error::Error; -use crate::http::fields::{HeaderMap, header_map_from_wasi}; pub use http::response::{Builder, Response}; diff --git a/src/http/scheme.rs b/src/sys/p2/http/scheme.rs similarity index 100% rename from src/http/scheme.rs rename to src/sys/p2/http/scheme.rs diff --git a/src/http/server.rs b/src/sys/p2/http/server.rs similarity index 74% rename from src/http/server.rs rename to src/sys/p2/http/server.rs index 9fb6ff4..709197f 100644 --- a/src/http/server.rs +++ b/src/sys/p2/http/server.rs @@ -1,24 +1,7 @@ -//! HTTP servers -//! -//! The WASI HTTP server uses the [typed main] idiom, with a `main` function -//! that takes a [`Request`] and succeeds with a [`Response`], using the -//! [`http_server`] macro: -//! -//! ```no_run -//! use wstd::http::{Request, Response, Body, Error}; -//! #[wstd::http_server] -//! async fn main(_request: Request) -> Result, Error> { -//! Ok(Response::new("Hello!\n".into())) -//! } -//! ``` -//! -//! [typed main]: https://sunfishcode.github.io/typed-main-wasi-presentation/chapter_1.html -//! [`Request`]: crate::http::Request -//! [`Responder`]: crate::http::server::Responder -//! [`Response`]: crate::http::Response -//! [`http_server`]: crate::http_server +//! HTTP servers (wasip2 backend). -use super::{Body, Error, Response, error::ErrorCode, fields::header_map_to_wasi}; +use super::fields::header_map_to_wasi; +use crate::http::{Body, Error, Response, error::ErrorCode}; use http::header::CONTENT_LENGTH; use wasip2::exports::http::incoming_handler::ResponseOutparam; use wasip2::http::types::OutgoingResponse; diff --git a/src/sys/p2/io/mod.rs b/src/sys/p2/io/mod.rs new file mode 100644 index 0000000..9323962 --- /dev/null +++ b/src/sys/p2/io/mod.rs @@ -0,0 +1,5 @@ +mod stdio; +mod streams; + +pub use stdio::*; +pub use streams::*; diff --git a/src/io/stdio.rs b/src/sys/p2/io/stdio.rs similarity index 98% rename from src/io/stdio.rs rename to src/sys/p2/io/stdio.rs index b2ac153..fa183a3 100644 --- a/src/io/stdio.rs +++ b/src/sys/p2/io/stdio.rs @@ -1,4 +1,4 @@ -use super::{AsyncInputStream, AsyncOutputStream, AsyncRead, AsyncWrite, Result}; +use crate::io::{AsyncInputStream, AsyncOutputStream, AsyncRead, AsyncWrite, Result}; use std::cell::LazyCell; use wasip2::cli::terminal_input::TerminalInput; use wasip2::cli::terminal_output::TerminalOutput; diff --git a/src/io/streams.rs b/src/sys/p2/io/streams.rs similarity index 99% rename from src/io/streams.rs rename to src/sys/p2/io/streams.rs index 3676d21..5f95ca5 100644 --- a/src/io/streams.rs +++ b/src/sys/p2/io/streams.rs @@ -1,5 +1,5 @@ -use super::{AsyncPollable, AsyncRead, AsyncWrite}; -use crate::runtime::WaitFor; +use crate::io::{AsyncRead, AsyncWrite}; +use crate::runtime::{AsyncPollable, WaitFor}; use std::future::{Future, poll_fn}; use std::pin::Pin; use std::sync::{Mutex, OnceLock}; diff --git a/src/sys/p2/mod.rs b/src/sys/p2/mod.rs new file mode 100644 index 0000000..f7f323a --- /dev/null +++ b/src/sys/p2/mod.rs @@ -0,0 +1,8 @@ +//! The wasip2 (`wasm32-wasip2`) backend. + +pub mod http; +pub mod io; +pub mod net; +pub mod rand; +pub mod runtime; +pub mod time; diff --git a/src/net/mod.rs b/src/sys/p2/net/mod.rs similarity index 100% rename from src/net/mod.rs rename to src/sys/p2/net/mod.rs diff --git a/src/net/tcp_listener.rs b/src/sys/p2/net/tcp_listener.rs similarity index 100% rename from src/net/tcp_listener.rs rename to src/sys/p2/net/tcp_listener.rs diff --git a/src/net/tcp_stream.rs b/src/sys/p2/net/tcp_stream.rs similarity index 81% rename from src/net/tcp_stream.rs rename to src/sys/p2/net/tcp_stream.rs index af3674a..5ce66d8 100644 --- a/src/net/tcp_stream.rs +++ b/src/sys/p2/net/tcp_stream.rs @@ -1,5 +1,4 @@ -use std::io::ErrorKind; -use std::net::{SocketAddr, ToSocketAddrs}; +use std::net::SocketAddr; use wasip2::sockets::instance_network::instance_network; use wasip2::sockets::network::Ipv4SocketAddress; use wasip2::sockets::tcp::{IpAddressFamily, IpSocketAddress}; @@ -29,29 +28,6 @@ impl TcpStream { } } - /// Opens a TCP connection to a remote host. - /// - /// `addr` is an address of the remote host. Anything which implements the - /// [`ToSocketAddrs`] trait can be supplied as the address. If `addr` - /// yields multiple addresses, connect will be attempted with each of the - /// addresses until a connection is successful. If none of the addresses - /// result in a successful connection, the error returned from the last - /// connection attempt (the last address) is returned. - pub async fn connect(addr: impl ToSocketAddrs) -> io::Result { - let addrs = addr.to_socket_addrs()?; - let mut last_err = None; - for addr in addrs { - match TcpStream::connect_addr(addr).await { - Ok(stream) => return Ok(stream), - Err(e) => last_err = Some(e), - } - } - - Err(last_err.unwrap_or_else(|| { - io::Error::new(ErrorKind::InvalidInput, "could not resolve to any address") - })) - } - /// Establishes a connection to the specified `addr`. pub async fn connect_addr(addr: SocketAddr) -> io::Result { let family = match addr { diff --git a/src/sys/p2/rand/mod.rs b/src/sys/p2/rand/mod.rs new file mode 100644 index 0000000..c572382 --- /dev/null +++ b/src/sys/p2/rand/mod.rs @@ -0,0 +1,16 @@ +//! Random number generation primitives for the wasip2 backend. +//! +//! This is the platform half of the [`crate::rand`] facade: it only reaches the +//! host RNG. The length guard and slice copy live in the facade. + +use wasip2::random; + +/// Return `len` cryptographically secure random bytes. +pub fn random_bytes(len: u64) -> Vec { + random::random::get_random_bytes(len) +} + +/// Return `len` insecure, non-cryptographic random bytes. +pub fn insecure_random_bytes(len: u64) -> Vec { + random::insecure::get_insecure_random_bytes(len) +} diff --git a/src/runtime/block_on.rs b/src/sys/p2/runtime/block_on.rs similarity index 100% rename from src/runtime/block_on.rs rename to src/sys/p2/runtime/block_on.rs diff --git a/src/runtime/mod.rs b/src/sys/p2/runtime/mod.rs similarity index 72% rename from src/runtime/mod.rs rename to src/sys/p2/runtime/mod.rs index 24b9fc2..009eb1f 100644 --- a/src/runtime/mod.rs +++ b/src/sys/p2/runtime/mod.rs @@ -8,12 +8,11 @@ //! necessary wakers to work. #![deny(missing_debug_implementations, nonstandard_style)] -#![warn(missing_docs, unreachable_pub)] +#![warn(missing_docs)] mod block_on; mod reactor; -pub use ::async_task::Task; pub use block_on::block_on; pub use reactor::{AsyncPollable, Reactor, WaitFor}; use std::cell::RefCell; @@ -23,14 +22,3 @@ use std::cell::RefCell; std::thread_local! { pub(crate) static REACTOR: RefCell> = const { RefCell::new(None) }; } - -/// Spawn a `Future` as a `Task` on the current `Reactor`. -/// -/// Panics if called from outside `block_on`. -pub fn spawn(fut: F) -> Task -where - F: std::future::Future + 'static, - T: 'static, -{ - Reactor::current().spawn(fut) -} diff --git a/src/runtime/reactor.rs b/src/sys/p2/runtime/reactor.rs similarity index 100% rename from src/runtime/reactor.rs rename to src/sys/p2/runtime/reactor.rs diff --git a/src/sys/p2/time/mod.rs b/src/sys/p2/time/mod.rs new file mode 100644 index 0000000..92b8983 --- /dev/null +++ b/src/sys/p2/time/mod.rs @@ -0,0 +1,78 @@ +//! Monotonic and system clocks for the wasip2 backend. +//! +//! This is the platform half of the [`crate::time`] facade. The facade owns the +//! portable `Duration`/`Instant`/`Timer` types and all of their arithmetic; +//! this module provides only the primitives that genuinely depend on the WASI +//! 0.2 clocks. See [`crate::sys`] for the full backend contract. + +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; +use wasip2::clocks::{monotonic_clock, wall_clock}; + +use crate::runtime::{Reactor, WaitFor}; + +/// A measurement of the monotonic clock, in nanoseconds. +/// +/// The facade's `Instant` wraps this. Keeping it a plain integer lets the +/// facade own all time arithmetic without coupling to the backend. +pub type MonotonicInstant = monotonic_clock::Instant; + +/// A span of monotonic-clock time, in nanoseconds. +pub type MonotonicDuration = monotonic_clock::Duration; + +/// Return the current monotonic-clock instant. +pub fn now() -> MonotonicInstant { + monotonic_clock::now() +} + +/// A measurement of the system clock, useful for talking to external entities +/// like the file system or other processes. May be converted losslessly to a +/// more useful `std::time::SystemTime` to provide more methods. +#[derive(Debug, Clone, Copy)] +#[allow(dead_code)] +pub struct SystemTime(wall_clock::Datetime); + +impl SystemTime { + pub fn now() -> Self { + Self(wall_clock::now()) + } +} + +impl From for std::time::SystemTime { + fn from(st: SystemTime) -> Self { + std::time::SystemTime::UNIX_EPOCH + + std::time::Duration::from_secs(st.0.seconds) + + std::time::Duration::from_nanos(st.0.nanoseconds.into()) + } +} + +/// A future that resolves once the monotonic clock reaches a deadline. +/// +/// Created by [`sleep_until`]. This is the backend `Sleep` type named by the +/// facade's `Timer`/`Wait`; on p2 it is a thin wrapper over a reactor-scheduled +/// `monotonic-clock` pollable. +#[must_use = "futures do nothing unless polled or .awaited"] +#[derive(Debug)] +pub struct Sleep { + wait_for: WaitFor, +} + +impl Future for Sleep { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + Pin::new(&mut self.wait_for).poll(cx) + } +} + +/// Create a [`Sleep`] future that resolves when the monotonic clock reaches +/// `deadline`. +/// +/// Must be called from within [`crate::runtime::block_on`]. +pub fn sleep_until(deadline: MonotonicInstant) -> Sleep { + let pollable = Reactor::current().schedule(monotonic_clock::subscribe_instant(deadline)); + Sleep { + wait_for: pollable.wait_for(), + } +} diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 0000000..a60b4da --- /dev/null +++ b/src/time.rs @@ -0,0 +1,439 @@ +//! Async time interfaces. +//! +//! This module is a target-agnostic *facade*: it owns the portable +//! `Duration`/`Instant`/`Timer`/`Interval` types and all of their arithmetic, +//! and is written once against the small clock contract each backend provides +//! under `crate::sys::time` (see [`crate::sys`]). The only backend-specific +//! type re-exported here is [`SystemTime`]. + +use pin_project_lite::pin_project; +use std::future::{Future, IntoFuture}; +use std::ops::{Add, AddAssign, Sub, SubAssign}; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use crate::iter::AsyncIterator; + +pub use crate::sys::time::SystemTime; + +pub(crate) mod utils { + use std::io; + + pub(crate) fn timeout_err(msg: &'static str) -> io::Error { + io::Error::new(io::ErrorKind::TimedOut, msg) + } +} + +/// A Duration type to represent a span of time, typically used for system +/// timeouts. +/// +/// This type wraps `std::time::Duration` so we can implement traits on it +/// without coherence issues, just like if we were implementing this in the +/// stdlib. +#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Clone, Copy)] +pub struct Duration(pub(crate) crate::sys::time::MonotonicDuration); +impl Duration { + /// Creates a new `Duration` from the specified number of whole seconds and + /// additional nanoseconds. + #[must_use] + #[inline] + pub fn new(secs: u64, nanos: u32) -> Duration { + std::time::Duration::new(secs, nanos).into() + } + + /// Creates a new `Duration` from the specified number of whole seconds. + #[must_use] + #[inline] + pub fn from_secs(secs: u64) -> Duration { + std::time::Duration::from_secs(secs).into() + } + + /// Creates a new `Duration` from the specified number of milliseconds. + #[must_use] + #[inline] + pub fn from_millis(millis: u64) -> Self { + std::time::Duration::from_millis(millis).into() + } + + /// Creates a new `Duration` from the specified number of microseconds. + #[must_use] + #[inline] + pub fn from_micros(micros: u64) -> Self { + std::time::Duration::from_micros(micros).into() + } + + /// Creates a new `Duration` from the specified number of nanoseconds. + #[must_use] + #[inline] + pub fn from_nanos(nanos: u64) -> Self { + std::time::Duration::from_nanos(nanos).into() + } + + /// Creates a new `Duration` from the specified number of seconds represented + /// as `f64`. + /// + /// # Panics + /// This constructor will panic if `secs` is not finite, negative or overflows `Duration`. + /// + /// # Examples + /// ```no_run + /// use wstd::time::Duration; + /// + /// let dur = Duration::from_secs_f64(2.7); + /// assert_eq!(dur, Duration::new(2, 700_000_000)); + /// ``` + #[must_use] + #[inline] + pub fn from_secs_f64(secs: f64) -> Duration { + std::time::Duration::from_secs_f64(secs).into() + } + + /// Creates a new `Duration` from the specified number of seconds represented + /// as `f32`. + /// + /// # Panics + /// This constructor will panic if `secs` is not finite, negative or overflows `Duration`. + #[must_use] + #[inline] + pub fn from_secs_f32(secs: f32) -> Duration { + std::time::Duration::from_secs_f32(secs).into() + } + + /// Returns the number of whole seconds contained by this `Duration`. + #[must_use] + #[inline] + pub const fn as_secs(&self) -> u64 { + self.0 / 1_000_000_000 + } + + /// Returns the number of whole milliseconds contained by this `Duration`. + #[must_use] + #[inline] + pub const fn as_millis(&self) -> u128 { + (self.0 / 1_000_000) as u128 + } + + /// Returns the number of whole microseconds contained by this `Duration`. + #[must_use] + #[inline] + pub const fn as_micros(&self) -> u128 { + (self.0 / 1_000) as u128 + } + + /// Returns the total number of nanoseconds contained by this `Duration`. + #[must_use] + #[inline] + pub const fn as_nanos(&self) -> u128 { + self.0 as u128 + } +} + +impl From for Duration { + fn from(inner: std::time::Duration) -> Self { + Self( + inner + .as_nanos() + .try_into() + .expect("only dealing with durations that can fit in u64"), + ) + } +} + +impl From for std::time::Duration { + fn from(duration: Duration) -> Self { + Self::from_nanos(duration.0) + } +} + +impl Add for Duration { + type Output = Self; + + fn add(self, rhs: Duration) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl AddAssign for Duration { + fn add_assign(&mut self, rhs: Duration) { + *self = Self(self.0 + rhs.0) + } +} + +impl Sub for Duration { + type Output = Self; + + fn sub(self, rhs: Duration) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl SubAssign for Duration { + fn sub_assign(&mut self, rhs: Duration) { + *self = Self(self.0 - rhs.0) + } +} + +impl IntoFuture for Duration { + type Output = Instant; + + type IntoFuture = Wait; + + fn into_future(self) -> Self::IntoFuture { + crate::task::sleep(self) + } +} + +/// A measurement of a monotonically nondecreasing clock. Opaque and useful only +/// with Duration. +/// +/// This type wraps `std::time::Duration` so we can implement traits on it +/// without coherence issues, just like if we were implementing this in the +/// stdlib. +#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Clone, Copy)] +pub struct Instant(pub(crate) crate::sys::time::MonotonicInstant); + +impl Instant { + /// Returns an instant corresponding to "now". + /// + /// # Examples + /// + /// ```no_run + /// use wstd::time::Instant; + /// + /// let now = Instant::now(); + /// ``` + #[must_use] + pub fn now() -> Self { + Instant(crate::sys::time::now()) + } + + /// Returns the amount of time elapsed from another instant to this one, or zero duration if + /// that instant is later than this one. + pub fn duration_since(&self, earlier: Instant) -> Duration { + Duration::from_nanos(self.0.saturating_sub(earlier.0)) + } + + /// Returns the amount of time elapsed since this instant. + pub fn elapsed(&self) -> Duration { + Instant::now().duration_since(*self) + } +} + +impl Add for Instant { + type Output = Self; + + fn add(self, rhs: Duration) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl AddAssign for Instant { + fn add_assign(&mut self, rhs: Duration) { + *self = Self(self.0 + rhs.0) + } +} + +impl Sub for Instant { + type Output = Self; + + fn sub(self, rhs: Duration) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl SubAssign for Instant { + fn sub_assign(&mut self, rhs: Duration) { + *self = Self(self.0 - rhs.0) + } +} + +impl IntoFuture for Instant { + type Output = Instant; + + type IntoFuture = Wait; + + fn into_future(self) -> Self::IntoFuture { + crate::task::sleep_until(self) + } +} + +/// An async iterator representing notifications at fixed interval. +pub fn interval(duration: Duration) -> Interval { + Interval { duration } +} + +/// An async iterator representing notifications at fixed interval. +/// +/// See the [`interval`] function for more. +#[derive(Debug)] +pub struct Interval { + duration: Duration, +} +impl AsyncIterator for Interval { + type Item = Instant; + + async fn next(&mut self) -> Option { + Some(Timer::after(self.duration).wait().await) + } +} + +/// A measurement that resolves at a deadline, or never. +/// +/// A `Timer` records *when* it should fire when it is constructed; each call to +/// [`Timer::wait`] then builds a fresh [`Wait`] future against the backend +/// clock. Because the deadline is captured up front, `wait` is repeatable and a +/// `Timer` can be polled into more than once. +#[derive(Debug)] +pub struct Timer(TimerKind); + +#[derive(Debug, Clone, Copy)] +enum TimerKind { + /// Never fires; the resulting [`Wait`] is pending forever. + Never, + /// Fires once the monotonic clock reaches this instant. + At(Instant), +} + +impl Timer { + /// Create a `Timer` that never fires. + pub fn never() -> Timer { + Timer(TimerKind::Never) + } + /// Create a `Timer` that fires at `deadline`. + pub fn at(deadline: Instant) -> Timer { + Timer(TimerKind::At(deadline)) + } + /// Create a `Timer` that fires `duration` from now. + /// + /// The deadline is computed at construction time, matching the behavior of + /// `std::time` and preserving it across repeated [`wait`](Timer::wait) + /// calls. + pub fn after(duration: Duration) -> Timer { + Timer(TimerKind::At(Instant::now() + duration)) + } + /// Reset the `Timer` to fire `duration` from now. + pub fn set_after(&mut self, duration: Duration) { + *self = Self::after(duration); + } + /// Create a future that resolves when the `Timer` fires. + pub fn wait(&self) -> Wait { + let sleep = match self.0 { + TimerKind::Never => None, + TimerKind::At(deadline) => Some(crate::sys::time::sleep_until(deadline.0)), + }; + Wait { sleep } + } +} + +pin_project! { + /// Future created by [`Timer::wait`] + #[must_use = "futures do nothing unless polled or .awaited"] + pub struct Wait { + #[pin] + sleep: Option + } +} + +impl Future for Wait { + type Output = Instant; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + match this.sleep.as_pin_mut() { + None => Poll::Pending, + Some(sleep) => match sleep.poll(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(()) => Poll::Ready(Instant::now()), + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new_from_as() { + assert_eq!(Duration::new(456, 864209753).as_secs(), 456); + assert_eq!(Duration::new(456, 864209753).as_millis(), 456864); + assert_eq!(Duration::new(456, 864209753).as_micros(), 456864209); + assert_eq!(Duration::new(456, 864209753).as_nanos(), 456864209753); + + assert_eq!(Duration::from_secs(9876543210).as_secs(), 9876543210); + assert_eq!( + Duration::from_secs(9876543210).as_millis(), + 9_876_543_210_000 + ); + assert_eq!( + Duration::from_secs(9876543210).as_micros(), + 9_876_543_210_000_000 + ); + assert_eq!( + Duration::from_secs(9876543210).as_nanos(), + 9_876_543_210_000_000_000 + ); + + assert_eq!(Duration::from_millis(9876543210).as_secs(), 9876543); + assert_eq!(Duration::from_millis(9876543210).as_millis(), 9876543210); + assert_eq!( + Duration::from_millis(9876543210).as_micros(), + 9_876_543_210_000 + ); + assert_eq!( + Duration::from_millis(9876543210).as_nanos(), + 9_876_543_210_000_000 + ); + + assert_eq!(Duration::from_micros(9876543210).as_secs(), 9876); + assert_eq!(Duration::from_micros(9876543210).as_millis(), 9876543); + assert_eq!(Duration::from_micros(9876543210).as_micros(), 9876543210); + assert_eq!( + Duration::from_micros(9876543210).as_nanos(), + 9_876_543_210_000 + ); + + assert_eq!(Duration::from_nanos(9876543210).as_secs(), 9); + assert_eq!(Duration::from_nanos(9876543210).as_millis(), 9876); + assert_eq!(Duration::from_nanos(9876543210).as_micros(), 9876543); + assert_eq!(Duration::from_nanos(9876543210).as_nanos(), 9876543210); + } + + #[test] + fn test_from_secs_float() { + assert_eq!(Duration::from_secs_f64(158.9).as_secs(), 158); + assert_eq!(Duration::from_secs_f32(158.9).as_secs(), 158); + assert_eq!(Duration::from_secs_f64(159.1).as_secs(), 159); + assert_eq!(Duration::from_secs_f32(159.1).as_secs(), 159); + } + + #[test] + fn test_duration_since() { + let x = Instant::now(); + let d = Duration::new(456, 789); + let y = x + d; + assert_eq!(y.duration_since(x), d); + } + + async fn debug_duration(what: &str, f: impl Future) { + let start = Instant::now(); + let now = f.await; + let d = now.duration_since(start); + let d: std::time::Duration = d.into(); + println!("{what} awaited for {} s", d.as_secs_f32()); + } + + #[test] + fn timer_now() { + crate::runtime::block_on(debug_duration("timer_now", async { + Timer::at(Instant::now()).wait().await + })); + } + + #[test] + fn timer_after_100_milliseconds() { + crate::runtime::block_on(debug_duration("timer_after_100_milliseconds", async { + Timer::after(Duration::from_millis(100)).wait().await + })); + } +} diff --git a/src/time/duration.rs b/src/time/duration.rs deleted file mode 100644 index 7f67ceb..0000000 --- a/src/time/duration.rs +++ /dev/null @@ -1,216 +0,0 @@ -use super::{Instant, Wait}; -use std::future::IntoFuture; -use std::ops::{Add, AddAssign, Sub, SubAssign}; -use wasip2::clocks::monotonic_clock; - -/// A Duration type to represent a span of time, typically used for system -/// timeouts. -/// -/// This type wraps `std::time::Duration` so we can implement traits on it -/// without coherence issues, just like if we were implementing this in the -/// stdlib. -#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Clone, Copy)] -pub struct Duration(pub(crate) monotonic_clock::Duration); -impl Duration { - /// Creates a new `Duration` from the specified number of whole seconds and - /// additional nanoseconds. - #[must_use] - #[inline] - pub fn new(secs: u64, nanos: u32) -> Duration { - std::time::Duration::new(secs, nanos).into() - } - - /// Creates a new `Duration` from the specified number of whole seconds. - #[must_use] - #[inline] - pub fn from_secs(secs: u64) -> Duration { - std::time::Duration::from_secs(secs).into() - } - - /// Creates a new `Duration` from the specified number of milliseconds. - #[must_use] - #[inline] - pub fn from_millis(millis: u64) -> Self { - std::time::Duration::from_millis(millis).into() - } - - /// Creates a new `Duration` from the specified number of microseconds. - #[must_use] - #[inline] - pub fn from_micros(micros: u64) -> Self { - std::time::Duration::from_micros(micros).into() - } - - /// Creates a new `Duration` from the specified number of nanoseconds. - #[must_use] - #[inline] - pub fn from_nanos(nanos: u64) -> Self { - std::time::Duration::from_nanos(nanos).into() - } - - /// Creates a new `Duration` from the specified number of seconds represented - /// as `f64`. - /// - /// # Panics - /// This constructor will panic if `secs` is not finite, negative or overflows `Duration`. - /// - /// # Examples - /// ```no_run - /// use wstd::time::Duration; - /// - /// let dur = Duration::from_secs_f64(2.7); - /// assert_eq!(dur, Duration::new(2, 700_000_000)); - /// ``` - #[must_use] - #[inline] - pub fn from_secs_f64(secs: f64) -> Duration { - std::time::Duration::from_secs_f64(secs).into() - } - - /// Creates a new `Duration` from the specified number of seconds represented - /// as `f32`. - /// - /// # Panics - /// This constructor will panic if `secs` is not finite, negative or overflows `Duration`. - #[must_use] - #[inline] - pub fn from_secs_f32(secs: f32) -> Duration { - std::time::Duration::from_secs_f32(secs).into() - } - - /// Returns the number of whole seconds contained by this `Duration`. - #[must_use] - #[inline] - pub const fn as_secs(&self) -> u64 { - self.0 / 1_000_000_000 - } - - /// Returns the number of whole milliseconds contained by this `Duration`. - #[must_use] - #[inline] - pub const fn as_millis(&self) -> u128 { - (self.0 / 1_000_000) as u128 - } - - /// Returns the number of whole microseconds contained by this `Duration`. - #[must_use] - #[inline] - pub const fn as_micros(&self) -> u128 { - (self.0 / 1_000) as u128 - } - - /// Returns the total number of nanoseconds contained by this `Duration`. - #[must_use] - #[inline] - pub const fn as_nanos(&self) -> u128 { - self.0 as u128 - } -} - -impl From for Duration { - fn from(inner: std::time::Duration) -> Self { - Self( - inner - .as_nanos() - .try_into() - .expect("only dealing with durations that can fit in u64"), - ) - } -} - -impl From for std::time::Duration { - fn from(duration: Duration) -> Self { - Self::from_nanos(duration.0) - } -} - -impl Add for Duration { - type Output = Self; - - fn add(self, rhs: Duration) -> Self::Output { - Self(self.0 + rhs.0) - } -} - -impl AddAssign for Duration { - fn add_assign(&mut self, rhs: Duration) { - *self = Self(self.0 + rhs.0) - } -} - -impl Sub for Duration { - type Output = Self; - - fn sub(self, rhs: Duration) -> Self::Output { - Self(self.0 - rhs.0) - } -} - -impl SubAssign for Duration { - fn sub_assign(&mut self, rhs: Duration) { - *self = Self(self.0 - rhs.0) - } -} - -impl IntoFuture for Duration { - type Output = Instant; - - type IntoFuture = Wait; - - fn into_future(self) -> Self::IntoFuture { - crate::task::sleep(self) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_new_from_as() { - assert_eq!(Duration::new(456, 864209753).as_secs(), 456); - assert_eq!(Duration::new(456, 864209753).as_millis(), 456864); - assert_eq!(Duration::new(456, 864209753).as_micros(), 456864209); - assert_eq!(Duration::new(456, 864209753).as_nanos(), 456864209753); - - assert_eq!(Duration::from_secs(9876543210).as_secs(), 9876543210); - assert_eq!(Duration::from_secs(9876543210).as_millis(), 9876543210_000); - assert_eq!( - Duration::from_secs(9876543210).as_micros(), - 9876543210_000000 - ); - assert_eq!( - Duration::from_secs(9876543210).as_nanos(), - 9876543210_000000000 - ); - - assert_eq!(Duration::from_millis(9876543210).as_secs(), 9876543); - assert_eq!(Duration::from_millis(9876543210).as_millis(), 9876543210); - assert_eq!( - Duration::from_millis(9876543210).as_micros(), - 9876543210_000 - ); - assert_eq!( - Duration::from_millis(9876543210).as_nanos(), - 9876543210_000000 - ); - - assert_eq!(Duration::from_micros(9876543210).as_secs(), 9876); - assert_eq!(Duration::from_micros(9876543210).as_millis(), 9876543); - assert_eq!(Duration::from_micros(9876543210).as_micros(), 9876543210); - assert_eq!(Duration::from_micros(9876543210).as_nanos(), 9876543210_000); - - assert_eq!(Duration::from_nanos(9876543210).as_secs(), 9); - assert_eq!(Duration::from_nanos(9876543210).as_millis(), 9876); - assert_eq!(Duration::from_nanos(9876543210).as_micros(), 9876543); - assert_eq!(Duration::from_nanos(9876543210).as_nanos(), 9876543210); - } - - #[test] - fn test_from_secs_float() { - assert_eq!(Duration::from_secs_f64(158.9).as_secs(), 158); - assert_eq!(Duration::from_secs_f32(158.9).as_secs(), 158); - assert_eq!(Duration::from_secs_f64(159.1).as_secs(), 159); - assert_eq!(Duration::from_secs_f32(159.1).as_secs(), 159); - } -} diff --git a/src/time/instant.rs b/src/time/instant.rs deleted file mode 100644 index 6e9cf97..0000000 --- a/src/time/instant.rs +++ /dev/null @@ -1,91 +0,0 @@ -use super::{Duration, Wait}; -use std::future::IntoFuture; -use std::ops::{Add, AddAssign, Sub, SubAssign}; -use wasip2::clocks::monotonic_clock; - -/// A measurement of a monotonically nondecreasing clock. Opaque and useful only -/// with Duration. -/// -/// This type wraps `std::time::Duration` so we can implement traits on it -/// without coherence issues, just like if we were implementing this in the -/// stdlib. -#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Clone, Copy)] -pub struct Instant(pub(crate) monotonic_clock::Instant); - -impl Instant { - /// Returns an instant corresponding to "now". - /// - /// # Examples - /// - /// ```no_run - /// use wstd::time::Instant; - /// - /// let now = Instant::now(); - /// ``` - #[must_use] - pub fn now() -> Self { - Instant(wasip2::clocks::monotonic_clock::now()) - } - - /// Returns the amount of time elapsed from another instant to this one, or zero duration if - /// that instant is later than this one. - pub fn duration_since(&self, earlier: Instant) -> Duration { - Duration::from_nanos(self.0.saturating_sub(earlier.0)) - } - - /// Returns the amount of time elapsed since this instant. - pub fn elapsed(&self) -> Duration { - Instant::now().duration_since(*self) - } -} - -impl Add for Instant { - type Output = Self; - - fn add(self, rhs: Duration) -> Self::Output { - Self(self.0 + rhs.0) - } -} - -impl AddAssign for Instant { - fn add_assign(&mut self, rhs: Duration) { - *self = Self(self.0 + rhs.0) - } -} - -impl Sub for Instant { - type Output = Self; - - fn sub(self, rhs: Duration) -> Self::Output { - Self(self.0 - rhs.0) - } -} - -impl SubAssign for Instant { - fn sub_assign(&mut self, rhs: Duration) { - *self = Self(self.0 - rhs.0) - } -} - -impl IntoFuture for Instant { - type Output = Instant; - - type IntoFuture = Wait; - - fn into_future(self) -> Self::IntoFuture { - crate::task::sleep_until(self) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_duration_since() { - let x = Instant::now(); - let d = Duration::new(456, 789); - let y = x + d; - assert_eq!(y.duration_since(x), d); - } -} diff --git a/src/time/mod.rs b/src/time/mod.rs deleted file mode 100644 index db0e1b3..0000000 --- a/src/time/mod.rs +++ /dev/null @@ -1,138 +0,0 @@ -//! Async time interfaces. - -pub(crate) mod utils; - -mod duration; -mod instant; -pub use duration::Duration; -pub use instant::Instant; - -use pin_project_lite::pin_project; -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; -use wasip2::clocks::{ - monotonic_clock::{subscribe_duration, subscribe_instant}, - wall_clock, -}; - -use crate::{ - iter::AsyncIterator, - runtime::{AsyncPollable, Reactor}, -}; - -/// A measurement of the system clock, useful for talking to external entities -/// like the file system or other processes. May be converted losslessly to a -/// more useful `std::time::SystemTime` to provide more methods. -#[derive(Debug, Clone, Copy)] -#[allow(dead_code)] -pub struct SystemTime(wall_clock::Datetime); - -impl SystemTime { - pub fn now() -> Self { - Self(wall_clock::now()) - } -} - -impl From for std::time::SystemTime { - fn from(st: SystemTime) -> Self { - std::time::SystemTime::UNIX_EPOCH - + std::time::Duration::from_secs(st.0.seconds) - + std::time::Duration::from_nanos(st.0.nanoseconds.into()) - } -} - -/// An async iterator representing notifications at fixed interval. -pub fn interval(duration: Duration) -> Interval { - Interval { duration } -} - -/// An async iterator representing notifications at fixed interval. -/// -/// See the [`interval`] function for more. -#[derive(Debug)] -pub struct Interval { - duration: Duration, -} -impl AsyncIterator for Interval { - type Item = Instant; - - async fn next(&mut self) -> Option { - Some(Timer::after(self.duration).wait().await) - } -} - -#[derive(Debug)] -pub struct Timer(Option); - -impl Timer { - pub fn never() -> Timer { - Timer(None) - } - pub fn at(deadline: Instant) -> Timer { - let pollable = Reactor::current().schedule(subscribe_instant(deadline.0)); - Timer(Some(pollable)) - } - pub fn after(duration: Duration) -> Timer { - let pollable = Reactor::current().schedule(subscribe_duration(duration.0)); - Timer(Some(pollable)) - } - pub fn set_after(&mut self, duration: Duration) { - *self = Self::after(duration); - } - pub fn wait(&self) -> Wait { - let wait_for = self.0.as_ref().map(AsyncPollable::wait_for); - Wait { wait_for } - } -} - -pin_project! { - /// Future created by [`Timer::wait`] - #[must_use = "futures do nothing unless polled or .awaited"] - pub struct Wait { - #[pin] - wait_for: Option - } -} - -impl Future for Wait { - type Output = Instant; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - match this.wait_for.as_pin_mut() { - None => Poll::Pending, - Some(f) => match f.poll(cx) { - Poll::Pending => Poll::Pending, - Poll::Ready(()) => Poll::Ready(Instant::now()), - }, - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - async fn debug_duration(what: &str, f: impl Future) { - let start = Instant::now(); - let now = f.await; - let d = now.duration_since(start); - let d: std::time::Duration = d.into(); - println!("{what} awaited for {} s", d.as_secs_f32()); - } - - #[test] - fn timer_now() { - crate::runtime::block_on(debug_duration("timer_now", async { - Timer::at(Instant::now()).wait().await - })); - } - - #[test] - fn timer_after_100_milliseconds() { - crate::runtime::block_on(debug_duration("timer_after_100_milliseconds", async { - Timer::after(Duration::from_millis(100)).wait().await - })); - } -} diff --git a/src/time/utils.rs b/src/time/utils.rs deleted file mode 100644 index e6e3993..0000000 --- a/src/time/utils.rs +++ /dev/null @@ -1,5 +0,0 @@ -use std::io; - -pub(crate) fn timeout_err(msg: &'static str) -> io::Error { - io::Error::new(io::ErrorKind::TimedOut, msg) -}