From bc467b265fbe06ace24f5d9536bd8eb36ae4a601 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 22 Aug 2019 22:30:14 -0700 Subject: [PATCH] introduce JSON serialization for ops (#2799) Converts env(), exit(), execPath(), utime() and utimeSync() to use JSON instead of flatbuffers. --- cli/BUILD.gn | 7 +- cli/main.rs | 2 + cli/msg.fbs | 38 ----------- cli/ops/dispatch_flatbuffers.rs | 20 +----- cli/ops/dispatch_json.rs | 113 ++++++++++++++++++++++++++++++ cli/ops/dispatch_minimal.rs | 11 +-- cli/ops/fs.rs | 37 +++++----- cli/ops/mod.rs | 36 +++++++++- cli/ops/os.rs | 117 ++++++++++---------------------- cli/ops/utils.rs | 2 +- js/dispatch.ts | 15 +++- js/dispatch_flatbuffers.ts | 2 +- js/dispatch_json.ts | 94 +++++++++++++++++++++++++ js/dispatch_minimal.ts | 2 +- js/os.ts | 63 ++++------------- js/utime.ts | 37 +++++----- 16 files changed, 355 insertions(+), 241 deletions(-) create mode 100644 cli/ops/dispatch_json.rs create mode 100644 js/dispatch_json.ts diff --git a/cli/BUILD.gn b/cli/BUILD.gn index c7a80907ed439c..08050ae06ac1a2 100644 --- a/cli/BUILD.gn +++ b/cli/BUILD.gn @@ -83,7 +83,9 @@ ts_sources = [ "../js/dir.ts", "../js/dispatch.ts", "../js/dispatch_flatbuffers.ts", + "../js/dispatch_json.ts", "../js/dispatch_minimal.ts", + "../js/dom_file.ts", "../js/dom_types.ts", "../js/dom_util.ts", "../js/error_stack.ts", @@ -91,12 +93,11 @@ ts_sources = [ "../js/event.ts", "../js/event_target.ts", "../js/fetch.ts", - "../js/format_error.ts", - "../js/dom_file.ts", "../js/file_info.ts", "../js/files.ts", "../js/flatbuffers.ts", "../js/form_data.ts", + "../js/format_error.ts", "../js/get_random_values.ts", "../js/globals.ts", "../js/headers.ts", @@ -134,10 +135,10 @@ ts_sources = [ "../js/url_search_params.ts", "../js/util.ts", "../js/utime.ts", + "../js/version.ts", "../js/window.ts", "../js/workers.ts", "../js/write_file.ts", - "../js/version.ts", "../js/xeval.ts", "../tsconfig.json", diff --git a/cli/main.rs b/cli/main.rs index 3f8f113b2298a9..2e82b8ee868fe9 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -13,6 +13,8 @@ extern crate indexmap; #[cfg(unix)] extern crate nix; extern crate rand; +extern crate serde; +extern crate serde_derive; extern crate url; mod ansi; diff --git a/cli/msg.fbs b/cli/msg.fbs index 8fb92fee9f01c6..3a40b80f570b49 100644 --- a/cli/msg.fbs +++ b/cli/msg.fbs @@ -12,9 +12,6 @@ union Any { Cwd, CwdRes, Dial, - Environ, - EnvironRes, - Exit, Fetch, FetchSourceFile, FetchSourceFileRes, @@ -29,8 +26,6 @@ union Any { HostGetMessageRes, HostGetWorkerClosed, HostPostMessage, - IsTTY, - IsTTYRes, Kill, Link, Listen, @@ -77,9 +72,6 @@ union Any { Truncate, HomeDir, HomeDirRes, - ExecPath, - ExecPathRes, - Utime, WorkerGetMessage, WorkerGetMessageRes, WorkerPostMessage, @@ -286,21 +278,11 @@ table GlobalTimerRes { } table GlobalTimerStop { } -table Exit { - code: int; -} - -table Environ {} - table SetEnv { key: string; value: string; } -table EnvironRes { - map: [KeyValue]; -} - table KeyValue { key: string; value: string; @@ -469,18 +451,6 @@ table HomeDirRes { path: string; } -table ExecPath {} - -table ExecPathRes { - path: string; -} - -table Utime { - filename: string; - atime: uint64; - mtime: uint64; -} - table Open { filename: string; perm: uint; @@ -600,14 +570,6 @@ table NowRes { subsec_nanos: uint32; } -table IsTTY {} - -table IsTTYRes { - stdin: bool; - stdout: bool; - stderr: bool; -} - table Seek { rid: uint32; offset: int; diff --git a/cli/ops/dispatch_flatbuffers.rs b/cli/ops/dispatch_flatbuffers.rs index 2b2e5050dfc013..b9dd4d9fa2e9d2 100644 --- a/cli/ops/dispatch_flatbuffers.rs +++ b/cli/ops/dispatch_flatbuffers.rs @@ -13,13 +13,11 @@ use super::files::{op_close, op_open, op_read, op_seek, op_write}; use super::fs::{ op_chdir, op_chmod, op_chown, op_copy_file, op_cwd, op_link, op_make_temp_dir, op_mkdir, op_read_dir, op_read_link, op_remove, op_rename, - op_stat, op_symlink, op_truncate, op_utime, + op_stat, op_symlink, op_truncate, }; use super::metrics::op_metrics; use super::net::{op_accept, op_dial, op_listen, op_shutdown}; -use super::os::{ - op_env, op_exec_path, op_exit, op_home_dir, op_is_tty, op_set_env, op_start, -}; +use super::os::{op_home_dir, op_set_env, op_start}; use super::performance::op_now; use super::permissions::{op_permissions, op_revoke_permission}; use super::process::{op_kill, op_run, op_run_status}; @@ -65,13 +63,8 @@ pub fn dispatch( let op_result = op_func(state, &base, zero_copy); - let state = state.clone(); - match op_result { - Ok(Op::Sync(buf)) => { - state.metrics_op_completed(buf.len()); - Op::Sync(buf) - } + Ok(Op::Sync(buf)) => Op::Sync(buf), Ok(Op::Async(fut)) => { let result_fut = Box::new( fut @@ -107,7 +100,6 @@ pub fn dispatch( }, ) }; - state.metrics_op_completed(buf.len()); Ok(buf) }) .map_err(|err| panic!("unexpected error {:?}", err)), @@ -129,7 +121,6 @@ pub fn dispatch( ..Default::default() }, ); - state.metrics_op_completed(response_buf.len()); Op::Sync(response_buf) } } @@ -162,9 +153,6 @@ pub fn op_selector_std(inner_type: msg::Any) -> Option { msg::Any::CreateWorker => Some(op_create_worker), msg::Any::Cwd => Some(op_cwd), msg::Any::Dial => Some(op_dial), - msg::Any::Environ => Some(op_env), - msg::Any::ExecPath => Some(op_exec_path), - msg::Any::Exit => Some(op_exit), msg::Any::Fetch => Some(op_fetch), msg::Any::FetchSourceFile => Some(op_fetch_source_file), msg::Any::FormatError => Some(op_format_error), @@ -174,7 +162,6 @@ pub fn op_selector_std(inner_type: msg::Any) -> Option { msg::Any::HostGetMessage => Some(op_host_get_message), msg::Any::HostGetWorkerClosed => Some(op_host_get_worker_closed), msg::Any::HostPostMessage => Some(op_host_post_message), - msg::Any::IsTTY => Some(op_is_tty), msg::Any::Kill => Some(op_kill), msg::Any::Link => Some(op_link), msg::Any::Listen => Some(op_listen), @@ -203,7 +190,6 @@ pub fn op_selector_std(inner_type: msg::Any) -> Option { msg::Any::Symlink => Some(op_symlink), msg::Any::Truncate => Some(op_truncate), msg::Any::HomeDir => Some(op_home_dir), - msg::Any::Utime => Some(op_utime), msg::Any::Write => Some(op_write), // TODO(ry) split these out so that only the appropriate Workers can access diff --git a/cli/ops/dispatch_json.rs b/cli/ops/dispatch_json.rs new file mode 100644 index 00000000000000..a575aedb3e1d42 --- /dev/null +++ b/cli/ops/dispatch_json.rs @@ -0,0 +1,113 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use crate::state::ThreadSafeState; +use crate::tokio_util; +use deno::*; +use futures::Future; +use futures::Poll; +pub use serde_derive::Deserialize; +use serde_json::json; +pub use serde_json::Value; + +pub type AsyncJsonOp = Box + Send>; + +pub enum JsonOp { + Sync(Value), + Async(AsyncJsonOp), +} + +fn json_err(err: ErrBox) -> Value { + use crate::deno_error::GetErrorKind; + json!({ + "message": err.to_string(), + "kind": err.kind() as u32, + }) +} + +pub type Dispatcher = fn( + state: &ThreadSafeState, + args: Value, + zero_copy: Option, +) -> Result; + +fn serialize_result( + promise_id: Option, + result: Result, +) -> Buf { + let value = match result { + Ok(v) => json!({ "ok": v, "promiseId": promise_id }), + Err(err) => json!({ "err": json_err(err), "promiseId": promise_id }), + }; + let vec = serde_json::to_vec(&value).unwrap(); + vec.into_boxed_slice() +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct AsyncArgs { + promise_id: Option, +} + +pub fn dispatch( + d: Dispatcher, + state: &ThreadSafeState, + control: &[u8], + zero_copy: Option, +) -> CoreOp { + let async_args: AsyncArgs = serde_json::from_slice(control).unwrap(); + let promise_id = async_args.promise_id; + let is_sync = promise_id.is_none(); + + let result = serde_json::from_slice(control) + .map_err(ErrBox::from) + .and_then(move |args| d(state, args, zero_copy)); + match result { + Ok(JsonOp::Sync(sync_value)) => { + assert!(promise_id.is_none()); + CoreOp::Sync(serialize_result(promise_id, Ok(sync_value))) + } + Ok(JsonOp::Async(fut)) => { + assert!(promise_id.is_some()); + let fut2 = Box::new(fut.then(move |result| -> Result { + Ok(serialize_result(promise_id, result)) + })); + CoreOp::Async(fut2) + } + Err(sync_err) => { + let buf = serialize_result(promise_id, Err(sync_err)); + if is_sync { + CoreOp::Sync(buf) + } else { + CoreOp::Async(Box::new(futures::future::ok(buf))) + } + } + } +} + +// This is just type conversion. Implement From trait? +// See https://github.com/tokio-rs/tokio/blob/ffd73a64e7ec497622b7f939e38017afe7124dc4/tokio-fs/src/lib.rs#L76-L85 +fn convert_blocking_json(f: F) -> Poll +where + F: FnOnce() -> Result, +{ + use futures::Async::*; + match tokio_threadpool::blocking(f) { + Ok(Ready(Ok(v))) => Ok(Ready(v)), + Ok(Ready(Err(err))) => Err(err), + Ok(NotReady) => Ok(NotReady), + Err(err) => panic!("blocking error {}", err), + } +} + +pub fn blocking_json(is_sync: bool, f: F) -> Result +where + F: 'static + Send + FnOnce() -> Result, +{ + if is_sync { + Ok(JsonOp::Sync(f()?)) + } else { + Ok(JsonOp::Async(Box::new(futures::sync::oneshot::spawn( + tokio_util::poll_fn(move || convert_blocking_json(f)), + &tokio_executor::DefaultExecutor::current(), + )))) + } +} diff --git a/cli/ops/dispatch_minimal.rs b/cli/ops/dispatch_minimal.rs index 37ad568132cea2..22d0a92f87a7b8 100644 --- a/cli/ops/dispatch_minimal.rs +++ b/cli/ops/dispatch_minimal.rs @@ -74,19 +74,15 @@ fn test_parse_min_record() { pub fn dispatch( d: Dispatcher, - state: &ThreadSafeState, + _state: &ThreadSafeState, control: &[u8], zero_copy: Option, ) -> CoreOp { let mut record = parse_min_record(control).unwrap(); let is_sync = record.promise_id == 0; - // TODO(ry) Currently there aren't any sync minimal ops. This is just a sanity // check. Remove later. assert!(!is_sync); - - let state = state.clone(); - let rid = record.arg; let min_op = d(rid, zero_copy); @@ -102,10 +98,9 @@ pub fn dispatch( record.result = -1; } } - let buf: Buf = record.into(); - state.metrics_op_completed(buf.len()); - Ok(buf) + Ok(record.into()) })); + if is_sync { Op::Sync(fut.wait().unwrap()) } else { diff --git a/cli/ops/fs.rs b/cli/ops/fs.rs index d46ed91e1ce9c1..f655e4e2db486b 100644 --- a/cli/ops/fs.rs +++ b/cli/ops/fs.rs @@ -1,5 +1,6 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. use super::dispatch_flatbuffers::serialize_response; +use super::dispatch_json::{blocking_json, Deserialize, JsonOp, Value}; use super::utils::*; use crate::deno_error::DenoError; use crate::deno_error::ErrorKind; @@ -13,7 +14,6 @@ use std::convert::From; use std::fs; use std::path::PathBuf; use std::time::UNIX_EPOCH; -use utime; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; @@ -456,24 +456,27 @@ pub fn op_make_temp_dir( }) } +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct Utime { + promise_id: Option, + filename: String, + atime: u64, + mtime: u64, +} + pub fn op_utime( state: &ThreadSafeState, - base: &msg::Base<'_>, - data: Option, -) -> CliOpResult { - assert!(data.is_none()); - - let inner = base.inner_as_utime().unwrap(); - let filename = String::from(inner.filename().unwrap()); - let atime = inner.atime(); - let mtime = inner.mtime(); - - state.check_write(&filename)?; - - blocking(base.sync(), move || { - debug!("op_utimes {} {} {}", filename, atime, mtime); - utime::set_file_times(filename, atime, mtime)?; - Ok(empty_buf()) + args: Value, + _zero_copy: Option, +) -> Result { + let args: Utime = serde_json::from_value(args)?; + state.check_write(&args.filename)?; + let is_sync = args.promise_id.is_none(); + blocking_json(is_sync, move || { + debug!("op_utimes {} {} {}", args.filename, args.atime, args.mtime); + utime::set_file_times(args.filename, args.atime, args.mtime)?; + Ok(json!({})) }) } diff --git a/cli/ops/mod.rs b/cli/ops/mod.rs index 92c0f8e62b8ef1..24013296076e64 100644 --- a/cli/ops/mod.rs +++ b/cli/ops/mod.rs @@ -4,6 +4,7 @@ use deno::*; mod compiler; mod dispatch_flatbuffers; +mod dispatch_json; mod dispatch_minimal; mod errors; mod fetch; @@ -23,9 +24,16 @@ mod timers; mod utils; mod workers; +// Warning! These values are duplicated in the TypeScript code (js/dispatch.ts), +// update with care. pub const OP_FLATBUFFER: OpId = 44; pub const OP_READ: OpId = 1; pub const OP_WRITE: OpId = 2; +pub const OP_EXIT: OpId = 3; +pub const OP_IS_TTY: OpId = 4; +pub const OP_ENV: OpId = 5; +pub const OP_EXEC_PATH: OpId = 6; +pub const OP_UTIME: OpId = 7; pub fn dispatch( state: &ThreadSafeState, @@ -43,10 +51,36 @@ pub fn dispatch( OP_WRITE => { dispatch_minimal::dispatch(io::op_write, state, control, zero_copy) } + OP_EXIT => dispatch_json::dispatch(os::op_exit, state, control, zero_copy), + OP_IS_TTY => { + dispatch_json::dispatch(os::op_is_tty, state, control, zero_copy) + } + OP_ENV => dispatch_json::dispatch(os::op_env, state, control, zero_copy), + OP_EXEC_PATH => { + dispatch_json::dispatch(os::op_exec_path, state, control, zero_copy) + } + OP_UTIME => { + dispatch_json::dispatch(fs::op_utime, state, control, zero_copy) + } OP_FLATBUFFER => dispatch_flatbuffers::dispatch(state, control, zero_copy), _ => panic!("bad op_id"), }; state.metrics_op_dispatched(bytes_sent_control, bytes_sent_zero_copy); - op + + match op { + Op::Sync(buf) => { + state.metrics_op_completed(buf.len()); + Op::Sync(buf) + } + Op::Async(fut) => { + use crate::futures::Future; + let state = state.clone(); + let result_fut = Box::new(fut.map(move |buf: Buf| { + state.clone().metrics_op_completed(buf.len()); + buf + })); + Op::Async(result_fut) + } + } } diff --git a/cli/ops/os.rs b/cli/ops/os.rs index fbf430d7adeb39..53ef63c6071685 100644 --- a/cli/ops/os.rs +++ b/cli/ops/os.rs @@ -1,16 +1,18 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. use super::dispatch_flatbuffers::serialize_response; +use super::dispatch_json::{Deserialize, JsonOp, Value}; use super::utils::*; use crate::ansi; use crate::fs as deno_fs; use crate::msg; -use crate::msg_util; use crate::state::ThreadSafeState; use crate::version; use atty; use deno::*; use flatbuffers::FlatBufferBuilder; use log; +use std::collections::HashMap; +use std::env; use url::Url; pub fn op_start( @@ -25,7 +27,7 @@ pub fn op_start( let argv = state.argv.iter().map(String::as_str).collect::>(); let argv_off = builder.create_vector_of_strings(argv.as_slice()); - let cwd_path = std::env::current_dir().unwrap(); + let cwd_path = env::current_dir().unwrap(); let cwd_off = builder.create_string(deno_fs::normalize_path(cwd_path.as_ref()).as_ref()); @@ -110,32 +112,16 @@ pub fn op_home_dir( pub fn op_exec_path( state: &ThreadSafeState, - base: &msg::Base<'_>, - data: Option, -) -> CliOpResult { - assert!(data.is_none()); - let cmd_id = base.cmd_id(); - + _args: Value, + _zero_copy: Option, +) -> Result { state.check_env()?; - - let builder = &mut FlatBufferBuilder::new(); - let current_exe = std::env::current_exe().unwrap(); - // Now apply URL parser to current exe to get fully resolved path, otherwise we might get - // `./` and `../` bits in `exec_path` + let current_exe = env::current_exe().unwrap(); + // Now apply URL parser to current exe to get fully resolved path, otherwise + // we might get `./` and `../` bits in `exec_path` let exe_url = Url::from_file_path(current_exe).unwrap(); - let path = exe_url.to_file_path().unwrap().to_str().unwrap().to_owned(); - let path = Some(builder.create_string(&path)); - let inner = msg::ExecPathRes::create(builder, &msg::ExecPathResArgs { path }); - - ok_buf(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::ExecPathRes, - ..Default::default() - }, - )) + let path = exe_url.to_file_path().unwrap(); + Ok(JsonOp::Sync(json!(path))) } pub fn op_set_env( @@ -148,71 +134,42 @@ pub fn op_set_env( let key = inner.key().unwrap(); let value = inner.value().unwrap(); state.check_env()?; - std::env::set_var(key, value); + env::set_var(key, value); ok_buf(empty_buf()) } pub fn op_env( state: &ThreadSafeState, - base: &msg::Base<'_>, - data: Option, -) -> CliOpResult { - assert!(data.is_none()); - let cmd_id = base.cmd_id(); - + _args: Value, + _zero_copy: Option, +) -> Result { state.check_env()?; + let v = env::vars().collect::>(); + Ok(JsonOp::Sync(json!(v))) +} - let builder = &mut FlatBufferBuilder::new(); - let vars: Vec<_> = std::env::vars() - .map(|(key, value)| msg_util::serialize_key_value(builder, &key, &value)) - .collect(); - let tables = builder.create_vector(&vars); - let inner = msg::EnvironRes::create( - builder, - &msg::EnvironResArgs { map: Some(tables) }, - ); - let response_buf = serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::EnvironRes, - ..Default::default() - }, - ); - ok_buf(response_buf) +#[derive(Deserialize)] +struct Exit { + code: i32, } pub fn op_exit( - _state: &ThreadSafeState, - base: &msg::Base<'_>, - _data: Option, -) -> CliOpResult { - let inner = base.inner_as_exit().unwrap(); - std::process::exit(inner.code()) + _s: &ThreadSafeState, + args: Value, + _zero_copy: Option, +) -> Result { + let args: Exit = serde_json::from_value(args)?; + std::process::exit(args.code) } pub fn op_is_tty( - _state: &ThreadSafeState, - base: &msg::Base<'_>, - _data: Option, -) -> CliOpResult { - let builder = &mut FlatBufferBuilder::new(); - let inner = msg::IsTTYRes::create( - builder, - &msg::IsTTYResArgs { - stdin: atty::is(atty::Stream::Stdin), - stdout: atty::is(atty::Stream::Stdout), - stderr: atty::is(atty::Stream::Stderr), - }, - ); - ok_buf(serialize_response( - base.cmd_id(), - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::IsTTYRes, - ..Default::default() - }, - )) + _s: &ThreadSafeState, + _args: Value, + _zero_copy: Option, +) -> Result { + Ok(JsonOp::Sync(json!({ + "stdin": atty::is(atty::Stream::Stdin), + "stdout": atty::is(atty::Stream::Stdout), + "stderr": atty::is(atty::Stream::Stderr), + }))) } diff --git a/cli/ops/utils.rs b/cli/ops/utils.rs index a9b0b442c5f25e..95b13b77aeea1e 100644 --- a/cli/ops/utils.rs +++ b/cli/ops/utils.rs @@ -19,7 +19,7 @@ pub fn empty_buf() -> Buf { // This is just type conversion. Implement From trait? // See https://github.com/tokio-rs/tokio/blob/ffd73a64e7ec497622b7f939e38017afe7124dc4/tokio-fs/src/lib.rs#L76-L85 -fn convert_blocking(f: F) -> Poll +pub fn convert_blocking(f: F) -> Poll where F: FnOnce() -> Result, { diff --git a/js/dispatch.ts b/js/dispatch.ts index 423469d3805459..0c5c59553fb4d5 100644 --- a/js/dispatch.ts +++ b/js/dispatch.ts @@ -1,20 +1,29 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. import * as minimal from "./dispatch_minimal"; import * as flatbuffers from "./dispatch_flatbuffers"; +import * as json from "./dispatch_json"; // These consts are shared with Rust. Update with care. export const OP_FLATBUFFER = 44; export const OP_READ = 1; export const OP_WRITE = 2; +export const OP_EXIT = 3; +export const OP_IS_TTY = 4; +export const OP_ENV = 5; +export const OP_EXEC_PATH = 6; +export const OP_UTIME = 7; -export function handleAsyncMsgFromRust(opId: number, ui8: Uint8Array): void { +export function asyncMsgFromRust(opId: number, ui8: Uint8Array): void { switch (opId) { case OP_FLATBUFFER: - flatbuffers.handleAsyncMsgFromRust(opId, ui8); + flatbuffers.asyncMsgFromRust(opId, ui8); break; case OP_WRITE: case OP_READ: - minimal.handleAsyncMsgFromRust(opId, ui8); + minimal.asyncMsgFromRust(opId, ui8); + break; + case OP_UTIME: + json.asyncMsgFromRust(opId, ui8); break; default: throw Error("bad opId"); diff --git a/js/dispatch_flatbuffers.ts b/js/dispatch_flatbuffers.ts index 87a01037cb720f..0e375dbdf548ad 100644 --- a/js/dispatch_flatbuffers.ts +++ b/js/dispatch_flatbuffers.ts @@ -19,7 +19,7 @@ interface FlatbufferRecord { base: msg.Base; } -export function handleAsyncMsgFromRust(opId: number, ui8: Uint8Array): void { +export function asyncMsgFromRust(opId: number, ui8: Uint8Array): void { let { promiseId, base } = flatbufferRecordFromBuf(ui8); const promise = promiseTable.get(promiseId); util.assert(promise != null, `Expecting promise in table. ${promiseId}`); diff --git a/js/dispatch_json.ts b/js/dispatch_json.ts new file mode 100644 index 00000000000000..e8c97616454ce4 --- /dev/null +++ b/js/dispatch_json.ts @@ -0,0 +1,94 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +// Do not add flatbuffer dependencies to this module. +// TODO(ry) Currently ErrorKind enum is defined in FlatBuffers. Therefore +// we must still reference the msg_generated.ts. This should be removed! +import { ErrorKind } from "gen/cli/msg_generated"; +import * as util from "./util"; +import { TextEncoder, TextDecoder } from "./text_encoding"; +import { core } from "./core"; +import { DenoError } from "./errors"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Ok = any; + +interface JsonError { + kind: ErrorKind; + message: string; +} + +interface JsonResponse { + ok?: Ok; + err?: JsonError; + promiseId?: number; // only present in async mesasges. +} + +const promiseTable = new Map>(); +let _nextPromiseId = 1; + +function nextPromiseId(): number { + return _nextPromiseId++; +} + +function decode(ui8: Uint8Array): JsonResponse { + const s = new TextDecoder().decode(ui8); + return JSON.parse(s) as JsonResponse; +} + +function encode(args: object): Uint8Array { + const s = JSON.stringify(args); + return new TextEncoder().encode(s); +} + +function toDenoError(err: JsonError): DenoError { + return new DenoError(err.kind, err.message); +} + +export function asyncMsgFromRust(opId: number, res: Uint8Array): void { + const { ok, err, promiseId } = decode(res); + const promise = promiseTable.get(promiseId!)!; + if (!promise) { + throw Error(`Async op ${opId} had bad promiseId`); + } + promiseTable.delete(promiseId!); + + if (err) { + promise.reject(toDenoError(err)); + } else if (ok) { + promise.resolve(ok); + } else { + util.unreachable(); + } +} + +export function sendSync( + opId: number, + args: object = {}, + zeroCopy?: Uint8Array +): Ok { + const argsUi8 = encode(args); + const res = core.dispatch(opId, argsUi8, zeroCopy); + if (!res) { + return; + } + const { ok, err, promiseId } = decode(res); + util.assert(!promiseId); + if (err) { + throw toDenoError(err); + } + return ok; +} + +export function sendAsync( + opId: number, + args: object = {}, + zeroCopy?: Uint8Array +): Promise { + const promiseId = nextPromiseId(); + args = Object.assign(args, { promiseId }); + const argsUi8 = encode(args); + const promise = util.createResolvable(); + promiseTable.set(promiseId, promise); + const r = core.dispatch(opId, argsUi8, zeroCopy); + util.assert(!r); + return promise; +} diff --git a/js/dispatch_minimal.ts b/js/dispatch_minimal.ts index fc3fc61b9da97e..9a310fd22014d3 100644 --- a/js/dispatch_minimal.ts +++ b/js/dispatch_minimal.ts @@ -40,7 +40,7 @@ const scratchBytes = new Uint8Array( ); util.assert(scratchBytes.byteLength === scratch32.length * 4); -export function handleAsyncMsgFromRust(opId: number, ui8: Uint8Array): void { +export function asyncMsgFromRust(opId: number, ui8: Uint8Array): void { const buf32 = new Int32Array(ui8.buffer, ui8.byteOffset, ui8.byteLength / 4); const record = recordFromBufMinimal(opId, buf32); const { promiseId, result } = record; diff --git a/js/os.ts b/js/os.ts index 59c44145dd2949..f8938ab70350ad 100644 --- a/js/os.ts +++ b/js/os.ts @@ -1,7 +1,8 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. import { core } from "./core"; -import { handleAsyncMsgFromRust } from "./dispatch"; +import * as dispatch from "./dispatch"; import { sendSync, msg, flatbuffers } from "./dispatch_flatbuffers"; +import * as dispatchJson from "./dispatch_json"; import { assert } from "./util"; import * as util from "./util"; import { window } from "./window"; @@ -23,21 +24,12 @@ function setGlobals(pid_: number, noColor_: boolean): void { * console.log(Deno.isTTY().stdout); */ export function isTTY(): { stdin: boolean; stdout: boolean; stderr: boolean } { - const builder = flatbuffers.createBuilder(); - const inner = msg.IsTTY.createIsTTY(builder); - const baseRes = sendSync(builder, msg.Any.IsTTY, inner)!; - assert(msg.Any.IsTTYRes === baseRes.innerType()); - const res = new msg.IsTTYRes(); - assert(baseRes.inner(res) != null); - - return { stdin: res.stdin(), stdout: res.stdout(), stderr: res.stderr() }; + return dispatchJson.sendSync(dispatch.OP_IS_TTY); } /** Exit the Deno process with optional exit code. */ -export function exit(exitCode = 0): never { - const builder = flatbuffers.createBuilder(); - const inner = msg.Exit.createExit(builder, exitCode); - sendSync(builder, msg.Any.Exit, inner); +export function exit(code = 0): never { + dispatchJson.sendSync(dispatch.OP_EXIT, { code }); return util.unreachable(); } @@ -49,22 +41,6 @@ function setEnv(key: string, value: string): void { sendSync(builder, msg.Any.SetEnv, inner); } -function createEnv(inner: msg.EnvironRes): { [index: string]: string } { - const env: { [index: string]: string } = {}; - - for (let i = 0; i < inner.mapLength(); i++) { - const item = inner.map(i)!; - env[item.key()!] = item.value()!; - } - - return new Proxy(env, { - set(obj, prop: string, value: string): boolean { - setEnv(prop, value); - return Reflect.set(obj, prop, value); - } - }); -} - /** Returns a snapshot of the environment variables at invocation. Mutating a * property in the object will set that variable in the environment for * the process. The environment object will only accept `string`s @@ -77,19 +53,13 @@ function createEnv(inner: msg.EnvironRes): { [index: string]: string } { * console.log(myEnv.TEST_VAR == newEnv.TEST_VAR); */ export function env(): { [index: string]: string } { - /* Ideally we could write - const res = sendSync({ - command: msg.Command.ENV, + const env = dispatchJson.sendSync(dispatch.OP_ENV); + return new Proxy(env, { + set(obj, prop: string, value: string): boolean { + setEnv(prop, value); + return Reflect.set(obj, prop, value); + } }); - */ - const builder = flatbuffers.createBuilder(); - const inner = msg.Environ.createEnviron(builder); - const baseRes = sendSync(builder, msg.Any.Environ, inner)!; - assert(msg.Any.EnvironRes === baseRes.innerType()); - const res = new msg.EnvironRes(); - assert(baseRes.inner(res) != null); - // TypeScript cannot track assertion above, therefore not null assertion - return createEnv(res); } /** Send to the privileged side that we have setup and are ready. */ @@ -111,7 +81,7 @@ export function start( preserveDenoNamespace = true, source?: string ): msg.StartRes { - core.setAsyncHandler(handleAsyncMsgFromRust); + core.setAsyncHandler(dispatch.asyncMsgFromRust); // First we send an empty `Start` message to let the privileged side know we // are ready. The response should be a `StartRes` message containing the CLI @@ -163,12 +133,5 @@ export function homeDir(): string { * Requires the `--allow-env` flag. */ export function execPath(): string { - const builder = flatbuffers.createBuilder(); - const inner = msg.ExecPath.createExecPath(builder); - const baseRes = sendSync(builder, msg.Any.ExecPath, inner)!; - assert(msg.Any.ExecPathRes === baseRes.innerType()); - const res = new msg.ExecPathRes(); - assert(baseRes.inner(res) != null); - const path = res.path()!; - return path; + return dispatchJson.sendSync(dispatch.OP_EXEC_PATH); } diff --git a/js/utime.ts b/js/utime.ts index 89914b4cabef35..c71710458bcf0b 100644 --- a/js/utime.ts +++ b/js/utime.ts @@ -1,24 +1,9 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -import { sendSync, sendAsync, msg, flatbuffers } from "./dispatch_flatbuffers"; -import * as util from "./util"; +import { sendSync, sendAsync } from "./dispatch_json"; +import { OP_UTIME } from "./dispatch"; -function req( - filename: string, - atime: number | Date, - mtime: number | Date -): [flatbuffers.Builder, msg.Any, flatbuffers.Offset] { - const atimeSec = atime instanceof Date ? Math.floor(+atime / 1000) : atime; - const mtimeSec = mtime instanceof Date ? Math.floor(+mtime / 1000) : mtime; - - const builder = flatbuffers.createBuilder(); - const filename_ = builder.createString(filename); - const atimeParts = util.splitNumberToParts(atimeSec); - const atimeMS_ = builder.createLong(atimeParts[0], atimeParts[1]); - const mtimeParts = util.splitNumberToParts(mtimeSec); - const mtimeMS_ = builder.createLong(mtimeParts[0], mtimeParts[1]); - - const inner = msg.Utime.createUtime(builder, filename_, atimeMS_, mtimeMS_); - return [builder, msg.Any.Utime, inner]; +function toSecondsFromEpoch(v: number | Date): number { + return v instanceof Date ? v.valueOf() / 1000 : v; } /** Synchronously changes the access and modification times of a file system @@ -32,7 +17,12 @@ export function utimeSync( atime: number | Date, mtime: number | Date ): void { - sendSync(...req(filename, atime, mtime)); + sendSync(OP_UTIME, { + filename, + // TODO(ry) split atime, mtime into [seconds, nanoseconds] tuple + atime: toSecondsFromEpoch(atime), + mtime: toSecondsFromEpoch(mtime) + }); } /** Changes the access and modification times of a file system object @@ -46,5 +36,10 @@ export async function utime( atime: number | Date, mtime: number | Date ): Promise { - await sendAsync(...req(filename, atime, mtime)); + await sendAsync(OP_UTIME, { + filename, + // TODO(ry) split atime, mtime into [seconds, nanoseconds] tuple + atime: toSecondsFromEpoch(atime), + mtime: toSecondsFromEpoch(mtime) + }); }