summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/01_core.js88
-rw-r--r--core/bindings.rs92
-rw-r--r--core/examples/http_bench_json_ops.js4
-rw-r--r--core/extensions.rs3
-rw-r--r--core/runtime.rs57
5 files changed, 192 insertions, 52 deletions
diff --git a/core/01_core.js b/core/01_core.js
index 7bee019d9..5df11c382 100644
--- a/core/01_core.js
+++ b/core/01_core.js
@@ -28,7 +28,7 @@
SymbolFor,
setQueueMicrotask,
} = window.__bootstrap.primordials;
- const ops = window.Deno.core.ops;
+ const { ops } = window.Deno.core;
const errorMap = {};
// Builtin v8 / JS errors
@@ -159,21 +159,63 @@
return res;
}
- function opAsync(opName, ...args) {
- const promiseId = nextPromiseId++;
- let p = setPromise(promiseId);
- try {
- ops[opName](promiseId, ...args);
- } catch (err) {
- // Cleanup the just-created promise
- getPromise(promiseId);
- // Rethrow the error
- throw err;
+ function rollPromiseId() {
+ return nextPromiseId++;
+ }
+
+ // Generate async op wrappers. See core/bindings.rs
+ function initializeAsyncOps() {
+ function genAsyncOp(op, name, args) {
+ return new Function(
+ "setPromise",
+ "getPromise",
+ "promiseIdSymbol",
+ "rollPromiseId",
+ "handleOpCallTracing",
+ "op",
+ "unwrapOpResult",
+ "PromisePrototypeThen",
+ `
+ return function ${name}(${args}) {
+ const id = rollPromiseId();
+ let promise = PromisePrototypeThen(setPromise(id), unwrapOpResult);
+ try {
+ op(id, ${args});
+ } catch (err) {
+ // Cleanup the just-created promise
+ getPromise(id);
+ // Rethrow the error
+ throw err;
+ }
+ handleOpCallTracing("${name}", id, promise);
+ promise[promiseIdSymbol] = id;
+ return promise;
+ }
+ `,
+ )(
+ setPromise,
+ getPromise,
+ promiseIdSymbol,
+ rollPromiseId,
+ handleOpCallTracing,
+ op,
+ unwrapOpResult,
+ PromisePrototypeThen,
+ );
}
- p = PromisePrototypeThen(p, unwrapOpResult);
+
+ // { <name>: <argc>, ... }
+ for (const ele of Object.entries(ops.asyncOpsInfo())) {
+ if (!ele) continue;
+ const [name, argc] = ele;
+ const op = ops[name];
+ const args = Array.from({ length: argc }, (_, i) => `arg${i}`).join(", ");
+ ops[name] = genAsyncOp(op, name, args);
+ }
+ }
+
+ function handleOpCallTracing(opName, promiseId, p) {
if (opCallTracingEnabled) {
- // Capture a stack trace by creating a new `Error` object. We remove the
- // first 6 characters (the `Error\n` prefix) to get just the stack trace.
const stack = StringPrototypeSlice(new Error().stack, 6);
MapPrototypeSet(opCallTraces, promiseId, { opName, stack });
p = PromisePrototypeFinally(
@@ -181,9 +223,10 @@
() => MapPrototypeDelete(opCallTraces, promiseId),
);
}
- // Save the id on the promise so it can later be ref'ed or unref'ed
- p[promiseIdSymbol] = promiseId;
- return p;
+ }
+
+ function opAsync(opName, ...args) {
+ return ops[opName](...args);
}
function refOp(promiseId) {
@@ -303,6 +346,7 @@
// Extra Deno.core.* exports
const core = ObjectAssign(globalThis.Deno.core, {
opAsync,
+ initializeAsyncOps,
resources,
metrics,
registerErrorBuilder,
@@ -322,11 +366,11 @@
setPromiseHooks,
close: (rid) => ops.op_close(rid),
tryClose: (rid) => ops.op_try_close(rid),
- read: opAsync.bind(null, "op_read"),
- readAll: opAsync.bind(null, "op_read_all"),
- write: opAsync.bind(null, "op_write"),
- writeAll: opAsync.bind(null, "op_write_all"),
- shutdown: opAsync.bind(null, "op_shutdown"),
+ read: (rid, buffer) => ops.op_read(rid, buffer),
+ readAll: (rid) => ops.op_read_all(rid),
+ write: (rid, buffer) => ops.op_write(rid, buffer),
+ writeAll: (rid, buffer) => ops.op_write_all(rid, buffer),
+ shutdown: (rid) => ops.op_shutdown(rid),
print: (msg, isErr) => ops.op_print(msg, isErr),
setMacrotaskCallback: (fn) => ops.op_set_macrotask_callback(fn),
setNextTickCallback: (fn) => ops.op_set_next_tick_callback(fn),
diff --git a/core/bindings.rs b/core/bindings.rs
index 1a869f3c4..b50c77df2 100644
--- a/core/bindings.rs
+++ b/core/bindings.rs
@@ -97,6 +97,7 @@ pub fn initialize_context<'s>(
scope: &mut v8::HandleScope<'s, ()>,
op_ctxs: &[OpCtx],
snapshot_loaded: bool,
+ will_snapshot: bool,
) -> v8::Local<'s, v8::Context> {
let scope = &mut v8::EscapableHandleScope::new(scope);
@@ -112,7 +113,9 @@ pub fn initialize_context<'s>(
let ops_obj = JsRuntime::grab_global::<v8::Object>(scope, "Deno.core.ops")
.expect("Deno.core.ops to exist");
initialize_ops(scope, ops_obj, op_ctxs, snapshot_loaded);
-
+ if !will_snapshot {
+ initialize_async_ops_info(scope, ops_obj, op_ctxs);
+ }
return scope.escape(context);
}
@@ -124,8 +127,10 @@ pub fn initialize_context<'s>(
// Bind functions to Deno.core.ops.*
let ops_obj = JsRuntime::ensure_objs(scope, global, "Deno.core.ops").unwrap();
-
- initialize_ops(scope, ops_obj, op_ctxs, snapshot_loaded);
+ if !will_snapshot {
+ initialize_async_ops_info(scope, ops_obj, op_ctxs);
+ }
+ initialize_ops(scope, ops_obj, op_ctxs, !will_snapshot);
scope.escape(context)
}
@@ -586,3 +591,84 @@ pub fn throw_type_error(scope: &mut v8::HandleScope, message: impl AsRef<str>) {
let exception = v8::Exception::type_error(scope, message);
scope.throw_exception(exception);
}
+
+struct AsyncOpsInfo {
+ ptr: *const OpCtx,
+ len: usize,
+}
+
+impl<'s> IntoIterator for &'s AsyncOpsInfo {
+ type Item = &'s OpCtx;
+ type IntoIter = AsyncOpsInfoIterator<'s>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ AsyncOpsInfoIterator {
+ // SAFETY: OpCtx slice is valid for the lifetime of the Isolate
+ info: unsafe { std::slice::from_raw_parts(self.ptr, self.len) },
+ index: 0,
+ }
+ }
+}
+
+struct AsyncOpsInfoIterator<'s> {
+ info: &'s [OpCtx],
+ index: usize,
+}
+
+impl<'s> Iterator for AsyncOpsInfoIterator<'s> {
+ type Item = &'s OpCtx;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ loop {
+ match self.info.get(self.index) {
+ Some(ctx) if ctx.decl.is_async => {
+ self.index += 1;
+ return Some(ctx);
+ }
+ Some(_) => {
+ self.index += 1;
+ }
+ None => return None,
+ }
+ }
+ }
+}
+
+fn async_ops_info(
+ scope: &mut v8::HandleScope,
+ args: v8::FunctionCallbackArguments,
+ mut rv: v8::ReturnValue,
+) {
+ let async_op_names = v8::Object::new(scope);
+ let external: v8::Local<v8::External> = args.data().try_into().unwrap();
+ let info: &AsyncOpsInfo =
+ // SAFETY: external is guaranteed to be a valid pointer to AsyncOpsInfo
+ unsafe { &*(external.value() as *const AsyncOpsInfo) };
+ for ctx in info {
+ let name = v8::String::new(scope, ctx.decl.name).unwrap();
+ let argc = v8::Integer::new(scope, ctx.decl.argc as i32);
+ async_op_names.set(scope, name.into(), argc.into());
+ }
+ rv.set(async_op_names.into());
+}
+
+fn initialize_async_ops_info(
+ scope: &mut v8::HandleScope,
+ ops_obj: v8::Local<v8::Object>,
+ op_ctxs: &[OpCtx],
+) {
+ let key = v8::String::new(scope, "asyncOpsInfo").unwrap();
+ let external = v8::External::new(
+ scope,
+ Box::into_raw(Box::new(AsyncOpsInfo {
+ ptr: op_ctxs as *const [OpCtx] as _,
+ len: op_ctxs.len(),
+ })) as *mut c_void,
+ );
+ let val = v8::Function::builder(async_ops_info)
+ .data(external.into())
+ .build(scope)
+ .unwrap();
+ val.set_name(key);
+ ops_obj.set(scope, key.into(), val.into());
+}
diff --git a/core/examples/http_bench_json_ops.js b/core/examples/http_bench_json_ops.js
index 883ef1752..261d91559 100644
--- a/core/examples/http_bench_json_ops.js
+++ b/core/examples/http_bench_json_ops.js
@@ -2,6 +2,8 @@
// This is not a real HTTP server. We read blindly one time into 'requestBuf',
// then write this fixed 'responseBuf'. The point of this benchmark is to
// exercise the event loop in a simple yet semi-realistic way.
+Deno.core.initializeAsyncOps();
+
const requestBuf = new Uint8Array(64 * 1024);
const responseBuf = new Uint8Array(
"HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World\n"
@@ -16,7 +18,7 @@ function listen() {
/** Accepts a connection, returns rid. */
function accept(serverRid) {
- return Deno.core.opAsync("op_accept", serverRid);
+ return Deno.core.ops.op_accept(serverRid);
}
async function serve(rid) {
diff --git a/core/extensions.rs b/core/extensions.rs
index 846770d1f..7b9ab5908 100644
--- a/core/extensions.rs
+++ b/core/extensions.rs
@@ -16,6 +16,9 @@ pub struct OpDecl {
pub enabled: bool,
pub is_async: bool,
pub is_unstable: bool,
+ /// V8 argument count. Used as an optimization
+ /// hint by `core.initalizeAsyncOps`.
+ pub argc: usize,
pub is_v8: bool,
pub fast_fn: Option<Box<dyn FastFunction>>,
}
diff --git a/core/runtime.rs b/core/runtime.rs
index e574bf9d3..e191596da 100644
--- a/core/runtime.rs
+++ b/core/runtime.rs
@@ -384,7 +384,8 @@ impl JsRuntime {
isolate_ptr.read()
};
let scope = &mut v8::HandleScope::new(&mut isolate);
- let context = bindings::initialize_context(scope, &op_ctxs, false);
+ let context =
+ bindings::initialize_context(scope, &op_ctxs, false, true);
global_context = v8::Global::new(scope, context);
scope.set_default_context(context);
}
@@ -422,7 +423,7 @@ impl JsRuntime {
};
let scope = &mut v8::HandleScope::new(&mut isolate);
let context =
- bindings::initialize_context(scope, &op_ctxs, snapshot_loaded);
+ bindings::initialize_context(scope, &op_ctxs, snapshot_loaded, false);
global_context = v8::Global::new(scope, context);
}
@@ -550,6 +551,7 @@ impl JsRuntime {
scope,
&self.state.borrow().op_ctxs,
self.built_from_snapshot,
+ false,
);
JsRealm::new(v8::Global::new(scope, context))
};
@@ -2243,6 +2245,7 @@ pub mod tests {
#[derive(Copy, Clone)]
enum Mode {
Async,
+ AsyncDeferred,
AsyncZeroCopy(bool),
}
@@ -2251,20 +2254,28 @@ pub mod tests {
dispatch_count: Arc<AtomicUsize>,
}
- #[op(deferred)]
+ #[op]
async fn op_test(
rc_op_state: Rc<RefCell<OpState>>,
control: u8,
buf: Option<ZeroCopyBuf>,
) -> Result<u8, AnyError> {
+ #![allow(clippy::await_holding_refcell_ref)] // False positive.
let op_state_ = rc_op_state.borrow();
let test_state = op_state_.borrow::<TestState>();
test_state.dispatch_count.fetch_add(1, Ordering::Relaxed);
- match test_state.mode {
+ let mode = test_state.mode;
+ drop(op_state_);
+ match mode {
Mode::Async => {
assert_eq!(control, 42);
Ok(43)
}
+ Mode::AsyncDeferred => {
+ tokio::task::yield_now().await;
+ assert_eq!(control, 42);
+ Ok(43)
+ }
Mode::AsyncZeroCopy(has_buffer) => {
assert_eq!(buf.is_some(), has_buffer);
if let Some(buf) = buf {
@@ -2314,14 +2325,15 @@ pub mod tests {
#[test]
fn test_ref_unref_ops() {
- let (mut runtime, _dispatch_count) = setup(Mode::Async);
+ let (mut runtime, _dispatch_count) = setup(Mode::AsyncDeferred);
runtime
.execute_script(
"filename.js",
r#"
+ Deno.core.initializeAsyncOps();
var promiseIdSymbol = Symbol.for("Deno.core.internalPromiseId");
- var p1 = Deno.core.opAsync("op_test", 42);
- var p2 = Deno.core.opAsync("op_test", 42);
+ var p1 = Deno.core.ops.op_test(42);
+ var p2 = Deno.core.ops.op_test(42);
"#,
)
.unwrap();
@@ -2374,6 +2386,7 @@ pub mod tests {
"filename.js",
r#"
let control = 42;
+ Deno.core.initializeAsyncOps();
Deno.core.opAsync("op_test", control);
async function main() {
Deno.core.opAsync("op_test", control);
@@ -2392,6 +2405,7 @@ pub mod tests {
.execute_script(
"filename.js",
r#"
+ Deno.core.initializeAsyncOps();
const p = Deno.core.opAsync("op_test", 42);
if (p[Symbol.for("Deno.core.internalPromiseId")] == undefined) {
throw new Error("missing id on returned promise");
@@ -2408,6 +2422,7 @@ pub mod tests {
.execute_script(
"filename.js",
r#"
+ Deno.core.initializeAsyncOps();
Deno.core.opAsync("op_test");
"#,
)
@@ -2422,6 +2437,7 @@ pub mod tests {
.execute_script(
"filename.js",
r#"
+ Deno.core.initializeAsyncOps();
let zero_copy_a = new Uint8Array([0]);
Deno.core.opAsync("op_test", null, zero_copy_a);
"#,
@@ -3021,7 +3037,6 @@ pub mod tests {
function main() {
console.log("asdf);
}
-
main();
"#,
);
@@ -3041,18 +3056,16 @@ function assert(cond) {
throw Error("assert");
}
}
-
function main() {
assert(false);
}
-
main();
"#,
);
let expected_error = r#"Error: assert
at assert (error_stack.js:4:11)
- at main (error_stack.js:9:3)
- at error_stack.js:12:1"#;
+ at main (error_stack.js:8:3)
+ at error_stack.js:10:1"#;
assert_eq!(result.unwrap_err().to_string(), expected_error);
}
@@ -3070,7 +3083,6 @@ main();
throw new Error("async");
});
})();
-
try {
await p;
} catch (error) {
@@ -3083,7 +3095,7 @@ main();
let expected_error = r#"Error: async
at error_async_stack.js:5:13
at async error_async_stack.js:4:5
- at async error_async_stack.js:10:5"#;
+ at async error_async_stack.js:9:5"#;
match runtime.poll_event_loop(cx, false) {
Poll::Ready(Err(e)) => {
@@ -3138,6 +3150,7 @@ if (errMessage !== "higher-level sync error: original sync error") {
.execute_script(
"test_error_context_async.js",
r#"
+Deno.core.initializeAsyncOps();
(async () => {
let errMessage;
try {
@@ -3176,7 +3189,6 @@ function assertEquals(a, b) {
const sab = new SharedArrayBuffer(16);
const i32a = new Int32Array(sab);
globalThis.resolved = false;
-
(function() {
const result = Atomics.waitAsync(i32a, 0, 0);
result.value.then(
@@ -3184,7 +3196,6 @@ globalThis.resolved = false;
() => { assertUnreachable();
});
})();
-
const notify_return_value = Atomics.notify(i32a, 0, 1);
assertEquals(1, notify_return_value);
"#,
@@ -3294,7 +3305,7 @@ assertEquals(1, notify_return_value);
runtime
.execute_script(
"op_async_borrow.js",
- "Deno.core.opAsync('op_async_borrow')",
+ "Deno.core.initializeAsyncOps(); Deno.core.ops.op_async_borrow()",
)
.unwrap();
runtime.run_event_loop(false).await.unwrap();
@@ -3368,7 +3379,8 @@ Deno.core.ops.op_sync_serialize_object_with_numbers_as_keys({
.execute_script(
"op_async_serialize_object_with_numbers_as_keys.js",
r#"
-Deno.core.opAsync('op_async_serialize_object_with_numbers_as_keys', {
+Deno.core.initializeAsyncOps();
+Deno.core.ops.op_async_serialize_object_with_numbers_as_keys({
lines: {
100: {
unit: "m"
@@ -3406,6 +3418,7 @@ Deno.core.opAsync('op_async_serialize_object_with_numbers_as_keys', {
.execute_script(
"macrotasks_and_nextticks.js",
r#"
+ Deno.core.initializeAsyncOps();
(async function () {
const results = [];
Deno.core.ops.op_set_macrotask_callback(() => {
@@ -3416,7 +3429,6 @@ Deno.core.opAsync('op_async_serialize_object_with_numbers_as_keys', {
results.push("nextTick");
Deno.core.ops.op_set_has_tick_scheduled(false);
});
-
Deno.core.ops.op_set_has_tick_scheduled(true);
await Deno.core.opAsync('op_async_sleep');
if (results[0] != "nextTick") {
@@ -3627,7 +3639,6 @@ Deno.core.opAsync('op_async_serialize_object_with_numbers_as_keys', {
Deno.core.ops.op_store_pending_promise_exception(promise);
Deno.core.ops.op_promise_reject();
});
-
new Promise((_, reject) => reject(Error("reject")));
"#,
)
@@ -3645,7 +3656,6 @@ Deno.core.opAsync('op_async_serialize_object_with_numbers_as_keys', {
prev(...args);
});
}
-
new Promise((_, reject) => reject(Error("reject")));
"#,
)
@@ -3695,7 +3705,6 @@ Deno.core.opAsync('op_async_serialize_object_with_numbers_as_keys', {
Deno.core.ops.op_set_promise_reject_callback((type, promise, reason) => {
Deno.core.ops.op_promise_reject();
});
-
throw new Error('top level throw');
"#;
@@ -3826,8 +3835,6 @@ Deno.core.opAsync('op_async_serialize_object_with_numbers_as_keys', {
const a1b = a1.subarray(0, 3);
const a2 = new Uint8Array([5,10,15]);
const a2b = a2.subarray(0, 3);
-
-
if (!(a1.length > 0 && a1b.length > 0)) {
throw new Error("a1 & a1b should have a length");
}
@@ -3838,7 +3845,6 @@ Deno.core.opAsync('op_async_serialize_object_with_numbers_as_keys', {
if (a1.length > 0 || a1b.length > 0) {
throw new Error("expecting a1 & a1b to be detached");
}
-
const a3 = Deno.core.ops.op_boomerang(a2b);
if (a3.byteLength != 3) {
throw new Error(`Expected a3.byteLength === 3, got ${a3.byteLength}`);
@@ -3849,7 +3855,6 @@ Deno.core.opAsync('op_async_serialize_object_with_numbers_as_keys', {
if (a2.byteLength > 0 || a2b.byteLength > 0) {
throw new Error("expecting a2 & a2b to be detached, a3 re-attached");
}
-
const wmem = new WebAssembly.Memory({ initial: 1, maximum: 2 });
const w32 = new Uint32Array(wmem.buffer);
w32[0] = 1; w32[1] = 2; w32[2] = 3;