Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core: introduce extensions #9800

Merged
merged 65 commits into from
Apr 28, 2021
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
f9bf613
core: foundations of JsRuntimeModules
AaronO Mar 15, 2021
0b6e240
fmt
AaronO Mar 16, 2021
2fc18da
core: add include_js_files!() helper macro
AaronO Mar 16, 2021
adb5a1c
Move ops init code to respective op_crates
AaronO Mar 16, 2021
bbfbf25
Remove op_crate ops from runtime/ops/mod.rs
AaronO Mar 16, 2021
613fb8c
core: add declare_ops! macro
AaronO Mar 16, 2021
2366361
webgpu: simplify op declaration using declare_ops!
AaronO Mar 16, 2021
9c9df67
fmt
AaronO Mar 16, 2021
1ce3da4
test declare_ops! on small url op_crate
AaronO Mar 16, 2021
b336228
core/runtime_modules: add BasicModule
AaronO Mar 16, 2021
7555755
fmt
AaronO Mar 16, 2021
c541c59
core: add placeholder JsRuntime.init_mod_js() and .init_mod_ops()
AaronO Mar 16, 2021
2941be1
runtime: init JsRuntime with JsRuntimeModules
AaronO Mar 16, 2021
f7d1aaf
core: init state in JsRuntime.init_mod_ops()
AaronO Mar 16, 2021
b26f14b
runtime/web_worker: align with master's removal of deno namespace for…
AaronO Mar 17, 2021
49c8b88
Merge branch 'main' into core/js-runtime-modules
AaronO Mar 17, 2021
aae9ebc
fix typo
AaronO Mar 17, 2021
1192a85
typo...
AaronO Mar 17, 2021
c32c6e5
core: simplify JsRuntime.init_mod_js()
AaronO Mar 18, 2021
e2bac14
core: improve declare_ops! and include_js_files! macros
AaronO Mar 18, 2021
37a6c3f
Add unit test to check for op-metrics on op_crates
AaronO Mar 18, 2021
dd61b13
Convert metrics to a runtime extension
AaronO Mar 18, 2021
dcdd86c
Implement OpMiddleware and extension builder
AaronO Mar 18, 2021
dca4175
core: implement all trait methods for MultiModule
AaronO Mar 18, 2021
4934f9c
fmt
AaronO Mar 18, 2021
b7e3911
Use defaults user-agent and ca_data in op_create_http_client
AaronO Mar 18, 2021
d93c971
Revert "Use defaults user-agent and ca_data in op_create_http_client"
AaronO Mar 18, 2021
59d2a13
fmt
AaronO Mar 18, 2021
92736d6
Merge branch 'main' into core/js-runtime-modules
AaronO Mar 18, 2021
2d6d132
fmt
AaronO Mar 18, 2021
d9c3613
Merge branch 'main' into core/js-runtime-modules
AaronO Mar 18, 2021
63274b2
Merge branch 'main' into core/js-runtime-modules
AaronO Mar 18, 2021
7963691
Remove duplicate HttpClientDefaults
AaronO Mar 18, 2021
0efc263
Merge branch 'main' into core/js-runtime-modules
ry Mar 23, 2021
2120156
Exclude MultiModule for now
ry Mar 23, 2021
ce425ae
Rename JsRuntimeModule to Extension. Remove BasicModuleBuilder
ry Mar 23, 2021
60147a9
Remove example
ry Mar 23, 2021
ee50474
Downgrade Extension from a trait to a single concrete type
AaronO Mar 24, 2021
16bf35f
Remove declare_ops macro
ry Mar 24, 2021
afea31c
clean up
ry Mar 24, 2021
1a10dbb
fix
ry Mar 24, 2021
d2a9df8
Merge branch 'main' into core/js-runtime-modules
AaronO Apr 21, 2021
c3d5ebf
fmt
AaronO Apr 21, 2021
ab4129e
runtime/ops: delete files of op_crate ops
AaronO Apr 21, 2021
0fe2085
runtime/ops/runtime: drop duplicate op_metrics reg
AaronO Apr 21, 2021
7b4503a
runtime/workers: remove duplicate inits of some op_crates
AaronO Apr 21, 2021
8723251
core/runtime: remove duplicate OpState import
AaronO Apr 21, 2021
d95fffd
runtime/ops/mod: remove already converted op_crate mods
AaronO Apr 21, 2021
e5cfd88
lint
AaronO Apr 21, 2021
0bf6948
Fix url_ops bench
AaronO Apr 21, 2021
0d84bff
fmt
AaronO Apr 21, 2021
64e1380
Convert file op_crate to an Extension
AaronO Apr 21, 2021
f3afb7d
Convert timers op_crate to an Extension
AaronO Apr 21, 2021
badee09
typo
AaronO Apr 21, 2021
14be760
lint
AaronO Apr 21, 2021
6fd4098
fix metricsForOpCrates
AaronO Apr 21, 2021
761d08d
Merge branch 'main' into core/js-runtime-modules
AaronO Apr 22, 2021
2ae11fd
core/runtime: remove JsRuntime::execute_static() for now
AaronO Apr 22, 2021
5bb7857
Merge branch 'main' into core/js-runtime-modules
AaronO Apr 26, 2021
e18edd4
core/runtime: sync ops-cache after initing extension ops
AaronO Apr 26, 2021
05d830d
minor doc typo
AaronO Apr 26, 2021
3236445
reset CI
AaronO Apr 26, 2021
c8a11cb
core/extensions: panic when an extensions ops are inited twice
AaronO Apr 26, 2021
f051463
core: remove RcOpRegistrar type alias
AaronO Apr 26, 2021
2f129d0
cleanup: kill OpRegistrar trait in favour of 1st class op middleware
AaronO Apr 28, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions cli/tests/unit/metrics_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,15 @@ unitTest(
assert(metrics.opsDispatchedAsync === metrics.opsCompletedAsync);
},
);

// Test that ops from op_crates have metrics (via OpMiddleware)
unitTest(function metricsForOpCrates(): void {
const _ = new URL("https://deno.land");

const m1 = Deno.metrics().ops["op_url_parse"];
assert(m1.opsDispatched > 0);
assert(m1.opsCompleted > 0);
assert(m1.bytesSentControl > 0);
assert(m1.bytesSentData >= 0);
assert(m1.bytesReceived > 0);
});
136 changes: 136 additions & 0 deletions core/extensions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use std::cell::RefCell;
use std::rc::Rc;

use crate::error::AnyError;
use crate::{OpFn, OpId, OpState};

pub type SourcePair = (&'static str, &'static str);
pub type OpPair = (&'static str, Box<OpFn>);
pub type RcOpRegistrar = Rc<RefCell<dyn OpRegistrar>>;
AaronO marked this conversation as resolved.
Show resolved Hide resolved
pub type OpMiddlewareFn = dyn Fn(&'static str, Box<OpFn>) -> Box<OpFn>;
pub type OpStateFn = dyn Fn(&mut OpState) -> Result<(), AnyError>;

#[derive(Default)]
pub struct Extension {
js_files: Option<Vec<SourcePair>>,
ops: Option<Vec<OpPair>>,
opstate_fn: Option<Box<OpStateFn>>,
middleware_fn: Option<Box<OpMiddlewareFn>>,
}

impl Extension {
pub fn new(
js_files: Option<Vec<SourcePair>>,
ops: Option<Vec<OpPair>>,
AaronO marked this conversation as resolved.
Show resolved Hide resolved
opstate_fn: Option<Box<OpStateFn>>,
middleware_fn: Option<Box<OpMiddlewareFn>>,
) -> Self {
AaronO marked this conversation as resolved.
Show resolved Hide resolved
Self {
js_files,
ops,
opstate_fn,
middleware_fn,
}
}

pub fn pure_js(js_files: Vec<SourcePair>) -> Self {
Self::new(Some(js_files), None, None, None)
}

pub fn with_ops(
js_files: Vec<SourcePair>,
ops: Vec<OpPair>,
opstate_fn: Option<Box<OpStateFn>>,
) -> Self {
Self::new(Some(js_files), Some(ops), opstate_fn, None)
}
}

// Note: this used to be a trait, but we "downgraded" it to a single concrete type
// for the initial iteration, it will like become a trait in the future
impl Extension {
/// returns JS source code to be loaded into the isolate (either at snapshotting,
/// or at startup). as a vector of a tuple of the file name, and the source code.
pub(crate) fn init_js(&self) -> Vec<SourcePair> {
AaronO marked this conversation as resolved.
Show resolved Hide resolved
match &self.js_files {
Some(files) => files.clone(),
None => vec![],
}
}

/// Called at JsRuntime startup to initialize ops in the isolate.
pub(crate) fn init_ops(&mut self, registrar: RcOpRegistrar) {
// NOTE: not idempotent
// TODO: fail if called twice ?
AaronO marked this conversation as resolved.
Show resolved Hide resolved
if let Some(ops) = self.ops.take() {
for (name, opfn) in ops {
registrar.borrow_mut().register_op(name, opfn);
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I think it should panic if called second time

}

/// Allows setting up the initial op-state of an isolate at startup.
pub(crate) fn init_state(&self, state: &mut OpState) -> Result<(), AnyError> {
match &self.opstate_fn {
Some(ofn) => ofn(state),
None => Ok(()),
}
}

/// init_registrar lets us middleware op registrations, it's called before init_ops
pub(crate) fn init_registrar(
&mut self,
registrar: RcOpRegistrar,
) -> RcOpRegistrar {
match self.middleware_fn.take() {
Some(middleware_fn) => Rc::new(RefCell::new(OpMiddleware {
registrar,
middleware_fn,
})),
None => registrar,
}
}
}

/// The OpRegistrar trait allows building op "middleware" such as:
/// OpMetrics, OpTracing or OpDisabler that wrap OpFns for profiling, debugging, etc...
/// JsRuntime is itself an OpRegistrar
pub trait OpRegistrar {
fn register_op(&mut self, name: &'static str, op_fn: Box<OpFn>) -> OpId;
}

/// OpMiddleware wraps an original OpRegistrar with an OpMiddlewareFn
pub struct OpMiddleware {
registrar: RcOpRegistrar,
middleware_fn: Box<OpMiddlewareFn>,
}

impl OpRegistrar for OpMiddleware {
fn register_op(&mut self, name: &'static str, op_fn: Box<OpFn>) -> OpId {
let new_op = (self.middleware_fn)(name, op_fn);
self.registrar.borrow_mut().register_op(name, new_op)
}
}

/// Helps embed JS files in an extension. Returns Vec<(&'static str, &'static str)>
/// representing the filename and source code.
///
/// Example:
/// ```ignore
/// include_js_files!(
/// prefix "deno:op_crates/hello",
/// "01_hello.js",
/// "02_goodbye.js",
/// )
/// ```
#[macro_export]
macro_rules! include_js_files {
(prefix $prefix:literal, $($file:literal,)+) => {
vec![
$((
concat!($prefix, "/", $file),
include_str!($file),
),)+
]
};
}
5 changes: 5 additions & 0 deletions core/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod async_cancel;
mod async_cell;
mod bindings;
pub mod error;
mod extensions;
mod flags;
mod gotham_state;
mod module_specifier;
Expand Down Expand Up @@ -81,6 +82,10 @@ pub use crate::runtime::JsErrorCreateFn;
pub use crate::runtime::JsRuntime;
pub use crate::runtime::RuntimeOptions;
pub use crate::runtime::Snapshot;
// pub use crate::runtime_modules::include_js_files!;
pub use crate::extensions::Extension;
pub use crate::extensions::OpRegistrar;
pub use crate::extensions::RcOpRegistrar;
pub use crate::zero_copy_buf::BufVec;
pub use crate::zero_copy_buf::ZeroCopyBuf;

Expand Down
86 changes: 86 additions & 0 deletions core/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ use crate::ops::*;
use crate::shared_queue::SharedQueue;
use crate::shared_queue::RECOMMENDED_SIZE;
use crate::BufVec;
use crate::Extension;
use crate::OpRegistrar;
use crate::OpState;
use crate::RcOpRegistrar;
use futures::channel::mpsc;
use futures::future::poll_fn;
use futures::stream::FuturesUnordered;
Expand Down Expand Up @@ -81,6 +84,7 @@ pub struct JsRuntime {
snapshot_creator: Option<v8::SnapshotCreator>,
has_snapshotted: bool,
allocations: IsolateAllocations,
extensions: Vec<Extension>,
}

struct DynImportModEvaluate {
Expand Down Expand Up @@ -180,6 +184,10 @@ pub struct RuntimeOptions {
/// executed tries to load modules.
pub module_loader: Option<Rc<dyn ModuleLoader>>,

/// JsRuntime extensions, not to be confused with ES modules
/// these are sets of ops and other JS code to be initialized.
pub extensions: Vec<Extension>,

/// V8 snapshot that should be loaded on startup.
///
/// Currently can't be used with `will_snapshot`.
Expand Down Expand Up @@ -297,6 +305,7 @@ impl JsRuntime {
snapshot_creator: maybe_snapshot_creator,
has_snapshotted: false,
allocations: IsolateAllocations::default(),
extensions: options.extensions,
};

if !has_startup_snapshot {
Expand Down Expand Up @@ -349,6 +358,41 @@ impl JsRuntime {
.unwrap();
}

/// Initializes JS of provided Extensions
// NOTE: this will probably change when streamlining snapshot flow
AaronO marked this conversation as resolved.
Show resolved Hide resolved
pub fn init_extension_js(&mut self) -> Result<(), AnyError> {
// Take extensions to avoid double-borrow
let mut extensions: Vec<Extension> = self.extensions.drain(..).collect();
AaronO marked this conversation as resolved.
Show resolved Hide resolved
for m in extensions.iter_mut() {
let js_files = m.init_js();
for (filename, source) in js_files {
self.execute_static(filename, source)?;
}
}
self.extensions = extensions;
Ok(())
}

/// Initializes ops of provided Extensions
// NOTE: this will probably change when streamlining snapshot flow
AaronO marked this conversation as resolved.
Show resolved Hide resolved
pub fn init_extension_ops(&mut self) -> Result<(), AnyError> {
AaronO marked this conversation as resolved.
Show resolved Hide resolved
let op_state = self.op_state();
// Original OpRegistrar
let mut registrar: RcOpRegistrar =
Rc::new(RefCell::new(JsRuntimeOpRegistrar(op_state.clone())));

// Wrap registrar
for e in self.extensions.iter_mut() {
registrar = e.init_registrar(registrar.clone());
}
AaronO marked this conversation as resolved.
Show resolved Hide resolved

for e in self.extensions.iter_mut() {
e.init_state(&mut op_state.borrow_mut())?;
e.init_ops(registrar.clone());
}
Ok(())
}

/// Executes a JavaScript code to initialize shared queue binding
/// between Rust and JS.
///
Expand Down Expand Up @@ -411,6 +455,40 @@ impl JsRuntime {
}
}

// same as execute() but for internal JS code
fn execute_static(
AaronO marked this conversation as resolved.
Show resolved Hide resolved
&mut self,
js_filename: &'static str,
js_source: &'static str,
) -> Result<(), AnyError> {
let context = self.global_context();

let scope = &mut v8::HandleScope::with_context(self.v8_isolate(), context);

let source = v8::String::new(scope, js_source).unwrap();
let name = v8::String::new(scope, js_filename).unwrap();
let origin = bindings::script_origin(scope, name);

let tc_scope = &mut v8::TryCatch::new(scope);

let script = match v8::Script::compile(tc_scope, source, Some(&origin)) {
Some(script) => script,
None => {
let exception = tc_scope.exception().unwrap();
return exception_to_err_result(tc_scope, exception, false);
}
};

match script.run(tc_scope) {
Some(_) => Ok(()),
None => {
assert!(tc_scope.has_caught());
let exception = tc_scope.exception().unwrap();
exception_to_err_result(tc_scope, exception, false)
}
}
}

/// Takes a snapshot. The isolate should have been created with will_snapshot
/// set to true.
///
Expand Down Expand Up @@ -1480,6 +1558,14 @@ impl JsRuntime {
}
}

struct JsRuntimeOpRegistrar(Rc<RefCell<OpState>>);

impl OpRegistrar for JsRuntimeOpRegistrar {
fn register_op(&mut self, name: &str, op_fn: Box<OpFn>) -> OpId {
self.0.borrow_mut().op_table.register_op(name, op_fn)
}
}

#[cfg(test)]
pub mod tests {
use super::*;
Expand Down
24 changes: 8 additions & 16 deletions op_crates/console/lib.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.

use deno_core::JsRuntime;
use deno_core::include_js_files;
use deno_core::Extension;
use std::path::PathBuf;

/// Load and execute the javascript code.
pub fn init(isolate: &mut JsRuntime) {
let files = vec![
(
"deno:op_crates/console/01_colors.js",
include_str!("01_colors.js"),
),
(
"deno:op_crates/console/02_console.js",
include_str!("02_console.js"),
),
];
for (url, source_code) in files {
isolate.execute(url, source_code).unwrap();
}
pub fn init() -> Extension {
Extension::pure_js(include_js_files!(
prefix "deno:op_crates/console",
"01_colors.js",
"02_console.js",
))
}

pub fn get_declaration() -> PathBuf {
Expand Down
31 changes: 21 additions & 10 deletions op_crates/crypto/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,38 @@
#![deny(warnings)]

use deno_core::error::AnyError;
use deno_core::include_js_files;
use deno_core::json_op_sync;
use deno_core::serde_json::json;
use deno_core::serde_json::Value;
use deno_core::JsRuntime;
use deno_core::Extension;
use deno_core::OpState;
use deno_core::ZeroCopyBuf;
use rand::rngs::StdRng;
use rand::thread_rng;
use rand::Rng;
use rand::SeedableRng;
use std::path::PathBuf;

pub use rand; // Re-export rand

/// Execute this crates' JS source files.
pub fn init(isolate: &mut JsRuntime) {
let files = vec![(
"deno:op_crates/crypto/01_crypto.js",
include_str!("01_crypto.js"),
)];
for (url, source_code) in files {
isolate.execute(url, source_code).unwrap();
}
pub fn init(maybe_seed: Option<u64>) -> Extension {
Extension::with_ops(
include_js_files!(
prefix "deno:op_crates/crypto",
"01_crypto.js",
),
vec![(
"op_crypto_get_random_values",
json_op_sync(op_crypto_get_random_values),
)],
Some(Box::new(move |state| {
if let Some(seed) = maybe_seed {
state.put(StdRng::seed_from_u64(seed));
}
Ok(())
})),
)
}

pub fn op_crypto_get_random_values(
Expand Down
Loading