summaryrefslogtreecommitdiff
path: root/core/runtime.rs
diff options
context:
space:
mode:
Diffstat (limited to 'core/runtime.rs')
-rw-r--r--core/runtime.rs293
1 files changed, 132 insertions, 161 deletions
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));
}
}