summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatt Mastracci <matthew@mastracci.com>2024-02-16 08:35:51 -0700
committerGitHub <noreply@github.com>2024-02-16 15:35:51 +0000
commitf705906256f4a4d4ec262db64322c9fc3e5beb81 (patch)
tree1138630ebc1b10a2d0d71343568fa13f146bbfbe
parente06934fe77cb7991fc2c767f221577a2e96506a7 (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.rs1
-rw-r--r--ext/web/02_timers.js22
-rw-r--r--ext/web/lib.rs2
-rw-r--r--ext/web/timers.rs17
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 {