Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

monotonic clock: introduce duration type, split subscribe #7358

Merged
merged 10 commits into from
Oct 30, 2023
7 changes: 2 additions & 5 deletions crates/wasi-preview1-component-adapter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1748,13 +1748,10 @@ pub unsafe extern "C" fn poll_oneoff(
clock.timeout
};

monotonic_clock::subscribe(timeout, false)
monotonic_clock::subscribe_duration(timeout)
}

CLOCKID_MONOTONIC => {
let s = monotonic_clock::subscribe(clock.timeout, absolute);
s
}
CLOCKID_MONOTONIC => monotonic_clock::subscribe_instant(clock.timeout),

_ => return Err(ERRNO_INVAL),
}
Expand Down
51 changes: 39 additions & 12 deletions crates/wasi/src/preview2/host/clocks.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![allow(unused_variables)]

use crate::preview2::bindings::{
clocks::monotonic_clock::{self, Instant},
clocks::monotonic_clock::{self, Duration as WasiDuration, Instant},
clocks::timezone::{self, TimezoneDisplay},
clocks::wall_clock::{self, Datetime},
};
Expand Down Expand Up @@ -52,30 +52,57 @@ impl<T: WasiView> monotonic_clock::Host for T {
Ok(self.ctx().monotonic_clock.resolution())
}

fn subscribe(&mut self, when: Instant, absolute: bool) -> anyhow::Result<Resource<Pollable>> {
fn subscribe_instant(&mut self, when: Instant) -> anyhow::Result<Resource<Pollable>> {
let clock_now = self.ctx().monotonic_clock.now();
let duration = if absolute {
Duration::from_nanos(when - clock_now)
let deadline = if when <= clock_now {
let duration = Duration::from_nanos(when - clock_now);
tokio::time::Instant::now()
.checked_add(duration)
// This should only reachable if we are running on a platform
// where std::time::Instant (of which tokio's Instant is a
// newtype wrapper) represents less than a u64 in nanoseconds,
// which appears to always be true in practice, but isnt a
// guarantee provided by std.
.ok_or_else(|| anyhow::anyhow!("time overflow: duration {duration:?}"))?
} else {
Duration::from_nanos(when)
tokio::time::Instant::now()
};
let deadline = tokio::time::Instant::now()
.checked_add(duration)
.ok_or_else(|| anyhow::anyhow!("time overflow: duration {duration:?}"))?;
// NB: this resource created here is not actually exposed to wasm, it's
// only an internal implementation detail used to match the signature
// expected by `subscribe`.
let sleep = self.table_mut().push(Sleep(deadline))?;
let sleep = self.table_mut().push(Deadline::Instant(deadline))?;
subscribe(self.table_mut(), sleep)
}
pchickey marked this conversation as resolved.
Show resolved Hide resolved

fn subscribe_duration(&mut self, duration: WasiDuration) -> anyhow::Result<Resource<Pollable>> {
let clock_now = self.ctx().monotonic_clock.now();
let duration = Duration::from_nanos(duration);
let sleep = if let Some(deadline) = tokio::time::Instant::now().checked_add(duration) {
// NB: this resource created here is not actually exposed to wasm, it's
// only an internal implementation detail used to match the signature
// expected by `subscribe`.
self.table_mut().push(Deadline::Instant(deadline))?
} else {
// If the user specifies a time so far in the future we can't
// represent it, wait forever rather than trap.
self.table_mut().push(Deadline::Never)?
};
subscribe(self.table_mut(), sleep)
}
}

struct Sleep(tokio::time::Instant);
enum Deadline {
Instant(tokio::time::Instant),
Never,
}

#[async_trait::async_trait]
impl Subscribe for Sleep {
impl Subscribe for Deadline {
async fn ready(&mut self) {
tokio::time::sleep_until(self.0).await;
match self {
Deadline::Instant(instant) => tokio::time::sleep_until(*instant).await,
Deadline::Never => std::future::pending().await,
}
}
}

Expand Down
12 changes: 9 additions & 3 deletions crates/wasi/src/preview2/preview1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2069,9 +2069,15 @@ impl<
}
_ => return Err(types::Errno::Inval.into()),
};
monotonic_clock::Host::subscribe(self, timeout, absolute)
.context("failed to call `monotonic_clock::subscribe`")
.map_err(types::Error::trap)?
if absolute {
monotonic_clock::Host::subscribe_instant(self, timeout)
.context("failed to call `monotonic_clock::subscribe_instant`")
.map_err(types::Error::trap)?
} else {
monotonic_clock::Host::subscribe_duration(self, timeout)
.context("failed to call `monotonic_clock::subscribe_duration`")
.map_err(types::Error::trap)?
}
}
types::SubscriptionU::FdRead(types::SubscriptionFdReadwrite {
file_descriptor,
Expand Down
26 changes: 19 additions & 7 deletions crates/wasi/wit/deps/clocks/monotonic-clock.wit
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,34 @@
interface monotonic-clock {
use wasi:io/[email protected].{pollable};

/// A timestamp in nanoseconds.
/// 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.
type instant = u64;

/// A duration of time, in nanoseconds.
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.
now: func() -> instant;

/// Query the resolution of the clock.
resolution: func() -> instant;
/// Query the resolution of the clock. Returns the duration of time
/// corresponding to a clock tick.
resolution: func() -> duration;

/// Create a `pollable` which will resolve once the specified time has been
/// reached.
subscribe: func(
/// Create a `pollable` which will resolve once the specified instant
/// occured.
subscribe-instant: func(
when: instant,
absolute: bool
) -> pollable;

/// Create a `pollable` which will resolve once the given duration has
/// elapsed, starting at the time at which this function was called.
/// occured.
subscribe-duration: func(
when: duration,
) -> pollable;
}