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

Surface dynamic import in core #2472

Merged
merged 5 commits into from
Jun 10, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
181 changes: 181 additions & 0 deletions core/isolate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use crate::js_errors::JSError;
use crate::libdeno;
use crate::libdeno::deno_buf;
use crate::libdeno::deno_dyn_import_id;
use crate::libdeno::deno_mod;
use crate::libdeno::deno_pinned_buf;
use crate::libdeno::PinnedBuf;
Expand All @@ -19,6 +20,7 @@ use futures::task;
use futures::Async::*;
use futures::Future;
use futures::Poll;
use libc::c_char;
use libc::c_void;
use std::ffi::CStr;
use std::ffi::CString;
Expand Down Expand Up @@ -52,9 +54,33 @@ pub enum StartupData<'a> {

type DispatchFn = Fn(&[u8], Option<PinnedBuf>) -> Op;

pub type DynImportFuture = Box<dyn Future<Item = deno_mod, Error = ()> + Send>;
type DynImportFn = Fn(&str, &str) -> DynImportFuture;

/// Wraps DynImportFuture to include the deno_dyn_import_id, so that it doesn't
/// need to be exposed.
struct DynImport {
id: deno_dyn_import_id,
inner: DynImportFuture,
}

impl Future for DynImport {
type Item = (deno_dyn_import_id, deno_mod);
type Error = ();
fn poll(&mut self) -> Poll<Self::Item, ()> {
ry marked this conversation as resolved.
Show resolved Hide resolved
match self.inner.poll() {
Ok(Ready(mod_id)) => Ok(Ready((self.id, mod_id))),
Ok(NotReady) => Ok(NotReady),
// Note that mod_id 0 indicates error.
Err(()) => Ok(Ready((self.id, 0))),
}
}
}

#[derive(Default)]
pub struct Config {
dispatch: Option<Arc<DispatchFn>>,
dyn_import: Option<Arc<DynImportFn>>,
pub will_snapshot: bool,
}

Expand All @@ -68,6 +94,13 @@ impl Config {
{
self.dispatch = Some(Arc::new(f));
}

pub fn dyn_import<F>(&mut self, f: F)
where
F: Fn(&str, &str) -> DynImportFuture + Send + Sync + 'static,
{
self.dyn_import = Some(Arc::new(f));
}
}

/// A single execution context of JavaScript. Corresponds roughly to the "Web
Expand All @@ -85,6 +118,7 @@ pub struct Isolate {
needs_init: bool,
shared: SharedQueue,
pending_ops: FuturesUnordered<OpAsyncFuture>,
pending_dyn_imports: FuturesUnordered<DynImport>,
ry marked this conversation as resolved.
Show resolved Hide resolved
have_unpolled_ops: bool,
}

Expand Down Expand Up @@ -121,6 +155,7 @@ impl Isolate {
load_snapshot: Snapshot2::empty(),
shared: shared.as_deno_buf(),
recv_cb: Self::pre_dispatch,
dyn_import_cb: Self::dyn_import,
};

// Seperate into Option values for each startup type
Expand All @@ -147,6 +182,7 @@ impl Isolate {
needs_init,
pending_ops: FuturesUnordered::new(),
have_unpolled_ops: false,
pending_dyn_imports: FuturesUnordered::new(),
};

// If we want to use execute this has to happen here sadly.
Expand Down Expand Up @@ -174,6 +210,27 @@ impl Isolate {
}
}

extern "C" fn dyn_import(
user_data: *mut c_void,
specifier: *const c_char,
referrer: *const c_char,
id: deno_dyn_import_id,
) {
assert_ne!(user_data, std::ptr::null_mut());
let isolate = unsafe { Isolate::from_raw_ptr(user_data) };
let specifier = unsafe { CStr::from_ptr(specifier).to_str().unwrap() };
let referrer = unsafe { CStr::from_ptr(referrer).to_str().unwrap() };
debug!("dyn_import specifier {} referrer {} ", specifier, referrer);

if let Some(ref f) = isolate.config.dyn_import {
let inner = f(specifier, referrer);
let fut = DynImport { inner, id };
isolate.pending_dyn_imports.push(fut);
ry marked this conversation as resolved.
Show resolved Hide resolved
} else {
panic!("dyn_import callback not set")
}
}

extern "C" fn pre_dispatch(
user_data: *mut c_void,
control_argv0: deno_buf,
Expand Down Expand Up @@ -340,6 +397,26 @@ impl Isolate {
assert_ne!(snapshot.data_len, 0);
Ok(snapshot)
}

fn dyn_import_done(
&self,
id: libdeno::deno_dyn_import_id,
mod_id: deno_mod,
) -> Result<(), JSError> {
unsafe {
libdeno::deno_dyn_import(
self.libdeno_isolate,
self.as_raw_ptr(),
id,
mod_id,
)
};
if let Some(js_error) = self.last_exception() {
assert_eq!(id, 0);
return Err(js_error);
}
Ok(())
}
}

/// Called during mod_instantiate() to resolve imports.
Expand Down Expand Up @@ -440,6 +517,21 @@ impl Future for Isolate {
let mut overflow_response: Option<Buf> = None;

loop {
// If there are any pending dyn_import futures, do those first.
if !self.pending_dyn_imports.is_empty() {
match self.pending_dyn_imports.poll() {
Err(()) => panic!("unexpected dyn_import error"),
Ok(Ready(None)) => break,
Ok(NotReady) => break,
Ok(Ready(Some((dyn_import_id, mod_id)))) => {
debug!("dyn_import_done {} {}", dyn_import_id, mod_id);
self.dyn_import_done(dyn_import_id, mod_id)?;
}
}
}

// Now handle actual ops,

self.have_unpolled_ops = false;
#[allow(clippy::match_wild_err_arm)]
match self.pending_ops.poll() {
Expand Down Expand Up @@ -764,6 +856,95 @@ pub mod tests {
});
}

#[test]
fn dyn_import_err() {
// Test an erroneous dynamic import where the specified module isn't found.
run_in_task(|| {
let count = Arc::new(AtomicUsize::new(0));
let count_ = count.clone();
let mut config = Config::default();
config.dyn_import(move |specifier, referrer| {
count_.fetch_add(1, Ordering::Relaxed);
assert_eq!(specifier, "foo.js");
assert_eq!(referrer, "dyn_import2.js");
Box::new(futures::future::err(()))
});
let mut isolate = Isolate::new(StartupData::None, config);
js_check(isolate.execute(
"dyn_import2.js",
r#"
(async () => {
await import("foo.js");
})();
"#,
));
assert_eq!(count.load(Ordering::Relaxed), 1);

// We should get an error here.
let result = isolate.poll();
assert!(result.is_err());
})
}

#[test]
fn dyn_import_ok() {
run_in_task(|| {
let count = Arc::new(AtomicUsize::new(0));
let count_ = count.clone();

// Sometimes Rust is really annoying.
use std::sync::Mutex;
let mod_b = Arc::new(Mutex::new(0));
let mod_b2 = mod_b.clone();

let mut config = Config::default();
config.dyn_import(move |_specifier, referrer| {
count_.fetch_add(1, Ordering::Relaxed);
// assert_eq!(specifier, "foo.js");
assert_eq!(referrer, "dyn_import3.js");
let mod_id = mod_b2.lock().unwrap();
Box::new(futures::future::ok(*mod_id))
});

let mut isolate = Isolate::new(StartupData::None, config);

// Instantiate mod_b
{
let mut mod_id = mod_b.lock().unwrap();
*mod_id = isolate
.mod_new(false, "b.js", "export function b() { return 'b' }")
.unwrap();
let mut resolve = move |_specifier: &str,
_referrer: deno_mod|
-> deno_mod { unreachable!() };
js_check(isolate.mod_instantiate(*mod_id, &mut resolve));
}
// Dynamically import mod_b
js_check(isolate.execute(
"dyn_import3.js",
r#"
(async () => {
let mod = await import("foo1.js");
if (mod.b() !== 'b') {
throw Error("bad1");
}
// And again!
mod = await import("foo2.js");
if (mod.b() !== 'b') {
throw Error("bad2");
}
})();
"#,
));

assert_eq!(count.load(Ordering::Relaxed), 1);
assert_eq!(Ok(Ready(())), isolate.poll());
assert_eq!(count.load(Ordering::Relaxed), 2);
assert_eq!(Ok(Ready(())), isolate.poll());
assert_eq!(count.load(Ordering::Relaxed), 2);
})
}

#[test]
fn terminate_execution() {
let (tx, rx) = std::sync::mpsc::channel::<bool>();
Expand Down
23 changes: 23 additions & 0 deletions core/libdeno.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,23 @@ type deno_recv_cb = unsafe extern "C" fn(
zero_copy_buf: deno_pinned_buf,
);

/// Called when dynamic import is called in JS: import('foo')
/// Embedder must call deno_dyn_import() with the specified id and
/// the module.
#[allow(non_camel_case_types)]
type deno_dyn_import_cb = unsafe extern "C" fn(
user_data: *mut c_void,
specifier: *const c_char,
referrer: *const c_char,
id: deno_dyn_import_id,
);

#[allow(non_camel_case_types)]
pub type deno_mod = i32;

#[allow(non_camel_case_types)]
pub type deno_dyn_import_id = i32;

#[allow(non_camel_case_types)]
type deno_resolve_cb = unsafe extern "C" fn(
user_data: *mut c_void,
Expand All @@ -208,6 +222,7 @@ pub struct deno_config<'a> {
pub load_snapshot: Snapshot2<'a>,
pub shared: deno_buf,
pub recv_cb: deno_recv_cb,
pub dyn_import_cb: deno_dyn_import_cb,
}

#[cfg(not(windows))]
Expand Down Expand Up @@ -292,6 +307,14 @@ extern "C" {
id: deno_mod,
);

/// Call exactly once for every deno_dyn_import_cb.
pub fn deno_dyn_import(
i: *const isolate,
user_data: *const c_void,
id: deno_dyn_import_id,
mod_id: deno_mod,
);

pub fn deno_snapshot_new(i: *const isolate) -> Snapshot1<'static>;

#[allow(dead_code)]
Expand Down
6 changes: 5 additions & 1 deletion core/libdeno/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,8 @@ v8::MaybeLocal<v8::Promise> HostImportModuleDynamicallyCallback(
auto* isolate = context->GetIsolate();
DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
v8::Isolate::Scope isolate_scope(isolate);
v8::Context::Scope context_scope(context);
v8::EscapableHandleScope handle_scope(isolate);

v8::String::Utf8Value specifier_str(isolate, specifier);

Expand All @@ -544,7 +546,9 @@ v8::MaybeLocal<v8::Promise> HostImportModuleDynamicallyCallback(

d->dyn_import_cb_(d->user_data_, *specifier_str, *referrer_name_str,
import_id);
return resolver->GetPromise();

auto promise = resolver->GetPromise();
return handle_scope.Escape(promise);
}

void DenoIsolate::AddIsolate(v8::Isolate* isolate) {
Expand Down
4 changes: 3 additions & 1 deletion core/libdeno/deno.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ void deno_mod_instantiate(Deno* d, void* user_data, deno_mod id,
void deno_mod_evaluate(Deno* d, void* user_data, deno_mod id);

// Call exactly once for every deno_dyn_import_cb.
void deno_dyn_import(Deno* d, deno_dyn_import_id id, deno_mod mod_id);
// Note this call will execute JS.
void deno_dyn_import(Deno* d, void* user_data, deno_dyn_import_id id,
deno_mod mod_id);

#ifdef __cplusplus
} // extern "C"
Expand Down
19 changes: 16 additions & 3 deletions core/libdeno/modules.cc
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,19 @@ void deno_mod_evaluate(Deno* d_, void* user_data, deno_mod id) {
}
}

void deno_dyn_import(Deno* d_, deno_dyn_import_id import_id, deno_mod mod_id) {
void deno_dyn_import(Deno* d_, void* user_data, deno_dyn_import_id import_id,
deno_mod mod_id) {
auto* d = unwrap(d_);
deno::UserDataScope user_data_scope(d, user_data);

auto* isolate = d->isolate_;
v8::Isolate::Scope isolate_scope(isolate);
v8::Locker locker(isolate);
v8::HandleScope handle_scope(isolate);
auto context = d->context_.Get(d->isolate_);
v8::Context::Scope context_scope(context);

v8::TryCatch try_catch(isolate);

auto it = d->dyn_import_map_.find(import_id);
if (it == d->dyn_import_map_.end()) {
Expand All @@ -168,9 +174,13 @@ void deno_dyn_import(Deno* d_, deno_dyn_import_id import_id, deno_mod mod_id) {
/// Resolve.
auto persistent_promise = &it->second;
auto promise = persistent_promise->Get(isolate);
persistent_promise->Reset();

auto* info = d->GetModuleInfo(mod_id);

// Do the following callback into JS?? Is user_data_scope needed?
persistent_promise->Reset();
d->dyn_import_map_.erase(it);

if (info == nullptr) {
// Resolution error.
promise->Reject(context, v8::Null(isolate)).ToChecked();
Expand All @@ -181,7 +191,10 @@ void deno_dyn_import(Deno* d_, deno_dyn_import_id import_id, deno_mod mod_id) {
Local<Value> module_namespace = module->GetModuleNamespace();
promise->Resolve(context, module_namespace).ToChecked();
}
d->dyn_import_map_.erase(it);

if (try_catch.HasCaught()) {
HandleException(context, try_catch.Exception());
}
}

} // extern "C"
Loading