summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock9
-rw-r--r--Cargo.toml1
-rw-r--r--op_crates/timers/01_timers.js (renamed from runtime/js/11_timers.js)20
-rw-r--r--op_crates/timers/Cargo.toml18
-rw-r--r--op_crates/timers/README.md5
-rw-r--r--op_crates/timers/lib.rs165
-rw-r--r--runtime/Cargo.toml14
-rw-r--r--runtime/build.rs2
-rw-r--r--runtime/ops/timers.rs171
-rw-r--r--runtime/permissions.rs11
10 files changed, 257 insertions, 159 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 665a46b0e..f53edf434 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -674,6 +674,7 @@ dependencies = [
"deno_crypto",
"deno_fetch",
"deno_file",
+ "deno_timers",
"deno_url",
"deno_web",
"deno_webgpu",
@@ -711,6 +712,14 @@ dependencies = [
]
[[package]]
+name = "deno_timers"
+version = "0.1.0"
+dependencies = [
+ "deno_core",
+ "tokio",
+]
+
+[[package]]
name = "deno_url"
version = "0.3.0"
dependencies = [
diff --git a/Cargo.toml b/Cargo.toml
index d99f66fd3..b5ffb58f5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,6 +10,7 @@ members = [
"test_util",
"op_crates/crypto",
"op_crates/fetch",
+ "op_crates/timers",
"op_crates/url",
"op_crates/web",
"op_crates/webgpu",
diff --git a/runtime/js/11_timers.js b/op_crates/timers/01_timers.js
index cfd9ad72d..d6d6436fe 100644
--- a/runtime/js/11_timers.js
+++ b/op_crates/timers/01_timers.js
@@ -2,9 +2,27 @@
"use strict";
((window) => {
- const assert = window.__bootstrap.util.assert;
const core = window.Deno.core;
+ // Shamelessly cribbed from op_crates/fetch/11_streams.js
+ class AssertionError extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "AssertionError";
+ }
+ }
+
+ /**
+ * @param {unknown} cond
+ * @param {string=} msg
+ * @returns {asserts cond}
+ */
+ function assert(cond, msg = "Assertion failed.") {
+ if (!cond) {
+ throw new AssertionError(msg);
+ }
+ }
+
function opStopGlobalTimer() {
core.opSync("op_global_timer_stop");
}
diff --git a/op_crates/timers/Cargo.toml b/op_crates/timers/Cargo.toml
new file mode 100644
index 000000000..31d5645f2
--- /dev/null
+++ b/op_crates/timers/Cargo.toml
@@ -0,0 +1,18 @@
+# Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+
+[package]
+name = "deno_timers"
+version = "0.1.0"
+edition = "2018"
+description = "Timers API implementation for Deno"
+authors = ["the Deno authors"]
+license = "MIT"
+readme = "README.md"
+repository = "https://github.com/denoland/deno"
+
+[lib]
+path = "lib.rs"
+
+[dependencies]
+deno_core = { version = "0.84.0", path = "../../core" }
+tokio = { version = "1.4.0", features = ["full"] }
diff --git a/op_crates/timers/README.md b/op_crates/timers/README.md
new file mode 100644
index 000000000..5a2a8e516
--- /dev/null
+++ b/op_crates/timers/README.md
@@ -0,0 +1,5 @@
+# deno_timers
+
+This crate implements the timers API.
+
+Spec: https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers
diff --git a/op_crates/timers/lib.rs b/op_crates/timers/lib.rs
new file mode 100644
index 000000000..047b6923c
--- /dev/null
+++ b/op_crates/timers/lib.rs
@@ -0,0 +1,165 @@
+// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+
+//! This module helps deno implement timers.
+//!
+//! As an optimization, we want to avoid an expensive calls into rust for every
+//! setTimeout in JavaScript. Thus in //js/timers.ts a data structure is
+//! implemented that calls into Rust for only the smallest timeout. Thus we
+//! only need to be able to start, cancel and await a single timer (or Delay, as Tokio
+//! calls it) for an entire Isolate. This is what is implemented here.
+
+use deno_core::error::AnyError;
+use deno_core::futures;
+use deno_core::futures::channel::oneshot;
+use deno_core::futures::FutureExt;
+use deno_core::futures::TryFutureExt;
+use deno_core::OpState;
+use deno_core::ZeroCopyBuf;
+use std::cell::RefCell;
+use std::future::Future;
+use std::pin::Pin;
+use std::rc::Rc;
+use std::thread::sleep;
+use std::time::Duration;
+use std::time::Instant;
+
+pub trait TimersPermission {
+ fn allow_hrtime(&mut self) -> bool;
+ fn check_unstable(&self, state: &OpState, api_name: &'static str);
+}
+
+pub fn init(rt: &mut deno_core::JsRuntime) {
+ rt.execute(
+ "deno:op_crates/timers/01_timers.js",
+ include_str!("01_timers.js"),
+ )
+ .unwrap();
+}
+
+pub type StartTime = Instant;
+
+type TimerFuture = Pin<Box<dyn Future<Output = Result<(), ()>>>>;
+
+#[derive(Default)]
+pub struct GlobalTimer {
+ tx: Option<oneshot::Sender<()>>,
+ pub future: Option<TimerFuture>,
+}
+
+impl GlobalTimer {
+ pub fn cancel(&mut self) {
+ if let Some(tx) = self.tx.take() {
+ tx.send(()).ok();
+ }
+ }
+
+ pub fn new_timeout(&mut self, deadline: Instant) {
+ if self.tx.is_some() {
+ self.cancel();
+ }
+ assert!(self.tx.is_none());
+ self.future.take();
+
+ let (tx, rx) = oneshot::channel();
+ self.tx = Some(tx);
+
+ let delay = tokio::time::sleep_until(deadline.into()).boxed_local();
+ let rx = rx
+ .map_err(|err| panic!("Unexpected error in receiving channel {:?}", err));
+
+ let fut = futures::future::select(delay, rx)
+ .then(|_| futures::future::ok(()))
+ .boxed_local();
+ self.future = Some(fut);
+ }
+}
+
+#[allow(clippy::unnecessary_wraps)]
+pub fn op_global_timer_stop(
+ state: &mut OpState,
+ _args: (),
+ _zero_copy: Option<ZeroCopyBuf>,
+) -> Result<(), AnyError> {
+ let global_timer = state.borrow_mut::<GlobalTimer>();
+ global_timer.cancel();
+ Ok(())
+}
+
+// Set up a timer that will be later awaited by JS promise.
+// It's a separate op, because canceling a timeout immediately
+// after setting it caused a race condition (because Tokio timeout)
+// might have been registered after next event loop tick.
+//
+// See https://github.com/denoland/deno/issues/7599 for more
+// details.
+#[allow(clippy::unnecessary_wraps)]
+pub fn op_global_timer_start(
+ state: &mut OpState,
+ timeout: u64,
+ _zero_copy: Option<ZeroCopyBuf>,
+) -> Result<(), AnyError> {
+ let deadline = Instant::now() + Duration::from_millis(timeout);
+ let global_timer = state.borrow_mut::<GlobalTimer>();
+ global_timer.new_timeout(deadline);
+ Ok(())
+}
+
+pub async fn op_global_timer(
+ state: Rc<RefCell<OpState>>,
+ _args: (),
+ _zero_copy: Option<ZeroCopyBuf>,
+) -> Result<(), AnyError> {
+ let maybe_timer_fut = {
+ let mut s = state.borrow_mut();
+ let global_timer = s.borrow_mut::<GlobalTimer>();
+ global_timer.future.take()
+ };
+ if let Some(timer_fut) = maybe_timer_fut {
+ let _ = timer_fut.await;
+ }
+ Ok(())
+}
+
+// Returns a milliseconds and nanoseconds subsec
+// since the start time of the deno runtime.
+// If the High precision flag is not set, the
+// nanoseconds are rounded on 2ms.
+#[allow(clippy::unnecessary_wraps)]
+pub fn op_now<TP>(
+ state: &mut OpState,
+ _argument: (),
+ _zero_copy: Option<ZeroCopyBuf>,
+) -> Result<f64, AnyError>
+where
+ TP: TimersPermission + 'static,
+{
+ let start_time = state.borrow::<StartTime>();
+ let seconds = start_time.elapsed().as_secs();
+ let mut subsec_nanos = start_time.elapsed().subsec_nanos() as f64;
+ let reduced_time_precision = 2_000_000.0; // 2ms in nanoseconds
+
+ // If the permission is not enabled
+ // Round the nano result on 2 milliseconds
+ // see: https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#Reduced_time_precision
+ if !state.borrow_mut::<TP>().allow_hrtime() {
+ subsec_nanos -= subsec_nanos % reduced_time_precision;
+ }
+
+ let result = (seconds * 1_000) as f64 + (subsec_nanos / 1_000_000.0);
+
+ Ok(result)
+}
+
+#[allow(clippy::unnecessary_wraps)]
+pub fn op_sleep_sync<TP>(
+ state: &mut OpState,
+ millis: u64,
+ _zero_copy: Option<ZeroCopyBuf>,
+) -> Result<(), AnyError>
+where
+ TP: TimersPermission + 'static,
+{
+ state.borrow::<TP>().check_unstable(state, "Deno.sleepSync");
+ sleep(Duration::from_millis(millis));
+ Ok(())
+}
diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml
index f44ed64bb..b69d67838 100644
--- a/runtime/Cargo.toml
+++ b/runtime/Cargo.toml
@@ -18,32 +18,34 @@ name = "hello_runtime"
path = "examples/hello_runtime.rs"
[build-dependencies]
-deno_core = { path = "../core", version = "0.84.0" }
deno_console = { path = "../op_crates/console", version = "0.3.0" }
+deno_core = { path = "../core", version = "0.84.0" }
deno_crypto = { path = "../op_crates/crypto", version = "0.17.0" }
deno_fetch = { path = "../op_crates/fetch", version = "0.25.0" }
deno_file = { path = "../op_crates/file", version = "0.2.0" }
-deno_web = { path = "../op_crates/web", version = "0.33.0" }
+deno_timers = { path = "../op_crates/timers", version = "0.1.0" }
deno_url = { path = "../op_crates/url", version = "0.3.0" }
+deno_web = { path = "../op_crates/web", version = "0.33.0" }
+deno_webgpu = { path = "../op_crates/webgpu", version = "0.4.0" }
deno_webidl = { path = "../op_crates/webidl", version = "0.3.0" }
deno_websocket = { path = "../op_crates/websocket", version = "0.8.0" }
-deno_webgpu = { path = "../op_crates/webgpu", version = "0.4.0" }
[target.'cfg(windows)'.build-dependencies]
winres = "0.1.11"
winapi = "0.3.9"
[dependencies]
-deno_core = { path = "../core", version = "0.84.0" }
deno_console = { path = "../op_crates/console", version = "0.3.0" }
+deno_core = { path = "../core", version = "0.84.0" }
deno_crypto = { path = "../op_crates/crypto", version = "0.17.0" }
deno_fetch = { path = "../op_crates/fetch", version = "0.25.0" }
deno_file = { path = "../op_crates/file", version = "0.2.0" }
-deno_web = { path = "../op_crates/web", version = "0.33.0" }
+deno_timers = { path = "../op_crates/timers", version = "0.1.0" }
deno_url = { path = "../op_crates/url", version = "0.3.0" }
+deno_web = { path = "../op_crates/web", version = "0.33.0" }
+deno_webgpu = { path = "../op_crates/webgpu", version = "0.4.0" }
deno_webidl = { path = "../op_crates/webidl", version = "0.3.0" }
deno_websocket = { path = "../op_crates/websocket", version = "0.8.0" }
-deno_webgpu = { path = "../op_crates/webgpu", version = "0.4.0" }
atty = "0.2.14"
bytes = "1"
diff --git a/runtime/build.rs b/runtime/build.rs
index d7d8cb78f..efa949493 100644
--- a/runtime/build.rs
+++ b/runtime/build.rs
@@ -13,8 +13,10 @@ fn create_snapshot(
snapshot_path: &Path,
files: Vec<PathBuf>,
) {
+ // Initialization order matters.
deno_webidl::init(&mut js_runtime);
deno_console::init(&mut js_runtime);
+ deno_timers::init(&mut js_runtime);
deno_url::init(&mut js_runtime);
deno_web::init(&mut js_runtime);
deno_file::init(&mut js_runtime);
diff --git a/runtime/ops/timers.rs b/runtime/ops/timers.rs
index 8e709440e..3401c36f1 100644
--- a/runtime/ops/timers.rs
+++ b/runtime/ops/timers.rs
@@ -1,161 +1,28 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-
-//! This module helps deno implement timers.
-//!
-//! As an optimization, we want to avoid an expensive calls into rust for every
-//! setTimeout in JavaScript. Thus in //js/timers.ts a data structure is
-//! implemented that calls into Rust for only the smallest timeout. Thus we
-//! only need to be able to start, cancel and await a single timer (or Delay, as Tokio
-//! calls it) for an entire Isolate. This is what is implemented here.
-
use crate::permissions::Permissions;
-use deno_core::error::AnyError;
-use deno_core::futures;
-use deno_core::futures::channel::oneshot;
-use deno_core::futures::FutureExt;
-use deno_core::futures::TryFutureExt;
-use deno_core::OpState;
-use deno_core::ZeroCopyBuf;
-use std::cell::RefCell;
-use std::future::Future;
-use std::pin::Pin;
-use std::rc::Rc;
-use std::thread::sleep;
-use std::time::Duration;
-use std::time::Instant;
-
-pub type StartTime = Instant;
-
-type TimerFuture = Pin<Box<dyn Future<Output = Result<(), ()>>>>;
-
-#[derive(Default)]
-pub struct GlobalTimer {
- tx: Option<oneshot::Sender<()>>,
- pub future: Option<TimerFuture>,
-}
-
-impl GlobalTimer {
- pub fn cancel(&mut self) {
- if let Some(tx) = self.tx.take() {
- tx.send(()).ok();
- }
- }
-
- pub fn new_timeout(&mut self, deadline: Instant) {
- if self.tx.is_some() {
- self.cancel();
- }
- assert!(self.tx.is_none());
- self.future.take();
-
- let (tx, rx) = oneshot::channel();
- self.tx = Some(tx);
-
- let delay = tokio::time::sleep_until(deadline.into()).boxed_local();
- let rx = rx
- .map_err(|err| panic!("Unexpected error in receiving channel {:?}", err));
-
- let fut = futures::future::select(delay, rx)
- .then(|_| futures::future::ok(()))
- .boxed_local();
- self.future = Some(fut);
- }
-}
pub fn init(rt: &mut deno_core::JsRuntime) {
{
let op_state = rt.op_state();
let mut state = op_state.borrow_mut();
- state.put::<GlobalTimer>(GlobalTimer::default());
- state.put::<StartTime>(StartTime::now());
+ state.put(deno_timers::GlobalTimer::default());
+ state.put(deno_timers::StartTime::now());
}
- super::reg_sync(rt, "op_global_timer_stop", op_global_timer_stop);
- super::reg_sync(rt, "op_global_timer_start", op_global_timer_start);
- super::reg_async(rt, "op_global_timer", op_global_timer);
- super::reg_sync(rt, "op_now", op_now);
- super::reg_sync(rt, "op_sleep_sync", op_sleep_sync);
-}
-
-#[allow(clippy::unnecessary_wraps)]
-fn op_global_timer_stop(
- state: &mut OpState,
- _args: (),
- _zero_copy: Option<ZeroCopyBuf>,
-) -> Result<(), AnyError> {
- let global_timer = state.borrow_mut::<GlobalTimer>();
- global_timer.cancel();
- Ok(())
-}
-
-// Set up a timer that will be later awaited by JS promise.
-// It's a separate op, because canceling a timeout immediately
-// after setting it caused a race condition (because Tokio timeout)
-// might have been registered after next event loop tick.
-//
-// See https://github.com/denoland/deno/issues/7599 for more
-// details.
-#[allow(clippy::unnecessary_wraps)]
-fn op_global_timer_start(
- state: &mut OpState,
- timeout: u64,
- _zero_copy: Option<ZeroCopyBuf>,
-) -> Result<(), AnyError> {
- let deadline = Instant::now() + Duration::from_millis(timeout);
- let global_timer = state.borrow_mut::<GlobalTimer>();
- global_timer.new_timeout(deadline);
- Ok(())
-}
-
-async fn op_global_timer(
- state: Rc<RefCell<OpState>>,
- _args: (),
- _zero_copy: Option<ZeroCopyBuf>,
-) -> Result<(), AnyError> {
- let maybe_timer_fut = {
- let mut s = state.borrow_mut();
- let global_timer = s.borrow_mut::<GlobalTimer>();
- global_timer.future.take()
- };
- if let Some(timer_fut) = maybe_timer_fut {
- let _ = timer_fut.await;
- }
- Ok(())
-}
-
-// Returns a milliseconds and nanoseconds subsec
-// since the start time of the deno runtime.
-// If the High precision flag is not set, the
-// nanoseconds are rounded on 2ms.
-#[allow(clippy::unnecessary_wraps)]
-fn op_now(
- op_state: &mut OpState,
- _argument: (),
- _zero_copy: Option<ZeroCopyBuf>,
-) -> Result<f64, AnyError> {
- let start_time = op_state.borrow::<StartTime>();
- let seconds = start_time.elapsed().as_secs();
- let mut subsec_nanos = start_time.elapsed().subsec_nanos() as f64;
- let reduced_time_precision = 2_000_000.0; // 2ms in nanoseconds
-
- // If the permission is not enabled
- // Round the nano result on 2 milliseconds
- // see: https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#Reduced_time_precision
- if op_state.borrow_mut::<Permissions>().hrtime.check().is_err() {
- subsec_nanos -= subsec_nanos % reduced_time_precision;
- }
-
- let result = (seconds * 1_000) as f64 + (subsec_nanos / 1_000_000.0);
-
- Ok(result)
-}
-
-#[allow(clippy::unnecessary_wraps)]
-fn op_sleep_sync(
- state: &mut OpState,
- millis: u64,
- _zero_copy: Option<ZeroCopyBuf>,
-) -> Result<(), AnyError> {
- super::check_unstable(state, "Deno.sleepSync");
- sleep(Duration::from_millis(millis));
- Ok(())
+ super::reg_sync(
+ rt,
+ "op_global_timer_stop",
+ deno_timers::op_global_timer_stop,
+ );
+ super::reg_sync(
+ rt,
+ "op_global_timer_start",
+ deno_timers::op_global_timer_start,
+ );
+ super::reg_async(rt, "op_global_timer", deno_timers::op_global_timer);
+ super::reg_sync(rt, "op_now", deno_timers::op_now::<Permissions>);
+ super::reg_sync(
+ rt,
+ "op_sleep_sync",
+ deno_timers::op_sleep_sync::<Permissions>,
+ );
}
diff --git a/runtime/permissions.rs b/runtime/permissions.rs
index e43e0eaa0..3bdfd16bb 100644
--- a/runtime/permissions.rs
+++ b/runtime/permissions.rs
@@ -9,6 +9,7 @@ use deno_core::serde::Deserialize;
use deno_core::serde::Serialize;
use deno_core::url;
use deno_core::ModuleSpecifier;
+use deno_core::OpState;
use log::debug;
use std::collections::HashSet;
use std::fmt;
@@ -968,6 +969,16 @@ impl deno_fetch::FetchPermissions for Permissions {
}
}
+impl deno_timers::TimersPermission for Permissions {
+ fn allow_hrtime(&mut self) -> bool {
+ self.hrtime.check().is_ok()
+ }
+
+ fn check_unstable(&self, state: &OpState, api_name: &'static str) {
+ crate::ops::check_unstable(state, api_name);
+ }
+}
+
impl deno_websocket::WebSocketPermissions for Permissions {
fn check_net_url(&mut self, url: &url::Url) -> Result<(), AnyError> {
self.net.check_url(url)