2019-02-26 17:36:05 -05:00
|
|
|
/// To run this benchmark:
|
|
|
|
///
|
|
|
|
/// > DENO_BUILD_MODE=release ./tools/build.py && \
|
|
|
|
/// ./target/release/deno_core_http_bench --multi-thread
|
2019-03-30 19:30:40 -04:00
|
|
|
extern crate deno;
|
2019-02-26 17:36:05 -05:00
|
|
|
extern crate futures;
|
|
|
|
extern crate libc;
|
|
|
|
extern crate tokio;
|
|
|
|
|
|
|
|
#[macro_use]
|
|
|
|
extern crate log;
|
|
|
|
#[macro_use]
|
|
|
|
extern crate lazy_static;
|
|
|
|
|
2019-03-30 19:30:40 -04:00
|
|
|
use deno::*;
|
2019-02-26 17:36:05 -05:00
|
|
|
use futures::future::lazy;
|
|
|
|
use std::env;
|
2019-10-28 20:42:44 -04:00
|
|
|
use std::io::Error;
|
|
|
|
use std::io::ErrorKind;
|
2019-02-26 17:36:05 -05:00
|
|
|
use std::net::SocketAddr;
|
|
|
|
use std::sync::Mutex;
|
2019-10-23 12:32:28 -04:00
|
|
|
use std::sync::MutexGuard;
|
2019-02-26 17:36:05 -05:00
|
|
|
use tokio::prelude::*;
|
|
|
|
|
2019-04-07 22:40:58 -04:00
|
|
|
static LOGGER: Logger = Logger;
|
|
|
|
struct Logger;
|
|
|
|
impl log::Log for Logger {
|
|
|
|
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
|
|
|
metadata.level() <= log::max_level()
|
|
|
|
}
|
|
|
|
fn log(&self, record: &log::Record) {
|
|
|
|
if self.enabled(record.metadata()) {
|
|
|
|
println!("{} - {}", record.level(), record.args());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fn flush(&self) {}
|
|
|
|
}
|
|
|
|
|
2019-03-14 19:17:52 -04:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
2019-03-11 17:57:36 -04:00
|
|
|
pub struct Record {
|
2019-06-14 13:58:20 -04:00
|
|
|
pub promise_id: i32,
|
2019-03-11 17:57:36 -04:00
|
|
|
pub arg: i32,
|
|
|
|
pub result: i32,
|
|
|
|
}
|
|
|
|
|
2019-03-14 19:17:52 -04:00
|
|
|
impl Into<Buf> for Record {
|
|
|
|
fn into(self) -> Buf {
|
2019-08-07 14:02:29 -04:00
|
|
|
let buf32 = vec![self.promise_id, self.arg, self.result].into_boxed_slice();
|
|
|
|
let ptr = Box::into_raw(buf32) as *mut [u8; 3 * 4];
|
2019-03-14 19:17:52 -04:00
|
|
|
unsafe { Box::from_raw(ptr) }
|
|
|
|
}
|
|
|
|
}
|
2019-03-11 17:57:36 -04:00
|
|
|
|
2019-03-14 19:17:52 -04:00
|
|
|
impl From<&[u8]> for Record {
|
|
|
|
fn from(s: &[u8]) -> Record {
|
2019-04-17 09:25:51 -04:00
|
|
|
#[allow(clippy::cast_ptr_alignment)]
|
2019-03-14 19:17:52 -04:00
|
|
|
let ptr = s.as_ptr() as *const i32;
|
2019-08-07 14:02:29 -04:00
|
|
|
let ints = unsafe { std::slice::from_raw_parts(ptr, 3) };
|
2019-03-14 19:17:52 -04:00
|
|
|
Record {
|
2019-06-14 13:58:20 -04:00
|
|
|
promise_id: ints[0],
|
2019-08-07 14:02:29 -04:00
|
|
|
arg: ints[1],
|
|
|
|
result: ints[2],
|
2019-03-14 19:17:52 -04:00
|
|
|
}
|
|
|
|
}
|
2019-03-11 17:57:36 -04:00
|
|
|
}
|
|
|
|
|
2019-03-14 19:17:52 -04:00
|
|
|
impl From<Buf> for Record {
|
|
|
|
fn from(buf: Buf) -> Record {
|
2019-08-07 14:02:29 -04:00
|
|
|
assert_eq!(buf.len(), 3 * 4);
|
2019-04-17 09:25:51 -04:00
|
|
|
#[allow(clippy::cast_ptr_alignment)]
|
2019-08-07 14:02:29 -04:00
|
|
|
let ptr = Box::into_raw(buf) as *mut [i32; 3];
|
2019-03-14 19:17:52 -04:00
|
|
|
let ints: Box<[i32]> = unsafe { Box::from_raw(ptr) };
|
2019-08-07 14:02:29 -04:00
|
|
|
assert_eq!(ints.len(), 3);
|
2019-03-14 19:17:52 -04:00
|
|
|
Record {
|
2019-06-14 13:58:20 -04:00
|
|
|
promise_id: ints[0],
|
2019-08-07 14:02:29 -04:00
|
|
|
arg: ints[1],
|
|
|
|
result: ints[2],
|
2019-03-14 19:17:52 -04:00
|
|
|
}
|
2019-03-11 17:57:36 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-14 19:17:52 -04:00
|
|
|
#[test]
|
|
|
|
fn test_record_from() {
|
|
|
|
let r = Record {
|
2019-06-14 13:58:20 -04:00
|
|
|
promise_id: 1,
|
2019-03-14 19:17:52 -04:00
|
|
|
arg: 3,
|
|
|
|
result: 4,
|
|
|
|
};
|
|
|
|
let expected = r.clone();
|
|
|
|
let buf: Buf = r.into();
|
|
|
|
#[cfg(target_endian = "little")]
|
|
|
|
assert_eq!(
|
|
|
|
buf,
|
2019-08-07 14:02:29 -04:00
|
|
|
vec![1u8, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0].into_boxed_slice()
|
2019-03-14 19:17:52 -04:00
|
|
|
);
|
|
|
|
let actual = Record::from(buf);
|
|
|
|
assert_eq!(actual, expected);
|
|
|
|
// TODO test From<&[u8]> for Record
|
2019-03-11 17:57:36 -04:00
|
|
|
}
|
|
|
|
|
2019-09-30 14:59:44 -04:00
|
|
|
pub type HttpOp = dyn Future<Item = i32, Error = std::io::Error> + Send;
|
2019-04-23 18:58:00 -04:00
|
|
|
|
2019-09-30 14:59:44 -04:00
|
|
|
pub type HttpOpHandler =
|
|
|
|
fn(record: Record, zero_copy_buf: Option<PinnedBuf>) -> Box<HttpOp>;
|
|
|
|
|
|
|
|
fn http_op(
|
|
|
|
handler: HttpOpHandler,
|
|
|
|
) -> impl Fn(&[u8], Option<PinnedBuf>) -> CoreOp {
|
|
|
|
move |control: &[u8], zero_copy_buf: Option<PinnedBuf>| -> CoreOp {
|
|
|
|
let record = Record::from(control);
|
|
|
|
let is_sync = record.promise_id == 0;
|
|
|
|
let op = handler(record.clone(), zero_copy_buf);
|
|
|
|
|
|
|
|
let mut record_a = record.clone();
|
|
|
|
let mut record_b = record.clone();
|
|
|
|
|
|
|
|
let fut = Box::new(
|
|
|
|
op.and_then(move |result| {
|
2019-04-23 18:58:00 -04:00
|
|
|
record_a.result = result;
|
|
|
|
Ok(record_a)
|
2019-07-31 17:11:37 -04:00
|
|
|
})
|
|
|
|
.or_else(|err| -> Result<Record, ()> {
|
2019-04-23 18:58:00 -04:00
|
|
|
eprintln!("unexpected err {}", err);
|
|
|
|
record_b.result = -1;
|
|
|
|
Ok(record_b)
|
2019-07-31 17:11:37 -04:00
|
|
|
})
|
|
|
|
.then(|result| -> Result<Buf, ()> {
|
2019-04-23 18:58:00 -04:00
|
|
|
let record = result.unwrap();
|
|
|
|
Ok(record.into())
|
|
|
|
}),
|
2019-09-30 14:59:44 -04:00
|
|
|
);
|
2019-05-01 18:22:32 -04:00
|
|
|
|
2019-09-30 14:59:44 -04:00
|
|
|
if is_sync {
|
|
|
|
Op::Sync(fut.wait().unwrap())
|
|
|
|
} else {
|
|
|
|
Op::Async(fut)
|
|
|
|
}
|
2019-05-01 18:22:32 -04:00
|
|
|
}
|
2019-03-11 17:57:36 -04:00
|
|
|
}
|
|
|
|
|
2019-02-26 17:36:05 -05:00
|
|
|
fn main() {
|
|
|
|
let main_future = lazy(move || {
|
|
|
|
// TODO currently isolate.execute() must be run inside tokio, hence the
|
|
|
|
// lazy(). It would be nice to not have that contraint. Probably requires
|
|
|
|
// using v8::MicrotasksPolicy::kExplicit
|
2019-04-08 10:12:43 -04:00
|
|
|
|
|
|
|
let js_source = include_str!("http_bench.js");
|
|
|
|
|
|
|
|
let startup_data = StartupData::Script(Script {
|
|
|
|
source: js_source,
|
|
|
|
filename: "http_bench.js",
|
|
|
|
});
|
|
|
|
|
2019-06-12 13:53:24 -04:00
|
|
|
let mut isolate = deno::Isolate::new(startup_data, false);
|
2019-09-30 14:59:44 -04:00
|
|
|
isolate.register_op("listen", http_op(op_listen));
|
|
|
|
isolate.register_op("accept", http_op(op_accept));
|
|
|
|
isolate.register_op("read", http_op(op_read));
|
|
|
|
isolate.register_op("write", http_op(op_write));
|
|
|
|
isolate.register_op("close", http_op(op_close));
|
2019-03-18 20:03:37 -04:00
|
|
|
|
2019-02-26 17:36:05 -05:00
|
|
|
isolate.then(|r| {
|
|
|
|
js_check(r);
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
});
|
|
|
|
|
|
|
|
let args: Vec<String> = env::args().collect();
|
2019-04-21 11:34:18 -04:00
|
|
|
// NOTE: `--help` arg will display V8 help and exit
|
2019-03-30 19:30:40 -04:00
|
|
|
let args = deno::v8_set_flags(args);
|
2019-04-07 22:40:58 -04:00
|
|
|
|
|
|
|
log::set_logger(&LOGGER).unwrap();
|
|
|
|
log::set_max_level(if args.iter().any(|a| a == "-D") {
|
|
|
|
log::LevelFilter::Debug
|
|
|
|
} else {
|
|
|
|
log::LevelFilter::Warn
|
|
|
|
});
|
|
|
|
|
|
|
|
if args.iter().any(|a| a == "--multi-thread") {
|
2019-02-26 17:36:05 -05:00
|
|
|
println!("multi-thread");
|
|
|
|
tokio::run(main_future);
|
|
|
|
} else {
|
|
|
|
println!("single-thread");
|
|
|
|
tokio::runtime::current_thread::run(main_future);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-28 20:42:44 -04:00
|
|
|
pub fn bad_resource() -> Error {
|
|
|
|
Error::new(ErrorKind::NotFound, "bad resource id")
|
|
|
|
}
|
|
|
|
|
2019-10-23 12:32:28 -04:00
|
|
|
struct TcpListener(tokio::net::TcpListener);
|
|
|
|
|
2019-10-28 20:42:44 -04:00
|
|
|
impl Resource for TcpListener {
|
|
|
|
fn inspect_repr(&self) -> &str {
|
|
|
|
"tcpListener"
|
|
|
|
}
|
|
|
|
}
|
2019-10-23 12:32:28 -04:00
|
|
|
|
|
|
|
struct TcpStream(tokio::net::TcpStream);
|
|
|
|
|
2019-10-28 20:42:44 -04:00
|
|
|
impl Resource for TcpStream {
|
|
|
|
fn inspect_repr(&self) -> &str {
|
|
|
|
"tcpStream"
|
|
|
|
}
|
|
|
|
}
|
2019-02-26 17:36:05 -05:00
|
|
|
|
|
|
|
lazy_static! {
|
2019-10-23 12:32:28 -04:00
|
|
|
static ref RESOURCE_TABLE: Mutex<ResourceTable> =
|
|
|
|
Mutex::new(ResourceTable::default());
|
2019-02-26 17:36:05 -05:00
|
|
|
}
|
|
|
|
|
2019-10-23 12:32:28 -04:00
|
|
|
fn lock_resource_table<'a>() -> MutexGuard<'a, ResourceTable> {
|
|
|
|
RESOURCE_TABLE.lock().unwrap()
|
2019-02-26 17:36:05 -05:00
|
|
|
}
|
|
|
|
|
2019-09-30 14:59:44 -04:00
|
|
|
fn op_accept(record: Record, _zero_copy_buf: Option<PinnedBuf>) -> Box<HttpOp> {
|
2019-10-23 12:32:28 -04:00
|
|
|
let rid = record.arg as u32;
|
|
|
|
debug!("accept {}", rid);
|
|
|
|
let fut = futures::future::poll_fn(move || {
|
|
|
|
let mut table = lock_resource_table();
|
2019-10-28 20:42:44 -04:00
|
|
|
let listener =
|
|
|
|
table.get_mut::<TcpListener>(rid).ok_or_else(bad_resource)?;
|
2019-10-23 12:32:28 -04:00
|
|
|
listener.0.poll_accept()
|
|
|
|
})
|
|
|
|
.and_then(move |(stream, addr)| {
|
|
|
|
debug!("accept success {}", addr);
|
|
|
|
let mut table = lock_resource_table();
|
|
|
|
let rid = table.add(Box::new(TcpStream(stream)));
|
|
|
|
Ok(rid as i32)
|
|
|
|
});
|
|
|
|
Box::new(fut)
|
2019-02-26 17:36:05 -05:00
|
|
|
}
|
|
|
|
|
2019-09-30 14:59:44 -04:00
|
|
|
fn op_listen(
|
|
|
|
_record: Record,
|
|
|
|
_zero_copy_buf: Option<PinnedBuf>,
|
|
|
|
) -> Box<HttpOp> {
|
2019-03-11 17:57:36 -04:00
|
|
|
debug!("listen");
|
2019-10-23 12:32:28 -04:00
|
|
|
let addr = "127.0.0.1:4544".parse::<SocketAddr>().unwrap();
|
|
|
|
let listener = tokio::net::TcpListener::bind(&addr).unwrap();
|
|
|
|
let mut table = lock_resource_table();
|
|
|
|
let rid = table.add(Box::new(TcpListener(listener)));
|
|
|
|
Box::new(futures::future::ok(rid as i32))
|
2019-03-11 17:57:36 -04:00
|
|
|
}
|
|
|
|
|
2019-09-30 14:59:44 -04:00
|
|
|
fn op_close(record: Record, _zero_copy_buf: Option<PinnedBuf>) -> Box<HttpOp> {
|
2019-03-11 17:57:36 -04:00
|
|
|
debug!("close");
|
2019-10-23 12:32:28 -04:00
|
|
|
let rid = record.arg as u32;
|
|
|
|
let mut table = lock_resource_table();
|
|
|
|
let fut = match table.close(rid) {
|
2019-10-28 20:42:44 -04:00
|
|
|
Some(_) => futures::future::ok(0),
|
|
|
|
None => futures::future::err(bad_resource()),
|
2019-10-23 12:32:28 -04:00
|
|
|
};
|
|
|
|
Box::new(fut)
|
2019-03-11 17:57:36 -04:00
|
|
|
}
|
|
|
|
|
2019-09-30 14:59:44 -04:00
|
|
|
fn op_read(record: Record, zero_copy_buf: Option<PinnedBuf>) -> Box<HttpOp> {
|
2019-10-23 12:32:28 -04:00
|
|
|
let rid = record.arg as u32;
|
2019-02-26 17:36:05 -05:00
|
|
|
debug!("read rid={}", rid);
|
2019-04-28 15:31:10 -04:00
|
|
|
let mut zero_copy_buf = zero_copy_buf.unwrap();
|
2019-10-23 12:32:28 -04:00
|
|
|
let fut = futures::future::poll_fn(move || {
|
|
|
|
let mut table = lock_resource_table();
|
2019-10-28 20:42:44 -04:00
|
|
|
let stream = table.get_mut::<TcpStream>(rid).ok_or_else(bad_resource)?;
|
2019-10-23 12:32:28 -04:00
|
|
|
stream.0.poll_read(&mut zero_copy_buf)
|
|
|
|
})
|
|
|
|
.and_then(move |nread| {
|
|
|
|
debug!("read success {}", nread);
|
|
|
|
Ok(nread as i32)
|
|
|
|
});
|
|
|
|
Box::new(fut)
|
2019-02-26 17:36:05 -05:00
|
|
|
}
|
|
|
|
|
2019-09-30 14:59:44 -04:00
|
|
|
fn op_write(record: Record, zero_copy_buf: Option<PinnedBuf>) -> Box<HttpOp> {
|
2019-10-23 12:32:28 -04:00
|
|
|
let rid = record.arg as u32;
|
2019-02-26 17:36:05 -05:00
|
|
|
debug!("write rid={}", rid);
|
2019-04-28 15:31:10 -04:00
|
|
|
let zero_copy_buf = zero_copy_buf.unwrap();
|
2019-10-23 12:32:28 -04:00
|
|
|
let fut = futures::future::poll_fn(move || {
|
|
|
|
let mut table = lock_resource_table();
|
2019-10-28 20:42:44 -04:00
|
|
|
let stream = table.get_mut::<TcpStream>(rid).ok_or_else(bad_resource)?;
|
2019-10-23 12:32:28 -04:00
|
|
|
stream.0.poll_write(&zero_copy_buf)
|
|
|
|
})
|
|
|
|
.and_then(move |nwritten| {
|
|
|
|
debug!("write success {}", nwritten);
|
|
|
|
Ok(nwritten as i32)
|
|
|
|
});
|
|
|
|
Box::new(fut)
|
2019-02-26 17:36:05 -05:00
|
|
|
}
|
|
|
|
|
2019-07-10 18:53:48 -04:00
|
|
|
fn js_check(r: Result<(), ErrBox>) {
|
2019-02-26 17:36:05 -05:00
|
|
|
if let Err(e) = r {
|
|
|
|
panic!(e.to_string());
|
|
|
|
}
|
|
|
|
}
|