Skip to content

Commit

Permalink
refactor(core): JsRuntime is not a Future (denoland#7855)
Browse files Browse the repository at this point in the history
This commit rewrites deno_core::JsRuntime to not implement Future
trait.

Instead there are two separate methods:
- JsRuntime::poll_event_loop() - does single tick of event loop
- JsRuntime::run_event_loop() - runs event loop to completion
  • Loading branch information
bartlomieju authored Oct 7, 2020
1 parent 8bd7c93 commit d8879fe
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 66 deletions.
2 changes: 1 addition & 1 deletion cli/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ impl Future for Worker {
// We always poll the inspector if it exists.
let _ = inner.inspector.as_mut().map(|i| i.poll_unpin(cx));
inner.waker.register(cx.waker());
inner.js_runtime.poll_unpin(cx)
inner.js_runtime.poll_event_loop(cx)
}
}

Expand Down
9 changes: 6 additions & 3 deletions core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ bindings.

This Rust crate contains the essential V8 bindings for Deno's command-line
interface (Deno CLI). The main abstraction here is the JsRuntime which provides
a way to execute JavaScript. The JsRuntime is modeled as a
`Future<Item=(), Error=JsError>` which completes once all of its ops have
completed.
a way to execute JavaScript.

The JsRuntime implements an event loop abstraction for the executed code that
keeps track of all pending tasks (async ops, dynamic module loads). It is user's
responsibility to drive that loop by using `JsRuntime::run_event_loop` method -
it must be executed in the context of Rust's future executor (eg. tokio, smol).

In order to bind Rust functions into JavaScript, use the `Deno.core.dispatch()`
function to trigger the "dispatch" callback in Rust. The user is responsible for
Expand Down
2 changes: 1 addition & 1 deletion core/examples/http_bench_bin_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ fn main() {
include_str!("http_bench_bin_ops.js"),
)
.unwrap();
js_runtime.await
js_runtime.run_event_loop().await
};
runtime.block_on(future).unwrap();
}
Expand Down
2 changes: 1 addition & 1 deletion core/examples/http_bench_json_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ fn main() {
include_str!("http_bench_json_ops.js"),
)
.unwrap();
js_runtime.await
js_runtime.run_event_loop().await
};
runtime.block_on(future).unwrap();
}
113 changes: 59 additions & 54 deletions core/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,55 +453,51 @@ impl JsRuntime {
.remove_near_heap_limit_callback(cb, heap_limit);
}
}
}

extern "C" fn near_heap_limit_callback<F>(
data: *mut c_void,
current_heap_limit: usize,
initial_heap_limit: usize,
) -> usize
where
F: FnMut(usize, usize) -> usize,
{
let callback = unsafe { &mut *(data as *mut F) };
callback(current_heap_limit, initial_heap_limit)
}

impl Future for JsRuntime {
type Output = Result<(), AnyError>;
/// Runs event loop to completion
///
/// This future resolves when:
/// - there are no more pending dynamic imports
/// - there are no more pending ops
pub async fn run_event_loop(&mut self) -> Result<(), AnyError> {
poll_fn(|cx| self.poll_event_loop(cx)).await
}

fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let runtime = self.get_mut();
runtime.shared_init();
/// Runs a single tick of event loop
pub fn poll_event_loop(
&mut self,
cx: &mut Context,
) -> Poll<Result<(), AnyError>> {
self.shared_init();

let state_rc = Self::state(runtime.v8_isolate());
let state_rc = Self::state(self.v8_isolate());
{
let state = state_rc.borrow();
state.waker.register(cx.waker());
}

// Top level modules
runtime.poll_mod_evaluate(cx)?;
self.evaluate_pending_modules()?;

// Dynamic module loading - ie. modules loaded using "import()"
{
let poll_imports = runtime.prepare_dyn_imports(cx)?;
let poll_imports = self.prepare_dyn_imports(cx)?;
assert!(poll_imports.is_ready());

let poll_imports = runtime.poll_dyn_imports(cx)?;
let poll_imports = self.poll_dyn_imports(cx)?;
assert!(poll_imports.is_ready());

runtime.poll_dyn_imports_evaluate(cx)?;
self.evaluate_dyn_imports()?;

runtime.check_promise_exceptions()?;
self.check_promise_exceptions()?;
}

// Ops
{
let overflow_response = runtime.poll_pending_ops(cx);
runtime.async_op_response(overflow_response)?;
runtime.drain_macrotasks()?;
runtime.check_promise_exceptions()?;
let overflow_response = self.poll_pending_ops(cx);
self.async_op_response(overflow_response)?;
self.drain_macrotasks()?;
self.check_promise_exceptions()?;
}

let state = state_rc.borrow();
Expand All @@ -527,6 +523,18 @@ impl Future for JsRuntime {
}
}

extern "C" fn near_heap_limit_callback<F>(
data: *mut c_void,
current_heap_limit: usize,
initial_heap_limit: usize,
) -> usize
where
F: FnMut(usize, usize) -> usize,
{
let callback = unsafe { &mut *(data as *mut F) };
callback(current_heap_limit, initial_heap_limit)
}

impl JsRuntimeState {
// Called by V8 during `Isolate::mod_instantiate`.
pub fn module_resolve_cb(
Expand Down Expand Up @@ -859,7 +867,7 @@ impl JsRuntime {
debug!("received module evaluate");
return Poll::Ready(result.unwrap());
}
let _r = self.poll_unpin(cx)?;
let _r = self.poll_event_loop(cx)?;
Poll::Pending
})
.await
Expand Down Expand Up @@ -1030,7 +1038,7 @@ impl JsRuntime {
}
}

fn poll_mod_evaluate(&mut self, _cx: &mut Context) -> Result<(), AnyError> {
fn evaluate_pending_modules(&mut self) -> Result<(), AnyError> {
let state_rc = Self::state(self.v8_isolate());

let context = self.global_context();
Expand Down Expand Up @@ -1074,10 +1082,7 @@ impl JsRuntime {
Ok(())
}

fn poll_dyn_imports_evaluate(
&mut self,
_cx: &mut Context,
) -> Result<(), AnyError> {
fn evaluate_dyn_imports(&mut self) -> Result<(), AnyError> {
let state_rc = Self::state(self.v8_isolate());

loop {
Expand Down Expand Up @@ -1440,13 +1445,13 @@ pub mod tests {
futures::executor::block_on(lazy(move |cx| f(cx)));
}

fn poll_until_ready<F>(future: &mut F, max_poll_count: usize) -> F::Output
where
F: Future + Unpin,
{
fn poll_until_ready(
runtime: &mut JsRuntime,
max_poll_count: usize,
) -> Result<(), AnyError> {
let mut cx = Context::from_waker(futures::task::noop_waker_ref());
for _ in 0..max_poll_count {
match future.poll_unpin(&mut cx) {
match runtime.poll_event_loop(&mut cx) {
Poll::Pending => continue,
Poll::Ready(val) => return val,
}
Expand Down Expand Up @@ -1662,7 +1667,7 @@ pub mod tests {
)
.unwrap();
assert_eq!(dispatch_count.load(Ordering::Relaxed), 1);
assert!(matches!(runtime.poll_unpin(cx), Poll::Ready(Ok(_))));
assert!(matches!(runtime.poll_event_loop(cx), Poll::Ready(Ok(_))));
assert_eq!(dispatch_count.load(Ordering::Relaxed), 1);
runtime
.execute(
Expand All @@ -1675,11 +1680,11 @@ pub mod tests {
)
.unwrap();
assert_eq!(dispatch_count.load(Ordering::Relaxed), 2);
assert!(matches!(runtime.poll_unpin(cx), Poll::Ready(Ok(_))));
assert!(matches!(runtime.poll_event_loop(cx), Poll::Ready(Ok(_))));
runtime.execute("check3.js", "assert(nrecv == 2)").unwrap();
assert_eq!(dispatch_count.load(Ordering::Relaxed), 2);
// We are idle, so the next poll should be the last.
assert!(matches!(runtime.poll_unpin(cx), Poll::Ready(Ok(_))));
assert!(matches!(runtime.poll_event_loop(cx), Poll::Ready(Ok(_))));
});
}

Expand All @@ -1703,7 +1708,7 @@ pub mod tests {
assert_eq!(dispatch_count.load(Ordering::Relaxed), 1);
// The above op never finish, but runtime can finish
// because the op is an unreffed async op.
assert!(matches!(runtime.poll_unpin(cx), Poll::Ready(Ok(_))));
assert!(matches!(runtime.poll_event_loop(cx), Poll::Ready(Ok(_))));
})
}

Expand Down Expand Up @@ -1833,7 +1838,7 @@ pub mod tests {
)
.unwrap();
assert_eq!(dispatch_count.load(Ordering::Relaxed), 1);
assert!(matches!(runtime.poll_unpin(cx), Poll::Ready(Ok(_))));
assert!(matches!(runtime.poll_event_loop(cx), Poll::Ready(Ok(_))));
runtime
.execute("check.js", "assert(asyncRecv == 1);")
.unwrap();
Expand Down Expand Up @@ -1925,7 +1930,7 @@ pub mod tests {
"#,
)
.unwrap();
if let Poll::Ready(Err(_)) = runtime.poll_unpin(&mut cx) {
if let Poll::Ready(Err(_)) = runtime.poll_event_loop(&mut cx) {
unreachable!();
}
});
Expand All @@ -1938,7 +1943,7 @@ pub mod tests {
runtime
.execute("core_test.js", include_str!("core_test.js"))
.unwrap();
if let Poll::Ready(Err(_)) = runtime.poll_unpin(&mut cx) {
if let Poll::Ready(Err(_)) = runtime.poll_event_loop(&mut cx) {
unreachable!();
}
});
Expand All @@ -1964,7 +1969,7 @@ pub mod tests {
include_str!("encode_decode_test.js"),
)
.unwrap();
if let Poll::Ready(Err(_)) = runtime.poll_unpin(&mut cx) {
if let Poll::Ready(Err(_)) = runtime.poll_event_loop(&mut cx) {
unreachable!();
}
});
Expand Down Expand Up @@ -2272,7 +2277,7 @@ pub mod tests {

assert_eq!(count.load(Ordering::Relaxed), 0);
// We should get an error here.
let result = runtime.poll_unpin(cx);
let result = runtime.poll_event_loop(cx);
if let Poll::Ready(Ok(_)) = result {
unreachable!();
}
Expand Down Expand Up @@ -2365,14 +2370,14 @@ pub mod tests {
.unwrap();

// First poll runs `prepare_load` hook.
assert!(matches!(runtime.poll_unpin(cx), Poll::Pending));
assert!(matches!(runtime.poll_event_loop(cx), Poll::Pending));
assert_eq!(prepare_load_count.load(Ordering::Relaxed), 1);

// Second poll actually loads modules into the isolate.
assert!(matches!(runtime.poll_unpin(cx), Poll::Ready(Ok(_))));
assert!(matches!(runtime.poll_event_loop(cx), Poll::Ready(Ok(_))));
assert_eq!(resolve_count.load(Ordering::Relaxed), 4);
assert_eq!(load_count.load(Ordering::Relaxed), 2);
assert!(matches!(runtime.poll_unpin(cx), Poll::Ready(Ok(_))));
assert!(matches!(runtime.poll_event_loop(cx), Poll::Ready(Ok(_))));
assert_eq!(resolve_count.load(Ordering::Relaxed), 4);
assert_eq!(load_count.load(Ordering::Relaxed), 2);
})
Expand Down Expand Up @@ -2404,10 +2409,10 @@ pub mod tests {
)
.unwrap();
// First poll runs `prepare_load` hook.
let _ = runtime.poll_unpin(cx);
let _ = runtime.poll_event_loop(cx);
assert_eq!(prepare_load_count.load(Ordering::Relaxed), 1);
// Second poll triggers error
let _ = runtime.poll_unpin(cx);
let _ = runtime.poll_event_loop(cx);
})
}

Expand Down Expand Up @@ -2538,7 +2543,7 @@ main();
at async error_async_stack.js:10:5
"#;

match runtime.poll_unpin(cx) {
match runtime.poll_event_loop(cx) {
Poll::Ready(Err(e)) => {
assert_eq!(e.to_string(), expected_error);
}
Expand Down
11 changes: 5 additions & 6 deletions op_crates/web/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ pub fn get_declaration() -> PathBuf {
mod tests {
use deno_core::JsRuntime;
use futures::future::lazy;
use futures::future::FutureExt;
use futures::task::Context;
use futures::task::Poll;

Expand All @@ -102,7 +101,7 @@ mod tests {
include_str!("abort_controller_test.js"),
)
.unwrap();
if let Poll::Ready(Err(_)) = isolate.poll_unpin(&mut cx) {
if let Poll::Ready(Err(_)) = isolate.poll_event_loop(&mut cx) {
unreachable!();
}
});
Expand All @@ -115,7 +114,7 @@ mod tests {
isolate
.execute("event_test.js", include_str!("event_test.js"))
.unwrap();
if let Poll::Ready(Err(_)) = isolate.poll_unpin(&mut cx) {
if let Poll::Ready(Err(_)) = isolate.poll_event_loop(&mut cx) {
unreachable!();
}
});
Expand All @@ -134,7 +133,7 @@ mod tests {
} else {
unreachable!();
}
if let Poll::Ready(Err(_)) = isolate.poll_unpin(&mut cx) {
if let Poll::Ready(Err(_)) = isolate.poll_event_loop(&mut cx) {
unreachable!();
}
});
Expand All @@ -147,7 +146,7 @@ mod tests {
isolate
.execute("event_target_test.js", include_str!("event_target_test.js"))
.unwrap();
if let Poll::Ready(Err(_)) = isolate.poll_unpin(&mut cx) {
if let Poll::Ready(Err(_)) = isolate.poll_event_loop(&mut cx) {
unreachable!();
}
});
Expand All @@ -163,7 +162,7 @@ mod tests {
include_str!("text_encoding_test.js"),
)
.unwrap();
if let Poll::Ready(Err(_)) = isolate.poll_unpin(&mut cx) {
if let Poll::Ready(Err(_)) = isolate.poll_event_loop(&mut cx) {
unreachable!();
}
});
Expand Down

0 comments on commit d8879fe

Please sign in to comment.