mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-15 04:49:52 +00:00
Merge pull request #6584 from dotCooCoo/fix/web-plugin-budget-clock
Plug-ins: the web build times the first-view budget and each run's limit
This commit is contained in:
@@ -22,6 +22,10 @@ craft-vectorcraft-format = { workspace = true }
|
||||
sha2 = { version = "0.10", default-features = false }
|
||||
thiserror = { workspace = true }
|
||||
wasmi = { workspace = true }
|
||||
# The monotonic clock of run deadlines and the first-view budget: `std::time::Instant` on the
|
||||
# desktop, the page's `performance.now()` in the web build. It is MIT OR Apache-2.0 and already in
|
||||
# the build through eframe.
|
||||
web-time = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
wat = { workspace = true }
|
||||
|
||||
@@ -86,20 +86,15 @@ pub struct Refreshed {
|
||||
pub failed: usize,
|
||||
}
|
||||
|
||||
/// Milliseconds on a monotonic clock, `None` where there is none (the web build).
|
||||
/// Milliseconds on a monotonic clock, `None` from a clock that cannot be read.
|
||||
pub type Clock = Arc<dyn Fn() -> Option<u64> + Send + Sync>;
|
||||
|
||||
/// Milliseconds since the first reading in this process (native builds); `None` on the web build,
|
||||
/// whose `std` has no monotonic clock.
|
||||
/// Milliseconds since the first reading in this process, from `web_time::Instant`: the system's
|
||||
/// monotonic clock on the desktop and the page's `performance.now()` in the web build.
|
||||
fn monotonic() -> Option<u64> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
static START: std::sync::OnceLock<std::time::Instant> = std::sync::OnceLock::new();
|
||||
let start = START.get_or_init(std::time::Instant::now);
|
||||
Some(u64::try_from(start.elapsed().as_millis()).unwrap_or(u64::MAX))
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
None
|
||||
static START: std::sync::OnceLock<web_time::Instant> = std::sync::OnceLock::new();
|
||||
let start = START.get_or_init(web_time::Instant::now);
|
||||
Some(u64::try_from(start.elapsed().as_millis()).unwrap_or(u64::MAX))
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
@@ -579,6 +574,14 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
/// The default clock, which the web build also reads, gives the first view its budget.
|
||||
#[test]
|
||||
fn the_default_clock_gives_the_first_view_a_budget() {
|
||||
let d = Deferrals::new();
|
||||
assert!(matches!(d.allowance(), Allowance::Until { ms: OPENING_BUDGET_MS, .. }));
|
||||
assert!(!d.passed(u64::MAX));
|
||||
}
|
||||
|
||||
/// A stage drawn with several inputs (the paths of a group) holds one record for each, and is
|
||||
/// listed and counted once.
|
||||
#[test]
|
||||
|
||||
@@ -24,8 +24,7 @@ use crate::{Error, Kind, Limits, Plugin, Result, registry};
|
||||
/// document: 200 times a filter's base budget.
|
||||
pub const FUEL: u64 = 10_000_000_000;
|
||||
|
||||
/// Wall-clock budget of one export, in milliseconds (native builds; the web build has no clock,
|
||||
/// and the instruction budget alone bounds it there).
|
||||
/// Wall-clock budget of one export, in milliseconds.
|
||||
pub const WALL_TIME_MS: u64 = 300_000;
|
||||
|
||||
/// Most warnings an export reports.
|
||||
|
||||
+14
-14
@@ -321,29 +321,20 @@ pub struct Report {
|
||||
/// The limit a run past its wall-clock deadline exceeded ([`Error::Limit`]).
|
||||
pub(crate) const TIME_BUDGET: &str = "time budget";
|
||||
|
||||
/// When a run must stop (native: a wall-clock deadline).
|
||||
/// When a run must stop: a deadline on the monotonic clock (`web_time::Instant`, the page's
|
||||
/// `performance.now()` in the web build).
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct Deadline {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
at: std::time::Instant,
|
||||
at: web_time::Instant,
|
||||
}
|
||||
|
||||
impl Deadline {
|
||||
pub(crate) fn new(wall_time_ms: u64) -> Self {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let _ = wall_time_ms;
|
||||
Self {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
at: std::time::Instant::now() + std::time::Duration::from_millis(wall_time_ms),
|
||||
}
|
||||
Self { at: web_time::Instant::now() + std::time::Duration::from_millis(wall_time_ms) }
|
||||
}
|
||||
|
||||
pub(crate) fn passed(&self) -> bool {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if std::time::Instant::now() >= self.at {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
web_time::Instant::now() >= self.at
|
||||
}
|
||||
|
||||
pub(crate) fn check(&self) -> Result<()> {
|
||||
@@ -816,6 +807,15 @@ mod tests {
|
||||
Run::new("test", true, false, Deadline::new(60_000))
|
||||
}
|
||||
|
||||
/// A deadline reads the monotonic clock that the web build also reads: one of 0 ms has
|
||||
/// passed, and one a day away has not.
|
||||
#[test]
|
||||
fn a_deadline_passes_on_the_monotonic_clock() {
|
||||
assert!(Deadline::new(0).passed());
|
||||
assert!(matches!(Deadline::new(0).check(), Err(Error::Limit(l)) if l == TIME_BUDGET));
|
||||
assert!(!Deadline::new(86_400_000).passed());
|
||||
}
|
||||
|
||||
/// Progress is clamped: negative counts are 0 and `done` never passes a known total.
|
||||
#[test]
|
||||
fn progress_is_clamped() {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
//! permission): no WASI, file system, network, clock or randomness. A module that
|
||||
//! imports anything else is rejected;
|
||||
//! - an **instruction budget** (fuel) per call, metered in slices so a wall-clock deadline is
|
||||
//! also enforced (native builds), so infinite loops end with an error. A filter may ask for a
|
||||
//! also enforced, so infinite loops end with an error. A filter may ask for a
|
||||
//! larger one ([`Budget::Heavy`]), which the host lets the user allow before installing it;
|
||||
//! - a **linear-memory cap**, a **recursion-depth cap** and wasmi's strict module limits;
|
||||
//! - a **fresh instance per run**, so no state leaks between calls; what a plug-in keeps is only
|
||||
|
||||
@@ -29,7 +29,7 @@ const FUEL_COSTS: CustomFuelCosts = CustomFuelCosts { bytes_copied_per_fuel: 64,
|
||||
/// the host learns that only once the run has ended.
|
||||
pub const HEAVY_FUEL: u64 = 1_000_000_000;
|
||||
|
||||
/// Wall-clock budget of a heavy filter's run, in milliseconds (native builds).
|
||||
/// Wall-clock budget of a heavy filter's run, in milliseconds.
|
||||
pub const HEAVY_WALL_TIME_MS: u64 = 300_000;
|
||||
|
||||
/// The limit a run reports when its input and parameters together are past half its memory.
|
||||
@@ -56,8 +56,8 @@ pub struct Limits {
|
||||
pub fuel_per_byte: u64,
|
||||
/// Deepest call nesting.
|
||||
pub max_recursion_depth: usize,
|
||||
/// Wall-clock budget for one run, in milliseconds. Native builds only: the web build has no
|
||||
/// monotonic clock in `std`, so the fuel budget alone bounds it there.
|
||||
/// Wall-clock budget for one run, in milliseconds. The web build measures it with the page's
|
||||
/// `performance.now()`.
|
||||
pub wall_time_ms: u64,
|
||||
/// The most a heavy filter ([`crate::Budget::Heavy`]) is given instead of `fuel_base` and
|
||||
/// `wall_time_ms`: what the host allows, which the module's manifest can ask for but never
|
||||
|
||||
Reference in New Issue
Block a user