1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-25 16:49:18 -05:00

perf: eager poll async ops in Isolate (#3046)

This commit is contained in:
Bartek Iwańczuk 2019-10-14 23:46:27 +02:00 committed by Ryan Dahl
parent 6056595357
commit 4221b90c3f
8 changed files with 240 additions and 93 deletions

View file

@ -75,11 +75,17 @@ export async function sendAsync(
const promiseId = nextPromiseId(); const promiseId = nextPromiseId();
args = Object.assign(args, { promiseId }); args = Object.assign(args, { promiseId });
const promise = util.createResolvable<Ok>(); const promise = util.createResolvable<Ok>();
promiseTable.set(promiseId, promise);
const argsUi8 = encode(args); const argsUi8 = encode(args);
const resUi8 = core.dispatch(opId, argsUi8, zeroCopy); const buf = core.dispatch(opId, argsUi8, zeroCopy);
util.assert(resUi8 == null); if (buf) {
// Sync result.
const res = decode(buf);
promise.resolve(res);
} else {
// Async result.
promiseTable.set(promiseId, promise);
}
const res = await promise; const res = await promise;
return unwrapResponse(res); return unwrapResponse(res);

View file

@ -61,8 +61,20 @@ export function sendAsyncMinimal(
scratch32[1] = arg; scratch32[1] = arg;
scratch32[2] = 0; // result scratch32[2] = 0; // result
const promise = util.createResolvable<number>(); const promise = util.createResolvable<number>();
const buf = core.dispatch(opId, scratchBytes, zeroCopy);
if (buf) {
const buf32 = new Int32Array(
buf.buffer,
buf.byteOffset,
buf.byteLength / 4
);
const record = recordFromBufMinimal(opId, buf32);
// Sync result.
promise.resolve(record.result);
} else {
// Async result.
promiseTableMin.set(promiseId, promise); promiseTableMin.set(promiseId, promise);
core.dispatch(opId, scratchBytes, zeroCopy); }
return promise; return promise;
} }

View file

@ -298,13 +298,17 @@ fn eval_command(flags: DenoFlags, argv: Vec<String>) {
} }
fn bundle_command(flags: DenoFlags, argv: Vec<String>) { fn bundle_command(flags: DenoFlags, argv: Vec<String>) {
let (mut _worker, state) = create_worker_and_state(flags, argv); let (worker, state) = create_worker_and_state(flags, argv);
let main_module = state.main_module().unwrap(); let main_module = state.main_module().unwrap();
assert!(state.argv.len() >= 3); assert!(state.argv.len() >= 3);
let out_file = state.argv[2].clone(); let out_file = state.argv[2].clone();
debug!(">>>>> bundle_async START"); debug!(">>>>> bundle_async START");
let bundle_future = state // NOTE: we need to poll `worker` otherwise TS compiler worker won't run properly
let main_future = lazy(move || {
worker.then(move |result| {
js_check(result);
state
.ts_compiler .ts_compiler
.bundle_async(state.clone(), main_module.to_string(), out_file) .bundle_async(state.clone(), main_module.to_string(), out_file)
.map_err(|err| { .map_err(|err| {
@ -315,8 +319,10 @@ fn bundle_command(flags: DenoFlags, argv: Vec<String>) {
.and_then(move |_| { .and_then(move |_| {
debug!(">>>>> bundle_async END"); debug!(">>>>> bundle_async END");
Ok(()) Ok(())
})
})
}); });
tokio_util::run(bundle_future); tokio_util::run(main_future);
} }
fn run_repl(flags: DenoFlags, argv: Vec<String>) { fn run_repl(flags: DenoFlags, argv: Vec<String>) {

View file

@ -7,6 +7,7 @@ use futures::Poll;
use std::io; use std::io;
use std::mem; use std::mem;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::ops::FnOnce;
use tokio; use tokio;
use tokio::net::TcpStream; use tokio::net::TcpStream;
use tokio::runtime; use tokio::runtime;
@ -78,6 +79,7 @@ where
#[derive(Debug)] #[derive(Debug)]
enum AcceptState { enum AcceptState {
Eager(Resource),
Pending(Resource), Pending(Resource),
Empty, Empty,
} }
@ -85,7 +87,7 @@ enum AcceptState {
/// Simply accepts a connection. /// Simply accepts a connection.
pub fn accept(r: Resource) -> Accept { pub fn accept(r: Resource) -> Accept {
Accept { Accept {
state: AcceptState::Pending(r), state: AcceptState::Eager(r),
} }
} }
@ -107,6 +109,16 @@ impl Future for Accept {
// in TcpListener resource. // in TcpListener resource.
// In this way, when the listener is closed, the task can be // In this way, when the listener is closed, the task can be
// notified to error out (instead of stuck forever). // notified to error out (instead of stuck forever).
AcceptState::Eager(ref mut r) => match r.poll_accept() {
Ok(futures::prelude::Async::Ready(t)) => t,
Ok(futures::prelude::Async::NotReady) => {
self.state = AcceptState::Pending(r.to_owned());
return Ok(futures::prelude::Async::NotReady);
}
Err(e) => {
return Err(e);
}
},
AcceptState::Pending(ref mut r) => match r.poll_accept() { AcceptState::Pending(ref mut r) => match r.poll_accept() {
Ok(futures::prelude::Async::Ready(t)) => { Ok(futures::prelude::Async::Ready(t)) => {
r.untrack_task(); r.untrack_task();
@ -126,8 +138,8 @@ impl Future for Accept {
}; };
match mem::replace(&mut self.state, AcceptState::Empty) { match mem::replace(&mut self.state, AcceptState::Empty) {
AcceptState::Pending(_) => Ok((stream, addr).into()),
AcceptState::Empty => panic!("invalid internal state"), AcceptState::Empty => panic!("invalid internal state"),
_ => Ok((stream, addr).into()),
} }
} }
} }
@ -166,3 +178,16 @@ where
{ {
f.map_err(|err| panic!("Future got unexpected error: {:?}", err)) f.map_err(|err| panic!("Future got unexpected error: {:?}", err))
} }
#[cfg(test)]
pub fn run_in_task<F>(f: F)
where
F: FnOnce() + Send + 'static,
{
let fut = futures::future::lazy(move || {
f();
futures::future::ok(())
});
run(fut)
}

View file

@ -263,7 +263,7 @@ mod tests {
#[test] #[test]
fn test_worker_messages() { fn test_worker_messages() {
tokio_util::init(|| { tokio_util::run_in_task(|| {
let mut worker = create_test_worker(); let mut worker = create_test_worker();
let source = r#" let source = r#"
onmessage = function(e) { onmessage = function(e) {
@ -314,7 +314,7 @@ mod tests {
#[test] #[test]
fn removed_from_resource_table_on_close() { fn removed_from_resource_table_on_close() {
tokio_util::init(|| { tokio_util::run_in_task(|| {
let mut worker = create_test_worker(); let mut worker = create_test_worker();
worker worker
.execute("onmessage = () => { delete window.onmessage; }") .execute("onmessage = () => { delete window.onmessage; }")
@ -349,7 +349,7 @@ mod tests {
#[test] #[test]
fn execute_mod_resolve_error() { fn execute_mod_resolve_error() {
tokio_util::init(|| { tokio_util::run_in_task(|| {
// "foo" is not a valid module specifier so this should return an error. // "foo" is not a valid module specifier so this should return an error.
let mut worker = create_test_worker(); let mut worker = create_test_worker();
let module_specifier = let module_specifier =
@ -361,7 +361,7 @@ mod tests {
#[test] #[test]
fn execute_mod_002_hello() { fn execute_mod_002_hello() {
tokio_util::init(|| { tokio_util::run_in_task(|| {
// This assumes cwd is project root (an assumption made throughout the // This assumes cwd is project root (an assumption made throughout the
// tests). // tests).
let mut worker = create_test_worker(); let mut worker = create_test_worker();

View file

@ -43,11 +43,25 @@ function send(promiseId, opId, arg, zeroCopy = null) {
function sendAsync(opId, arg, zeroCopy = null) { function sendAsync(opId, arg, zeroCopy = null) {
const promiseId = nextPromiseId++; const promiseId = nextPromiseId++;
const p = createResolvable(); const p = createResolvable();
const buf = send(promiseId, opId, arg, zeroCopy);
if (buf) {
const record = recordFromBuf(buf);
// Sync result.
p.resolve(record.result);
} else {
// Async result.
promiseMap.set(promiseId, p); promiseMap.set(promiseId, p);
send(promiseId, opId, arg, zeroCopy); }
return p; return p;
} }
/** Returns i32 number */
function sendSync(opId, arg) {
const buf = send(0, opId, arg);
const record = recordFromBuf(buf);
return record.result;
}
function recordFromBuf(buf) { function recordFromBuf(buf) {
assert(buf.byteLength === 3 * 4); assert(buf.byteLength === 3 * 4);
const buf32 = new Int32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4); const buf32 = new Int32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
@ -58,13 +72,6 @@ function recordFromBuf(buf) {
}; };
} }
/** Returns i32 number */
function sendSync(opId, arg) {
const buf = send(0, opId, arg);
const record = recordFromBuf(buf);
return record.result;
}
function handleAsyncMsgFromRust(opId, buf) { function handleAsyncMsgFromRust(opId, buf) {
const record = recordFromBuf(buf); const record = recordFromBuf(buf);
const { promiseId, result } = record; const { promiseId, result } = record;

View file

@ -315,6 +315,21 @@ impl Isolate {
PinnedBuf::new(zero_copy_buf), PinnedBuf::new(zero_copy_buf),
); );
let op = match op {
Op::Async(mut fut) => {
// Tries to greedily poll async ops once. Often they are immediately ready, in
// which case they can be turned into a sync op before we return to V8. This
// can save a boundary crossing.
#[allow(clippy::match_wild_err_arm)]
match fut.poll() {
Err(_) => panic!("unexpected op error"),
Ok(Ready(buf)) => Op::Sync(buf),
Ok(NotReady) => Op::Async(fut),
}
}
Op::Sync(buf) => Op::Sync(buf),
};
debug_assert_eq!(isolate.shared.size(), 0); debug_assert_eq!(isolate.shared.size(), 0);
match op { match op {
Op::Sync(buf) => { Op::Sync(buf) => {
@ -748,8 +763,34 @@ pub mod tests {
) )
} }
struct DelayedFuture {
counter: u32,
buf: Box<[u8]>,
}
impl DelayedFuture {
pub fn new(buf: Box<[u8]>) -> Self {
DelayedFuture { counter: 0, buf }
}
}
impl Future for DelayedFuture {
type Item = Box<[u8]>;
type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if self.counter > 0 {
return Ok(Async::Ready(self.buf.clone()));
}
self.counter += 1;
Ok(Async::NotReady)
}
}
pub enum Mode { pub enum Mode {
AsyncImmediate, AsyncImmediate,
AsyncDelayed,
OverflowReqSync, OverflowReqSync,
OverflowResSync, OverflowResSync,
OverflowReqAsync, OverflowReqAsync,
@ -772,6 +813,12 @@ pub mod tests {
let buf = vec![43u8, 0, 0, 0].into_boxed_slice(); let buf = vec![43u8, 0, 0, 0].into_boxed_slice();
Op::Async(Box::new(futures::future::ok(buf))) Op::Async(Box::new(futures::future::ok(buf)))
} }
Mode::AsyncDelayed => {
assert_eq!(control.len(), 1);
assert_eq!(control[0], 42);
let buf = vec![43u8, 0, 0, 0].into_boxed_slice();
Op::Async(Box::new(DelayedFuture::new(buf)))
}
Mode::OverflowReqSync => { Mode::OverflowReqSync => {
assert_eq!(control.len(), 100 * 1024 * 1024); assert_eq!(control.len(), 100 * 1024 * 1024);
let buf = vec![43u8, 0, 0, 0].into_boxed_slice(); let buf = vec![43u8, 0, 0, 0].into_boxed_slice();
@ -789,7 +836,7 @@ pub mod tests {
Mode::OverflowReqAsync => { Mode::OverflowReqAsync => {
assert_eq!(control.len(), 100 * 1024 * 1024); assert_eq!(control.len(), 100 * 1024 * 1024);
let buf = vec![43u8, 0, 0, 0].into_boxed_slice(); let buf = vec![43u8, 0, 0, 0].into_boxed_slice();
Op::Async(Box::new(futures::future::ok(buf))) Op::Async(Box::new(DelayedFuture::new(buf)))
} }
Mode::OverflowResAsync => { Mode::OverflowResAsync => {
assert_eq!(control.len(), 1); assert_eq!(control.len(), 1);
@ -798,7 +845,7 @@ pub mod tests {
vec.resize(100 * 1024 * 1024, 0); vec.resize(100 * 1024 * 1024, 0);
vec[0] = 4; vec[0] = 4;
let buf = vec.into_boxed_slice(); let buf = vec.into_boxed_slice();
Op::Async(Box::new(futures::future::ok(buf))) Op::Async(Box::new(DelayedFuture::new(buf)))
} }
} }
}; };
@ -889,6 +936,50 @@ pub mod tests {
run_in_task(|| { run_in_task(|| {
let (mut isolate, dispatch_count) = setup(Mode::AsyncImmediate); let (mut isolate, dispatch_count) = setup(Mode::AsyncImmediate);
js_check(isolate.execute(
"setup2.js",
r#"
let nrecv = 0;
Deno.core.setAsyncHandler((opId, buf) => {
nrecv++;
});
"#,
));
assert_eq!(dispatch_count.load(Ordering::Relaxed), 0);
js_check(isolate.execute(
"check1.js",
r#"
assert(nrecv == 0);
let control = new Uint8Array([42]);
const res1 = Deno.core.send(1, control);
assert(res1);
assert(nrecv == 0);
"#,
));
assert_eq!(dispatch_count.load(Ordering::Relaxed), 1);
assert_eq!(dispatch_count.load(Ordering::Relaxed), 1);
js_check(isolate.execute(
"check2.js",
r#"
assert(nrecv == 0);
Deno.core.send(1, control);
assert(nrecv == 0);
"#,
));
assert_eq!(dispatch_count.load(Ordering::Relaxed), 2);
assert_eq!(dispatch_count.load(Ordering::Relaxed), 2);
assert_eq!(Async::Ready(()), isolate.poll().unwrap());
js_check(isolate.execute("check3.js", "assert(nrecv == 0)"));
// We are idle, so the next poll should be the last.
assert_eq!(Async::Ready(()), isolate.poll().unwrap());
});
}
#[test]
fn test_poll_async_delayed_ops() {
run_in_task(|| {
let (mut isolate, dispatch_count) = setup(Mode::AsyncDelayed);
js_check(isolate.execute( js_check(isolate.execute(
"setup2.js", "setup2.js",
r#" r#"

View file

@ -165,7 +165,7 @@ void deno_respond(Deno* d_, void* user_data, deno_op_id op_id, deno_buf buf) {
if (d->current_args_ != nullptr) { if (d->current_args_ != nullptr) {
// Synchronous response. // Synchronous response.
// Note op_id is not passed back in the case of synchronous response. // Note op_id is not passed back in the case of synchronous response.
if (buf.data_ptr != nullptr) { if (buf.data_ptr != nullptr && buf.data_len > 0) {
auto ab = deno::ImportBuf(d, buf); auto ab = deno::ImportBuf(d, buf);
d->current_args_->GetReturnValue().Set(ab); d->current_args_->GetReturnValue().Set(ab);
} }