summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/01_core.js25
-rw-r--r--core/Cargo.toml1
-rw-r--r--core/bindings.rs370
-rw-r--r--core/error_codes.rs2
-rw-r--r--core/examples/disable_ops.rs2
-rw-r--r--core/examples/hello_world.rs34
-rw-r--r--core/examples/http_bench_json_ops.js4
-rw-r--r--core/examples/http_bench_json_ops.rs13
-rw-r--r--core/examples/schedule_task.rs12
-rw-r--r--core/extensions.rs9
-rw-r--r--core/lib.rs31
-rw-r--r--core/modules.rs41
-rw-r--r--core/ops.rs147
-rw-r--r--core/ops_builtin.rs94
-rw-r--r--core/ops_json.rs166
-rw-r--r--core/ops_metrics.rs12
-rw-r--r--core/runtime.rs293
17 files changed, 456 insertions, 800 deletions
diff --git a/core/01_core.js b/core/01_core.js
index b6c72e5d2..747d69241 100644
--- a/core/01_core.js
+++ b/core/01_core.js
@@ -16,7 +16,6 @@
ErrorCaptureStackTrace,
Promise,
ObjectEntries,
- ObjectFreeze,
ObjectFromEntries,
MapPrototypeGet,
MapPrototypeDelete,
@@ -27,11 +26,12 @@
ObjectAssign,
SymbolFor,
} = window.__bootstrap.primordials;
+ const ops = window.Deno.core.ops;
+ const opIds = Object.keys(ops).reduce((a, v, i) => ({ ...a, [v]: i }), {});
// Available on start due to bindings.
- const { opcallSync, opcallAsync, refOp_, unrefOp_ } = window.Deno.core;
+ const { refOp_, unrefOp_ } = window.Deno.core;
- let opsCache = {};
const errorMap = {};
// Builtin v8 / JS errors
registerErrorClass("Error", Error);
@@ -110,15 +110,6 @@
return promiseRing[idx] != NO_PROMISE;
}
- function ops() {
- return opsCache;
- }
-
- function syncOpsCache() {
- // op id 0 is a special value to retrieve the map of registered ops.
- opsCache = ObjectFreeze(ObjectFromEntries(opcallSync(0)));
- }
-
function opresolve() {
for (let i = 0; i < arguments.length; i += 2) {
const promiseId = arguments[i];
@@ -160,7 +151,7 @@
function opAsync(opName, arg1 = null, arg2 = null) {
const promiseId = nextPromiseId++;
- const maybeError = opcallAsync(opsCache[opName], promiseId, arg1, arg2);
+ const maybeError = ops[opName](opIds[opName], promiseId, arg1, arg2);
// Handle sync error (e.g: error parsing args)
if (maybeError) return unwrapOpResult(maybeError);
let p = PromisePrototypeThen(setPromise(promiseId), unwrapOpResult);
@@ -179,8 +170,8 @@
return p;
}
- function opSync(opName, arg1 = null, arg2 = null) {
- return unwrapOpResult(opcallSync(opsCache[opName], arg1, arg2));
+ function opSync(opName, arg1, arg2) {
+ return unwrapOpResult(ops[opName](opIds[opName], arg1, arg2));
}
function refOp(promiseId) {
@@ -228,7 +219,7 @@
function metrics() {
const [aggregate, perOps] = opSync("op_metrics");
aggregate.ops = ObjectFromEntries(ArrayPrototypeMap(
- ObjectEntries(opsCache),
+ ObjectEntries(opIds),
([opName, opId]) => [opName, perOps[opId]],
));
return aggregate;
@@ -257,7 +248,6 @@
const core = ObjectAssign(globalThis.Deno.core, {
opAsync,
opSync,
- ops,
close,
tryClose,
read,
@@ -269,7 +259,6 @@
registerErrorBuilder,
registerErrorClass,
opresolve,
- syncOpsCache,
BadResource,
BadResourcePrototype,
Interrupted,
diff --git a/core/Cargo.toml b/core/Cargo.toml
index db18b025e..b8ae6bff4 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -14,6 +14,7 @@ path = "lib.rs"
[dependencies]
anyhow = "1.0.55"
+deno_ops = { path = "../ops", version = "0.1.0" }
futures = "0.3.21"
indexmap = "1.7.0"
libc = "0.2.106"
diff --git a/core/bindings.rs b/core/bindings.rs
index f84711abf..4e5c68675 100644
--- a/core/bindings.rs
+++ b/core/bindings.rs
@@ -1,6 +1,7 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use crate::error::is_instance_of_error;
+use crate::extensions::OpPair;
use crate::modules::get_module_type_from_assertions;
use crate::modules::parse_import_assertions;
use crate::modules::validate_import_assertions;
@@ -8,21 +9,18 @@ use crate::modules::ImportAssertionsKind;
use crate::modules::ModuleMap;
use crate::resolve_url_or_path;
use crate::JsRuntime;
-use crate::Op;
-use crate::OpId;
-use crate::OpPayload;
-use crate::OpResult;
-use crate::OpTable;
+use crate::OpState;
use crate::PromiseId;
use crate::ZeroCopyBuf;
use anyhow::Error;
use log::debug;
-use once_cell::sync::Lazy;
use serde::Deserialize;
use serde::Serialize;
use serde_v8::to_v8;
use std::cell::RefCell;
use std::option::Option;
+use std::os::raw::c_void;
+use std::rc::Rc;
use url::Url;
use v8::HandleScope;
use v8::Local;
@@ -31,88 +29,88 @@ use v8::SharedArrayBuffer;
use v8::ValueDeserializerHelper;
use v8::ValueSerializerHelper;
-const UNDEFINED_OP_ID_MSG: &str =
- "invalid op id: received `undefined` instead of an integer.
-This error is often caused by a typo in an op name, or not calling
-JsRuntime::sync_ops_cache() after JsRuntime initialization.";
-
-pub static EXTERNAL_REFERENCES: Lazy<v8::ExternalReferences> =
- Lazy::new(|| {
- v8::ExternalReferences::new(&[
- v8::ExternalReference {
- function: opcall_async.map_fn_to(),
- },
- v8::ExternalReference {
- function: opcall_sync.map_fn_to(),
- },
- v8::ExternalReference {
- function: ref_op.map_fn_to(),
- },
- v8::ExternalReference {
- function: unref_op.map_fn_to(),
- },
- v8::ExternalReference {
- function: set_macrotask_callback.map_fn_to(),
- },
- v8::ExternalReference {
- function: set_nexttick_callback.map_fn_to(),
- },
- v8::ExternalReference {
- function: set_promise_reject_callback.map_fn_to(),
- },
- v8::ExternalReference {
- function: set_uncaught_exception_callback.map_fn_to(),
- },
- v8::ExternalReference {
- function: run_microtasks.map_fn_to(),
- },
- v8::ExternalReference {
- function: has_tick_scheduled.map_fn_to(),
- },
- v8::ExternalReference {
- function: set_has_tick_scheduled.map_fn_to(),
- },
- v8::ExternalReference {
- function: eval_context.map_fn_to(),
- },
- v8::ExternalReference {
- function: queue_microtask.map_fn_to(),
- },
- v8::ExternalReference {
- function: create_host_object.map_fn_to(),
- },
- v8::ExternalReference {
- function: encode.map_fn_to(),
- },
- v8::ExternalReference {
- function: decode.map_fn_to(),
- },
- v8::ExternalReference {
- function: serialize.map_fn_to(),
- },
- v8::ExternalReference {
- function: deserialize.map_fn_to(),
- },
- v8::ExternalReference {
- function: get_promise_details.map_fn_to(),
- },
- v8::ExternalReference {
- function: get_proxy_details.map_fn_to(),
- },
- v8::ExternalReference {
- function: is_proxy.map_fn_to(),
- },
- v8::ExternalReference {
- function: memory_usage.map_fn_to(),
- },
- v8::ExternalReference {
- function: call_console.map_fn_to(),
- },
- v8::ExternalReference {
- function: set_wasm_streaming_callback.map_fn_to(),
- },
- ])
+pub fn external_references(
+ ops: &[OpPair],
+ op_state: Rc<RefCell<OpState>>,
+) -> v8::ExternalReferences {
+ let mut refs = vec![
+ v8::ExternalReference {
+ function: ref_op.map_fn_to(),
+ },
+ v8::ExternalReference {
+ function: unref_op.map_fn_to(),
+ },
+ v8::ExternalReference {
+ function: set_macrotask_callback.map_fn_to(),
+ },
+ v8::ExternalReference {
+ function: set_nexttick_callback.map_fn_to(),
+ },
+ v8::ExternalReference {
+ function: set_promise_reject_callback.map_fn_to(),
+ },
+ v8::ExternalReference {
+ function: set_uncaught_exception_callback.map_fn_to(),
+ },
+ v8::ExternalReference {
+ function: run_microtasks.map_fn_to(),
+ },
+ v8::ExternalReference {
+ function: has_tick_scheduled.map_fn_to(),
+ },
+ v8::ExternalReference {
+ function: set_has_tick_scheduled.map_fn_to(),
+ },
+ v8::ExternalReference {
+ function: eval_context.map_fn_to(),
+ },
+ v8::ExternalReference {
+ function: queue_microtask.map_fn_to(),
+ },
+ v8::ExternalReference {
+ function: create_host_object.map_fn_to(),
+ },
+ v8::ExternalReference {
+ function: encode.map_fn_to(),
+ },
+ v8::ExternalReference {
+ function: decode.map_fn_to(),
+ },
+ v8::ExternalReference {
+ function: serialize.map_fn_to(),
+ },
+ v8::ExternalReference {
+ function: deserialize.map_fn_to(),
+ },
+ v8::ExternalReference {
+ function: get_promise_details.map_fn_to(),
+ },
+ v8::ExternalReference {
+ function: get_proxy_details.map_fn_to(),
+ },
+ v8::ExternalReference {
+ function: is_proxy.map_fn_to(),
+ },
+ v8::ExternalReference {
+ function: memory_usage.map_fn_to(),
+ },
+ v8::ExternalReference {
+ function: call_console.map_fn_to(),
+ },
+ v8::ExternalReference {
+ function: set_wasm_streaming_callback.map_fn_to(),
+ },
+ ];
+ let op_refs = ops
+ .iter()
+ .map(|(_, opref)| v8::ExternalReference { function: *opref });
+ refs.extend(op_refs);
+ let raw_op_state = Rc::as_ptr(&op_state) as *mut c_void;
+ refs.push(v8::ExternalReference {
+ pointer: raw_op_state,
});
+ v8::ExternalReferences::new(&refs)
+}
pub fn script_origin<'a>(
s: &mut v8::HandleScope<'a>,
@@ -154,6 +152,9 @@ pub fn module_origin<'a>(
pub fn initialize_context<'s>(
scope: &mut v8::HandleScope<'s, ()>,
+ ops: &[OpPair],
+ snapshot_loaded: bool,
+ op_state: Rc<RefCell<OpState>>,
) -> v8::Local<'s, v8::Context> {
let scope = &mut v8::EscapableHandleScope::new(scope);
@@ -162,17 +163,43 @@ pub fn initialize_context<'s>(
let scope = &mut v8::ContextScope::new(scope, context);
- // global.Deno = { core: {} };
let deno_key = v8::String::new(scope, "Deno").unwrap();
+ let core_key = v8::String::new(scope, "core").unwrap();
+ let ops_key = v8::String::new(scope, "ops").unwrap();
+ // Snapshot already registered `Deno.core.ops` but
+ // extensions may provide ops that aren't part of the snapshot.
+ //
+ // TODO(@littledivy): This is extra complexity for
+ // a really weird usecase. Remove this once all
+ // tsc ops are static at snapshot time.
+ if snapshot_loaded {
+ // Grab Deno.core.ops object
+ let deno_val = global.get(scope, deno_key.into()).unwrap();
+ let deno_val = v8::Local::<v8::Object>::try_from(deno_val)
+ .expect("`Deno` not in global scope.");
+ let core_val = deno_val.get(scope, core_key.into()).unwrap();
+ let core_val = v8::Local::<v8::Object>::try_from(core_val)
+ .expect("`Deno.core` not in global scope");
+ let ops_val = core_val.get(scope, ops_key.into()).unwrap();
+ let ops_val = v8::Local::<v8::Object>::try_from(ops_val)
+ .expect("`Deno.core.ops` not in global scope");
+
+ let raw_op_state = Rc::as_ptr(&op_state) as *const c_void;
+ for (name, opfn) in ops {
+ set_func_raw(scope, ops_val, name, *opfn, raw_op_state);
+ }
+ return scope.escape(context);
+ }
+
+ // global.Deno = { core: { ops: {} } };
let deno_val = v8::Object::new(scope);
global.set(scope, deno_key.into(), deno_val.into());
- let core_key = v8::String::new(scope, "core").unwrap();
let core_val = v8::Object::new(scope);
deno_val.set(scope, core_key.into(), core_val.into());
+ let ops_val = v8::Object::new(scope);
+ core_val.set(scope, ops_key.into(), ops_val.into());
// Bind functions to Deno.core.*
- set_func(scope, core_val, "opcallSync", opcall_sync);
- set_func(scope, core_val, "opcallAsync", opcall_async);
set_func(scope, core_val, "refOp_", ref_op);
set_func(scope, core_val, "unrefOp_", unref_op);
set_func(
@@ -227,10 +254,14 @@ pub fn initialize_context<'s>(
// Direct bindings on `window`.
set_func(scope, global, "queueMicrotask", queue_microtask);
+ // Bind functions to Deno.core.ops.*
+ let raw_op_state = Rc::as_ptr(&op_state) as *const c_void;
+ for (name, opfn) in ops {
+ set_func_raw(scope, ops_val, name, *opfn, raw_op_state);
+ }
scope.escape(context)
}
-#[inline(always)]
pub fn set_func(
scope: &mut v8::HandleScope<'_>,
obj: v8::Local<v8::Object>,
@@ -238,8 +269,26 @@ pub fn set_func(
callback: impl v8::MapFnTo<v8::FunctionCallback>,
) {
let key = v8::String::new(scope, name).unwrap();
- let tmpl = v8::FunctionTemplate::new(scope, callback);
- let val = tmpl.get_function(scope).unwrap();
+ let val = v8::Function::new(scope, callback).unwrap();
+ val.set_name(key);
+ obj.set(scope, key.into(), val.into());
+}
+
+// Register a raw v8::FunctionCallback
+// with some external data.
+pub fn set_func_raw(
+ scope: &mut v8::HandleScope<'_>,
+ obj: v8::Local<v8::Object>,
+ name: &'static str,
+ callback: v8::FunctionCallback,
+ external_data: *const c_void,
+) {
+ let key = v8::String::new(scope, name).unwrap();
+ let external = v8::External::new(scope, external_data as *mut c_void);
+ let val = v8::Function::builder_raw(callback)
+ .data(external.into())
+ .build(scope)
+ .unwrap();
val.set_name(key);
obj.set(scope, key.into(), val.into());
}
@@ -460,137 +509,6 @@ pub extern "C" fn promise_reject_callback(message: v8::PromiseRejectMessage) {
}
}
-fn opcall_sync<'s>(
- scope: &mut v8::HandleScope<'s>,
- args: v8::FunctionCallbackArguments,
- mut rv: v8::ReturnValue,
-) {
- let state_rc = JsRuntime::state(scope);
- let state = state_rc.borrow_mut();
-
- let op_id = match v8::Local::<v8::Integer>::try_from(args.get(0))
- .map(|l| l.value() as OpId)
- .map_err(Error::from)
- {
- Ok(op_id) => op_id,
- Err(err) => {
- let msg = if args.get(0).is_undefined() {
- UNDEFINED_OP_ID_MSG.to_string()
- } else {
- format!("invalid op id: {}", err)
- };
- throw_type_error(scope, msg);
- return;
- }
- };
-
- // opcall(0) returns obj of all ops, handle as special case
- if op_id == 0 {
- // TODO: Serialize as HashMap when serde_v8 supports maps ...
- let ops = OpTable::op_entries(state.op_state.clone());
- rv.set(to_v8(scope, ops).unwrap());
- return;
- }
-
- // Deserializable args (may be structured args or ZeroCopyBuf)
- let a = args.get(1);
- let b = args.get(2);
-
- let payload = OpPayload {
- scope,
- a,
- b,
- op_id,
- promise_id: 0,
- };
- let op = OpTable::route_op(op_id, state.op_state.clone(), payload);
- match op {
- Op::Sync(result) => {
- state.op_state.borrow().tracker.track_sync(op_id);
- rv.set(result.to_v8(scope).unwrap());
- }
- Op::NotFound => {
- throw_type_error(scope, format!("Unknown op id: {}", op_id));
- }
- // Async ops (ref or unref)
- _ => {
- throw_type_error(
- scope,
- format!("Can not call an async op [{}] with opSync()", op_id),
- );
- }
- }
-}
-
-fn opcall_async<'s>(
- scope: &mut v8::HandleScope<'s>,
- args: v8::FunctionCallbackArguments,
- mut rv: v8::ReturnValue,
-) {
- let state_rc = JsRuntime::state(scope);
- let mut state = state_rc.borrow_mut();
-
- let op_id = match v8::Local::<v8::Integer>::try_from(args.get(0))
- .map(|l| l.value() as OpId)
- .map_err(Error::from)
- {
- Ok(op_id) => op_id,
- Err(err) => {
- let msg = if args.get(0).is_undefined() {
- UNDEFINED_OP_ID_MSG.to_string()
- } else {
- format!("invalid op id: {}", err)
- };
- throw_type_error(scope, msg);
- return;
- }
- };
-
- // PromiseId
- let arg1 = args.get(1);
- let promise_id = v8::Local::<v8::Integer>::try_from(arg1)
- .map(|l| l.value() as PromiseId)
- .map_err(Error::from);
- // Fail if promise id invalid (not an int)
- let promise_id: PromiseId = match promise_id {
- Ok(promise_id) => promise_id,
- Err(err) => {
- throw_type_error(scope, format!("invalid promise id: {}", err));
- return;
- }
- };
-
- // Deserializable args (may be structured args or ZeroCopyBuf)
- let a = args.get(2);
- let b = args.get(3);
-
- let payload = OpPayload {
- scope,
- a,
- b,
- op_id,
- promise_id,
- };
- let op = OpTable::route_op(op_id, state.op_state.clone(), payload);
- match op {
- Op::Sync(result) => match result {
- OpResult::Ok(_) => throw_type_error(
- scope,
- format!("Can not call a sync op [{}] with opAsync()", op_id),
- ),
- OpResult::Err(_) => rv.set(result.to_v8(scope).unwrap()),
- },
- Op::Async(fut) => {
- state.op_state.borrow().tracker.track_async(op_id);
- state.pending_ops.push(fut);
- state.have_unpolled_ops = true;
- }
- Op::NotFound => {
- throw_type_error(scope, format!("Unknown op id: {}", op_id));
- }
- }
-}
-
fn ref_op<'s>(
scope: &mut v8::HandleScope<'s>,
args: v8::FunctionCallbackArguments,
@@ -1471,7 +1389,7 @@ fn is_proxy(
rv.set(v8::Boolean::new(scope, args.get(0).is_proxy()).into())
}
-fn throw_type_error(scope: &mut v8::HandleScope, message: impl AsRef<str>) {
+pub fn throw_type_error(scope: &mut v8::HandleScope, message: impl AsRef<str>) {
let message = v8::String::new(scope, message.as_ref()).unwrap();
let exception = v8::Exception::type_error(scope, message);
scope.throw_exception(exception);
diff --git a/core/error_codes.rs b/core/error_codes.rs
index 43a6c4391..b34cbe639 100644
--- a/core/error_codes.rs
+++ b/core/error_codes.rs
@@ -1,6 +1,6 @@
use anyhow::Error;
-pub(crate) fn get_error_code(err: &Error) -> Option<&'static str> {
+pub fn get_error_code(err: &Error) -> Option<&'static str> {
err
.downcast_ref::<std::io::Error>()
.map(|e| match e.raw_os_error() {
diff --git a/core/examples/disable_ops.rs b/core/examples/disable_ops.rs
index 0612845c8..9565d6e7f 100644
--- a/core/examples/disable_ops.rs
+++ b/core/examples/disable_ops.rs
@@ -9,7 +9,7 @@ use deno_core::RuntimeOptions;
fn main() {
let my_ext = Extension::builder()
.middleware(|name, opfn| match name {
- "op_print" => deno_core::void_op_sync(),
+ "op_print" => deno_core::void_op_sync::v8_cb(),
_ => opfn,
})
.build();
diff --git a/core/examples/hello_world.rs b/core/examples/hello_world.rs
index 42c9779f3..bfca5447c 100644
--- a/core/examples/hello_world.rs
+++ b/core/examples/hello_world.rs
@@ -2,27 +2,37 @@
//! This example shows you how to define ops in Rust and then call them from
//! JavaScript.
-use deno_core::op_sync;
+use deno_core::op;
use deno_core::Extension;
use deno_core::JsRuntime;
+use deno_core::OpState;
use deno_core::RuntimeOptions;
+// This is a hack to make the `#[op]` macro work with
+// deno_core examples.
+// You can remove this:
+use deno_core::*;
+
+#[op]
+fn op_sum(
+ _state: &mut OpState,
+ nums: Vec<f64>,
+ _: (),
+) -> Result<f64, deno_core::error::AnyError> {
+ // Sum inputs
+ let sum = nums.iter().fold(0.0, |a, v| a + v);
+ // return as a Result<f64, AnyError>
+ Ok(sum)
+}
+
fn main() {
// Build a deno_core::Extension providing custom ops
let ext = Extension::builder()
.ops(vec![
// An op for summing an array of numbers
- (
- "op_sum",
- // The op-layer automatically deserializes inputs
- // and serializes the returned Result & value
- op_sync(|_state, nums: Vec<f64>, _: ()| {
- // Sum inputs
- let sum = nums.iter().fold(0.0, |a, v| a + v);
- // return as a Result<f64, AnyError>
- Ok(sum)
- }),
- ),
+ // The op-layer automatically deserializes inputs
+ // and serializes the returned Result & value
+ op_sum::decl(),
])
.build();
diff --git a/core/examples/http_bench_json_ops.js b/core/examples/http_bench_json_ops.js
index 22e1b468c..5b16776f4 100644
--- a/core/examples/http_bench_json_ops.js
+++ b/core/examples/http_bench_json_ops.js
@@ -11,12 +11,12 @@ const responseBuf = new Uint8Array(
/** Listens on 0.0.0.0:4500, returns rid. */
function listen() {
- return Deno.core.opSync("listen");
+ return Deno.core.opSync("op_listen");
}
/** Accepts a connection, returns rid. */
function accept(serverRid) {
- return Deno.core.opAsync("accept", serverRid);
+ return Deno.core.opAsync("op_accept", serverRid);
}
async function serve(rid) {
diff --git a/core/examples/http_bench_json_ops.rs b/core/examples/http_bench_json_ops.rs
index 9e60df4a4..3f608eeae 100644
--- a/core/examples/http_bench_json_ops.rs
+++ b/core/examples/http_bench_json_ops.rs
@@ -1,5 +1,6 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use deno_core::anyhow::Error;
+use deno_core::op;
use deno_core::AsyncRefCell;
use deno_core::AsyncResult;
use deno_core::CancelHandle;
@@ -17,6 +18,11 @@ use std::rc::Rc;
use tokio::io::AsyncReadExt;
use tokio::io::AsyncWriteExt;
+// This is a hack to make the `#[op]` macro work with
+// deno_core examples.
+// You can remove this:
+use deno_core::*;
+
struct Logger;
impl log::Log for Logger {
@@ -119,10 +125,7 @@ impl From<tokio::net::TcpStream> for TcpStream {
fn create_js_runtime() -> JsRuntime {
let ext = deno_core::Extension::builder()
- .ops(vec![
- ("listen", deno_core::op_sync(op_listen)),
- ("accept", deno_core::op_async(op_accept)),
- ])
+ .ops(vec![op_listen::decl(), op_accept::decl()])
.build();
JsRuntime::new(deno_core::RuntimeOptions {
@@ -131,6 +134,7 @@ fn create_js_runtime() -> JsRuntime {
})
}
+#[op]
fn op_listen(state: &mut OpState, _: (), _: ()) -> Result<ResourceId, Error> {
log::debug!("listen");
let addr = "127.0.0.1:4544".parse::<SocketAddr>().unwrap();
@@ -141,6 +145,7 @@ fn op_listen(state: &mut OpState, _: (), _: ()) -> Result<ResourceId, Error> {
Ok(rid)
}
+#[op]
async fn op_accept(
state: Rc<RefCell<OpState>>,
rid: ResourceId,
diff --git a/core/examples/schedule_task.rs b/core/examples/schedule_task.rs
index 2f4909b4f..3ada86417 100644
--- a/core/examples/schedule_task.rs
+++ b/core/examples/schedule_task.rs
@@ -1,6 +1,7 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use deno_core::anyhow::Error;
+use deno_core::op;
use deno_core::Extension;
use deno_core::JsRuntime;
use deno_core::OpState;
@@ -9,14 +10,16 @@ use futures::channel::mpsc;
use futures::stream::StreamExt;
use std::task::Poll;
+// This is a hack to make the `#[op]` macro work with
+// deno_core examples.
+// You can remove this:
+use deno_core::*;
+
type Task = Box<dyn FnOnce()>;
fn main() {
let my_ext = Extension::builder()
- .ops(vec![(
- "op_schedule_task",
- deno_core::op_sync(op_schedule_task),
- )])
+ .ops(vec![op_schedule_task::decl()])
.event_loop_middleware(|state, cx| {
let recv = state.borrow_mut::<mpsc::UnboundedReceiver<Task>>();
let mut ref_loop = false;
@@ -58,6 +61,7 @@ fn main() {
runtime.block_on(future).unwrap();
}
+#[op]
fn op_schedule_task(state: &mut OpState, i: u8, _: ()) -> Result<(), Error> {
let tx = state.borrow_mut::<mpsc::UnboundedSender<Task>>();
tx.unbounded_send(Box::new(move || println!("Hello, world! x{}", i)))
diff --git a/core/extensions.rs b/core/extensions.rs
index 031cb073a..7361165f0 100644
--- a/core/extensions.rs
+++ b/core/extensions.rs
@@ -1,12 +1,13 @@
-use crate::OpFn;
+// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use crate::OpState;
use anyhow::Error;
use std::task::Context;
pub type SourcePair = (&'static str, Box<SourceLoadFn>);
pub type SourceLoadFn = dyn Fn() -> Result<String, Error>;
-pub type OpPair = (&'static str, Box<OpFn>);
-pub type OpMiddlewareFn = dyn Fn(&'static str, Box<OpFn>) -> Box<OpFn>;
+pub type OpFnRef = v8::FunctionCallback;
+pub type OpPair = (&'static str, OpFnRef);
+pub type OpMiddlewareFn = dyn Fn(&'static str, OpFnRef) -> OpFnRef;
pub type OpStateFn = dyn Fn(&mut OpState) -> Result<(), Error>;
pub type OpEventLoopFn = dyn Fn(&mut OpState, &mut Context) -> bool;
@@ -108,7 +109,7 @@ impl ExtensionBuilder {
pub fn middleware<F>(&mut self, middleware_fn: F) -> &mut Self
where
- F: Fn(&'static str, Box<OpFn>) -> Box<OpFn> + 'static,
+ F: Fn(&'static str, OpFnRef) -> OpFnRef + 'static,
{
self.middleware = Some(Box::new(middleware_fn));
self
diff --git a/core/lib.rs b/core/lib.rs
index 2f46c1ffc..29cdbba01 100644
--- a/core/lib.rs
+++ b/core/lib.rs
@@ -13,7 +13,6 @@ mod modules;
mod normalize_path;
mod ops;
mod ops_builtin;
-mod ops_json;
mod ops_metrics;
mod resources;
mod runtime;
@@ -44,6 +43,10 @@ pub use crate::async_cell::AsyncRefCell;
pub use crate::async_cell::AsyncRefFuture;
pub use crate::async_cell::RcLike;
pub use crate::async_cell::RcRef;
+pub use crate::extensions::Extension;
+pub use crate::extensions::ExtensionBuilder;
+pub use crate::extensions::OpMiddlewareFn;
+pub use crate::extensions::OpPair;
pub use crate::flags::v8_set_flags;
pub use crate::inspector::InspectorMsg;
pub use crate::inspector::InspectorMsgKind;
@@ -65,24 +68,21 @@ pub use crate::modules::ModuleSourceFuture;
pub use crate::modules::ModuleType;
pub use crate::modules::NoopModuleLoader;
pub use crate::normalize_path::normalize_path;
-pub use crate::ops::serialize_op_result;
pub use crate::ops::Op;
pub use crate::ops::OpAsyncFuture;
pub use crate::ops::OpCall;
+pub use crate::ops::OpError;
pub use crate::ops::OpFn;
pub use crate::ops::OpId;
-pub use crate::ops::OpPayload;
pub use crate::ops::OpResult;
pub use crate::ops::OpState;
-pub use crate::ops::OpTable;
pub use crate::ops::PromiseId;
pub use crate::ops_builtin::op_close;
pub use crate::ops_builtin::op_print;
pub use crate::ops_builtin::op_resources;
-pub use crate::ops_json::op_async;
-pub use crate::ops_json::op_sync;
-pub use crate::ops_json::void_op_async;
-pub use crate::ops_json::void_op_sync;
+pub use crate::ops_builtin::void_op_async;
+pub use crate::ops_builtin::void_op_sync;
+pub use crate::ops_metrics::OpsTracker;
pub use crate::resources::AsyncResult;
pub use crate::resources::Resource;
pub use crate::resources::ResourceId;
@@ -95,16 +95,21 @@ pub use crate::runtime::JsRuntime;
pub use crate::runtime::RuntimeOptions;
pub use crate::runtime::SharedArrayBufferStore;
pub use crate::runtime::Snapshot;
-// pub use crate::runtime_modules::include_js_files!;
-pub use crate::extensions::Extension;
-pub use crate::extensions::ExtensionBuilder;
-pub use crate::extensions::OpMiddlewareFn;
-pub use crate::extensions::OpPair;
+pub use deno_ops::op;
pub fn v8_version() -> &'static str {
v8::V8::get_version()
}
+/// An internal module re-exporting funcs used by the #[op] (`deno_ops`) macro
+#[doc(hidden)]
+pub mod _ops {
+ pub use super::bindings::throw_type_error;
+ pub use super::error_codes::get_error_code;
+ pub use super::ops::to_op_result;
+ pub use super::runtime::queue_async_op;
+}
+
/// A helper macro that will return a call site in Rust code. Should be
/// used when executing internal one-line scripts for JsRuntime lifecycle.
///
diff --git a/core/modules.rs b/core/modules.rs
index d4a0a86ef..7c5274193 100644
--- a/core/modules.rs
+++ b/core/modules.rs
@@ -1073,13 +1073,11 @@ impl ModuleMap {
#[cfg(test)]
mod tests {
use super::*;
- use crate::ops::OpCall;
- use crate::serialize_op_result;
+ use crate::error::AnyError;
use crate::Extension;
use crate::JsRuntime;
- use crate::Op;
- use crate::OpPayload;
use crate::RuntimeOptions;
+ use deno_ops::op;
use futures::future::FutureExt;
use parking_lot::Mutex;
use std::fmt;
@@ -1088,6 +1086,10 @@ mod tests {
use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
+ // deno_ops macros generate code assuming deno_core in scope.
+ mod deno_core {
+ pub use crate::*;
+ }
// TODO(ry) Sadly FuturesUnordered requires the current task to be set. So
// even though we are only using poll() in these tests and not Tokio, we must
@@ -1401,20 +1403,16 @@ import "/a.js";
let loader = Rc::new(ModsLoader::default());
let resolve_count = loader.count.clone();
- let dispatch_count = Arc::new(AtomicUsize::new(0));
- let dispatch_count_ = dispatch_count.clone();
+ static DISPATCH_COUNT: AtomicUsize = AtomicUsize::new(0);
- let op_test = move |state, payload: OpPayload| -> Op {
- dispatch_count_.fetch_add(1, Ordering::Relaxed);
- let (control, _): (u8, ()) = payload.deserialize().unwrap();
+ #[op]
+ fn op_test(_: &mut OpState, control: u8, _: ()) -> Result<u8, AnyError> {
+ DISPATCH_COUNT.fetch_add(1, Ordering::Relaxed);
assert_eq!(control, 42);
- let resp = (0, 1, serialize_op_result(Ok(43), state));
- Op::Async(OpCall::ready(resp))
- };
+ Ok(43)
+ }
- let ext = Extension::builder()
- .ops(vec![("op_test", Box::new(op_test))])
- .build();
+ let ext = Extension::builder().ops(vec![op_test::decl()]).build();
let mut runtime = JsRuntime::new(RuntimeOptions {
extensions: vec![ext],
@@ -1435,7 +1433,7 @@ import "/a.js";
)
.unwrap();
- assert_eq!(dispatch_count.load(Ordering::Relaxed), 0);
+ assert_eq!(DISPATCH_COUNT.load(Ordering::Relaxed), 0);
let module_map_rc = JsRuntime::module_map(runtime.v8_isolate());
@@ -1452,12 +1450,12 @@ import "/a.js";
import { b } from './b.js'
if (b() != 'b') throw Error();
let control = 42;
- Deno.core.opAsync("op_test", control);
+ Deno.core.opSync("op_test", control);
"#,
)
.unwrap();
- assert_eq!(dispatch_count.load(Ordering::Relaxed), 0);
+ assert_eq!(DISPATCH_COUNT.load(Ordering::Relaxed), 0);
let imports = module_map.get_requested_modules(mod_a);
assert_eq!(
imports,
@@ -1481,14 +1479,14 @@ import "/a.js";
};
runtime.instantiate_module(mod_b).unwrap();
- assert_eq!(dispatch_count.load(Ordering::Relaxed), 0);
+ assert_eq!(DISPATCH_COUNT.load(Ordering::Relaxed), 0);
assert_eq!(resolve_count.load(Ordering::SeqCst), 1);
runtime.instantiate_module(mod_a).unwrap();
- assert_eq!(dispatch_count.load(Ordering::Relaxed), 0);
+ assert_eq!(DISPATCH_COUNT.load(Ordering::Relaxed), 0);
let _ = runtime.mod_evaluate(mod_a);
- assert_eq!(dispatch_count.load(Ordering::Relaxed), 1);
+ assert_eq!(DISPATCH_COUNT.load(Ordering::Relaxed), 1);
}
#[test]
@@ -1766,7 +1764,6 @@ import "/a.js";
module_loader: Some(loader),
..Default::default()
});
- runtime.sync_ops_cache();
runtime
.execute_script(
"file:///dyn_import3.js",
diff --git a/core/ops.rs b/core/ops.rs
index 50197c9be..42718b8ff 100644
--- a/core/ops.rs
+++ b/core/ops.rs
@@ -1,10 +1,9 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
-use crate::error::type_error;
use crate::gotham_state::GothamState;
-use crate::ops_metrics::OpsTracker;
use crate::resources::ResourceTable;
use crate::runtime::GetErrorClassFn;
+use crate::OpsTracker;
use anyhow::Error;
use futures::future::maybe_done;
use futures::future::FusedFuture;
@@ -12,23 +11,19 @@ use futures::future::MaybeDone;
use futures::ready;
use futures::task::noop_waker;
use futures::Future;
-use indexmap::IndexMap;
-use serde::de::DeserializeOwned;
use serde::Serialize;
-use std::cell::RefCell;
use std::cell::UnsafeCell;
-use std::iter::once;
use std::ops::Deref;
use std::ops::DerefMut;
use std::pin::Pin;
-use std::rc::Rc;
use std::task::Context;
use std::task::Poll;
/// Wrapper around a Future, which causes that Future to be polled immediately.
-/// (Background: ops are stored in a `FuturesUnordered` structure which polls
+///
+/// Background: ops are stored in a `FuturesUnordered` structure which polls
/// them, but without the `OpCall` wrapper this doesn't happen until the next
-/// turn of the event loop, which is too late for certain ops.)
+/// turn of the event loop, which is too late for certain ops.
pub struct OpCall<T>(MaybeDone<Pin<Box<dyn Future<Output = T>>>>);
impl<T> OpCall<T> {
@@ -83,32 +78,10 @@ where
pub type PromiseId = i32;
pub type OpAsyncFuture = OpCall<(PromiseId, OpId, OpResult)>;
-pub type OpFn = dyn Fn(Rc<RefCell<OpState>>, OpPayload) -> Op + 'static;
+pub type OpFn =
+ fn(&mut v8::HandleScope, v8::FunctionCallbackArguments, v8::ReturnValue);
pub type OpId = usize;
-pub struct OpPayload<'a, 'b, 'c> {
- pub(crate) scope: &'a mut v8::HandleScope<'b>,
- pub(crate) a: v8::Local<'c, v8::Value>,
- pub(crate) b: v8::Local<'c, v8::Value>,
- pub(crate) op_id: OpId,
- pub(crate) promise_id: PromiseId,
-}
-
-impl<'a, 'b, 'c> OpPayload<'a, 'b, 'c> {
- pub fn deserialize<T: DeserializeOwned, U: DeserializeOwned>(
- self,
- ) -> Result<(T, U), Error> {
- let a: T = serde_v8::from_v8(self.scope, self.a)
- .map_err(Error::from)
- .map_err(|e| type_error(format!("Error parsing args: {}", e)))?;
-
- let b: U = serde_v8::from_v8(self.scope, self.b)
- .map_err(Error::from)
- .map_err(|e| type_error(format!("Error parsing args: {}", e)))?;
- Ok((a, b))
- }
-}
-
pub enum Op {
Sync(OpResult),
Async(OpAsyncFuture),
@@ -141,39 +114,43 @@ pub struct OpError {
code: Option<&'static str>,
}
-pub fn serialize_op_result<R: Serialize + 'static>(
+impl OpError {
+ pub fn new(get_class: GetErrorClassFn, err: Error) -> Self {
+ Self {
+ class_name: (get_class)(&err),
+ message: err.to_string(),
+ code: crate::error_codes::get_error_code(&err),
+ }
+ }
+}
+
+pub fn to_op_result<R: Serialize + 'static>(
+ get_class: GetErrorClassFn,
result: Result<R, Error>,
- state: Rc<RefCell<OpState>>,
) -> OpResult {
match result {
Ok(v) => OpResult::Ok(v.into()),
- Err(err) => OpResult::Err(OpError {
- class_name: (state.borrow().get_error_class_fn)(&err),
- message: err.to_string(),
- code: crate::error_codes::get_error_code(&err),
- }),
+ Err(err) => OpResult::Err(OpError::new(get_class, err)),
}
}
/// Maintains the resources and ops inside a JS runtime.
pub struct OpState {
pub resource_table: ResourceTable,
- pub op_table: OpTable,
pub get_error_class_fn: GetErrorClassFn,
- pub(crate) tracker: OpsTracker,
+ pub tracker: OpsTracker,
gotham_state: GothamState,
}
impl OpState {
- pub(crate) fn new() -> OpState {
+ pub fn new(ops_count: usize) -> OpState {
OpState {
resource_table: Default::default(),
- op_table: OpTable::default(),
get_error_class_fn: &|_| "Error",
+ gotham_state: Default::default(),
tracker: OpsTracker {
- ops: UnsafeCell::new(Vec::with_capacity(256)),
+ ops: UnsafeCell::new(vec![Default::default(); ops_count]),
},
- gotham_state: Default::default(),
}
}
}
@@ -191,81 +168,3 @@ impl DerefMut for OpState {
&mut self.gotham_state
}
}
-
-/// Collection for storing registered ops. The special 'get_op_catalog'
-/// op with OpId `0` is automatically added when the OpTable is created.
-pub struct OpTable(IndexMap<String, Rc<OpFn>>);
-
-impl OpTable {
- pub fn register_op<F>(&mut self, name: &str, op_fn: F) -> OpId
- where
- F: Fn(Rc<RefCell<OpState>>, OpPayload) -> Op + 'static,
- {
- let (op_id, prev) = self.0.insert_full(name.to_owned(), Rc::new(op_fn));
- assert!(prev.is_none());
- op_id
- }
-
- pub fn op_entries(state: Rc<RefCell<OpState>>) -> Vec<(String, OpId)> {
- state.borrow().op_table.0.keys().cloned().zip(0..).collect()
- }
-
- pub fn route_op(
- op_id: OpId,
- state: Rc<RefCell<OpState>>,
- payload: OpPayload,
- ) -> Op {
- let op_fn = state
- .borrow()
- .op_table
- .0
- .get_index(op_id)
- .map(|(_, op_fn)| op_fn.clone());
- match op_fn {
- Some(f) => (f)(state, payload),
- None => Op::NotFound,
- }
- }
-}
-
-impl Default for OpTable {
- fn default() -> Self {
- fn dummy(_state: Rc<RefCell<OpState>>, _p: OpPayload) -> Op {
- unreachable!()
- }
- Self(once(("ops".to_owned(), Rc::new(dummy) as _)).collect())
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn op_table() {
- let state = Rc::new(RefCell::new(OpState::new()));
-
- let foo_id;
- let bar_id;
- {
- let op_table = &mut state.borrow_mut().op_table;
- foo_id =
- op_table.register_op("foo", |_, _| Op::Sync(OpResult::Ok(321.into())));
- assert_eq!(foo_id, 1);
- bar_id =
- op_table.register_op("bar", |_, _| Op::Sync(OpResult::Ok(123.into())));
- assert_eq!(bar_id, 2);
- }
-
- let mut catalog_entries = OpTable::op_entries(state);
- catalog_entries.sort_by(|(_, id1), (_, id2)| id1.partial_cmp(id2).unwrap());
- assert_eq!(
- catalog_entries,
- vec![
- ("ops".to_owned(), 0),
- ("foo".to_owned(), 1),
- ("bar".to_owned(), 2)
- ]
- );
- }
-}
diff --git a/core/ops_builtin.rs b/core/ops_builtin.rs
index d759bc5ba..14d8c6e39 100644
--- a/core/ops_builtin.rs
+++ b/core/ops_builtin.rs
@@ -1,16 +1,13 @@
use crate::error::type_error;
use crate::include_js_files;
-use crate::op_async;
-use crate::op_sync;
use crate::ops_metrics::OpMetrics;
use crate::resources::ResourceId;
-use crate::void_op_async;
-use crate::void_op_sync;
use crate::Extension;
use crate::OpState;
use crate::Resource;
use crate::ZeroCopyBuf;
use anyhow::Error;
+use deno_ops::op;
use std::cell::RefCell;
use std::io::{stderr, stdout, Write};
use std::rc::Rc;
@@ -24,29 +21,40 @@ pub(crate) fn init_builtins() -> Extension {
"02_error.js",
))
.ops(vec![
- ("op_close", op_sync(op_close)),
- ("op_try_close", op_sync(op_try_close)),
- ("op_print", op_sync(op_print)),
- ("op_resources", op_sync(op_resources)),
- ("op_wasm_streaming_feed", op_sync(op_wasm_streaming_feed)),
- ("op_wasm_streaming_abort", op_sync(op_wasm_streaming_abort)),
- (
- "op_wasm_streaming_set_url",
- op_sync(op_wasm_streaming_set_url),
- ),
- ("op_metrics", op_sync(op_metrics)),
- ("op_void_sync", void_op_sync()),
- ("op_void_async", void_op_async()),
- // TODO(@AaronO): track IO metrics for builtin streams
- ("op_read", op_async(op_read)),
- ("op_write", op_async(op_write)),
- ("op_shutdown", op_async(op_shutdown)),
+ op_close::decl(),
+ op_try_close::decl(),
+ op_print::decl(),
+ op_resources::decl(),
+ op_wasm_streaming_feed::decl(),
+ op_wasm_streaming_abort::decl(),
+ op_wasm_streaming_set_url::decl(),
+ op_void_sync::decl(),
+ op_void_async::decl(),
+ // // TODO(@AaronO): track IO metrics for builtin streams
+ op_read::decl(),
+ op_write::decl(),
+ op_shutdown::decl(),
+ op_metrics::decl(),
])
.build()
}
+#[op]
+pub fn void_op_sync(_: &mut OpState, _: (), _: ()) -> Result<(), Error> {
+ Ok(())
+}
+
+pub async fn void_op_async(
+ _state: Rc<RefCell<OpState>>,
+ _: (),
+ _: (),
+) -> Result<(), Error> {
+ Ok(())
+}
+
/// Return map of resources with id as key
/// and string representation as value.
+#[op]
pub fn op_resources(
state: &mut OpState,
_: (),
@@ -60,7 +68,22 @@ pub fn op_resources(
Ok(serialized_resources)
}
+#[op]
+pub fn op_void_sync(_state: &mut OpState, _: (), _: ()) -> Result<(), Error> {
+ Ok(())
+}
+
+#[op]
+pub async fn op_void_async(
+ _state: Rc<RefCell<OpState>>,
+ _: (),
+ _: (),
+) -> Result<(), Error> {
+ Ok(())
+}
+
/// Remove a resource from the resource table.
+#[op]
pub fn op_close(
state: &mut OpState,
rid: Option<ResourceId>,
@@ -75,6 +98,7 @@ pub fn op_close(
/// Try to remove a resource from the resource table. If there is no resource
/// with the specified `rid`, this is a no-op.
+#[op]
pub fn op_try_close(
state: &mut OpState,
rid: Option<ResourceId>,
@@ -87,7 +111,19 @@ pub fn op_try_close(
Ok(())
}
+#[op]
+pub fn op_metrics(
+ state: &mut OpState,
+ _: (),
+ _: (),
+) -> Result<(OpMetrics, Vec<OpMetrics>), Error> {
+ let aggregate = state.tracker.aggregate();
+ let per_op = state.tracker.per_op();
+ Ok((aggregate, per_op))
+}
+
/// Builtin utility to print to stdout/stderr
+#[op]
pub fn op_print(
_state: &mut OpState,
msg: String,
@@ -119,6 +155,7 @@ impl Resource for WasmStreamingResource {
}
/// Feed bytes to WasmStreamingResource.
+#[op]
pub fn op_wasm_streaming_feed(
state: &mut OpState,
rid: ResourceId,
@@ -133,6 +170,7 @@ pub fn op_wasm_streaming_feed(
}
/// Abort a WasmStreamingResource.
+#[op]
pub fn op_wasm_streaming_abort(
state: &mut OpState,
rid: ResourceId,
@@ -153,6 +191,7 @@ pub fn op_wasm_streaming_abort(
Ok(())
}
+#[op]
pub fn op_wasm_streaming_set_url(
state: &mut OpState,
rid: ResourceId,
@@ -166,16 +205,7 @@ pub fn op_wasm_streaming_set_url(
Ok(())
}
-pub fn op_metrics(
- state: &mut OpState,
- _: (),
- _: (),
-) -> Result<(OpMetrics, Vec<OpMetrics>), Error> {
- let aggregate = state.tracker.aggregate();
- let per_op = state.tracker.per_op();
- Ok((aggregate, per_op))
-}
-
+#[op]
async fn op_read(
state: Rc<RefCell<OpState>>,
rid: ResourceId,
@@ -185,6 +215,7 @@ async fn op_read(
resource.read(buf).await.map(|n| n as u32)
}
+#[op]
async fn op_write(
state: Rc<RefCell<OpState>>,
rid: ResourceId,
@@ -194,6 +225,7 @@ async fn op_write(
resource.write(buf).await.map(|n| n as u32)
}
+#[op]
async fn op_shutdown(
state: Rc<RefCell<OpState>>,
rid: ResourceId,
diff --git a/core/ops_json.rs b/core/ops_json.rs
deleted file mode 100644
index 6d8af602f..000000000
--- a/core/ops_json.rs
+++ /dev/null
@@ -1,166 +0,0 @@
-// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
-
-use crate::ops::OpCall;
-use crate::serialize_op_result;
-use crate::Op;
-use crate::OpFn;
-use crate::OpState;
-use anyhow::Error;
-use serde::de::DeserializeOwned;
-use serde::Serialize;
-use std::cell::RefCell;
-use std::future::Future;
-use std::rc::Rc;
-
-/// A helper function that returns a sync NOP OpFn
-///
-/// It's mainly intended for embedders who want to disable ops, see ./examples/disable_ops.rs
-pub fn void_op_sync() -> Box<OpFn> {
- op_sync(|_, _: (), _: ()| Ok(()))
-}
-
-/// A helper function that returns an async NOP OpFn
-///
-/// It's mainly intended for embedders who want to disable ops, see ./examples/disable_ops.rs
-pub fn void_op_async() -> Box<OpFn> {
- op_async(|_, _: (), _: ()| futures::future::ok(()))
-}
-
-/// Creates an op that passes data synchronously using JSON.
-///
-/// The provided function `op_fn` has the following parameters:
-/// * `&mut OpState`: the op state, can be used to read/write resources in the runtime from an op.
-/// * `V`: the deserializable value that is passed to the Rust function.
-/// * `&mut [ZeroCopyBuf]`: raw bytes passed along, usually not needed if the JSON value is used.
-///
-/// `op_fn` returns a serializable value, which is directly returned to JavaScript.
-///
-/// When registering an op like this...
-/// ```ignore
-/// let mut runtime = JsRuntime::new(...);
-/// runtime.register_op("hello", deno_core::op_sync(Self::hello_op));
-/// runtime.sync_ops_cache();
-/// ```
-///
-/// ...it can be invoked from JS using the provided name, for example:
-/// ```js
-/// let result = Deno.core.opSync("hello", args);
-/// ```
-///
-/// `runtime.sync_ops_cache()` must be called after registering new ops
-/// A more complete example is available in the examples directory.
-pub fn op_sync<F, A, B, R>(op_fn: F) -> Box<OpFn>
-where
- F: Fn(&mut OpState, A, B) -> Result<R, Error> + 'static,
- A: DeserializeOwned,
- B: DeserializeOwned,
- R: Serialize + 'static,
-{
- Box::new(move |state, payload| -> Op {
- let result = payload
- .deserialize()
- .and_then(|(a, b)| op_fn(&mut state.borrow_mut(), a, b));
- Op::Sync(serialize_op_result(result, state))
- })
-}
-
-/// Creates an op that passes data asynchronously using JSON.
-///
-/// When this op is dispatched, the runtime doesn't exit while processing it.
-///
-/// The provided function `op_fn` has the following parameters:
-/// * `Rc<RefCell<OpState>`: the op state, can be used to read/write resources in the runtime from an op.
-/// * `V`: the deserializable value that is passed to the Rust function.
-/// * `BufVec`: raw bytes passed along, usually not needed if the JSON value is used.
-///
-/// `op_fn` returns a future, whose output is a serializable value. This value will be asynchronously
-/// returned to JavaScript.
-///
-/// When registering an op like this...
-/// ```ignore
-/// let mut runtime = JsRuntime::new(...);
-/// runtime.register_op("hello", deno_core::op_async(Self::hello_op));
-/// runtime.sync_ops_cache();
-/// ```
-///
-/// ...it can be invoked from JS using the provided name, for example:
-/// ```js
-/// let future = Deno.core.opAsync("hello", args);
-/// ```
-///
-/// `runtime.sync_ops_cache()` must be called after registering new ops
-/// A more complete example is available in the examples directory.
-pub fn op_async<F, A, B, R, RV>(op_fn: F) -> Box<OpFn>
-where
- F: Fn(Rc<RefCell<OpState>>, A, B) -> R + 'static,
- A: DeserializeOwned,
- B: DeserializeOwned,
- R: Future<Output = Result<RV, Error>> + 'static,
- RV: Serialize + 'static,
-{
- Box::new(move |state, payload| -> Op {
- let op_id = payload.op_id;
- let pid = payload.promise_id;
- // Deserialize args, sync error on failure
- let args = match payload.deserialize() {
- Ok(args) => args,
- Err(err) => {
- return Op::Sync(serialize_op_result(Err::<(), Error>(err), state))
- }
- };
- let (a, b) = args;
-
- use crate::futures::FutureExt;
- let fut = op_fn(state.clone(), a, b)
- .map(move |result| (pid, op_id, serialize_op_result(result, state)));
- Op::Async(OpCall::eager(fut))
- })
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[tokio::test]
- async fn op_async_stack_trace() {
- async fn op_throw(
- _state: Rc<RefCell<OpState>>,
- msg: Option<String>,
- _: (),
- ) -> Result<(), Error> {
- assert_eq!(msg.unwrap(), "hello");
- Err(crate::error::generic_error("foo"))
- }
-
- let ext = crate::Extension::builder()
- .ops(vec![("op_throw", op_async(op_throw))])
- .build();
-
- let mut runtime = crate::JsRuntime::new(crate::RuntimeOptions {
- extensions: vec![ext],
- ..Default::default()
- });
-
- runtime
- .execute_script(
- "<init>",
- r#"
- async function f1() {
- await Deno.core.opAsync('op_throw', 'hello');
- }
-
- async function f2() {
- await f1();
- }
-
- f2();
- "#,
- )
- .unwrap();
- let e = runtime.run_event_loop(false).await.unwrap_err().to_string();
- println!("{}", e);
- assert!(e.contains("Error: foo"));
- assert!(e.contains("at async f1 (<init>:"));
- assert!(e.contains("at async f2 (<init>:"));
- }
-}
diff --git a/core/ops_metrics.rs b/core/ops_metrics.rs
index 35c1faf1c..b068aa0ee 100644
--- a/core/ops_metrics.rs
+++ b/core/ops_metrics.rs
@@ -59,20 +59,10 @@ impl OpsTracker {
unsafe { &mut *self.ops.get() }
}
- #[inline]
- fn ensure_capacity(ops: &mut Vec<OpMetrics>, op_id: OpId) {
- if op_id >= ops.len() {
- ops.resize(1 + op_id, OpMetrics::default())
- }
- }
-
#[allow(clippy::mut_from_ref)]
#[inline]
fn metrics_mut(&self, id: OpId) -> &mut OpMetrics {
- let ops = self.ops_mut();
- // TODO(@AaronO): Pre-alloc capacity at runtime init once we forbid post-boot op registrations
- Self::ensure_capacity(ops, id);
- unsafe { ops.get_unchecked_mut(id) }
+ unsafe { self.ops_mut().get_unchecked_mut(id) }
}
#[inline]
diff --git a/core/runtime.rs b/core/runtime.rs
index 7555bc301..a4fa0c51f 100644
--- a/core/runtime.rs
+++ b/core/runtime.rs
@@ -6,6 +6,7 @@ use crate::error::generic_error;
use crate::error::ErrWithV8Handle;
use crate::error::JsError;
use crate::extensions::OpEventLoopFn;
+use crate::extensions::OpPair;
use crate::inspector::JsRuntimeInspector;
use crate::module_specifier::ModuleSpecifier;
use crate::modules::ModuleId;
@@ -16,13 +17,13 @@ use crate::modules::NoopModuleLoader;
use crate::ops::*;
use crate::Extension;
use crate::OpMiddlewareFn;
-use crate::OpPayload;
use crate::OpResult;
use crate::OpState;
use crate::PromiseId;
use anyhow::Error;
use futures::channel::oneshot;
use futures::future::poll_fn;
+use futures::future::Future;
use futures::future::FutureExt;
use futures::stream::FuturesUnordered;
use futures::stream::StreamExt;
@@ -143,7 +144,6 @@ pub type CompiledWasmModuleStore = CrossIsolateStore<v8::CompiledWasmModule>;
pub(crate) struct JsRuntimeState {
pub global_context: Option<v8::Global<v8::Context>>,
pub(crate) js_recv_cb: Option<v8::Global<v8::Function>>,
- pub(crate) js_sync_cb: Option<v8::Global<v8::Function>>,
pub(crate) js_macrotask_cbs: Vec<v8::Global<v8::Function>>,
pub(crate) js_nexttick_cbs: Vec<v8::Global<v8::Function>>,
pub(crate) js_promise_reject_cb: Option<v8::Global<v8::Function>>,
@@ -279,17 +279,37 @@ impl JsRuntime {
let has_startup_snapshot = options.startup_snapshot.is_some();
+ let js_error_create_fn = options
+ .js_error_create_fn
+ .unwrap_or_else(|| Rc::new(JsError::create));
+
+ // Add builtins extension
+ options
+ .extensions
+ .insert(0, crate::ops_builtin::init_builtins());
+
+ let ops = Self::collect_ops(&mut options.extensions);
+ let mut op_state = OpState::new(ops.len());
+
+ if let Some(get_error_class_fn) = options.get_error_class_fn {
+ op_state.get_error_class_fn = get_error_class_fn;
+ }
+
+ let op_state = Rc::new(RefCell::new(op_state));
+
+ let refs = bindings::external_references(&ops, op_state.clone());
+ let refs: &'static v8::ExternalReferences = Box::leak(Box::new(refs));
let global_context;
let (mut isolate, maybe_snapshot_creator) = if options.will_snapshot {
// TODO(ry) Support loading snapshots before snapshotting.
assert!(options.startup_snapshot.is_none());
- let mut creator =
- v8::SnapshotCreator::new(Some(&bindings::EXTERNAL_REFERENCES));
+ let mut creator = v8::SnapshotCreator::new(Some(refs));
let isolate = unsafe { creator.get_owned_isolate() };
let mut isolate = JsRuntime::setup_isolate(isolate);
{
let scope = &mut v8::HandleScope::new(&mut isolate);
- let context = bindings::initialize_context(scope);
+ let context =
+ bindings::initialize_context(scope, &ops, false, op_state.clone());
global_context = v8::Global::new(scope, context);
creator.set_default_context(context);
}
@@ -299,7 +319,7 @@ impl JsRuntime {
.create_params
.take()
.unwrap_or_else(v8::Isolate::create_params)
- .external_references(&**bindings::EXTERNAL_REFERENCES);
+ .external_references(&**refs);
let snapshot_loaded = if let Some(snapshot) = options.startup_snapshot {
params = match snapshot {
Snapshot::Static(data) => params.snapshot_blob(data),
@@ -315,13 +335,13 @@ impl JsRuntime {
let mut isolate = JsRuntime::setup_isolate(isolate);
{
let scope = &mut v8::HandleScope::new(&mut isolate);
- let context = if snapshot_loaded {
- v8::Context::new(scope)
- } else {
- // If no snapshot is provided, we initialize the context with empty
- // main source code and source maps.
- bindings::initialize_context(scope)
- };
+ let context = bindings::initialize_context(
+ scope,
+ &ops,
+ snapshot_loaded,
+ op_state.clone(),
+ );
+
global_context = v8::Global::new(scope, context);
}
(isolate, None)
@@ -334,17 +354,6 @@ impl JsRuntime {
.module_loader
.unwrap_or_else(|| Rc::new(NoopModuleLoader));
- let js_error_create_fn = options
- .js_error_create_fn
- .unwrap_or_else(|| Rc::new(JsError::create));
- let mut op_state = OpState::new();
-
- if let Some(get_error_class_fn) = options.get_error_class_fn {
- op_state.get_error_class_fn = get_error_class_fn;
- }
-
- let op_state = Rc::new(RefCell::new(op_state));
-
isolate.set_slot(Rc::new(RefCell::new(JsRuntimeState {
global_context: Some(global_context),
pending_promise_exceptions: HashMap::new(),
@@ -352,7 +361,6 @@ impl JsRuntime {
pending_mod_evaluate: None,
dyn_module_evaluate_idle_counter: 0,
js_recv_cb: None,
- js_sync_cb: None,
js_macrotask_cbs: vec![],
js_nexttick_cbs: vec![],
js_promise_reject_cb: None,
@@ -372,11 +380,6 @@ impl JsRuntime {
let module_map = ModuleMap::new(loader, op_state);
isolate.set_slot(Rc::new(RefCell::new(module_map)));
- // Add builtins extension
- options
- .extensions
- .insert(0, crate::ops_builtin::init_builtins());
-
let mut js_runtime = Self {
v8_isolate: Some(isolate),
inspector: Some(inspector),
@@ -394,10 +397,8 @@ impl JsRuntime {
}
// Init extension ops
js_runtime.init_extension_ops().unwrap();
- // Init callbacks (opresolve & syncOpsCache)
+ // Init callbacks (opresolve)
js_runtime.init_cbs();
- // Sync ops cache
- js_runtime.sync_ops_cache();
js_runtime
}
@@ -461,34 +462,44 @@ impl JsRuntime {
Ok(())
}
- /// Initializes ops of provided Extensions
- fn init_extension_ops(&mut self) -> Result<(), Error> {
- let op_state = self.op_state();
- // Take extensions to avoid double-borrow
- let mut extensions: Vec<Extension> = std::mem::take(&mut self.extensions);
-
+ /// Collects ops from extensions & applies middleware
+ fn collect_ops(extensions: &mut [Extension]) -> Vec<OpPair> {
// Middleware
let middleware: Vec<Box<OpMiddlewareFn>> = extensions
.iter_mut()
.filter_map(|e| e.init_middleware())
.collect();
+
// macroware wraps an opfn in all the middleware
let macroware =
move |name, opfn| middleware.iter().fold(opfn, |opfn, m| m(name, opfn));
- // Register ops
+ // Flatten ops & apply middlware
+ extensions
+ .iter_mut()
+ .filter_map(|e| e.init_ops())
+ .flatten()
+ .map(|(name, opfn)| (name, macroware(name, opfn)))
+ .collect()
+ }
+
+ /// Initializes ops of provided Extensions
+ fn init_extension_ops(&mut self) -> Result<(), Error> {
+ let op_state = self.op_state();
+ // Take extensions to avoid double-borrow
+ let mut extensions: Vec<Extension> = std::mem::take(&mut self.extensions);
+
+ // Setup state
for e in extensions.iter_mut() {
+ // ops are already registered during in bindings::initialize_context();
e.init_state(&mut op_state.borrow_mut())?;
- // Register each op after middlewaring it
- let ops = e.init_ops().unwrap_or_default();
- for (name, opfn) in ops {
- self.register_op(name, macroware(name, opfn));
- }
+ // Setup event-loop middleware
if let Some(middleware) = e.init_event_loop_middleware() {
self.event_loop_middlewares.push(middleware);
}
}
+
// Restore extensions
self.extensions = extensions;
@@ -511,22 +522,10 @@ impl JsRuntime {
fn init_cbs(&mut self) {
let mut scope = self.handle_scope();
let recv_cb = Self::grab_fn(&mut scope, "Deno.core.opresolve");
- let sync_cb = Self::grab_fn(&mut scope, "Deno.core.syncOpsCache");
// Put global handles in state
let state_rc = JsRuntime::state(&scope);
let mut state = state_rc.borrow_mut();
state.js_recv_cb.replace(recv_cb);
- state.js_sync_cb.replace(sync_cb);
- }
-
- /// Ensures core.js has the latest op-name to op-id mappings
- pub fn sync_ops_cache(&mut self) {
- let scope = &mut self.handle_scope();
- let state_rc = JsRuntime::state(scope);
- let js_sync_cb_handle = state_rc.borrow().js_sync_cb.clone().unwrap();
- let js_sync_cb = js_sync_cb_handle.open(scope);
- let this = v8::undefined(scope).into();
- js_sync_cb.call(scope, this, &[]);
}
/// Returns the runtime's op state, which can be used to maintain ops
@@ -612,7 +611,6 @@ impl JsRuntime {
))));
// Drop other v8::Global handles before snapshotting
std::mem::take(&mut state.borrow_mut().js_recv_cb);
- std::mem::take(&mut state.borrow_mut().js_sync_cb);
let snapshot_creator = self.snapshot_creator.as_mut().unwrap();
let snapshot = snapshot_creator
@@ -623,27 +621,6 @@ impl JsRuntime {
snapshot
}
- /// Registers an op that can be called from JavaScript.
- ///
- /// The _op_ mechanism allows to expose Rust functions to the JS runtime,
- /// which can be called using the provided `name`.
- ///
- /// This function provides byte-level bindings. To pass data via JSON, the
- /// following functions can be passed as an argument for `op_fn`:
- /// * [op_sync()](fn.op_sync.html)
- /// * [op_async()](fn.op_async.html)
- pub fn register_op<F>(&mut self, name: &str, op_fn: F) -> OpId
- where
- F: Fn(Rc<RefCell<OpState>>, OpPayload) -> Op + 'static,
- {
- Self::state(self.v8_isolate())
- .borrow_mut()
- .op_state
- .borrow_mut()
- .op_table
- .register_op(name, op_fn)
- }
-
/// Registers a callback on the isolate when the memory limits are approached.
/// Use this to prevent V8 from crashing the process when reaching the limit.
///
@@ -1552,13 +1529,11 @@ impl JsRuntime {
let mut state = state_rc.borrow_mut();
state.have_unpolled_ops = false;
- let op_state = state.op_state.clone();
-
while let Poll::Ready(Some(item)) = state.pending_ops.poll_next_unpin(cx)
{
let (promise_id, op_id, resp) = item;
- op_state.borrow().tracker.track_async_completed(op_id);
state.unrefed_ops.remove(&promise_id);
+ state.op_state.borrow().tracker.track_async_completed(op_id);
args.push(v8::Integer::new(scope, promise_id as i32).into());
args.push(resp.to_v8(scope).unwrap());
}
@@ -1654,22 +1629,37 @@ impl JsRuntime {
}
}
+#[inline]
+pub fn queue_async_op(
+ scope: &v8::Isolate,
+ op: impl Future<Output = (PromiseId, OpId, OpResult)> + 'static,
+) {
+ let state_rc = JsRuntime::state(scope);
+ let mut state = state_rc.borrow_mut();
+ state.pending_ops.push(OpCall::eager(op));
+ state.have_unpolled_ops = true;
+}
+
#[cfg(test)]
pub mod tests {
use super::*;
use crate::error::custom_error;
+ use crate::error::AnyError;
use crate::modules::ModuleSource;
use crate::modules::ModuleSourceFuture;
use crate::modules::ModuleType;
- use crate::op_async;
- use crate::op_sync;
use crate::ZeroCopyBuf;
+ use deno_ops::op;
use futures::future::lazy;
use std::ops::FnOnce;
use std::pin::Pin;
use std::rc::Rc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
+ // deno_ops macros generate code assuming deno_core in scope.
+ mod deno_core {
+ pub use crate::*;
+ }
pub fn run_in_task<F>(f: F)
where
@@ -1689,26 +1679,26 @@ pub mod tests {
dispatch_count: Arc<AtomicUsize>,
}
- fn op_test(rc_op_state: Rc<RefCell<OpState>>, payload: OpPayload) -> Op {
- let rc_op_state2 = rc_op_state.clone();
- let op_state_ = rc_op_state2.borrow();
+ #[op]
+ async fn op_test(
+ rc_op_state: Rc<RefCell<OpState>>,
+ control: u8,
+ buf: Option<ZeroCopyBuf>,
+ ) -> Result<u8, AnyError> {
+ let op_state_ = rc_op_state.borrow();
let test_state = op_state_.borrow::<TestState>();
test_state.dispatch_count.fetch_add(1, Ordering::Relaxed);
- let (control, buf): (u8, Option<ZeroCopyBuf>) =
- payload.deserialize().unwrap();
match test_state.mode {
Mode::Async => {
assert_eq!(control, 42);
- let resp = (0, 1, serialize_op_result(Ok(43), rc_op_state));
- Op::Async(OpCall::ready(resp))
+ Ok(43)
}
Mode::AsyncZeroCopy(has_buffer) => {
assert_eq!(buf.is_some(), has_buffer);
if let Some(buf) = buf {
assert_eq!(buf.len(), 1);
}
- let resp = (0, 1, serialize_op_result(Ok(43), rc_op_state));
- Op::Async(OpCall::ready(resp))
+ Ok(43)
}
}
}
@@ -1717,7 +1707,7 @@ pub mod tests {
let dispatch_count = Arc::new(AtomicUsize::new(0));
let dispatch_count2 = dispatch_count.clone();
let ext = Extension::builder()
- .ops(vec![("op_test", Box::new(op_test))])
+ .ops(vec![op_test::decl()])
.state(move |state| {
state.put(TestState {
mode,
@@ -2028,30 +2018,6 @@ pub mod tests {
}
#[test]
- fn test_pre_dispatch() {
- run_in_task(|cx| {
- let (mut runtime, _dispatch_count) = setup(Mode::Async);
- runtime
- .execute_script(
- "bad_op_id.js",
- r#"
- let thrown;
- try {
- Deno.core.opcallSync(100, null, null);
- } catch (e) {
- thrown = e;
- }
- assert(String(thrown) === "TypeError: Unknown op id: 100");
- "#,
- )
- .unwrap();
- if let Poll::Ready(Err(_)) = runtime.poll_event_loop(cx, false) {
- unreachable!();
- }
- });
- }
-
- #[test]
fn syntax_error() {
let mut runtime = JsRuntime::new(Default::default());
let src = "hocuspocus(";
@@ -2095,6 +2061,7 @@ pub mod tests {
#[test]
fn test_error_builder() {
+ #[op]
fn op_err(_: &mut OpState, _: (), _: ()) -> Result<(), Error> {
Err(custom_error("DOMExceptionOperationError", "abc"))
}
@@ -2104,9 +2071,7 @@ pub mod tests {
}
run_in_task(|cx| {
- let ext = Extension::builder()
- .ops(vec![("op_err", op_sync(op_err))])
- .build();
+ let ext = Extension::builder().ops(vec![op_err::decl()]).build();
let mut runtime = JsRuntime::new(RuntimeOptions {
extensions: vec![ext],
get_error_class_fn: Some(&get_error_class_name),
@@ -2177,7 +2142,7 @@ pub mod tests {
});
let cb_handle = runtime.v8_isolate().thread_safe_handle();
- let callback_invoke_count = Rc::new(AtomicUsize::default());
+ let callback_invoke_count = Rc::new(AtomicUsize::new(0));
let inner_invoke_count = Rc::clone(&callback_invoke_count);
runtime.add_near_heap_limit_callback(
@@ -2221,7 +2186,7 @@ pub mod tests {
});
let cb_handle = runtime.v8_isolate().thread_safe_handle();
- let callback_invoke_count_first = Rc::new(AtomicUsize::default());
+ let callback_invoke_count_first = Rc::new(AtomicUsize::new(0));
let inner_invoke_count_first = Rc::clone(&callback_invoke_count_first);
runtime.add_near_heap_limit_callback(
move |current_limit, _initial_limit| {
@@ -2230,7 +2195,7 @@ pub mod tests {
},
);
- let callback_invoke_count_second = Rc::new(AtomicUsize::default());
+ let callback_invoke_count_second = Rc::new(AtomicUsize::new(0));
let inner_invoke_count_second = Rc::clone(&callback_invoke_count_second);
runtime.add_near_heap_limit_callback(
move |current_limit, _initial_limit| {
@@ -2500,6 +2465,7 @@ assertEquals(1, notify_return_value);
async fn test_async_opstate_borrow() {
struct InnerState(u64);
+ #[op]
async fn op_async_borrow(
op_state: Rc<RefCell<OpState>>,
_: (),
@@ -2519,7 +2485,7 @@ assertEquals(1, notify_return_value);
}
let extension = Extension::builder()
- .ops(vec![("op_async_borrow", op_async(op_async_borrow))])
+ .ops(vec![op_async_borrow::decl()])
.state(|state| {
state.put(InnerState(42));
Ok(())
@@ -2542,6 +2508,7 @@ assertEquals(1, notify_return_value);
#[tokio::test]
async fn test_set_macrotask_callback_set_next_tick_callback() {
+ #[op]
async fn op_async_sleep(
_op_state: Rc<RefCell<OpState>>,
_: (),
@@ -2553,7 +2520,7 @@ assertEquals(1, notify_return_value);
}
let extension = Extension::builder()
- .ops(vec![("op_async_sleep", op_async(op_async_sleep))])
+ .ops(vec![op_async_sleep::decl()])
.build();
let mut runtime = JsRuntime::new(RuntimeOptions {
@@ -2617,25 +2584,23 @@ assertEquals(1, notify_return_value);
fn test_has_tick_scheduled() {
use futures::task::ArcWake;
- let macrotask = Arc::new(AtomicUsize::default());
- let macrotask_ = Arc::clone(&macrotask);
-
- let next_tick = Arc::new(AtomicUsize::default());
- let next_tick_ = Arc::clone(&next_tick);
+ static MACROTASK: AtomicUsize = AtomicUsize::new(0);
+ static NEXT_TICK: AtomicUsize = AtomicUsize::new(0);
- let op_macrotask = move |_: &mut OpState, _: (), _: ()| {
- macrotask_.fetch_add(1, Ordering::Relaxed);
+ #[op]
+ fn op_macrotask(_: &mut OpState, _: (), _: ()) -> Result<(), AnyError> {
+ MACROTASK.fetch_add(1, Ordering::Relaxed);
Ok(())
- };
+ }
- let op_next_tick = move |_: &mut OpState, _: (), _: ()| {
- next_tick_.fetch_add(1, Ordering::Relaxed);
+ #[op]
+ fn op_next_tick(_: &mut OpState, _: (), _: ()) -> Result<(), AnyError> {
+ NEXT_TICK.fetch_add(1, Ordering::Relaxed);
Ok(())
- };
+ }
let extension = Extension::builder()
- .ops(vec![("op_macrotask", op_sync(op_macrotask))])
- .ops(vec![("op_next_tick", op_sync(op_next_tick))])
+ .ops(vec![op_macrotask::decl(), op_next_tick::decl()])
.build();
let mut runtime = JsRuntime::new(RuntimeOptions {
@@ -2670,8 +2635,8 @@ assertEquals(1, notify_return_value);
let cx = &mut Context::from_waker(&waker);
assert!(matches!(runtime.poll_event_loop(cx, false), Poll::Pending));
- assert_eq!(1, macrotask.load(Ordering::Relaxed));
- assert_eq!(1, next_tick.load(Ordering::Relaxed));
+ assert_eq!(1, MACROTASK.load(Ordering::Relaxed));
+ assert_eq!(1, NEXT_TICK.load(Ordering::Relaxed));
assert_eq!(awoken_times.swap(0, Ordering::Relaxed), 1);
assert!(matches!(runtime.poll_event_loop(cx, false), Poll::Pending));
assert_eq!(awoken_times.swap(0, Ordering::Relaxed), 1);
@@ -2756,28 +2721,34 @@ assertEquals(1, notify_return_value);
#[tokio::test]
async fn test_set_promise_reject_callback() {
- let promise_reject = Arc::new(AtomicUsize::default());
- let promise_reject_ = Arc::clone(&promise_reject);
+ static PROMISE_REJECT: AtomicUsize = AtomicUsize::new(0);
+ static UNCAUGHT_EXCEPTION: AtomicUsize = AtomicUsize::new(0);
- let uncaught_exception = Arc::new(AtomicUsize::default());
- let uncaught_exception_ = Arc::clone(&uncaught_exception);
-
- let op_promise_reject = move |_: &mut OpState, _: (), _: ()| {
- promise_reject_.fetch_add(1, Ordering::Relaxed);
+ #[op]
+ fn op_promise_reject(
+ _: &mut OpState,
+ _: (),
+ _: (),
+ ) -> Result<(), AnyError> {
+ PROMISE_REJECT.fetch_add(1, Ordering::Relaxed);
Ok(())
- };
+ }
- let op_uncaught_exception = move |_: &mut OpState, _: (), _: ()| {
- uncaught_exception_.fetch_add(1, Ordering::Relaxed);
+ #[op]
+ fn op_uncaught_exception(
+ _: &mut OpState,
+ _: (),
+ _: (),
+ ) -> Result<(), AnyError> {
+ UNCAUGHT_EXCEPTION.fetch_add(1, Ordering::Relaxed);
Ok(())
- };
+ }
let extension = Extension::builder()
- .ops(vec![("op_promise_reject", op_sync(op_promise_reject))])
- .ops(vec![(
- "op_uncaught_exception",
- op_sync(op_uncaught_exception),
- )])
+ .ops(vec![
+ op_promise_reject::decl(),
+ op_uncaught_exception::decl(),
+ ])
.build();
let mut runtime = JsRuntime::new(RuntimeOptions {
@@ -2812,8 +2783,8 @@ assertEquals(1, notify_return_value);
.unwrap();
runtime.run_event_loop(false).await.unwrap();
- assert_eq!(1, promise_reject.load(Ordering::Relaxed));
- assert_eq!(1, uncaught_exception.load(Ordering::Relaxed));
+ assert_eq!(1, PROMISE_REJECT.load(Ordering::Relaxed));
+ assert_eq!(1, UNCAUGHT_EXCEPTION.load(Ordering::Relaxed));
runtime
.execute_script(
@@ -2840,7 +2811,7 @@ assertEquals(1, notify_return_value);
// printed to stderr.
runtime.run_event_loop(false).await.unwrap();
- assert_eq!(2, promise_reject.load(Ordering::Relaxed));
- assert_eq!(2, uncaught_exception.load(Ordering::Relaxed));
+ assert_eq!(2, PROMISE_REJECT.load(Ordering::Relaxed));
+ assert_eq!(2, UNCAUGHT_EXCEPTION.load(Ordering::Relaxed));
}
}