diff options
author | Matt Mastracci <matthew@mastracci.com> | 2024-02-16 08:35:51 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-16 15:35:51 +0000 |
commit | f705906256f4a4d4ec262db64322c9fc3e5beb81 (patch) | |
tree | 1138630ebc1b10a2d0d71343568fa13f146bbfbe | |
parent | e06934fe77cb7991fc2c767f221577a2e96506a7 (diff) |
chore(ext/web): refactor timer ops before landing op sanitizer (#22435)
Splitting the sleep and interval ops allows us to detect an interval
timer. We also remove the use of the `op_async_void_deferred` call.
A future PR will be able to split the op sanitizer messages for timers
and intervals.
-rw-r--r-- | cli/tools/test/fmt.rs | 1 | ||||
-rw-r--r-- | ext/web/02_timers.js | 22 | ||||
-rw-r--r-- | ext/web/lib.rs | 2 | ||||
-rw-r--r-- | ext/web/timers.rs | 17 |
4 files changed, 29 insertions, 13 deletions
diff --git a/cli/tools/test/fmt.rs b/cli/tools/test/fmt.rs index b0b37b5c4..931caf147 100644 --- a/cli/tools/test/fmt.rs +++ b/cli/tools/test/fmt.rs @@ -271,6 +271,7 @@ pub const OP_DETAILS: phf::Map<&'static str, [&'static str; 2]> = phf_map! { "op_run_status" => ["get the status of a subprocess", "awaiting the result of a `Deno.Process#status` call"], "op_seek_async" => ["seek in a file", "awaiting the result of a `Deno.File#seek` call"], "op_signal_poll" => ["get the next signal", "un-registering a OS signal handler"], + "op_sleep_interval" => ["sleep for a duration", "cancelling a `setTimeout` or `setInterval` call"], "op_sleep" => ["sleep for a duration", "cancelling a `setTimeout` or `setInterval` call"], "op_stat_async" => ["get file metadata", "awaiting the result of a `Deno.stat` call"], "op_symlink_async" => ["create a symlink", "awaiting the result of a `Deno.symlink` call"], diff --git a/ext/web/02_timers.js b/ext/web/02_timers.js index 2241e9614..8096c2ab5 100644 --- a/ext/web/02_timers.js +++ b/ext/web/02_timers.js @@ -4,8 +4,8 @@ import { core, primordials } from "ext:core/mod.js"; import { op_now, op_sleep, + op_sleep_interval, op_timer_handle, - op_void_async_deferred, } from "ext:core/ops"; const { ArrayPrototypePush, @@ -193,6 +193,7 @@ function initializeTimer( task, timeout, timerInfo, + repeat, ); return id; @@ -220,19 +221,13 @@ const scheduledTimers = { head: null, tail: null }; * after the timeout, if it hasn't been cancelled. * @param {number} millis * @param {{ cancelRid: number, isRef: boolean, promise: Promise<void> }} timerInfo + * @param {boolean} repeat */ -function runAfterTimeout(task, millis, timerInfo) { +function runAfterTimeout(task, millis, timerInfo, repeat) { const cancelRid = timerInfo.cancelRid; - let sleepPromise; - // If this timeout is scheduled for 0ms it means we want it to run at the - // end of the event loop turn. There's no point in setting up a Tokio timer, - // since its lowest resolution is 1ms. Firing of a "void async" op is better - // in this case, because the timer will take closer to 0ms instead of >1ms. - if (millis === 0) { - sleepPromise = op_void_async_deferred(); - } else { - sleepPromise = op_sleep(millis, cancelRid); - } + const sleepPromise = repeat + ? op_sleep_interval(millis, cancelRid) + : op_sleep(millis, cancelRid); timerInfo.promise = sleepPromise; if (!timerInfo.isRef) { core.unrefOpPromise(timerInfo.promise); @@ -395,7 +390,8 @@ function unrefTimer(id) { // for that reason: it lets promises make forward progress but can // still starve other parts of the event loop. function defer(go) { - PromisePrototypeThen(op_void_async_deferred(), () => go()); + // If we pass a delay of zero to op_sleep, it returns at the next event spin + PromisePrototypeThen(op_sleep(0, 0), () => go()); } export { diff --git a/ext/web/lib.rs b/ext/web/lib.rs index 3defbd74e..0601aff24 100644 --- a/ext/web/lib.rs +++ b/ext/web/lib.rs @@ -52,6 +52,7 @@ pub use crate::message_port::MessagePort; use crate::timers::op_now; use crate::timers::op_sleep; +use crate::timers::op_sleep_interval; use crate::timers::op_timer_handle; use crate::timers::StartTime; pub use crate::timers::TimersPermission; @@ -86,6 +87,7 @@ deno_core::extension!(deno_web, op_now<P>, op_timer_handle, op_sleep, + op_sleep_interval, op_transfer_arraybuffer, stream_resource::op_readable_stream_resource_allocate, stream_resource::op_readable_stream_resource_allocate_sized, diff --git a/ext/web/timers.rs b/ext/web/timers.rs index f7a0167df..f01dcb860 100644 --- a/ext/web/timers.rs +++ b/ext/web/timers.rs @@ -75,6 +75,16 @@ pub fn op_timer_handle(state: &mut OpState) -> ResourceId { .add(TimerHandle(CancelHandle::new_rc())) } +/// Bifurcate the op_sleep op into an interval one we can use for sanitization purposes. +#[op2(async(lazy), fast)] +pub async fn op_sleep_interval( + state: Rc<RefCell<OpState>>, + #[smi] millis: u64, + #[smi] rid: ResourceId, +) -> Result<bool, AnyError> { + op_sleep::call(state, millis, rid).await +} + /// Waits asynchronously until either `millis` milliseconds have passed or the /// [`TimerHandle`] resource given by `rid` has been canceled. /// @@ -85,6 +95,13 @@ pub async fn op_sleep( #[smi] millis: u64, #[smi] rid: ResourceId, ) -> Result<bool, AnyError> { + // If this timeout is scheduled for 0ms it means we want it to run at the + // end of the event loop turn. Since this is a lazy op, we can just return + // having already spun the event loop. + if millis == 0 { + return Ok(true); + } + // If the timer is not present in the resource table it was cancelled before // this op was polled. let Ok(handle) = state.borrow().resource_table.get::<TimerHandle>(rid) else { |