From cbd2a40ae4f60e1e5081614b03e7e8825a0fe909 Mon Sep 17 00:00:00 2001 From: Yosh Date: Tue, 9 Jun 2026 02:44:36 +0200 Subject: [PATCH 1/4] Move wasip2 impl into src/sys/p2 (pure relocation) This is a pure `git mv` relocation with no content changes, so git records clean renames and `git blame`/`--follow` lineage is preserved. The build is intentionally red after this commit; backend wiring and crate-root re-export shims are added in the following commit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/{ => sys/p2}/http/body.rs | 0 src/{ => sys/p2}/http/client.rs | 0 src/{ => sys/p2}/http/error.rs | 0 src/{ => sys/p2}/http/fields.rs | 0 src/{ => sys/p2}/http/method.rs | 0 src/{ => sys/p2}/http/mod.rs | 0 src/{ => sys/p2}/http/request.rs | 0 src/{ => sys/p2}/http/response.rs | 0 src/{ => sys/p2}/http/scheme.rs | 0 src/{ => sys/p2}/http/server.rs | 0 src/{ => sys/p2}/io/stdio.rs | 0 src/{ => sys/p2}/io/streams.rs | 0 src/{ => sys/p2}/net/mod.rs | 0 src/{ => sys/p2}/net/tcp_listener.rs | 0 src/{ => sys/p2}/net/tcp_stream.rs | 0 src/{ => sys/p2}/rand/mod.rs | 0 src/{ => sys/p2}/runtime/block_on.rs | 0 src/{ => sys/p2}/runtime/mod.rs | 0 src/{ => sys/p2}/runtime/reactor.rs | 0 src/{ => sys/p2}/time/duration.rs | 0 src/{ => sys/p2}/time/instant.rs | 0 src/{ => sys/p2}/time/mod.rs | 0 src/{ => sys/p2}/time/utils.rs | 0 23 files changed, 0 insertions(+), 0 deletions(-) rename src/{ => sys/p2}/http/body.rs (100%) rename src/{ => sys/p2}/http/client.rs (100%) rename src/{ => sys/p2}/http/error.rs (100%) rename src/{ => sys/p2}/http/fields.rs (100%) rename src/{ => sys/p2}/http/method.rs (100%) rename src/{ => sys/p2}/http/mod.rs (100%) rename src/{ => sys/p2}/http/request.rs (100%) rename src/{ => sys/p2}/http/response.rs (100%) rename src/{ => sys/p2}/http/scheme.rs (100%) rename src/{ => sys/p2}/http/server.rs (100%) rename src/{ => sys/p2}/io/stdio.rs (100%) rename src/{ => sys/p2}/io/streams.rs (100%) rename src/{ => sys/p2}/net/mod.rs (100%) rename src/{ => sys/p2}/net/tcp_listener.rs (100%) rename src/{ => sys/p2}/net/tcp_stream.rs (100%) rename src/{ => sys/p2}/rand/mod.rs (100%) rename src/{ => sys/p2}/runtime/block_on.rs (100%) rename src/{ => sys/p2}/runtime/mod.rs (100%) rename src/{ => sys/p2}/runtime/reactor.rs (100%) rename src/{ => sys/p2}/time/duration.rs (100%) rename src/{ => sys/p2}/time/instant.rs (100%) rename src/{ => sys/p2}/time/mod.rs (100%) rename src/{ => sys/p2}/time/utils.rs (100%) diff --git a/src/http/body.rs b/src/sys/p2/http/body.rs similarity index 100% rename from src/http/body.rs rename to src/sys/p2/http/body.rs diff --git a/src/http/client.rs b/src/sys/p2/http/client.rs similarity index 100% rename from src/http/client.rs rename to src/sys/p2/http/client.rs diff --git a/src/http/error.rs b/src/sys/p2/http/error.rs similarity index 100% rename from src/http/error.rs rename to src/sys/p2/http/error.rs diff --git a/src/http/fields.rs b/src/sys/p2/http/fields.rs similarity index 100% rename from src/http/fields.rs rename to src/sys/p2/http/fields.rs 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/http/mod.rs b/src/sys/p2/http/mod.rs similarity index 100% rename from src/http/mod.rs rename to src/sys/p2/http/mod.rs diff --git a/src/http/request.rs b/src/sys/p2/http/request.rs similarity index 100% rename from src/http/request.rs rename to src/sys/p2/http/request.rs diff --git a/src/http/response.rs b/src/sys/p2/http/response.rs similarity index 100% rename from src/http/response.rs rename to src/sys/p2/http/response.rs 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 100% rename from src/http/server.rs rename to src/sys/p2/http/server.rs diff --git a/src/io/stdio.rs b/src/sys/p2/io/stdio.rs similarity index 100% rename from src/io/stdio.rs rename to src/sys/p2/io/stdio.rs diff --git a/src/io/streams.rs b/src/sys/p2/io/streams.rs similarity index 100% rename from src/io/streams.rs rename to src/sys/p2/io/streams.rs 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 100% rename from src/net/tcp_stream.rs rename to src/sys/p2/net/tcp_stream.rs diff --git a/src/rand/mod.rs b/src/sys/p2/rand/mod.rs similarity index 100% rename from src/rand/mod.rs rename to src/sys/p2/rand/mod.rs 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 100% rename from src/runtime/mod.rs rename to src/sys/p2/runtime/mod.rs 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/time/duration.rs b/src/sys/p2/time/duration.rs similarity index 100% rename from src/time/duration.rs rename to src/sys/p2/time/duration.rs diff --git a/src/time/instant.rs b/src/sys/p2/time/instant.rs similarity index 100% rename from src/time/instant.rs rename to src/sys/p2/time/instant.rs diff --git a/src/time/mod.rs b/src/sys/p2/time/mod.rs similarity index 100% rename from src/time/mod.rs rename to src/sys/p2/time/mod.rs diff --git a/src/time/utils.rs b/src/sys/p2/time/utils.rs similarity index 100% rename from src/time/utils.rs rename to src/sys/p2/time/utils.rs From 19f2f24cc7ab26918d623ff5bacc6a94f3b1d196 Mon Sep 17 00:00:00 2001 From: Yosh Date: Tue, 9 Jun 2026 02:52:02 +0200 Subject: [PATCH 2/4] Wire up the src/sys backend with crate-root re-export shims Restores a green build after the pure relocation. The crate root now selects a platform backend via a single cfg-if in src/sys/mod.rs, and each moved module is re-exported through a thin shim so the public API is preserved exactly: * add `mod sys` plus src/sys/mod.rs (cfg-if) and src/sys/p2/mod.rs * add crate-root shims: http.rs, net.rs, rand.rs, runtime.rs, time.rs * re-export the moved stdio/streams via src/sys/p2/io and io/mod.rs * repoint the few internal `super::`/private-module paths the move broke * add the cfg-if dependency Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Cargo.toml | 2 ++ src/http.rs | 3 +++ src/io/mod.rs | 5 +---- src/lib.rs | 3 +++ src/net.rs | 3 +++ src/rand.rs | 3 +++ src/runtime.rs | 10 ++++++++++ src/sys/mod.rs | 17 +++++++++++++++++ src/sys/p2/http/body.rs | 7 ++----- src/sys/p2/http/response.rs | 3 ++- src/sys/p2/io/mod.rs | 5 +++++ src/sys/p2/io/stdio.rs | 2 +- src/sys/p2/io/streams.rs | 4 ++-- src/sys/p2/mod.rs | 8 ++++++++ src/sys/p2/runtime/mod.rs | 2 +- src/time.rs | 3 +++ 16 files changed, 66 insertions(+), 14 deletions(-) create mode 100644 src/http.rs create mode 100644 src/net.rs create mode 100644 src/rand.rs create mode 100644 src/runtime.rs create mode 100644 src/sys/mod.rs create mode 100644 src/sys/p2/io/mod.rs create mode 100644 src/sys/p2/mod.rs create mode 100644 src/time.rs diff --git a/Cargo.toml b/Cargo.toml index 4e248eb..1df71b8 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/src/http.rs b/src/http.rs new file mode 100644 index 0000000..ed2c4a2 --- /dev/null +++ b/src/http.rs @@ -0,0 +1,3 @@ +//! HTTP networking support +//! +pub use crate::sys::http::*; 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..6ddff7d --- /dev/null +++ b/src/net.rs @@ -0,0 +1,3 @@ +//! Async network abstractions. + +pub use crate::sys::net::*; diff --git a/src/rand.rs b/src/rand.rs new file mode 100644 index 0000000..3528400 --- /dev/null +++ b/src/rand.rs @@ -0,0 +1,3 @@ +//! Random number generation. + +pub use crate::sys::rand::*; diff --git a/src/runtime.rs b/src/runtime.rs new file mode 100644 index 0000000..71d3acc --- /dev/null +++ b/src/runtime.rs @@ -0,0 +1,10 @@ +//! 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 crate::sys::runtime::*; diff --git a/src/sys/mod.rs b/src/sys/mod.rs new file mode 100644 index 0000000..fd1edb3 --- /dev/null +++ b/src/sys/mod.rs @@ -0,0 +1,17 @@ +//! Platform-specific backends. +//! +//! Each supported target provides an implementation under `src/sys/`, selected +//! here by a single `cfg-if`. The rest of the crate is written against the +//! backend-agnostic `crate::sys::*` re-exports below, mirroring the layout used +//! by the `polling` crate. + +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::*; diff --git a/src/sys/p2/http/body.rs b/src/sys/p2/http/body.rs index 97b093c..db8e9ef 100644 --- a/src/sys/p2/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/sys/p2/http/response.rs b/src/sys/p2/http/response.rs index 2ab8d87..44f264b 100644 --- a/src/sys/p2/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/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/sys/p2/io/stdio.rs b/src/sys/p2/io/stdio.rs index b2ac153..fa183a3 100644 --- a/src/sys/p2/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/sys/p2/io/streams.rs b/src/sys/p2/io/streams.rs index 3676d21..5f95ca5 100644 --- a/src/sys/p2/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/sys/p2/runtime/mod.rs b/src/sys/p2/runtime/mod.rs index 24b9fc2..4c82015 100644 --- a/src/sys/p2/runtime/mod.rs +++ b/src/sys/p2/runtime/mod.rs @@ -8,7 +8,7 @@ //! necessary wakers to work. #![deny(missing_debug_implementations, nonstandard_style)] -#![warn(missing_docs, unreachable_pub)] +#![warn(missing_docs)] mod block_on; mod reactor; diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 0000000..2524af6 --- /dev/null +++ b/src/time.rs @@ -0,0 +1,3 @@ +//! Async time interfaces. + +pub use crate::sys::time::*; From 9ccff141cd9e94cd16fc428300c36abf6adc1ff3 Mon Sep 17 00:00:00 2001 From: Yosh Date: Tue, 9 Jun 2026 03:19:31 +0200 Subject: [PATCH 3/4] Document the backend contract and assert it at compile time Spell out, in `src/sys/mod.rs`, the duck-typed contract each `src/sys` backend must satisfy so the crate-root modules can be target-agnostic facades. Add a private `const _` block that statically checks the parts the facades depend on (the IO stream traits today), so backend drift fails fast with a clear message instead of surfacing deep inside a facade. No behavior or public-API change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/sys/mod.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/sys/mod.rs b/src/sys/mod.rs index fd1edb3..a8bdc7c 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -4,6 +4,43 @@ //! here by a single `cfg-if`. The rest of the crate is written against the //! backend-agnostic `crate::sys::*` re-exports below, mirroring the layout used //! by the `polling` crate. +//! +//! # Backend contract +//! +//! The crate-root modules (`crate::time`, `crate::io`, `crate::net`, …) are +//! *facades*: target-agnostic code, free of `#[cfg]`, written once against the +//! items a backend promises to provide. A backend is a module under `src/sys/` +//! (today only [`p2`]; a `p3` backend is planned) that supplies the following +//! duck-typed items, in the `std::sys` style — there is no shared `trait`, the +//! facade simply names `crate::sys::::` and the compiler checks +//! that the selected backend provides it. +//! +//! - `sys::time` +//! - `type MonotonicInstant` and `type MonotonicDuration`: nanosecond counts +//! on the monotonic clock. The facade owns all `Duration`/`Instant` +//! arithmetic, so these are plain integers (`u64` on p2). +//! - `fn now() -> MonotonicInstant`: read the monotonic clock. +//! - `struct Sleep`: a concrete `Future` that resolves at a +//! deadline. Backends may box internally (a native component-model future +//! cannot always be named), so the facade makes no assumptions about +//! `Sleep` beyond what it observes here. +//! - `fn sleep_until(deadline: MonotonicInstant) -> Sleep`. +//! - `struct SystemTime` with `fn now()` and `From for +//! std::time::SystemTime`. +//! - `sys::io` +//! - `struct AsyncInputStream: AsyncRead` and +//! `struct AsyncOutputStream: AsyncWrite`. +//! - `Stdin`/`Stdout`/`Stderr` and `stdin()`/`stdout()`/`stderr()`. +//! - `sys::net`: `TcpStream`, `TcpListener`. +//! - `sys::http`: `client::Client`, plus the `request`/`response`/`body`/ +//! `fields`/`method`/`scheme`/`server` modules and error types. +//! - `sys::rand`: `get_random_bytes`, `get_insecure_random_bytes`. +//! - `sys::runtime`: `block_on`, `spawn`, `Reactor`, `Task`. The reified +//! pollable types (`AsyncPollable`, `WaitFor`) are **p2-only**: they model +//! WASI 0.2's `pollable` resources and have no portable equivalent, so they +//! are intentionally left out of the common contract. While only one backend +//! exists they are re-exported with no `#[cfg]`; once a second backend lands +//! they become a single localized escape hatch rather than facade `#[cfg]`s. cfg_if::cfg_if! { if #[cfg(all(target_os = "wasi", target_env = "p2"))] { @@ -15,3 +52,15 @@ cfg_if::cfg_if! { } pub use backend::*; + +/// Compile-time assertions that the selected backend satisfies the parts of the +/// contract the facades rely on. These fail fast, with a clear message pointing +/// at the missing or mistyped item, instead of surfacing as a confusing error +/// deep inside a facade. +const _: fn() = || { + fn assert_async_read() {} + fn assert_async_write() {} + + assert_async_read::(); + assert_async_write::(); +}; From 56b0a73617dab3fe0d8df302eff90b739dcbd36f Mon Sep 17 00:00:00 2001 From: Yosh Date: Tue, 9 Jun 2026 03:23:09 +0200 Subject: [PATCH 4/4] Turn `time` into a cfg-free facade over a small clock contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Promote the portable time types — `Duration`, `Instant`, `Interval`, `interval`, `Timer`, `Wait`, and the crate-internal `utils::timeout_err` — out of the wasip2 backend and into the `crate::time` facade, where they are written once with no `#[cfg]` and own all of their own arithmetic. The wasip2 backend (`src/sys/p2/time`) keeps only the primitives that genuinely depend on the WASI 0.2 clocks: the `MonotonicInstant` / `MonotonicDuration` nanosecond aliases, `now`, `SystemTime`, and a `Sleep` future built by `sleep_until`. `Timer` now records its deadline at construction (`TimerKind::{Never, At}`) and builds a fresh backend `Sleep` on each `wait`, so a second backend (p3) only has to supply a different `Sleep`/`sleep_until` rather than re-implementing the facade. Extend the backend-contract assertions in `src/sys/mod.rs` accordingly. Public API is unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/sys/mod.rs | 60 ++--- src/sys/p2/time/duration.rs | 216 ------------------ src/sys/p2/time/instant.rs | 91 -------- src/sys/p2/time/mod.rs | 142 ++++-------- src/sys/p2/time/utils.rs | 5 - src/time.rs | 432 +++++++++++++++++++++++++++++++++++- 6 files changed, 489 insertions(+), 457 deletions(-) delete mode 100644 src/sys/p2/time/duration.rs delete mode 100644 src/sys/p2/time/instant.rs delete mode 100644 src/sys/p2/time/utils.rs diff --git a/src/sys/mod.rs b/src/sys/mod.rs index a8bdc7c..cbb4385 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -1,46 +1,16 @@ //! Platform-specific backends. //! //! Each supported target provides an implementation under `src/sys/`, selected -//! here by a single `cfg-if`. The rest of the crate is written against the -//! backend-agnostic `crate::sys::*` re-exports below, mirroring the layout used -//! by the `polling` crate. +//! 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 contract -//! -//! The crate-root modules (`crate::time`, `crate::io`, `crate::net`, …) are -//! *facades*: target-agnostic code, free of `#[cfg]`, written once against the -//! items a backend promises to provide. A backend is a module under `src/sys/` -//! (today only [`p2`]; a `p3` backend is planned) that supplies the following -//! duck-typed items, in the `std::sys` style — there is no shared `trait`, the -//! facade simply names `crate::sys::::` and the compiler checks -//! that the selected backend provides it. -//! -//! - `sys::time` -//! - `type MonotonicInstant` and `type MonotonicDuration`: nanosecond counts -//! on the monotonic clock. The facade owns all `Duration`/`Instant` -//! arithmetic, so these are plain integers (`u64` on p2). -//! - `fn now() -> MonotonicInstant`: read the monotonic clock. -//! - `struct Sleep`: a concrete `Future` that resolves at a -//! deadline. Backends may box internally (a native component-model future -//! cannot always be named), so the facade makes no assumptions about -//! `Sleep` beyond what it observes here. -//! - `fn sleep_until(deadline: MonotonicInstant) -> Sleep`. -//! - `struct SystemTime` with `fn now()` and `From for -//! std::time::SystemTime`. -//! - `sys::io` -//! - `struct AsyncInputStream: AsyncRead` and -//! `struct AsyncOutputStream: AsyncWrite`. -//! - `Stdin`/`Stdout`/`Stderr` and `stdin()`/`stdout()`/`stderr()`. -//! - `sys::net`: `TcpStream`, `TcpListener`. -//! - `sys::http`: `client::Client`, plus the `request`/`response`/`body`/ -//! `fields`/`method`/`scheme`/`server` modules and error types. -//! - `sys::rand`: `get_random_bytes`, `get_insecure_random_bytes`. -//! - `sys::runtime`: `block_on`, `spawn`, `Reactor`, `Task`. The reified -//! pollable types (`AsyncPollable`, `WaitFor`) are **p2-only**: they model -//! WASI 0.2's `pollable` resources and have no portable equivalent, so they -//! are intentionally left out of the common contract. While only one backend -//! exists they are re-exported with no `#[cfg]`; once a second backend lands -//! they become a single localized escape hatch rather than facade `#[cfg]`s. +//! 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"))] { @@ -53,14 +23,18 @@ cfg_if::cfg_if! { pub use backend::*; -/// Compile-time assertions that the selected backend satisfies the parts of the -/// contract the facades rely on. These fail fast, with a clear message pointing -/// at the missing or mistyped item, instead of surfacing as a confusing error -/// deep inside a facade. +// 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/sys/p2/time/duration.rs b/src/sys/p2/time/duration.rs deleted file mode 100644 index 7f67ceb..0000000 --- a/src/sys/p2/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/sys/p2/time/instant.rs b/src/sys/p2/time/instant.rs deleted file mode 100644 index 6e9cf97..0000000 --- a/src/sys/p2/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/sys/p2/time/mod.rs b/src/sys/p2/time/mod.rs index db0e1b3..92b8983 100644 --- a/src/sys/p2/time/mod.rs +++ b/src/sys/p2/time/mod.rs @@ -1,25 +1,30 @@ -//! Async time interfaces. +//! 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. -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 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; -use crate::{ - iter::AsyncIterator, - runtime::{AsyncPollable, Reactor}, -}; +/// 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 @@ -42,97 +47,32 @@ impl From for std::time::SystemTime { } } -/// An async iterator representing notifications at fixed interval. -pub fn interval(duration: Duration) -> Interval { - Interval { duration } -} - -/// An async iterator representing notifications at fixed interval. +/// A future that resolves once the monotonic clock reaches a deadline. /// -/// 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) - } -} - +/// 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 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 } - } +pub struct Sleep { + wait_for: WaitFor, } -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 Sleep { + type Output = (); -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()), - }, - } + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + Pin::new(&mut self.wait_for).poll(cx) } } -#[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 - })); +/// 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/sys/p2/time/utils.rs b/src/sys/p2/time/utils.rs deleted file mode 100644 index e6e3993..0000000 --- a/src/sys/p2/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) -} diff --git a/src/time.rs b/src/time.rs index 2524af6..043bb03 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,3 +1,433 @@ //! 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`]. -pub use crate::sys::time::*; +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(), 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); + } + + #[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 + })); + } +}