summaryrefslogtreecommitdiff
path: root/core/isolate.rs
diff options
context:
space:
mode:
Diffstat (limited to 'core/isolate.rs')
-rw-r--r--core/isolate.rs279
1 files changed, 204 insertions, 75 deletions
diff --git a/core/isolate.rs b/core/isolate.rs
index d3ac4457e..5d6088ca5 100644
--- a/core/isolate.rs
+++ b/core/isolate.rs
@@ -1,8 +1,9 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license.
-// Do not add dependenies to modules.rs. it should remain decoupled from the
-// isolate to keep the Isolate struct from becoming too bloating for users who
-// do not need asynchronous module loading.
+// Do not add any dependency to modules.rs!
+// modules.rs is complex and should remain decoupled from isolate.rs to keep the
+// Isolate struct from becoming too bloating for users who do not need
+// asynchronous module loading.
use crate::any_error::ErrBox;
use crate::js_errors::CoreJSError;
@@ -18,7 +19,9 @@ use crate::libdeno::Snapshot1;
use crate::libdeno::Snapshot2;
use crate::shared_queue::SharedQueue;
use crate::shared_queue::RECOMMENDED_SIZE;
-use futures::stream::{FuturesUnordered, Stream};
+use futures::stream::FuturesUnordered;
+use futures::stream::Stream;
+use futures::stream::StreamFuture;
use futures::task;
use futures::Async::*;
use futures::Future;
@@ -27,6 +30,7 @@ use libc::c_char;
use libc::c_void;
use std::ffi::CStr;
use std::ffi::CString;
+use std::fmt;
use std::ptr::null;
use std::sync::{Arc, Mutex, Once};
@@ -57,6 +61,76 @@ pub struct Script<'a> {
pub filename: &'a str,
}
+/// Represent result of fetching the source code of a module. Found module URL
+/// might be different from specified URL used for loading due to redirections
+/// (like HTTP 303). E.G. Both https://example.com/a.ts and
+/// https://example.com/b.ts may point to https://example.com/c.ts
+/// By keeping track of specified and found URL we can alias modules and avoid
+/// recompiling the same code 3 times.
+#[derive(Debug, Eq, PartialEq)]
+pub struct SourceCodeInfo {
+ pub code: String,
+ pub module_url_specified: String,
+ pub module_url_found: String,
+}
+
+#[derive(Debug, Eq, PartialEq)]
+pub enum RecursiveLoadEvent {
+ Fetch(SourceCodeInfo),
+ Instantiate(deno_mod),
+}
+
+pub trait ImportStream: Stream {
+ fn register(
+ &mut self,
+ source_code_info: SourceCodeInfo,
+ isolate: &mut Isolate,
+ ) -> Result<(), ErrBox>;
+}
+
+type DynImportStream =
+ Box<dyn ImportStream<Item = RecursiveLoadEvent, Error = ErrBox> + Send>;
+
+type DynImportFn = Fn(deno_dyn_import_id, &str, &str) -> DynImportStream;
+
+/// Wraps DynImportStream to include the deno_dyn_import_id, so that it doesn't
+/// need to be exposed.
+#[derive(Debug)]
+struct DynImport {
+ pub id: deno_dyn_import_id,
+ pub inner: DynImportStream,
+}
+
+impl fmt::Debug for DynImportStream {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "DynImportStream(..)")
+ }
+}
+
+impl Stream for DynImport {
+ type Item = (deno_dyn_import_id, RecursiveLoadEvent);
+ type Error = (deno_dyn_import_id, ErrBox);
+
+ fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
+ match self.inner.poll() {
+ Ok(Ready(Some(event))) => Ok(Ready(Some((self.id, event)))),
+ Ok(Ready(None)) => unreachable!(),
+ Err(e) => Err((self.id, e)),
+ Ok(NotReady) => Ok(NotReady),
+ }
+ }
+}
+
+impl ImportStream for DynImport {
+ fn register(
+ &mut self,
+ source_code_info: SourceCodeInfo,
+ isolate: &mut Isolate,
+ ) -> Result<(), ErrBox> {
+ self.inner.register(source_code_info, isolate)
+ }
+}
+
// TODO(ry) It's ugly that we have both Script and OwnedScript. Ideally we
// wouldn't expose such twiddly complexity.
struct OwnedScript {
@@ -83,31 +157,8 @@ pub enum StartupData<'a> {
None,
}
-pub type DynImportFuture =
- Box<dyn Future<Item = deno_mod, Error = ErrBox> + Send>;
-type DynImportFn = dyn Fn(&str, &str) -> DynImportFuture;
-
type JSErrorCreateFn = dyn Fn(V8Exception) -> ErrBox;
-/// 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 = (deno_mod, ErrBox);
- fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
- match self.inner.poll() {
- Ok(Ready(mod_id)) => Ok(Ready((self.id, mod_id))),
- Ok(NotReady) => Ok(NotReady),
- Err(e) => Err((self.id, e)),
- }
- }
-}
-
/// A single execution context of JavaScript. Corresponds roughly to the "Web
/// Worker" concept in the DOM. An Isolate is a Future that can be used with
/// Tokio. The Isolate future complete when there is an error or when all
@@ -125,7 +176,7 @@ pub struct Isolate {
needs_init: bool,
shared: SharedQueue,
pending_ops: FuturesUnordered<PendingOpFuture>,
- pending_dyn_imports: FuturesUnordered<DynImport>,
+ pending_dyn_imports: FuturesUnordered<StreamFuture<DynImport>>,
have_unpolled_ops: bool,
startup_script: Option<OwnedScript>,
}
@@ -208,7 +259,10 @@ impl Isolate {
pub fn set_dyn_import<F>(&mut self, f: F)
where
- F: Fn(&str, &str) -> DynImportFuture + Send + Sync + 'static,
+ F: Fn(deno_dyn_import_id, &str, &str) -> DynImportStream
+ + Send
+ + Sync
+ + 'static,
{
self.dyn_import = Some(Arc::new(f));
}
@@ -257,10 +311,10 @@ impl Isolate {
debug!("dyn_import specifier {} referrer {} ", specifier, referrer);
if let Some(ref f) = isolate.dyn_import {
- let inner = f(specifier, referrer);
- let fut = DynImport { inner, id };
+ let inner = f(id, specifier, referrer);
+ let stream = DynImport { inner, id };
task::current().notify();
- isolate.pending_dyn_imports.push(fut);
+ isolate.pending_dyn_imports.push(stream.into_future());
} else {
panic!("dyn_import callback not set")
}
@@ -434,18 +488,19 @@ impl Isolate {
let (mod_id, maybe_err_str) = match result {
Ok(mod_id) => (mod_id, None),
Err(None) => (0, None),
- Err(Some(err_str)) => (0, Some(err_str)),
+ Err(Some(err_str)) => (0, Some(CString::new(err_str).unwrap())),
+ };
+ let err_str_ptr = match maybe_err_str {
+ Some(ref err_str) => err_str.as_ptr(),
+ None => std::ptr::null(),
};
- let err_ptr = maybe_err_str
- .map(|e| e.as_ptr() as *const c_char)
- .unwrap_or(std::ptr::null());
unsafe {
libdeno::deno_dyn_import_done(
self.libdeno_isolate,
self.as_raw_ptr(),
id,
mod_id,
- err_ptr,
+ err_str_ptr,
)
};
self.check_last_exception().map_err(|err| {
@@ -453,6 +508,46 @@ impl Isolate {
err
})
}
+
+ fn poll_dyn_imports(&mut self) -> Poll<(), ErrBox> {
+ use RecursiveLoadEvent::*;
+ loop {
+ match self.pending_dyn_imports.poll() {
+ Ok(NotReady) | Ok(Ready(None)) => {
+ // There are no active dynamic import loaders, or none are ready.
+ return Ok(futures::Async::Ready(()));
+ }
+ Ok(Ready(Some((
+ Some((dyn_import_id, Fetch(source_code_info))),
+ mut stream,
+ )))) => {
+ // A module (not necessarily the one dynamically imported) has been
+ // fetched. Create and register it, and if successful, poll for the
+ // next recursive-load event related to this dynamic import.
+ match stream.register(source_code_info, self) {
+ Ok(()) => self.pending_dyn_imports.push(stream.into_future()),
+ Err(err) => {
+ self.dyn_import_done(dyn_import_id, Err(Some(err.to_string())))?
+ }
+ }
+ }
+ Ok(Ready(Some((Some((dyn_import_id, Instantiate(module_id))), _)))) => {
+ // The top-level module from a dynamic import has been instantiated.
+ match self.mod_evaluate(module_id) {
+ Ok(()) => self.dyn_import_done(dyn_import_id, Ok(module_id))?,
+ Err(..) => self.dyn_import_done(dyn_import_id, Err(None))?,
+ }
+ }
+ Err(((dyn_import_id, err), _)) => {
+ // A non-javascript error occurred; this could be due to a an invalid
+ // module specifier, or a problem with the source map, or a failure
+ // to fetch the module source code.
+ self.dyn_import_done(dyn_import_id, Err(Some(err.to_string())))?
+ }
+ Ok(Ready(Some((None, _)))) => unreachable!(),
+ }
+ }
+ }
}
/// Called during mod_instantiate() to resolve imports.
@@ -556,20 +651,7 @@ impl Future for Isolate {
loop {
// If there are any pending dyn_import futures, do those first.
- loop {
- match self.pending_dyn_imports.poll() {
- Ok(NotReady) | Ok(Ready(None)) => break,
- Ok(Ready(Some((dyn_import_id, mod_id)))) => {
- match self.mod_evaluate(mod_id) {
- Ok(()) => self.dyn_import_done(dyn_import_id, Ok(mod_id))?,
- Err(..) => self.dyn_import_done(dyn_import_id, Err(None))?,
- }
- }
- Err((dyn_import_id, err)) => {
- self.dyn_import_done(dyn_import_id, Err(Some(err.to_string())))?
- }
- }
- }
+ self.poll_dyn_imports()?;
// Now handle actual ops.
self.have_unpolled_ops = false;
@@ -612,7 +694,7 @@ impl Future for Isolate {
self.check_last_exception()?;
// We're idle if pending_ops is empty.
- if self.pending_ops.is_empty() {
+ if self.pending_ops.is_empty() && self.pending_dyn_imports.is_empty() {
Ok(futures::Async::Ready(()))
} else {
if self.have_unpolled_ops {
@@ -658,10 +740,11 @@ pub mod tests {
use futures::future::lazy;
use futures::future::ok;
use futures::Async;
+ use std::io;
use std::ops::FnOnce;
use std::sync::atomic::{AtomicUsize, Ordering};
- fn run_in_task<F, R>(f: F) -> R
+ pub fn run_in_task<F, R>(f: F) -> R
where
F: FnOnce() -> R,
{
@@ -864,6 +947,40 @@ pub mod tests {
});
}
+ struct MockImportStream(Vec<Result<RecursiveLoadEvent, ErrBox>>);
+
+ impl Stream for MockImportStream {
+ type Item = RecursiveLoadEvent;
+ type Error = ErrBox;
+ fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
+ let event = if self.0.is_empty() {
+ None
+ } else {
+ Some(self.0.remove(0)?)
+ };
+ Ok(Ready(event))
+ }
+ }
+
+ impl ImportStream for MockImportStream {
+ fn register(
+ &mut self,
+ module_data: SourceCodeInfo,
+ isolate: &mut Isolate,
+ ) -> Result<(), ErrBox> {
+ let id = isolate.mod_new(
+ false,
+ &module_data.module_url_found,
+ &module_data.code,
+ )?;
+ println!(
+ "MockImportStream register {} {}",
+ id, module_data.module_url_found
+ );
+ Ok(())
+ }
+ }
+
#[test]
fn dyn_import_err() {
// Test an erroneous dynamic import where the specified module isn't found.
@@ -871,13 +988,13 @@ pub mod tests {
let count = Arc::new(AtomicUsize::new(0));
let count_ = count.clone();
let mut isolate = Isolate::new(StartupData::None, false);
- isolate.set_dyn_import(move |specifier, referrer| {
+ isolate.set_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(
- std::io::Error::new(std::io::ErrorKind::Other, "oh no!").into(),
- ))
+ let err = io::Error::from(io::ErrorKind::NotFound);
+ let stream = MockImportStream(vec![Err(err.into())]);
+ Box::new(stream)
});
js_check(isolate.execute(
"dyn_import2.js",
@@ -902,17 +1019,29 @@ pub mod tests {
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 isolate = Isolate::new(StartupData::None, false);
- isolate.set_dyn_import(move |_specifier, referrer| {
- count_.fetch_add(1, Ordering::Relaxed);
- // assert_eq!(specifier, "foo.js");
+ isolate.set_dyn_import(move |_id, specifier, referrer| {
+ let c = count_.fetch_add(1, Ordering::Relaxed);
+ match c {
+ 0 => assert_eq!(specifier, "foo1.js"),
+ 1 => assert_eq!(specifier, "foo2.js"),
+ _ => unreachable!(),
+ }
assert_eq!(referrer, "dyn_import3.js");
- let mod_id = mod_b2.lock().unwrap();
- Box::new(futures::future::ok(*mod_id))
+ let mod_id = *mod_b2.lock().unwrap();
+ let source_code_info = SourceCodeInfo {
+ module_url_specified: "foo.js".to_owned(),
+ module_url_found: "foo.js".to_owned(),
+ code: "".to_owned(),
+ };
+ let stream = MockImportStream(vec![
+ Ok(RecursiveLoadEvent::Fetch(source_code_info)),
+ Ok(RecursiveLoadEvent::Instantiate(mod_id)),
+ ]);
+ Box::new(stream)
});
// Instantiate mod_b
@@ -930,18 +1059,18 @@ pub mod tests {
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");
- }
- })();
- "#,
+ (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);