diff options
Diffstat (limited to 'core/isolate.rs')
-rw-r--r-- | core/isolate.rs | 548 |
1 files changed, 548 insertions, 0 deletions
diff --git a/core/isolate.rs b/core/isolate.rs new file mode 100644 index 000000000..cdda7b815 --- /dev/null +++ b/core/isolate.rs @@ -0,0 +1,548 @@ +// Copyright 2018 the Deno authors. All rights reserved. MIT license. +use crate::js_errors::JSError; +use crate::libdeno; +use crate::libdeno::deno_buf; +use crate::libdeno::deno_mod; +use futures::Async; +use futures::Future; +use futures::Poll; +use libc::c_void; +use std::ffi::CStr; +use std::ffi::CString; +use std::sync::{Once, ONCE_INIT}; + +pub type Op<R> = dyn Future<Item = R, Error = ()> + Send; + +struct PendingOp<R> { + op: Box<Op<R>>, + polled_recently: bool, + zero_copy_id: usize, // non-zero if associated zero-copy buffer. +} + +impl<R> Future for PendingOp<R> { + type Item = R; + type Error = (); + + fn poll(&mut self) -> Poll<R, ()> { + // Do not call poll on ops we've already polled this turn. + if self.polled_recently { + Ok(Async::NotReady) + } else { + self.polled_recently = true; + let op = &mut self.op; + op.poll().map_err(|()| { + // Ops should not error. If an op experiences an error it needs to + // encode that error into the record R, so it can be returned to JS. + panic!("ops should not error") + }) + } + } +} + +pub trait Behavior<R> { + fn startup_snapshot(&mut self) -> Option<deno_buf>; + fn startup_shared(&mut self) -> Option<deno_buf>; + + fn resolve(&mut self, specifier: &str, referrer: deno_mod) -> deno_mod; + + fn recv(&mut self, record: R, zero_copy_buf: deno_buf) -> (bool, Box<Op<R>>); + + /// Clears the shared buffer. + fn records_reset(&mut self); + + /// Returns false if not enough room. + fn records_push(&mut self, record: R) -> bool; + + /// Returns none if empty. + fn records_shift(&mut self) -> Option<R>; +} + +pub struct Isolate<R, B: Behavior<R>> { + libdeno_isolate: *const libdeno::isolate, + behavior: B, + pending_ops: Vec<PendingOp<R>>, + polled_recently: bool, +} + +unsafe impl<R, B: Behavior<R>> Send for Isolate<R, B> {} + +impl<R, B: Behavior<R>> Drop for Isolate<R, B> { + fn drop(&mut self) { + unsafe { libdeno::deno_delete(self.libdeno_isolate) } + } +} + +static DENO_INIT: Once = ONCE_INIT; + +impl<R, B: Behavior<R>> Isolate<R, B> { + pub fn new(mut behavior: B) -> Self { + DENO_INIT.call_once(|| { + unsafe { libdeno::deno_init() }; + }); + + let config = libdeno::deno_config { + will_snapshot: 0, + load_snapshot: match behavior.startup_snapshot() { + Some(s) => s, + None => libdeno::deno_buf::empty(), + }, + shared: match behavior.startup_shared() { + Some(s) => s, + None => libdeno::deno_buf::empty(), + }, + recv_cb: Self::pre_dispatch, + }; + let libdeno_isolate = unsafe { libdeno::deno_new(config) }; + + Self { + libdeno_isolate, + behavior, + pending_ops: Vec::new(), + polled_recently: false, + } + } + + extern "C" fn pre_dispatch( + user_data: *mut c_void, + control_buf: deno_buf, + zero_copy_buf: deno_buf, + ) { + let isolate = unsafe { Isolate::<R, B>::from_raw_ptr(user_data) }; + assert_eq!(control_buf.len(), 0); + let zero_copy_id = zero_copy_buf.zero_copy_id; + + let req_record = isolate.behavior.records_shift().unwrap(); + + isolate.behavior.records_reset(); + + let (is_sync, op) = isolate.behavior.recv(req_record, zero_copy_buf); + if is_sync { + let res_record = op.wait().unwrap(); + let push_success = isolate.behavior.records_push(res_record); + assert!(push_success); + // TODO check that if JSError thrown during respond(), that it will be + // picked up. + let _ = isolate.respond(); + } else { + isolate.pending_ops.push(PendingOp { + op, + polled_recently: false, + zero_copy_id, + }); + isolate.polled_recently = false; + } + } + + pub fn zero_copy_release(&self, zero_copy_id: usize) { + unsafe { + libdeno::deno_zero_copy_release(self.libdeno_isolate, zero_copy_id) + } + } + + #[inline] + unsafe fn from_raw_ptr<'a>(ptr: *const c_void) -> &'a mut Self { + let ptr = ptr as *mut _; + &mut *ptr + } + + #[inline] + fn as_raw_ptr(&self) -> *const c_void { + self as *const _ as *const c_void + } + + pub fn execute( + &self, + js_filename: &str, + js_source: &str, + ) -> Result<(), JSError> { + let filename = CString::new(js_filename).unwrap(); + let source = CString::new(js_source).unwrap(); + unsafe { + libdeno::deno_execute( + self.libdeno_isolate, + self.as_raw_ptr(), + filename.as_ptr(), + source.as_ptr(), + ) + }; + if let Some(err) = self.last_exception() { + return Err(err); + } + Ok(()) + } + + fn last_exception(&self) -> Option<JSError> { + let ptr = unsafe { libdeno::deno_last_exception(self.libdeno_isolate) }; + if ptr.is_null() { + None + } else { + let cstr = unsafe { CStr::from_ptr(ptr) }; + let v8_exception = cstr.to_str().unwrap(); + debug!("v8_exception\n{}\n", v8_exception); + let js_error = JSError::from_v8_exception(v8_exception).unwrap(); + Some(js_error) + } + } + + pub fn check_promise_errors(&self) { + unsafe { + libdeno::deno_check_promise_errors(self.libdeno_isolate); + } + } + + fn respond(&mut self) -> Result<(), JSError> { + let buf = deno_buf::empty(); + unsafe { + libdeno::deno_respond(self.libdeno_isolate, self.as_raw_ptr(), buf) + } + if let Some(err) = self.last_exception() { + Err(err) + } else { + Ok(()) + } + } + + /// Low-level module creation. + /// You probably want to use IsolateState::mod_execute instead. + pub fn mod_new( + &self, + main: bool, + name: &str, + source: &str, + ) -> Result<deno_mod, JSError> { + let name_ = CString::new(name.to_string()).unwrap(); + let name_ptr = name_.as_ptr() as *const libc::c_char; + + let source_ = CString::new(source.to_string()).unwrap(); + let source_ptr = source_.as_ptr() as *const libc::c_char; + + let id = unsafe { + libdeno::deno_mod_new(self.libdeno_isolate, main, name_ptr, source_ptr) + }; + if let Some(js_error) = self.last_exception() { + assert_eq!(id, 0); + return Err(js_error); + } + + Ok(id) + } + + pub fn mod_get_imports(&self, id: deno_mod) -> Vec<String> { + let len = + unsafe { libdeno::deno_mod_imports_len(self.libdeno_isolate, id) }; + let mut out = Vec::new(); + for i in 0..len { + let specifier_ptr = + unsafe { libdeno::deno_mod_imports_get(self.libdeno_isolate, id, i) }; + let specifier_c: &CStr = unsafe { CStr::from_ptr(specifier_ptr) }; + let specifier: &str = specifier_c.to_str().unwrap(); + + out.push(specifier.to_string()); + } + out + } + + pub fn mod_instantiate(&self, id: deno_mod) -> Result<(), JSError> { + unsafe { + libdeno::deno_mod_instantiate( + self.libdeno_isolate, + self.as_raw_ptr(), + id, + Self::resolve_cb, + ) + }; + if let Some(js_error) = self.last_exception() { + return Err(js_error); + } + Ok(()) + } + + pub fn mod_evaluate(&self, id: deno_mod) -> Result<(), JSError> { + unsafe { + libdeno::deno_mod_evaluate(self.libdeno_isolate, self.as_raw_ptr(), id) + }; + if let Some(js_error) = self.last_exception() { + return Err(js_error); + } + Ok(()) + } + + /// Called during mod_instantiate() only. + extern "C" fn resolve_cb( + user_data: *mut libc::c_void, + specifier_ptr: *const libc::c_char, + referrer: deno_mod, + ) -> deno_mod { + let isolate = unsafe { Isolate::<R, B>::from_raw_ptr(user_data) }; + let specifier_c: &CStr = unsafe { CStr::from_ptr(specifier_ptr) }; + let specifier: &str = specifier_c.to_str().unwrap(); + isolate.behavior.resolve(specifier, referrer) + } +} + +struct LockerScope { + libdeno_isolate: *const libdeno::isolate, +} + +impl LockerScope { + fn new(libdeno_isolate: *const libdeno::isolate) -> LockerScope { + unsafe { libdeno::deno_lock(libdeno_isolate) } + LockerScope { libdeno_isolate } + } +} + +impl Drop for LockerScope { + fn drop(&mut self) { + unsafe { libdeno::deno_unlock(self.libdeno_isolate) } + } +} + +impl<R, B: Behavior<R>> Future for Isolate<R, B> { + type Item = (); + type Error = JSError; + + fn poll(&mut self) -> Poll<(), JSError> { + // Lock the current thread for V8. + let _locker = LockerScope::new(self.libdeno_isolate); + + // Clear poll_recently state both on the Isolate itself and + // on the pending ops. + self.polled_recently = false; + for pending in self.pending_ops.iter_mut() { + pending.polled_recently = false; + } + + while !self.polled_recently { + let mut completed_count = 0; + + debug!("poll loop"); + + self.polled_recently = true; + + self.behavior.records_reset(); + + let mut i = 0; + while i != self.pending_ops.len() { + let pending = &mut self.pending_ops[i]; + match pending.poll() { + Err(()) => panic!("unexpectd error"), + Ok(Async::NotReady) => { + i += 1; + } + Ok(Async::Ready(record)) => { + let completed = self.pending_ops.remove(i); + completed_count += 1; + + if completed.zero_copy_id > 0 { + self.zero_copy_release(completed.zero_copy_id); + } + + self.behavior.records_push(record); + } + } + } + + if completed_count > 0 { + debug!("respond"); + self.respond()?; + debug!("after respond"); + } + } + + self.check_promise_errors(); + if let Some(err) = self.last_exception() { + return Err(err); + } + + // We're idle if pending_ops is empty. + if self.pending_ops.is_empty() { + Ok(futures::Async::Ready(())) + } else { + Ok(futures::Async::NotReady) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashMap; + + fn js_check(r: Result<(), JSError>) { + if let Err(e) = r { + panic!(e.to_string()); + } + } + + struct TestBehavior { + recv_count: usize, + resolve_count: usize, + push_count: usize, + shift_count: usize, + reset_count: usize, + mod_map: HashMap<String, deno_mod>, + } + + impl TestBehavior { + fn new() -> Self { + Self { + recv_count: 0, + resolve_count: 0, + push_count: 0, + shift_count: 0, + reset_count: 0, + mod_map: HashMap::new(), + } + } + + fn register(&mut self, name: &str, id: deno_mod) { + self.mod_map.insert(name.to_string(), id); + } + } + + impl Behavior<()> for TestBehavior { + fn startup_snapshot(&mut self) -> Option<deno_buf> { + None + } + + fn startup_shared(&mut self) -> Option<deno_buf> { + None + } + + fn recv( + &mut self, + _record: (), + _zero_copy_buf: deno_buf, + ) -> (bool, Box<Op<()>>) { + self.recv_count += 1; + (false, Box::new(futures::future::ok(()))) + } + + fn resolve(&mut self, specifier: &str, _referrer: deno_mod) -> deno_mod { + self.resolve_count += 1; + match self.mod_map.get(specifier) { + Some(id) => *id, + None => 0, + } + } + + fn records_reset(&mut self) { + self.reset_count += 1; + } + + fn records_push(&mut self, _record: ()) -> bool { + self.push_count += 1; + true + } + + fn records_shift(&mut self) -> Option<()> { + self.shift_count += 1; + Some(()) + } + } + + #[test] + fn test_recv() { + let behavior = TestBehavior::new(); + let isolate = Isolate::new(behavior); + js_check(isolate.execute( + "filename.js", + r#" + libdeno.send(); + async function main() { + libdeno.send(); + } + main(); + "#, + )); + assert_eq!(isolate.behavior.recv_count, 2); + } + + #[test] + fn test_mods() { + let behavior = TestBehavior::new(); + let mut isolate = Isolate::new(behavior); + let mod_a = isolate + .mod_new( + true, + "a.js", + r#" + import { b } from 'b.js' + if (b() != 'b') throw Error(); + libdeno.send(); + "#, + ).unwrap(); + assert_eq!(isolate.behavior.recv_count, 0); + assert_eq!(isolate.behavior.resolve_count, 0); + + let imports = isolate.mod_get_imports(mod_a); + assert_eq!(imports, vec!["b.js".to_string()]); + + let mod_b = isolate + .mod_new(false, "b.js", "export function b() { return 'b' }") + .unwrap(); + let imports = isolate.mod_get_imports(mod_b); + assert_eq!(imports.len(), 0); + + js_check(isolate.mod_instantiate(mod_b)); + assert_eq!(isolate.behavior.recv_count, 0); + assert_eq!(isolate.behavior.resolve_count, 0); + + isolate.behavior.register("b.js", mod_b); + js_check(isolate.mod_instantiate(mod_a)); + assert_eq!(isolate.behavior.recv_count, 0); + assert_eq!(isolate.behavior.resolve_count, 1); + + js_check(isolate.mod_evaluate(mod_a)); + assert_eq!(isolate.behavior.recv_count, 1); + assert_eq!(isolate.behavior.resolve_count, 1); + } + + #[test] + fn test_poll_async_immediate_ops() { + let behavior = TestBehavior::new(); + let mut isolate = Isolate::new(behavior); + + js_check(isolate.execute( + "setup.js", + r#" + let nrecv = 0; + libdeno.recv(() => { + nrecv++; + }); + function assertEq(actual, expected) { + if (expected != actual) { + throw Error(`actual ${actual} expected ${expected} `); + } + } + "#, + )); + assert_eq!(isolate.behavior.recv_count, 0); + js_check(isolate.execute( + "check1.js", + r#" + assertEq(nrecv, 0); + libdeno.send(); + assertEq(nrecv, 0); + "#, + )); + assert_eq!(isolate.behavior.recv_count, 1); + assert_eq!(Ok(Async::Ready(())), isolate.poll()); + assert_eq!(isolate.behavior.recv_count, 1); + js_check(isolate.execute( + "check2.js", + r#" + assertEq(nrecv, 1); + libdeno.send(); + assertEq(nrecv, 1); + "#, + )); + assert_eq!(isolate.behavior.recv_count, 2); + assert_eq!(Ok(Async::Ready(())), isolate.poll()); + js_check(isolate.execute("check3.js", "assertEq(nrecv, 2)")); + assert_eq!(isolate.behavior.recv_count, 2); + // We are idle, so the next poll should be the last. + assert_eq!(Ok(Async::Ready(())), isolate.poll()); + } + +} |