diff options
Diffstat (limited to 'core/isolate.rs')
-rw-r--r-- | core/isolate.rs | 279 |
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); |