From 171a532910790379ebbfd85ddbb62b6f60a1fbff Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Tue, 21 Jan 2025 17:42:52 +0100 Subject: [PATCH] feat(wasi): add support for `wasi:clocks@0.3.0` Signed-off-by: Roman Volosatovs --- Cargo.lock | 2 + ci/vendor-wit.sh | 2 + crates/test-programs/Cargo.toml | 2 + .../test-programs/src/bin/preview2_sleep.rs | 2 +- .../test-programs/src/bin/preview3_sleep.rs | 62 +++++++++++++ crates/test-programs/src/lib.rs | 11 ++- crates/test-programs/src/sockets.rs | 2 +- crates/wasi-http/src/bindings.rs | 1 + crates/wasi-http/src/lib.rs | 8 +- .../monotonic-clock.wit | 45 ++++++++++ .../timezone.wit | 55 ++++++++++++ .../wall-clock.wit | 46 ++++++++++ .../clocks@3850f9d@wit-0.3.0-draft/world.wit | 11 +++ .../src/lib.rs | 2 +- crates/wasi/src/bindings.rs | 14 ++- crates/wasi/src/host/clocks.rs | 88 ++++++++++++++++--- crates/wasi/src/host/clocks/sync.rs | 28 ++++++ crates/wasi/src/host/filesystem.rs | 2 +- crates/wasi/src/lib.rs | 12 ++- crates/wasi/src/preview1.rs | 2 +- crates/wasi/tests/all/api.rs | 4 +- crates/wasi/tests/all/async_.rs | 4 + crates/wasi/tests/all/sync.rs | 4 + .../monotonic-clock.wit | 45 ++++++++++ .../timezone.wit | 55 ++++++++++++ .../wall-clock.wit | 46 ++++++++++ .../clocks@3850f9d@wit-0.3.0-draft/world.wit | 11 +++ crates/wasi/wit/world.wit | 1 + 28 files changed, 535 insertions(+), 32 deletions(-) create mode 100644 crates/test-programs/src/bin/preview3_sleep.rs create mode 100644 crates/wasi-http/wit/deps/clocks@3850f9d@wit-0.3.0-draft/monotonic-clock.wit create mode 100644 crates/wasi-http/wit/deps/clocks@3850f9d@wit-0.3.0-draft/timezone.wit create mode 100644 crates/wasi-http/wit/deps/clocks@3850f9d@wit-0.3.0-draft/wall-clock.wit create mode 100644 crates/wasi-http/wit/deps/clocks@3850f9d@wit-0.3.0-draft/world.wit create mode 100644 crates/wasi/src/host/clocks/sync.rs create mode 100644 crates/wasi/wit/deps/clocks@3850f9d@wit-0.3.0-draft/monotonic-clock.wit create mode 100644 crates/wasi/wit/deps/clocks@3850f9d@wit-0.3.0-draft/timezone.wit create mode 100644 crates/wasi/wit/deps/clocks@3850f9d@wit-0.3.0-draft/wall-clock.wit create mode 100644 crates/wasi/wit/deps/clocks@3850f9d@wit-0.3.0-draft/world.wit diff --git a/Cargo.lock b/Cargo.lock index 2653e82f7b57..be6b88f6c633 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3226,10 +3226,12 @@ dependencies = [ "getrandom", "libc", "sha2", + "tokio", "url", "wasi", "wasi-nn", "wit-bindgen", + "wit-bindgen-rt", ] [[package]] diff --git a/ci/vendor-wit.sh b/ci/vendor-wit.sh index 8967489c3c08..afd6955b86b0 100755 --- a/ci/vendor-wit.sh +++ b/ci/vendor-wit.sh @@ -44,6 +44,7 @@ make_vendor "wasi" " random@v0.2.3 sockets@v0.2.3 random@3e99124@wit-0.3.0-draft + clocks@3850f9d@wit-0.3.0-draft " make_vendor "wasi-http" " @@ -55,6 +56,7 @@ make_vendor "wasi-http" " sockets@v0.2.3 http@v0.2.3 random@3e99124@wit-0.3.0-draft + clocks@3850f9d@wit-0.3.0-draft " make_vendor "wasi-config" "config@f4d699b" diff --git a/crates/test-programs/Cargo.toml b/crates/test-programs/Cargo.toml index baff3bba8b79..5eb169d37aa5 100644 --- a/crates/test-programs/Cargo.toml +++ b/crates/test-programs/Cargo.toml @@ -15,9 +15,11 @@ anyhow = { workspace = true, features = ['std'] } wasi = "0.11.0" wasi-nn = "0.6.0" wit-bindgen = { workspace = true, features = ['default'] } +wit-bindgen-rt = "0.37" libc = { workspace = true } getrandom = "0.2.9" futures = { workspace = true, default-features = false, features = ['alloc'] } url = { workspace = true } sha2 = "0.10.2" base64 = "0.21.0" +tokio = { workspace = true, features = ["macros"] } diff --git a/crates/test-programs/src/bin/preview2_sleep.rs b/crates/test-programs/src/bin/preview2_sleep.rs index a6c8dae39840..a3ea8ec5f8f2 100644 --- a/crates/test-programs/src/bin/preview2_sleep.rs +++ b/crates/test-programs/src/bin/preview2_sleep.rs @@ -1,4 +1,4 @@ -use test_programs::wasi::clocks::monotonic_clock; +use test_programs::wasi::clocks0_2_3::monotonic_clock; fn main() { sleep_10ms(); diff --git a/crates/test-programs/src/bin/preview3_sleep.rs b/crates/test-programs/src/bin/preview3_sleep.rs new file mode 100644 index 000000000000..b205b2e530b8 --- /dev/null +++ b/crates/test-programs/src/bin/preview3_sleep.rs @@ -0,0 +1,62 @@ +use core::future::Future as _; +use core::pin::pin; +use core::ptr; +use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; + +use test_programs::wasi::clocks0_3_0::monotonic_clock; + +// Adapted from https://github.com/rust-lang/rust/blob/cd805f09ffbfa3896c8f50a619de9b67e1d9f3c3/library/core/src/task/wake.rs#L63-L77 +// TODO: Replace by `Waker::noop` once MSRV is raised to 1.85 +const NOOP_RAW_WAKER: RawWaker = { + const VTABLE: RawWakerVTable = RawWakerVTable::new( + // Cloning just returns a new no-op raw waker + |_| NOOP_RAW_WAKER, + // `wake` does nothing + |_| {}, + // `wake_by_ref` does nothing + |_| {}, + // Dropping does nothing as we don't allocate anything + |_| {}, + ); + RawWaker::new(ptr::null(), &VTABLE) +}; + +const NOOP_WAKER: &'static Waker = &unsafe { Waker::from_raw(NOOP_RAW_WAKER) }; + +#[tokio::main(flavor = "current_thread")] +async fn main() { + sleep_10ms().await; + sleep_0ms(); + sleep_backwards_in_time(); +} + +async fn sleep_10ms() { + let dur = 10_000_000; + monotonic_clock::wait_until(monotonic_clock::now() + dur).await; + monotonic_clock::wait_for(dur).await; +} + +fn sleep_0ms() { + let mut cx = Context::from_waker(NOOP_WAKER); + + assert_eq!( + pin!(monotonic_clock::wait_until(monotonic_clock::now())).poll(&mut cx), + Poll::Ready(()), + "waiting until now() is ready immediately", + ); + assert_eq!( + pin!(monotonic_clock::wait_for(0)).poll(&mut cx), + Poll::Ready(()), + "waiting for 0 is ready immediately", + ); +} + +fn sleep_backwards_in_time() { + let mut cx = Context::from_waker(NOOP_WAKER); + + assert_eq!( + pin!(monotonic_clock::wait_until(monotonic_clock::now() - 1)).poll(&mut cx), + Poll::Ready(()), + "waiting until instant which has passed is ready immediately", + ); +} diff --git a/crates/test-programs/src/lib.rs b/crates/test-programs/src/lib.rs index 882a9662b933..cce04c9a6e15 100644 --- a/crates/test-programs/src/lib.rs +++ b/crates/test-programs/src/lib.rs @@ -13,6 +13,7 @@ wit_bindgen::generate!({ include wasi:config/imports@0.2.0-draft; include wasi:keyvalue/imports@0.2.0-draft; + include wasi:clocks/imports@0.3.0; include wasi:random/imports@0.3.0; } ", @@ -23,6 +24,12 @@ wit_bindgen::generate!({ ], world: "wasmtime:test/test", features: ["cli-exit-with-code"], + async: { + imports: [ + "wasi:clocks/monotonic-clock@0.3.0#wait-for", + "wasi:clocks/monotonic-clock@0.3.0#wait-until", + ], + }, generate_all, }); @@ -42,8 +49,8 @@ pub mod proxy { "wasi:cli/stdout@0.2.3": crate::wasi::cli::stdout, "wasi:cli/stderr@0.2.3": crate::wasi::cli::stderr, "wasi:cli/stdin@0.2.3": crate::wasi::cli::stdin, - "wasi:clocks/monotonic-clock@0.2.3": crate::wasi::clocks::monotonic_clock, - "wasi:clocks/wall-clock@0.2.3": crate::wasi::clocks::wall_clock, + "wasi:clocks/monotonic-clock@0.2.3": crate::wasi::clocks0_2_3::monotonic_clock, + "wasi:clocks/wall-clock@0.2.3": crate::wasi::clocks0_2_3::wall_clock, }, }); } diff --git a/crates/test-programs/src/sockets.rs b/crates/test-programs/src/sockets.rs index e5bf79e0fa39..0c41031a10fc 100644 --- a/crates/test-programs/src/sockets.rs +++ b/crates/test-programs/src/sockets.rs @@ -1,4 +1,4 @@ -use crate::wasi::clocks::monotonic_clock; +use crate::wasi::clocks0_2_3::monotonic_clock; use crate::wasi::io::poll::{self, Pollable}; use crate::wasi::io::streams::{InputStream, OutputStream, StreamError}; use crate::wasi::random0_2_3 as random; diff --git a/crates/wasi-http/src/bindings.rs b/crates/wasi-http/src/bindings.rs index 1548dc567c48..8777bb1f0179 100644 --- a/crates/wasi-http/src/bindings.rs +++ b/crates/wasi-http/src/bindings.rs @@ -62,6 +62,7 @@ pub mod sync { "wasi:http": crate::bindings::http, // http is in this crate "wasi:io": wasmtime_wasi::bindings::sync::io, // io is sync "wasi:random": wasmtime_wasi::bindings::random0_2_3, // use correct `wasi:random` + "wasi:clocks": wasmtime_wasi::bindings::clocks0_2_3, // use correct `wasi:clocks` "wasi": wasmtime_wasi::bindings, // everything else }, require_store_data_send: true, diff --git a/crates/wasi-http/src/lib.rs b/crates/wasi-http/src/lib.rs index 0830c34591fa..7597f4156049 100644 --- a/crates/wasi-http/src/lib.rs +++ b/crates/wasi-http/src/lib.rs @@ -286,8 +286,8 @@ where { let io_closure = type_annotate_io::(|t| wasmtime_wasi::IoImpl(t)); let closure = type_annotate_wasi::(|t| wasmtime_wasi::WasiImpl(wasmtime_wasi::IoImpl(t))); - wasmtime_wasi::bindings::clocks::wall_clock::add_to_linker_get_host(l, closure)?; - wasmtime_wasi::bindings::clocks::monotonic_clock::add_to_linker_get_host(l, closure)?; + wasmtime_wasi::bindings::clocks0_2_3::wall_clock::add_to_linker_get_host(l, closure)?; + wasmtime_wasi::bindings::clocks0_2_3::monotonic_clock::add_to_linker_get_host(l, closure)?; wasmtime_wasi::bindings::io::poll::add_to_linker_get_host(l, io_closure)?; wasmtime_wasi::bindings::io::error::add_to_linker_get_host(l, io_closure)?; wasmtime_wasi::bindings::io::streams::add_to_linker_get_host(l, io_closure)?; @@ -385,8 +385,8 @@ where let io_closure = type_annotate_io::(|t| wasmtime_wasi::IoImpl(t)); let closure = type_annotate_wasi::(|t| wasmtime_wasi::WasiImpl(wasmtime_wasi::IoImpl(t))); - wasmtime_wasi::bindings::clocks::wall_clock::add_to_linker_get_host(l, closure)?; - wasmtime_wasi::bindings::clocks::monotonic_clock::add_to_linker_get_host(l, closure)?; + wasmtime_wasi::bindings::clocks0_2_3::wall_clock::add_to_linker_get_host(l, closure)?; + wasmtime_wasi::bindings::clocks0_2_3::monotonic_clock::add_to_linker_get_host(l, closure)?; wasmtime_wasi::bindings::sync::io::poll::add_to_linker_get_host(l, io_closure)?; wasmtime_wasi::bindings::sync::io::streams::add_to_linker_get_host(l, io_closure)?; wasmtime_wasi::bindings::io::error::add_to_linker_get_host(l, io_closure)?; diff --git a/crates/wasi-http/wit/deps/clocks@3850f9d@wit-0.3.0-draft/monotonic-clock.wit b/crates/wasi-http/wit/deps/clocks@3850f9d@wit-0.3.0-draft/monotonic-clock.wit new file mode 100644 index 000000000000..87ebdaac510a --- /dev/null +++ b/crates/wasi-http/wit/deps/clocks@3850f9d@wit-0.3.0-draft/monotonic-clock.wit @@ -0,0 +1,45 @@ +package wasi:clocks@0.3.0; +/// WASI Monotonic Clock is a clock API intended to let users measure elapsed +/// time. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// A monotonic clock is a clock which has an unspecified initial value, and +/// successive reads of the clock will produce non-decreasing values. +@since(version = 0.3.0) +interface monotonic-clock { + /// An instant in time, in nanoseconds. An instant is relative to an + /// unspecified initial value, and can only be compared to instances from + /// the same monotonic-clock. + @since(version = 0.3.0) + type instant = u64; + + /// A duration of time, in nanoseconds. + @since(version = 0.3.0) + type duration = u64; + + /// Read the current value of the clock. + /// + /// The clock is monotonic, therefore calling this function repeatedly will + /// produce a sequence of non-decreasing values. + @since(version = 0.3.0) + now: func() -> instant; + + /// Query the resolution of the clock. Returns the duration of time + /// corresponding to a clock tick. + @since(version = 0.3.0) + resolution: func() -> duration; + + /// Wait until the specified instant has occurred. + @since(version = 0.3.0) + wait-until: func( + when: instant, + ); + + /// Wait for the specified duration has elapsed. + @since(version = 0.3.0) + wait-for: func( + how-long: duration, + ); +} diff --git a/crates/wasi-http/wit/deps/clocks@3850f9d@wit-0.3.0-draft/timezone.wit b/crates/wasi-http/wit/deps/clocks@3850f9d@wit-0.3.0-draft/timezone.wit new file mode 100644 index 000000000000..ac9146834f80 --- /dev/null +++ b/crates/wasi-http/wit/deps/clocks@3850f9d@wit-0.3.0-draft/timezone.wit @@ -0,0 +1,55 @@ +package wasi:clocks@0.3.0; + +@unstable(feature = clocks-timezone) +interface timezone { + @unstable(feature = clocks-timezone) + use wall-clock.{datetime}; + + /// Return information needed to display the given `datetime`. This includes + /// the UTC offset, the time zone name, and a flag indicating whether + /// daylight saving time is active. + /// + /// If the timezone cannot be determined for the given `datetime`, return a + /// `timezone-display` for `UTC` with a `utc-offset` of 0 and no daylight + /// saving time. + @unstable(feature = clocks-timezone) + display: func(when: datetime) -> timezone-display; + + /// The same as `display`, but only return the UTC offset. + @unstable(feature = clocks-timezone) + utc-offset: func(when: datetime) -> s32; + + /// Information useful for displaying the timezone of a specific `datetime`. + /// + /// This information may vary within a single `timezone` to reflect daylight + /// saving time adjustments. + @unstable(feature = clocks-timezone) + record timezone-display { + /// The number of seconds difference between UTC time and the local + /// time of the timezone. + /// + /// The returned value will always be less than 86400 which is the + /// number of seconds in a day (24*60*60). + /// + /// In implementations that do not expose an actual time zone, this + /// should return 0. + utc-offset: s32, + + /// The abbreviated name of the timezone to display to a user. The name + /// `UTC` indicates Coordinated Universal Time. Otherwise, this should + /// reference local standards for the name of the time zone. + /// + /// In implementations that do not expose an actual time zone, this + /// should be the string `UTC`. + /// + /// In time zones that do not have an applicable name, a formatted + /// representation of the UTC offset may be returned, such as `-04:00`. + name: string, + + /// Whether daylight saving time is active. + /// + /// In implementations that do not expose an actual time zone, this + /// should return false. + in-daylight-saving-time: bool, + } +} diff --git a/crates/wasi-http/wit/deps/clocks@3850f9d@wit-0.3.0-draft/wall-clock.wit b/crates/wasi-http/wit/deps/clocks@3850f9d@wit-0.3.0-draft/wall-clock.wit new file mode 100644 index 000000000000..b7a85ab35636 --- /dev/null +++ b/crates/wasi-http/wit/deps/clocks@3850f9d@wit-0.3.0-draft/wall-clock.wit @@ -0,0 +1,46 @@ +package wasi:clocks@0.3.0; +/// WASI Wall Clock is a clock API intended to let users query the current +/// time. The name "wall" makes an analogy to a "clock on the wall", which +/// is not necessarily monotonic as it may be reset. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// A wall clock is a clock which measures the date and time according to +/// some external reference. +/// +/// External references may be reset, so this clock is not necessarily +/// monotonic, making it unsuitable for measuring elapsed time. +/// +/// It is intended for reporting the current date and time for humans. +@since(version = 0.3.0) +interface wall-clock { + /// A time and date in seconds plus nanoseconds. + @since(version = 0.3.0) + record datetime { + seconds: u64, + nanoseconds: u32, + } + + /// Read the current value of the clock. + /// + /// This clock is not monotonic, therefore calling this function repeatedly + /// will not necessarily produce a sequence of non-decreasing values. + /// + /// The returned timestamps represent the number of seconds since + /// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], + /// also known as [Unix Time]. + /// + /// The nanoseconds field of the output is always less than 1000000000. + /// + /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 + /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time + @since(version = 0.3.0) + now: func() -> datetime; + + /// Query the resolution of the clock. + /// + /// The nanoseconds field of the output is always less than 1000000000. + @since(version = 0.3.0) + resolution: func() -> datetime; +} diff --git a/crates/wasi-http/wit/deps/clocks@3850f9d@wit-0.3.0-draft/world.wit b/crates/wasi-http/wit/deps/clocks@3850f9d@wit-0.3.0-draft/world.wit new file mode 100644 index 000000000000..f97bcfef13b2 --- /dev/null +++ b/crates/wasi-http/wit/deps/clocks@3850f9d@wit-0.3.0-draft/world.wit @@ -0,0 +1,11 @@ +package wasi:clocks@0.3.0; + +@since(version = 0.3.0) +world imports { + @since(version = 0.3.0) + import monotonic-clock; + @since(version = 0.3.0) + import wall-clock; + @unstable(feature = clocks-timezone) + import timezone; +} diff --git a/crates/wasi-preview1-component-adapter/src/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs index 975d23094252..82406aa2e028 100644 --- a/crates/wasi-preview1-component-adapter/src/lib.rs +++ b/crates/wasi-preview1-component-adapter/src/lib.rs @@ -14,7 +14,7 @@ )] #![expect(clippy::allow_attributes, reason = "crate not migrated yet")] -use crate::bindings::wasi::clocks::{monotonic_clock, wall_clock}; +use crate::bindings::wasi::clocks0_2_3::{monotonic_clock, wall_clock}; use crate::bindings::wasi::io::poll; use crate::bindings::wasi::io::streams; use crate::bindings::wasi::random0_2_3::random; diff --git a/crates/wasi/src/bindings.rs b/crates/wasi/src/bindings.rs index a5c159086e27..ba9982480406 100644 --- a/crates/wasi/src/bindings.rs +++ b/crates/wasi/src/bindings.rs @@ -28,6 +28,7 @@ //! world my-world { //! include wasi:cli/command@0.2.3; //! +//! include wasi:clocks/imports@0.3.0; //! include wasi:random/imports@0.3.0; //! //! import custom-host; @@ -39,7 +40,8 @@ //! ", //! path: "wit", //! with: { -//! "wasi:clocks": wasmtime_wasi::bindings::clocks, +//! "wasi:clocks@0.2.3": wasmtime_wasi::bindings::clocks0_2_3, +//! "wasi:clocks@0.3.0": wasmtime_wasi::bindings::clocks0_3_0, //! "wasi:filesystem": wasmtime_wasi::bindings::filesystem, //! "wasi:io": wasmtime_wasi::bindings::io, //! "wasi:random@0.2.3": wasmtime_wasi::bindings::random0_2_3, @@ -106,6 +108,7 @@ /// world my-world { /// include wasi:cli/command@0.2.3; /// +/// include wasi:clocks/imports@0.3.0; /// include wasi:random/imports@0.3.0; /// /// import custom-host; @@ -117,7 +120,8 @@ /// ", /// path: "wit", /// with: { -/// "wasi:clocks": wasmtime_wasi::bindings::sync::clocks, +/// "wasi:clocks@0.2.3": wasmtime_wasi::bindings::sync::clocks0_2_3, +/// "wasi:clocks@0.3.0": wasmtime_wasi::bindings::sync::clocks0_3_0, /// "wasi:filesystem": wasmtime_wasi::bindings::sync::filesystem, /// "wasi:io": wasmtime_wasi::bindings::sync::io, /// "wasi:random@0.2.3": wasmtime_wasi::bindings::sync::random0_2_3, @@ -170,6 +174,7 @@ pub mod sync { world command { include wasi:cli/command@0.2.3; + include wasi:clocks/imports@0.3.0; include wasi:random/imports@0.3.0; } ", @@ -183,7 +188,7 @@ pub mod sync { with: { // These interfaces come from the outer module, as it's // sync/async agnostic. - "wasi:clocks": crate::bindings::clocks, + "wasi:clocks@0.2.3": crate::bindings::clocks0_2_3, "wasi:random@0.2.3": crate::bindings::random0_2_3, "wasi:random@0.3.0": crate::bindings::random0_3_0, "wasi:cli": crate::bindings::cli, @@ -362,6 +367,7 @@ mod async_io { world command { include wasi:cli/command@0.2.3; + include wasi:clocks/imports@0.3.0; include wasi:random/imports@0.3.0; } ", @@ -427,6 +433,8 @@ mod async_io { "[method]udp-socket.start-bind", "[method]udp-socket.stream", "[method]outgoing-datagram-stream.send", + "wait-for", + "wait-until", ], }, trappable_error_type: { diff --git a/crates/wasi/src/host/clocks.rs b/crates/wasi/src/host/clocks.rs index feefb9473b27..f8024b72d368 100644 --- a/crates/wasi/src/host/clocks.rs +++ b/crates/wasi/src/host/clocks.rs @@ -1,44 +1,80 @@ #![allow(unused_variables)] -use crate::bindings::{ - clocks::monotonic_clock::{self, Duration as WasiDuration, Instant}, - clocks::wall_clock::{self, Datetime}, -}; +use crate::bindings::clocks0_3_0::monotonic_clock::{Duration as WasiDuration, Instant}; +use crate::bindings::{clocks0_2_3, clocks0_3_0}; use crate::poll::{subscribe, Subscribe}; use crate::{IoView, Pollable, WasiImpl, WasiView}; use cap_std::time::SystemTime; use std::time::Duration; +use tokio::time::sleep; use wasmtime::component::Resource; -impl TryFrom for Datetime { +mod sync; + +impl TryFrom for clocks0_2_3::wall_clock::Datetime { + type Error = anyhow::Error; + + fn try_from(time: SystemTime) -> Result { + let duration = + time.duration_since(SystemTime::from_std(std::time::SystemTime::UNIX_EPOCH))?; + + Ok(Self { + seconds: duration.as_secs(), + nanoseconds: duration.subsec_nanos(), + }) + } +} + +impl TryFrom for clocks0_3_0::wall_clock::Datetime { type Error = anyhow::Error; fn try_from(time: SystemTime) -> Result { let duration = time.duration_since(SystemTime::from_std(std::time::SystemTime::UNIX_EPOCH))?; - Ok(Datetime { + Ok(Self { seconds: duration.as_secs(), nanoseconds: duration.subsec_nanos(), }) } } -impl wall_clock::Host for WasiImpl +impl clocks0_2_3::wall_clock::Host for WasiImpl +where + T: WasiView, +{ + fn now(&mut self) -> anyhow::Result { + let now = self.ctx().wall_clock.now(); + Ok(clocks0_2_3::wall_clock::Datetime { + seconds: now.as_secs(), + nanoseconds: now.subsec_nanos(), + }) + } + + fn resolution(&mut self) -> anyhow::Result { + let res = self.ctx().wall_clock.resolution(); + Ok(clocks0_2_3::wall_clock::Datetime { + seconds: res.as_secs(), + nanoseconds: res.subsec_nanos(), + }) + } +} + +impl clocks0_3_0::wall_clock::Host for WasiImpl where T: WasiView, { - fn now(&mut self) -> anyhow::Result { + fn now(&mut self) -> anyhow::Result { let now = self.ctx().wall_clock.now(); - Ok(Datetime { + Ok(clocks0_3_0::wall_clock::Datetime { seconds: now.as_secs(), nanoseconds: now.subsec_nanos(), }) } - fn resolution(&mut self) -> anyhow::Result { + fn resolution(&mut self) -> anyhow::Result { let res = self.ctx().wall_clock.resolution(); - Ok(Datetime { + Ok(clocks0_3_0::wall_clock::Datetime { seconds: res.as_secs(), nanoseconds: res.subsec_nanos(), }) @@ -64,7 +100,7 @@ fn subscribe_to_duration( subscribe(table, sleep) } -impl monotonic_clock::Host for WasiImpl +impl clocks0_2_3::monotonic_clock::Host for WasiImpl where T: WasiView, { @@ -107,3 +143,31 @@ impl Subscribe for Deadline { } } } + +impl clocks0_3_0::monotonic_clock::Host for WasiImpl +where + T: WasiView, +{ + fn now(&mut self) -> anyhow::Result { + Ok(self.ctx().monotonic_clock.now()) + } + + fn resolution(&mut self) -> anyhow::Result { + Ok(self.ctx().monotonic_clock.resolution()) + } + + async fn wait_until(&mut self, when: Instant) -> anyhow::Result<()> { + let clock_now = self.ctx().monotonic_clock.now(); + if when > clock_now { + sleep(Duration::from_nanos(when - clock_now)).await; + }; + Ok(()) + } + + async fn wait_for(&mut self, duration: WasiDuration) -> anyhow::Result<()> { + if duration > 0 { + sleep(Duration::from_nanos(duration)).await; + } + Ok(()) + } +} diff --git a/crates/wasi/src/host/clocks/sync.rs b/crates/wasi/src/host/clocks/sync.rs new file mode 100644 index 000000000000..4cce269bb477 --- /dev/null +++ b/crates/wasi/src/host/clocks/sync.rs @@ -0,0 +1,28 @@ +use crate::bindings::clocks0_3_0 as async_clocks; +use crate::bindings::sync::clocks0_3_0 as sync_clocks; +use crate::bindings::sync::clocks0_3_0::monotonic_clock::{Duration, Instant}; +use crate::runtime::in_tokio; +use crate::{WasiImpl, WasiView}; + +impl sync_clocks::monotonic_clock::Host for WasiImpl +where + T: WasiView, +{ + fn now(&mut self) -> anyhow::Result { + async_clocks::monotonic_clock::Host::now(self) + } + + fn resolution(&mut self) -> anyhow::Result { + async_clocks::monotonic_clock::Host::resolution(self) + } + + fn wait_until(&mut self, when: Instant) -> anyhow::Result<()> { + in_tokio(async_clocks::monotonic_clock::Host::wait_until(self, when)) + } + + fn wait_for(&mut self, duration: Duration) -> anyhow::Result<()> { + in_tokio(async_clocks::monotonic_clock::Host::wait_for( + self, duration, + )) + } +} diff --git a/crates/wasi/src/host/filesystem.rs b/crates/wasi/src/host/filesystem.rs index a8cfb91754f3..3bb7f890fa2b 100644 --- a/crates/wasi/src/host/filesystem.rs +++ b/crates/wasi/src/host/filesystem.rs @@ -1,4 +1,4 @@ -use crate::bindings::clocks::wall_clock; +use crate::bindings::clocks0_2_3::wall_clock; use crate::bindings::filesystem::preopens; use crate::bindings::filesystem::types::{ self, ErrorCode, HostDescriptor, HostDirectoryEntryStream, diff --git a/crates/wasi/src/lib.rs b/crates/wasi/src/lib.rs index 6b48f48f9aca..4b144a66c729 100644 --- a/crates/wasi/src/lib.rs +++ b/crates/wasi/src/lib.rs @@ -304,8 +304,10 @@ pub fn add_to_linker_with_options_async( let io_closure = io_type_annotate::(|t| IoImpl(t)); let closure = type_annotate::(|t| WasiImpl(IoImpl(t))); - crate::bindings::clocks::wall_clock::add_to_linker_get_host(l, closure)?; - crate::bindings::clocks::monotonic_clock::add_to_linker_get_host(l, closure)?; + crate::bindings::clocks0_2_3::monotonic_clock::add_to_linker_get_host(l, closure)?; + crate::bindings::clocks0_2_3::wall_clock::add_to_linker_get_host(l, closure)?; + crate::bindings::clocks0_3_0::monotonic_clock::add_to_linker_get_host(l, closure)?; + crate::bindings::clocks0_3_0::wall_clock::add_to_linker_get_host(l, closure)?; crate::bindings::filesystem::types::add_to_linker_get_host(l, closure)?; crate::bindings::filesystem::preopens::add_to_linker_get_host(l, closure)?; crate::bindings::io::error::add_to_linker_get_host(l, io_closure)?; @@ -408,8 +410,10 @@ pub fn add_to_linker_with_options_sync( let io_closure = io_type_annotate::(|t| IoImpl(t)); let closure = type_annotate::(|t| WasiImpl(IoImpl(t))); - crate::bindings::clocks::wall_clock::add_to_linker_get_host(l, closure)?; - crate::bindings::clocks::monotonic_clock::add_to_linker_get_host(l, closure)?; + crate::bindings::clocks0_2_3::wall_clock::add_to_linker_get_host(l, closure)?; + crate::bindings::clocks0_2_3::monotonic_clock::add_to_linker_get_host(l, closure)?; + crate::bindings::clocks0_3_0::wall_clock::add_to_linker_get_host(l, closure)?; + crate::bindings::sync::clocks0_3_0::monotonic_clock::add_to_linker_get_host(l, closure)?; crate::bindings::sync::filesystem::types::add_to_linker_get_host(l, closure)?; crate::bindings::filesystem::preopens::add_to_linker_get_host(l, closure)?; crate::bindings::io::error::add_to_linker_get_host(l, io_closure)?; diff --git a/crates/wasi/src/preview1.rs b/crates/wasi/src/preview1.rs index 91b48d8c25ad..98df191a8364 100644 --- a/crates/wasi/src/preview1.rs +++ b/crates/wasi/src/preview1.rs @@ -68,7 +68,7 @@ use crate::bindings::{ stderr::Host as _, stdin::Host as _, stdout::Host as _, terminal_input, terminal_output, terminal_stderr::Host as _, terminal_stdin::Host as _, terminal_stdout::Host as _, }, - clocks::{monotonic_clock, wall_clock}, + clocks0_2_3::{monotonic_clock, wall_clock}, filesystem::{preopens::Host as _, types as filesystem}, io::streams, }; diff --git a/crates/wasi/tests/all/api.rs b/crates/wasi/tests/all/api.rs index 955d2722236c..c17be7cc75d0 100644 --- a/crates/wasi/tests/all/api.rs +++ b/crates/wasi/tests/all/api.rs @@ -9,7 +9,7 @@ use wasmtime::Store; use wasmtime_wasi::bindings::Command; use wasmtime_wasi::{ add_to_linker_async, - bindings::{clocks::wall_clock, filesystem::types as filesystem}, + bindings::{clocks0_2_3::wall_clock, filesystem::types as filesystem}, DirPerms, FilePerms, HostMonotonicClock, HostWallClock, IoView, WasiCtx, WasiCtxBuilder, WasiView, }; @@ -134,7 +134,7 @@ wasmtime::component::bindgen!({ world: "test-reactor", async: true, with: { - "wasi:clocks": wasmtime_wasi::bindings::clocks, + "wasi:clocks@0.2.3": wasmtime_wasi::bindings::clocks0_2_3, "wasi:filesystem": wasmtime_wasi::bindings::filesystem, "wasi:io": wasmtime_wasi::bindings::io, "wasi:random@0.2.3": wasmtime_wasi::bindings::random0_2_3, diff --git a/crates/wasi/tests/all/async_.rs b/crates/wasi/tests/all/async_.rs index 659bb30e0df2..20a787090b17 100644 --- a/crates/wasi/tests/all/async_.rs +++ b/crates/wasi/tests/all/async_.rs @@ -401,6 +401,10 @@ async fn preview2_file_read_write() { .unwrap() } +#[test_log::test(tokio::test(flavor = "multi_thread"))] +async fn preview3_sleep() { + run(PREVIEW3_SLEEP_COMPONENT, false).await.unwrap() +} #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn preview3_random() { run(PREVIEW3_RANDOM_COMPONENT, false).await.unwrap() diff --git a/crates/wasi/tests/all/sync.rs b/crates/wasi/tests/all/sync.rs index 90ab151b16f3..a9f0fcb0d1bc 100644 --- a/crates/wasi/tests/all/sync.rs +++ b/crates/wasi/tests/all/sync.rs @@ -335,6 +335,10 @@ fn preview2_file_read_write() { run(PREVIEW2_FILE_READ_WRITE_COMPONENT, false).unwrap() } +#[test_log::test] +fn preview3_sleep() { + run(PREVIEW3_SLEEP_COMPONENT, false).unwrap() +} #[test_log::test] fn preview3_random() { run(PREVIEW3_RANDOM_COMPONENT, false).unwrap() diff --git a/crates/wasi/wit/deps/clocks@3850f9d@wit-0.3.0-draft/monotonic-clock.wit b/crates/wasi/wit/deps/clocks@3850f9d@wit-0.3.0-draft/monotonic-clock.wit new file mode 100644 index 000000000000..87ebdaac510a --- /dev/null +++ b/crates/wasi/wit/deps/clocks@3850f9d@wit-0.3.0-draft/monotonic-clock.wit @@ -0,0 +1,45 @@ +package wasi:clocks@0.3.0; +/// WASI Monotonic Clock is a clock API intended to let users measure elapsed +/// time. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// A monotonic clock is a clock which has an unspecified initial value, and +/// successive reads of the clock will produce non-decreasing values. +@since(version = 0.3.0) +interface monotonic-clock { + /// An instant in time, in nanoseconds. An instant is relative to an + /// unspecified initial value, and can only be compared to instances from + /// the same monotonic-clock. + @since(version = 0.3.0) + type instant = u64; + + /// A duration of time, in nanoseconds. + @since(version = 0.3.0) + type duration = u64; + + /// Read the current value of the clock. + /// + /// The clock is monotonic, therefore calling this function repeatedly will + /// produce a sequence of non-decreasing values. + @since(version = 0.3.0) + now: func() -> instant; + + /// Query the resolution of the clock. Returns the duration of time + /// corresponding to a clock tick. + @since(version = 0.3.0) + resolution: func() -> duration; + + /// Wait until the specified instant has occurred. + @since(version = 0.3.0) + wait-until: func( + when: instant, + ); + + /// Wait for the specified duration has elapsed. + @since(version = 0.3.0) + wait-for: func( + how-long: duration, + ); +} diff --git a/crates/wasi/wit/deps/clocks@3850f9d@wit-0.3.0-draft/timezone.wit b/crates/wasi/wit/deps/clocks@3850f9d@wit-0.3.0-draft/timezone.wit new file mode 100644 index 000000000000..ac9146834f80 --- /dev/null +++ b/crates/wasi/wit/deps/clocks@3850f9d@wit-0.3.0-draft/timezone.wit @@ -0,0 +1,55 @@ +package wasi:clocks@0.3.0; + +@unstable(feature = clocks-timezone) +interface timezone { + @unstable(feature = clocks-timezone) + use wall-clock.{datetime}; + + /// Return information needed to display the given `datetime`. This includes + /// the UTC offset, the time zone name, and a flag indicating whether + /// daylight saving time is active. + /// + /// If the timezone cannot be determined for the given `datetime`, return a + /// `timezone-display` for `UTC` with a `utc-offset` of 0 and no daylight + /// saving time. + @unstable(feature = clocks-timezone) + display: func(when: datetime) -> timezone-display; + + /// The same as `display`, but only return the UTC offset. + @unstable(feature = clocks-timezone) + utc-offset: func(when: datetime) -> s32; + + /// Information useful for displaying the timezone of a specific `datetime`. + /// + /// This information may vary within a single `timezone` to reflect daylight + /// saving time adjustments. + @unstable(feature = clocks-timezone) + record timezone-display { + /// The number of seconds difference between UTC time and the local + /// time of the timezone. + /// + /// The returned value will always be less than 86400 which is the + /// number of seconds in a day (24*60*60). + /// + /// In implementations that do not expose an actual time zone, this + /// should return 0. + utc-offset: s32, + + /// The abbreviated name of the timezone to display to a user. The name + /// `UTC` indicates Coordinated Universal Time. Otherwise, this should + /// reference local standards for the name of the time zone. + /// + /// In implementations that do not expose an actual time zone, this + /// should be the string `UTC`. + /// + /// In time zones that do not have an applicable name, a formatted + /// representation of the UTC offset may be returned, such as `-04:00`. + name: string, + + /// Whether daylight saving time is active. + /// + /// In implementations that do not expose an actual time zone, this + /// should return false. + in-daylight-saving-time: bool, + } +} diff --git a/crates/wasi/wit/deps/clocks@3850f9d@wit-0.3.0-draft/wall-clock.wit b/crates/wasi/wit/deps/clocks@3850f9d@wit-0.3.0-draft/wall-clock.wit new file mode 100644 index 000000000000..b7a85ab35636 --- /dev/null +++ b/crates/wasi/wit/deps/clocks@3850f9d@wit-0.3.0-draft/wall-clock.wit @@ -0,0 +1,46 @@ +package wasi:clocks@0.3.0; +/// WASI Wall Clock is a clock API intended to let users query the current +/// time. The name "wall" makes an analogy to a "clock on the wall", which +/// is not necessarily monotonic as it may be reset. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// A wall clock is a clock which measures the date and time according to +/// some external reference. +/// +/// External references may be reset, so this clock is not necessarily +/// monotonic, making it unsuitable for measuring elapsed time. +/// +/// It is intended for reporting the current date and time for humans. +@since(version = 0.3.0) +interface wall-clock { + /// A time and date in seconds plus nanoseconds. + @since(version = 0.3.0) + record datetime { + seconds: u64, + nanoseconds: u32, + } + + /// Read the current value of the clock. + /// + /// This clock is not monotonic, therefore calling this function repeatedly + /// will not necessarily produce a sequence of non-decreasing values. + /// + /// The returned timestamps represent the number of seconds since + /// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], + /// also known as [Unix Time]. + /// + /// The nanoseconds field of the output is always less than 1000000000. + /// + /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 + /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time + @since(version = 0.3.0) + now: func() -> datetime; + + /// Query the resolution of the clock. + /// + /// The nanoseconds field of the output is always less than 1000000000. + @since(version = 0.3.0) + resolution: func() -> datetime; +} diff --git a/crates/wasi/wit/deps/clocks@3850f9d@wit-0.3.0-draft/world.wit b/crates/wasi/wit/deps/clocks@3850f9d@wit-0.3.0-draft/world.wit new file mode 100644 index 000000000000..f97bcfef13b2 --- /dev/null +++ b/crates/wasi/wit/deps/clocks@3850f9d@wit-0.3.0-draft/world.wit @@ -0,0 +1,11 @@ +package wasi:clocks@0.3.0; + +@since(version = 0.3.0) +world imports { + @since(version = 0.3.0) + import monotonic-clock; + @since(version = 0.3.0) + import wall-clock; + @unstable(feature = clocks-timezone) + import timezone; +} diff --git a/crates/wasi/wit/world.wit b/crates/wasi/wit/world.wit index 3ccb79b17a4b..cc1ad8f720b1 100644 --- a/crates/wasi/wit/world.wit +++ b/crates/wasi/wit/world.wit @@ -4,5 +4,6 @@ package wasmtime:wasi; world bindings { include wasi:cli/imports@0.2.3; + include wasi:clocks/imports@0.3.0; include wasi:random/imports@0.3.0; }