refactor(cli): decouple ops from ProgramState and Flags (#8659)

This commit does major refactor of "Worker" and "WebWorker",
in order to decouple them from "ProgramState" and "Flags".
The main points of interest are "create_main_worker()" and
"create_web_worker_callback()" functions which are responsible
for creating "Worker" and "WebWorker" in CLI context.
As a result it is now possible to factor out common "runtime"
functionality into a separate crate.
This commit is contained in:
Bartek Iwańczuk 2020-12-11 18:49:26 +01:00 committed by GitHub
parent 9414dee9e5
commit 65e72b68ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 465 additions and 281 deletions

View file

@ -228,7 +228,7 @@ lazy_static! {
crate::version::deno(),
env!("PROFILE"),
env!("TARGET"),
crate::version::v8(),
deno_core::v8_version(),
crate::version::TYPESCRIPT
);
}

View file

@ -203,7 +203,7 @@ async fn server(
warp::reply::json(&json!({
"Browser": format!("Deno/{}", crate::version::deno()),
"Protocol-Version": "1.3",
"V8-Version": crate::version::v8(),
"V8-Version": deno_core::v8_version(),
}))
});

View file

@ -55,15 +55,22 @@ use crate::file_fetcher::FileFetcher;
use crate::file_watcher::ModuleResolutionResult;
use crate::flags::DenoSubcommand;
use crate::flags::Flags;
use crate::fmt_errors::PrettyJsError;
use crate::import_map::ImportMap;
use crate::media_type::MediaType;
use crate::module_loader::CliModuleLoader;
use crate::ops::worker_host::CreateWebWorkerCb;
use crate::permissions::Permissions;
use crate::program_state::exit_unstable;
use crate::program_state::ProgramState;
use crate::source_maps::apply_source_map;
use crate::specifier_handler::FetchHandler;
use crate::standalone::create_standalone_binary;
use crate::tools::installer::infer_name_from_url;
use crate::web_worker::WebWorker;
use crate::web_worker::WebWorkerOptions;
use crate::worker::MainWorker;
use crate::worker::WorkerOptions;
use deno_core::error::generic_error;
use deno_core::error::AnyError;
use deno_core::futures::future::FutureExt;
@ -86,6 +93,134 @@ use std::pin::Pin;
use std::rc::Rc;
use std::sync::Arc;
fn create_web_worker_callback(
program_state: Arc<ProgramState>,
) -> Arc<CreateWebWorkerCb> {
Arc::new(move |args| {
let global_state_ = program_state.clone();
let js_error_create_fn = Rc::new(move |core_js_error| {
let source_mapped_error =
apply_source_map(&core_js_error, global_state_.clone());
PrettyJsError::create(source_mapped_error)
});
let attach_inspector = program_state.maybe_inspector_server.is_some()
|| program_state.flags.coverage;
let maybe_inspector_server = program_state.maybe_inspector_server.clone();
let module_loader = CliModuleLoader::new_for_worker(program_state.clone());
let create_web_worker_cb =
create_web_worker_callback(program_state.clone());
let options = WebWorkerOptions {
args: program_state.flags.argv.clone(),
apply_source_maps: true,
debug_flag: program_state
.flags
.log_level
.map_or(false, |l| l == log::Level::Debug),
unstable: program_state.flags.unstable,
ca_filepath: program_state.flags.ca_file.clone(),
seed: program_state.flags.seed,
module_loader,
create_web_worker_cb,
js_error_create_fn: Some(js_error_create_fn),
use_deno_namespace: args.use_deno_namespace,
attach_inspector,
maybe_inspector_server,
};
let mut worker = WebWorker::from_options(
args.name,
args.permissions,
args.main_module,
args.worker_id,
&options,
);
// This block registers additional ops and state that
// are only available in the CLI
{
let js_runtime = &mut worker.js_runtime;
js_runtime
.op_state()
.borrow_mut()
.put::<Arc<ProgramState>>(program_state.clone());
// Applies source maps - works in conjuction with `js_error_create_fn`
// above
ops::errors::init(js_runtime);
if args.use_deno_namespace {
ops::runtime_compiler::init(js_runtime);
}
}
worker.bootstrap(&options);
worker
})
}
pub fn create_main_worker(
program_state: &Arc<ProgramState>,
main_module: ModuleSpecifier,
permissions: Permissions,
) -> MainWorker {
let module_loader = CliModuleLoader::new(program_state.clone());
let global_state_ = program_state.clone();
let js_error_create_fn = Rc::new(move |core_js_error| {
let source_mapped_error =
apply_source_map(&core_js_error, global_state_.clone());
PrettyJsError::create(source_mapped_error)
});
let attach_inspector = program_state.maybe_inspector_server.is_some()
|| program_state.flags.repl
|| program_state.flags.coverage;
let maybe_inspector_server = program_state.maybe_inspector_server.clone();
let should_break_on_first_statement =
program_state.flags.inspect_brk.is_some();
let create_web_worker_cb = create_web_worker_callback(program_state.clone());
let options = WorkerOptions {
apply_source_maps: true,
args: program_state.flags.argv.clone(),
debug_flag: program_state
.flags
.log_level
.map_or(false, |l| l == log::Level::Debug),
unstable: program_state.flags.unstable,
ca_filepath: program_state.flags.ca_file.clone(),
seed: program_state.flags.seed,
js_error_create_fn: Some(js_error_create_fn),
create_web_worker_cb,
attach_inspector,
maybe_inspector_server,
should_break_on_first_statement,
module_loader,
};
let mut worker = MainWorker::from_options(main_module, permissions, &options);
// This block registers additional ops and state that
// are only available in the CLI
{
let js_runtime = &mut worker.js_runtime;
js_runtime
.op_state()
.borrow_mut()
.put::<Arc<ProgramState>>(program_state.clone());
// Applies source maps - works in conjuction with `js_error_create_fn`
// above
ops::errors::init(js_runtime);
ops::runtime_compiler::init(js_runtime);
}
worker.bootstrap(&options);
worker
}
fn write_to_stdout_ignore_sigpipe(bytes: &[u8]) -> Result<(), std::io::Error> {
use std::io::ErrorKind;
@ -253,7 +388,7 @@ async fn install_command(
let program_state = ProgramState::new(preload_flags)?;
let main_module = ModuleSpecifier::resolve_url_or_path(&module_url)?;
let mut worker =
MainWorker::new(&program_state, main_module.clone(), permissions);
create_main_worker(&program_state, main_module.clone(), permissions);
// First, fetch and compile the module; this step ensures that the module exists.
worker.preload_module(&main_module).await?;
tools::installer::install(flags, &module_url, args, name, root, force)
@ -321,7 +456,7 @@ async fn eval_command(
let permissions = Permissions::from_flags(&flags);
let program_state = ProgramState::new(flags)?;
let mut worker =
MainWorker::new(&program_state, main_module.clone(), permissions);
create_main_worker(&program_state, main_module.clone(), permissions);
let main_module_url = main_module.as_url().to_owned();
// Create a dummy source file.
let source_code = if print {
@ -664,7 +799,7 @@ async fn run_repl(flags: Flags) -> Result<(), AnyError> {
let permissions = Permissions::from_flags(&flags);
let program_state = ProgramState::new(flags)?;
let mut worker =
MainWorker::new(&program_state, main_module.clone(), permissions);
create_main_worker(&program_state, main_module.clone(), permissions);
worker.run_event_loop().await?;
tools::repl::run(&program_state, worker).await
@ -675,8 +810,11 @@ async fn run_from_stdin(flags: Flags) -> Result<(), AnyError> {
let permissions = Permissions::from_flags(&flags);
let main_module =
ModuleSpecifier::resolve_url_or_path("./$deno$stdin.ts").unwrap();
let mut worker =
MainWorker::new(&program_state.clone(), main_module.clone(), permissions);
let mut worker = create_main_worker(
&program_state.clone(),
main_module.clone(),
permissions,
);
let mut source = Vec::new();
std::io::stdin().read_to_end(&mut source)?;
@ -755,7 +893,7 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<(), AnyError> {
let main_module = main_module.clone();
let program_state = ProgramState::new(flags)?;
let mut worker =
MainWorker::new(&program_state, main_module.clone(), permissions);
create_main_worker(&program_state, main_module.clone(), permissions);
debug!("main_module {}", main_module);
worker.execute_module(&main_module).await?;
worker.execute("window.dispatchEvent(new Event('load'))")?;
@ -788,7 +926,7 @@ async fn run_command(flags: Flags, script: String) -> Result<(), AnyError> {
let program_state = ProgramState::new(flags.clone())?;
let permissions = Permissions::from_flags(&flags);
let mut worker =
MainWorker::new(&program_state, main_module.clone(), permissions);
create_main_worker(&program_state, main_module.clone(), permissions);
debug!("main_module {}", main_module);
worker.execute_module(&main_module).await?;
worker.execute("window.dispatchEvent(new Event('load'))")?;
@ -857,7 +995,7 @@ async fn test_command(
}
let mut worker =
MainWorker::new(&program_state, main_module.clone(), permissions);
create_main_worker(&program_state, main_module.clone(), permissions);
let mut maybe_coverage_collector = if flags.coverage {
let session = worker.create_inspector_session();

View file

@ -1,6 +1,7 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use crate::diagnostics::Diagnostics;
use crate::program_state::ProgramState;
use crate::source_maps::get_orig_position;
use crate::source_maps::CachedMaps;
use deno_core::error::AnyError;
@ -11,6 +12,7 @@ use deno_core::OpState;
use deno_core::ZeroCopyBuf;
use serde::Deserialize;
use std::collections::HashMap;
use std::sync::Arc;
pub fn init(rt: &mut deno_core::JsRuntime) {
super::reg_json_sync(rt, "op_apply_source_map", op_apply_source_map);
@ -33,13 +35,15 @@ fn op_apply_source_map(
let args: ApplySourceMap = serde_json::from_value(args)?;
let mut mappings_map: CachedMaps = HashMap::new();
let program_state = state.borrow::<Arc<ProgramState>>().clone();
let (orig_file_name, orig_line_number, orig_column_number) =
get_orig_position(
args.file_name,
args.line_number.into(),
args.column_number.into(),
&mut mappings_map,
super::program_state(state),
program_state,
);
Ok(json!({

View file

@ -27,7 +27,6 @@ pub mod websocket;
pub mod worker_host;
use crate::metrics::metrics_op;
use crate::program_state::ProgramState;
use deno_core::error::AnyError;
use deno_core::json_op_async;
use deno_core::json_op_sync;
@ -39,7 +38,6 @@ use deno_core::ZeroCopyBuf;
use std::cell::RefCell;
use std::future::Future;
use std::rc::Rc;
use std::sync::Arc;
pub fn reg_json_async<F, R>(rt: &mut JsRuntime, name: &'static str, op_fn: F)
where
@ -57,24 +55,33 @@ where
rt.register_op(name, metrics_op(json_op_sync(op_fn)));
}
pub struct UnstableChecker {
pub unstable: bool,
}
impl UnstableChecker {
/// Quits the process if the --unstable flag was not provided.
///
/// This is intentionally a non-recoverable check so that people cannot probe
/// for unstable APIs from stable programs.
// NOTE(bartlomieju): keep in sync with `cli/program_state.rs`
pub fn check_unstable(&self, api_name: &str) {
if !self.unstable {
eprintln!(
"Unstable API '{}'. The --unstable flag must be provided.",
api_name
);
std::process::exit(70);
}
}
}
/// Helper for checking unstable features. Used for sync ops.
pub fn check_unstable(state: &OpState, api_name: &str) {
state.borrow::<Arc<ProgramState>>().check_unstable(api_name)
state.borrow::<UnstableChecker>().check_unstable(api_name)
}
/// Helper for checking unstable features. Used for async ops.
pub fn check_unstable2(state: &Rc<RefCell<OpState>>, api_name: &str) {
let state = state.borrow();
state.borrow::<Arc<ProgramState>>().check_unstable(api_name)
}
/// Helper for extracting the commonly used state. Used for sync ops.
pub fn program_state(state: &OpState) -> Arc<ProgramState> {
state.borrow::<Arc<ProgramState>>().clone()
}
/// Helper for extracting the commonly used state. Used for async ops.
pub fn global_state2(state: &Rc<RefCell<OpState>>) -> Arc<ProgramState> {
let state = state.borrow();
state.borrow::<Arc<ProgramState>>().clone()
state.borrow::<UnstableChecker>().check_unstable(api_name)
}

View file

@ -1,9 +1,7 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use crate::colors;
use crate::metrics::Metrics;
use crate::permissions::Permissions;
use crate::version;
use deno_core::error::AnyError;
use deno_core::serde_json;
use deno_core::serde_json::json;
@ -11,50 +9,17 @@ use deno_core::serde_json::Value;
use deno_core::ModuleSpecifier;
use deno_core::OpState;
use deno_core::ZeroCopyBuf;
use std::env;
type ApplySourceMaps = bool;
pub fn init(
rt: &mut deno_core::JsRuntime,
main_module: ModuleSpecifier,
apply_source_maps: bool,
) {
pub fn init(rt: &mut deno_core::JsRuntime, main_module: ModuleSpecifier) {
{
let op_state = rt.op_state();
let mut state = op_state.borrow_mut();
state.put::<ModuleSpecifier>(main_module);
state.put::<ApplySourceMaps>(apply_source_maps);
}
super::reg_json_sync(rt, "op_start", op_start);
super::reg_json_sync(rt, "op_main_module", op_main_module);
super::reg_json_sync(rt, "op_metrics", op_metrics);
}
fn op_start(
state: &mut OpState,
_args: Value,
_zero_copy: &mut [ZeroCopyBuf],
) -> Result<Value, AnyError> {
let apply_source_maps = *state.borrow::<ApplySourceMaps>();
let gs = &super::program_state(state);
Ok(json!({
"args": gs.flags.argv.clone(),
"applySourceMaps": apply_source_maps,
"debugFlag": gs.flags.log_level.map_or(false, |l| l == log::Level::Debug),
"denoVersion": version::deno(),
"noColor": !colors::use_color(),
"pid": std::process::id(),
"ppid": ppid(),
"target": env!("TARGET"),
"tsVersion": version::TYPESCRIPT,
"unstableFlag": gs.flags.unstable,
"v8Version": version::v8(),
"versionFlag": gs.flags.version,
}))
}
fn op_main_module(
state: &mut OpState,
_args: Value,
@ -93,7 +58,7 @@ fn op_metrics(
}))
}
fn ppid() -> Value {
pub fn ppid() -> Value {
#[cfg(windows)]
{
// Adopted from rustup:

View file

@ -7,10 +7,12 @@ use crate::module_graph::BundleType;
use crate::module_graph::EmitOptions;
use crate::module_graph::GraphBuilder;
use crate::permissions::Permissions;
use crate::program_state::ProgramState;
use crate::specifier_handler::FetchHandler;
use crate::specifier_handler::MemoryHandler;
use crate::specifier_handler::SpecifierHandler;
use crate::tsc_config;
use std::sync::Arc;
use deno_core::error::AnyError;
use deno_core::error::Context;
@ -51,7 +53,7 @@ async fn op_compile(
} else {
super::check_unstable2(&state, "Deno.compile");
}
let program_state = super::global_state2(&state);
let program_state = state.borrow().borrow::<Arc<ProgramState>>().clone();
let runtime_permissions = {
let state = state.borrow();
state.borrow::<Permissions>().clone()

View file

@ -33,7 +33,17 @@ use tokio_tungstenite::tungstenite::{
use tokio_tungstenite::{client_async, WebSocketStream};
use webpki::DNSNameRef;
pub fn init(rt: &mut deno_core::JsRuntime) {
#[derive(Clone)]
struct WsCaFile(String);
pub fn init(rt: &mut deno_core::JsRuntime, maybe_ca_file: Option<&str>) {
{
let op_state = rt.op_state();
let mut state = op_state.borrow_mut();
if let Some(ca_file) = maybe_ca_file {
state.put::<WsCaFile>(WsCaFile(ca_file.to_string()));
}
}
super::reg_json_sync(rt, "op_ws_check_permission", op_ws_check_permission);
super::reg_json_async(rt, "op_ws_create", op_ws_create);
super::reg_json_async(rt, "op_ws_send", op_ws_send);
@ -92,10 +102,7 @@ pub async fn op_ws_create(
);
}
let ca_file = {
let program_state = super::global_state2(&state);
program_state.flags.ca_file.clone()
};
let maybe_ca_file = state.borrow().try_borrow::<WsCaFile>().cloned();
let uri: Uri = args.url.parse()?;
let mut request = Request::builder().method(Method::GET).uri(&uri);
@ -128,8 +135,8 @@ pub async fn op_ws_create(
.root_store
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
if let Some(path) = ca_file {
let key_file = File::open(path)?;
if let Some(ws_ca_file) = maybe_ca_file {
let key_file = File::open(ws_ca_file.0)?;
let reader = &mut BufReader::new(key_file);
config.root_store.add_pem_file(reader).unwrap();
}

View file

@ -21,8 +21,27 @@ use std::cell::RefCell;
use std::collections::HashMap;
use std::convert::From;
use std::rc::Rc;
use std::sync::Arc;
use std::thread::JoinHandle;
pub struct CreateWebWorkerArgs {
pub name: String,
pub worker_id: u32,
pub permissions: Permissions,
pub main_module: ModuleSpecifier,
pub use_deno_namespace: bool,
}
pub type CreateWebWorkerCb =
dyn Fn(CreateWebWorkerArgs) -> WebWorker + Sync + Send;
/// A holder for callback that is used to create a new
/// WebWorker. It's a struct instead of a type alias
/// because `GothamState` used in `OpState` overrides
/// value if type alises have the same underlying type
#[derive(Clone)]
pub struct CreateWebWorkerCbHolder(Arc<CreateWebWorkerCb>);
#[derive(Deserialize)]
struct HostUnhandledErrorArgs {
message: String,
@ -31,12 +50,16 @@ struct HostUnhandledErrorArgs {
pub fn init(
rt: &mut deno_core::JsRuntime,
sender: Option<mpsc::Sender<WorkerEvent>>,
create_web_worker_cb: Arc<CreateWebWorkerCb>,
) {
{
let op_state = rt.op_state();
let mut state = op_state.borrow_mut();
state.put::<WorkersTable>(WorkersTable::default());
state.put::<WorkerId>(WorkerId::default());
let create_module_loader = CreateWebWorkerCbHolder(create_web_worker_cb);
state.put::<CreateWebWorkerCbHolder>(create_module_loader);
}
super::reg_json_sync(rt, "op_create_worker", op_create_worker);
super::reg_json_sync(
@ -102,11 +125,12 @@ fn op_create_worker(
}
let permissions = state.borrow::<Permissions>().clone();
let worker_id = state.take::<WorkerId>();
let create_module_loader = state.take::<CreateWebWorkerCbHolder>();
state.put::<CreateWebWorkerCbHolder>(create_module_loader.clone());
state.put::<WorkerId>(worker_id + 1);
let module_specifier = ModuleSpecifier::resolve_url(&specifier)?;
let worker_name = args_name.unwrap_or_else(|| "".to_string());
let program_state = super::program_state(state);
let (handle_sender, handle_receiver) =
std::sync::mpsc::sync_channel::<Result<WebWorkerHandle, AnyError>>(1);
@ -121,14 +145,14 @@ fn op_create_worker(
// - JS worker is useless - meaning it throws an exception and can't do anything else,
// all action done upon it should be noops
// - newly spawned thread exits
let worker = WebWorker::new(
worker_name,
permissions,
module_specifier.clone(),
program_state,
use_deno_namespace,
let worker = (create_module_loader.0)(CreateWebWorkerArgs {
name: worker_name,
worker_id,
);
permissions,
main_module: module_specifier.clone(),
use_deno_namespace,
});
// Send thread safe handle to newly created worker to host thread
handle_sender.send(Ok(worker.thread_safe_handle())).unwrap();

View file

@ -258,16 +258,6 @@ impl ProgramState {
}
}
/// Quits the process if the --unstable flag was not provided.
///
/// This is intentionally a non-recoverable check so that people cannot probe
/// for unstable APIs from stable programs.
pub fn check_unstable(&self, api_name: &str) {
if !self.flags.unstable {
exit_unstable(api_name);
}
}
#[cfg(test)]
pub fn mock(
argv: Vec<String>,

View file

@ -132,40 +132,31 @@ delete Object.prototype.__proto__;
core.jsonOpSync("op_worker_close");
}
function opStart() {
return core.jsonOpSync("op_start");
}
function opMainModule() {
return core.jsonOpSync("op_main_module");
}
// TODO(bartlomieju): temporary solution, must be fixed when moving
// dispatches to separate crates
function initOps() {
function runtimeStart(runtimeOptions, source) {
const opsMap = core.ops();
for (const [name, opId] of Object.entries(opsMap)) {
if (name === "op_write" || name === "op_read") {
core.setAsyncHandler(opId, dispatchMinimal.asyncMsgFromRust);
}
}
core.setMacrotaskCallback(timers.handleTimerMacrotask);
}
function runtimeStart(source) {
initOps();
// 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
// args and other info.
const s = opStart();
version.setVersions(s.denoVersion, s.v8Version, s.tsVersion);
build.setBuildInfo(s.target);
util.setLogDebug(s.debugFlag, source);
core.setMacrotaskCallback(timers.handleTimerMacrotask);
version.setVersions(
runtimeOptions.denoVersion,
runtimeOptions.v8Version,
runtimeOptions.tsVersion,
);
build.setBuildInfo(runtimeOptions.target);
util.setLogDebug(runtimeOptions.debugFlag, source);
// TODO(bartlomieju): a very crude way to disable
// source mapping of errors. This condition is true
// only for compiled standalone binaries.
let prepareStackTrace;
if (s.applySourceMaps) {
if (runtimeOptions.applySourceMaps) {
prepareStackTrace = core.createPrepareStackTrace(
errorStack.opApplySourceMap,
);
@ -173,8 +164,6 @@ delete Object.prototype.__proto__;
prepareStackTrace = core.createPrepareStackTrace();
}
Error.prepareStackTrace = prepareStackTrace;
return s;
}
function registerErrors() {
@ -283,7 +272,7 @@ delete Object.prototype.__proto__;
let hasBootstrapped = false;
function bootstrapMainRuntime() {
function bootstrapMainRuntime(runtimeOptions) {
if (hasBootstrapped) {
throw new Error("Worker runtime already bootstrapped");
}
@ -300,7 +289,8 @@ delete Object.prototype.__proto__;
defineEventHandler(window, "load", null);
defineEventHandler(window, "unload", null);
const { args, noColor, pid, ppid, unstableFlag } = runtimeStart();
runtimeStart(runtimeOptions);
const { args, noColor, pid, ppid, unstableFlag } = runtimeOptions;
registerErrors();
@ -335,7 +325,12 @@ delete Object.prototype.__proto__;
util.log("args", args);
}
function bootstrapWorkerRuntime(name, useDenoNamespace, internalName) {
function bootstrapWorkerRuntime(
runtimeOptions,
name,
useDenoNamespace,
internalName,
) {
if (hasBootstrapped) {
throw new Error("Worker runtime already bootstrapped");
}
@ -349,9 +344,12 @@ delete Object.prototype.__proto__;
Object.defineProperties(globalThis, { name: util.readOnly(name) });
Object.setPrototypeOf(globalThis, DedicatedWorkerGlobalScope.prototype);
eventTarget.setEventTargetData(globalThis);
const { unstableFlag, pid, noColor, args } = runtimeStart(
runtimeStart(
runtimeOptions,
internalName ?? name,
);
const { unstableFlag, pid, noColor, args } = runtimeOptions;
registerErrors();

View file

@ -1,9 +1,9 @@
use crate::colors;
use crate::flags::Flags;
use crate::permissions::Permissions;
use crate::program_state::ProgramState;
use crate::tokio_util;
use crate::worker::MainWorker;
use crate::worker::WorkerOptions;
use deno_core::error::type_error;
use deno_core::error::AnyError;
use deno_core::futures::FutureExt;
@ -21,6 +21,7 @@ use std::io::Write;
use std::path::PathBuf;
use std::pin::Pin;
use std::rc::Rc;
use std::sync::Arc;
const MAGIC_TRAILER: &[u8; 8] = b"d3n0l4nd";
@ -109,16 +110,29 @@ async fn run(source_code: String, args: Vec<String>) -> Result<(), AnyError> {
// TODO(lucacasonato): remove once you can specify this correctly through embedded metadata
flags.unstable = true;
let main_module = ModuleSpecifier::resolve_url(SPECIFIER)?;
let program_state = ProgramState::new(flags.clone())?;
let permissions = Permissions::allow_all();
let module_loader = Rc::new(EmbeddedModuleLoader(source_code));
let mut worker = MainWorker::from_options(
&program_state,
main_module.clone(),
permissions,
let create_web_worker_cb = Arc::new(|_| {
todo!("Worker are currently not supported in standalone binaries");
});
let options = WorkerOptions {
apply_source_maps: false,
args: flags.argv.clone(),
debug_flag: false,
unstable: true,
ca_filepath: None,
seed: None,
js_error_create_fn: None,
create_web_worker_cb,
attach_inspector: false,
maybe_inspector_server: None,
should_break_on_first_statement: false,
module_loader,
None,
);
};
let mut worker =
MainWorker::from_options(main_module.clone(), permissions, &options);
worker.bootstrap(&options);
worker.execute_module(&main_module).await?;
worker.execute("window.dispatchEvent(new Event('load'))")?;
worker.run_event_loop().await?;

View file

@ -2646,11 +2646,6 @@ itest!(fmt_stdin_check_not_formatted {
output_str: Some("Not formatted stdin\n"),
});
itest!(circular1 {
args: "run --reload circular1.js",
output: "circular1.js.out",
});
itest!(config {
args: "run --reload --config config.tsconfig.json config.ts",
exit_code: 1,

View file

@ -13,7 +13,3 @@ pub fn deno() -> String {
pub fn is_canary() -> bool {
option_env!("DENO_CANARY").is_some()
}
pub fn v8() -> &'static str {
deno_core::v8_version()
}

View file

@ -1,28 +1,31 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use crate::colors;
use crate::fmt_errors::PrettyJsError;
use crate::inspector::DenoInspector;
use crate::inspector::InspectorServer;
use crate::js;
use crate::metrics::Metrics;
use crate::module_loader::CliModuleLoader;
use crate::ops;
use crate::permissions::Permissions;
use crate::program_state::ProgramState;
use crate::source_maps::apply_source_map;
use crate::tokio_util::create_basic_runtime;
use crate::version;
use deno_core::error::AnyError;
use deno_core::futures::channel::mpsc;
use deno_core::futures::future::poll_fn;
use deno_core::futures::future::FutureExt;
use deno_core::futures::stream::StreamExt;
use deno_core::futures::task::AtomicWaker;
use deno_core::serde_json;
use deno_core::serde_json::json;
use deno_core::url::Url;
use deno_core::v8;
use deno_core::JsErrorCreateFn;
use deno_core::JsRuntime;
use deno_core::ModuleLoader;
use deno_core::ModuleSpecifier;
use deno_core::RuntimeOptions;
use std::env;
use std::rc::Rc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Arc;
@ -115,6 +118,7 @@ fn create_channels(
/// Each `WebWorker` is either a child of `MainWorker` or other
/// `WebWorker`.
pub struct WebWorker {
id: u32,
inspector: Option<Box<DenoInspector>>,
// Following fields are pub because they are accessed
// when creating a new WebWorker instance.
@ -125,46 +129,48 @@ pub struct WebWorker {
event_loop_idle: bool,
terminate_rx: mpsc::Receiver<()>,
handle: WebWorkerHandle,
pub has_deno_namespace: bool,
pub use_deno_namespace: bool,
}
pub struct WebWorkerOptions {
pub args: Vec<String>,
pub debug_flag: bool,
pub unstable: bool,
pub ca_filepath: Option<String>,
pub seed: Option<u64>,
pub module_loader: Rc<dyn ModuleLoader>,
pub create_web_worker_cb: Arc<ops::worker_host::CreateWebWorkerCb>,
pub js_error_create_fn: Option<Rc<JsErrorCreateFn>>,
pub use_deno_namespace: bool,
pub attach_inspector: bool,
pub maybe_inspector_server: Option<Arc<InspectorServer>>,
pub apply_source_maps: bool,
}
impl WebWorker {
pub fn new(
pub fn from_options(
name: String,
permissions: Permissions,
main_module: ModuleSpecifier,
program_state: Arc<ProgramState>,
has_deno_namespace: bool,
worker_id: u32,
options: &WebWorkerOptions,
) -> Self {
let module_loader = CliModuleLoader::new_for_worker(program_state.clone());
let global_state_ = program_state.clone();
let js_error_create_fn = Box::new(move |core_js_error| {
let source_mapped_error =
apply_source_map(&core_js_error, global_state_.clone());
PrettyJsError::create(source_mapped_error)
});
let mut js_runtime = JsRuntime::new(RuntimeOptions {
module_loader: Some(module_loader),
module_loader: Some(options.module_loader.clone()),
startup_snapshot: Some(js::deno_isolate_init()),
js_error_create_fn: Some(js_error_create_fn),
js_error_create_fn: options.js_error_create_fn.clone(),
get_error_class_fn: Some(&crate::errors::get_error_class_name),
..Default::default()
});
let inspector =
if let Some(inspector_server) = &program_state.maybe_inspector_server {
Some(DenoInspector::new(
&mut js_runtime,
Some(inspector_server.clone()),
))
} else if program_state.flags.coverage || program_state.flags.repl {
Some(DenoInspector::new(&mut js_runtime, None))
} else {
None
};
let inspector = if options.attach_inspector {
Some(DenoInspector::new(
&mut js_runtime,
options.maybe_inspector_server.clone(),
))
} else {
None
};
let (terminate_tx, terminate_rx) = mpsc::channel::<()>(1);
let isolate_handle = js_runtime.v8_isolate().thread_safe_handle();
@ -172,15 +178,16 @@ impl WebWorker {
create_channels(isolate_handle, terminate_tx);
let mut worker = Self {
id: worker_id,
inspector,
internal_channels,
js_runtime,
name: name.clone(),
name,
waker: AtomicWaker::new(),
event_loop_idle: false,
terminate_rx,
handle,
has_deno_namespace,
use_deno_namespace: options.use_deno_namespace,
};
{
@ -192,15 +199,21 @@ impl WebWorker {
let op_state = js_runtime.op_state();
let mut op_state = op_state.borrow_mut();
op_state.put::<Metrics>(Default::default());
op_state.put::<Arc<ProgramState>>(program_state.clone());
op_state.put::<Permissions>(permissions);
op_state.put::<ops::UnstableChecker>(ops::UnstableChecker {
unstable: options.unstable,
});
}
ops::web_worker::init(js_runtime, sender.clone(), handle);
ops::runtime::init(js_runtime, main_module, true);
ops::fetch::init(js_runtime, program_state.flags.ca_file.as_deref());
ops::runtime::init(js_runtime, main_module);
ops::fetch::init(js_runtime, options.ca_filepath.as_deref());
ops::timers::init(js_runtime);
ops::worker_host::init(js_runtime, Some(sender));
ops::worker_host::init(
js_runtime,
Some(sender),
options.create_web_worker_cb.clone(),
);
ops::reg_json_sync(js_runtime, "op_close", deno_core::op_close);
ops::reg_json_sync(js_runtime, "op_resources", deno_core::op_resources);
ops::reg_json_sync(
@ -208,11 +221,10 @@ impl WebWorker {
"op_domain_to_ascii",
deno_web::op_domain_to_ascii,
);
ops::errors::init(js_runtime);
ops::io::init(js_runtime);
ops::websocket::init(js_runtime);
ops::websocket::init(js_runtime, options.ca_filepath.as_deref());
if has_deno_namespace {
if options.use_deno_namespace {
ops::fs_events::init(js_runtime);
ops::fs::init(js_runtime);
ops::net::init(js_runtime);
@ -220,8 +232,7 @@ impl WebWorker {
ops::permissions::init(js_runtime);
ops::plugin::init(js_runtime);
ops::process::init(js_runtime);
ops::crypto::init(js_runtime, program_state.flags.seed);
ops::runtime_compiler::init(js_runtime);
ops::crypto::init(js_runtime, options.seed);
ops::signal::init(js_runtime);
ops::tls::init(js_runtime);
ops::tty::init(js_runtime);
@ -239,19 +250,38 @@ impl WebWorker {
op_state.resource_table.add("stderr", Box::new(stream));
}
}
worker
}
}
pub fn bootstrap(&mut self, options: &WebWorkerOptions) {
let runtime_options = json!({
"args": options.args,
"applySourceMaps": options.apply_source_maps,
"debugFlag": options.debug_flag,
"denoVersion": version::deno(),
"noColor": !colors::use_color(),
"pid": std::process::id(),
"ppid": ops::runtime::ppid(),
"target": env!("TARGET"),
"tsVersion": version::TYPESCRIPT,
"unstableFlag": options.unstable,
"v8Version": deno_core::v8_version(),
});
let runtime_options_str =
serde_json::to_string_pretty(&runtime_options).unwrap();
// Instead of using name for log we use `worker-${id}` because
// WebWorkers can have empty string as name.
let script = format!(
"bootstrap.workerRuntime(\"{}\", {}, \"worker-{}\")",
name, worker.has_deno_namespace, worker_id
"bootstrap.workerRuntime({}, \"{}\", {}, \"worker-{}\")",
runtime_options_str, self.name, options.use_deno_namespace, self.id
);
worker
self
.execute(&script)
.expect("Failed to execute worker bootstrap script");
worker
}
/// Same as execute2() but the filename defaults to "$CWD/__anonymous__".
@ -421,22 +451,39 @@ pub fn run_web_worker(
#[cfg(test)]
mod tests {
use super::*;
use crate::program_state::ProgramState;
use crate::tokio_util;
use deno_core::serde_json::json;
fn create_test_web_worker() -> WebWorker {
let main_module =
ModuleSpecifier::resolve_url_or_path("./hello.js").unwrap();
let program_state = ProgramState::mock(vec!["deno".to_string()], None);
WebWorker::new(
let module_loader = Rc::new(deno_core::NoopModuleLoader);
let create_web_worker_cb = Arc::new(|_| unreachable!());
let options = WebWorkerOptions {
args: vec![],
apply_source_maps: false,
debug_flag: false,
unstable: false,
ca_filepath: None,
seed: None,
module_loader,
create_web_worker_cb,
js_error_create_fn: None,
use_deno_namespace: false,
attach_inspector: false,
maybe_inspector_server: None,
};
let mut worker = WebWorker::from_options(
"TEST".to_string(),
Permissions::allow_all(),
main_module,
program_state,
false,
1,
)
&options,
);
worker.bootstrap(&options);
worker
}
#[tokio::test]

View file

@ -1,18 +1,19 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use crate::fmt_errors::PrettyJsError;
use crate::colors;
use crate::inspector::DenoInspector;
use crate::inspector::InspectorServer;
use crate::inspector::InspectorSession;
use crate::js;
use crate::metrics::Metrics;
use crate::module_loader::CliModuleLoader;
use crate::ops;
use crate::permissions::Permissions;
use crate::program_state::ProgramState;
use crate::source_maps::apply_source_map;
use crate::version;
use deno_core::error::AnyError;
use deno_core::futures::future::poll_fn;
use deno_core::futures::future::FutureExt;
use deno_core::serde_json;
use deno_core::serde_json::json;
use deno_core::url::Url;
use deno_core::JsErrorCreateFn;
use deno_core::JsRuntime;
@ -35,68 +36,51 @@ use std::task::Poll;
/// are descendants of this worker.
pub struct MainWorker {
inspector: Option<Box<DenoInspector>>,
js_runtime: JsRuntime,
pub js_runtime: JsRuntime,
should_break_on_first_statement: bool,
}
pub struct WorkerOptions {
pub apply_source_maps: bool,
pub args: Vec<String>,
pub debug_flag: bool,
pub unstable: bool,
pub ca_filepath: Option<String>,
pub seed: Option<u64>,
pub module_loader: Rc<dyn ModuleLoader>,
// Callback that will be invoked when creating new instance
// of WebWorker
pub create_web_worker_cb: Arc<ops::worker_host::CreateWebWorkerCb>,
pub js_error_create_fn: Option<Rc<JsErrorCreateFn>>,
pub attach_inspector: bool,
pub maybe_inspector_server: Option<Arc<InspectorServer>>,
pub should_break_on_first_statement: bool,
}
impl MainWorker {
pub fn new(
program_state: &Arc<ProgramState>,
main_module: ModuleSpecifier,
permissions: Permissions,
) -> Self {
let module_loader = CliModuleLoader::new(program_state.clone());
let global_state_ = program_state.clone();
let js_error_create_fn = Box::new(move |core_js_error| {
let source_mapped_error =
apply_source_map(&core_js_error, global_state_.clone());
PrettyJsError::create(source_mapped_error)
});
Self::from_options(
program_state,
main_module,
permissions,
module_loader,
Some(js_error_create_fn),
)
}
pub fn from_options(
program_state: &Arc<ProgramState>,
main_module: ModuleSpecifier,
permissions: Permissions,
module_loader: Rc<dyn ModuleLoader>,
js_error_create_fn: Option<Box<JsErrorCreateFn>>,
options: &WorkerOptions,
) -> Self {
// TODO(bartlomieju): this is hacky way to not apply source
// maps in JS
let apply_source_maps = js_error_create_fn.is_some();
let mut js_runtime = JsRuntime::new(RuntimeOptions {
module_loader: Some(module_loader),
module_loader: Some(options.module_loader.clone()),
startup_snapshot: Some(js::deno_isolate_init()),
js_error_create_fn,
js_error_create_fn: options.js_error_create_fn.clone(),
get_error_class_fn: Some(&crate::errors::get_error_class_name),
..Default::default()
});
let inspector =
if let Some(inspector_server) = &program_state.maybe_inspector_server {
Some(DenoInspector::new(
&mut js_runtime,
Some(inspector_server.clone()),
))
} else if program_state.flags.coverage || program_state.flags.repl {
Some(DenoInspector::new(&mut js_runtime, None))
} else {
None
};
let inspector = if options.attach_inspector {
Some(DenoInspector::new(
&mut js_runtime,
options.maybe_inspector_server.clone(),
))
} else {
None
};
let should_break_on_first_statement =
inspector.is_some() && program_state.flags.inspect_brk.is_some();
inspector.is_some() && options.should_break_on_first_statement;
let mut worker = Self {
inspector,
@ -111,15 +95,21 @@ impl MainWorker {
let op_state = js_runtime.op_state();
let mut op_state = op_state.borrow_mut();
op_state.put::<Metrics>(Default::default());
op_state.put::<Arc<ProgramState>>(program_state.clone());
op_state.put::<Permissions>(permissions);
op_state.put::<ops::UnstableChecker>(ops::UnstableChecker {
unstable: options.unstable,
});
}
ops::runtime::init(js_runtime, main_module, apply_source_maps);
ops::fetch::init(js_runtime, program_state.flags.ca_file.as_deref());
ops::runtime::init(js_runtime, main_module);
ops::fetch::init(js_runtime, options.ca_filepath.as_deref());
ops::timers::init(js_runtime);
ops::worker_host::init(js_runtime, None);
ops::crypto::init(js_runtime, program_state.flags.seed);
ops::worker_host::init(
js_runtime,
None,
options.create_web_worker_cb.clone(),
);
ops::crypto::init(js_runtime, options.seed);
ops::reg_json_sync(js_runtime, "op_close", deno_core::op_close);
ops::reg_json_sync(js_runtime, "op_resources", deno_core::op_resources);
ops::reg_json_sync(
@ -127,7 +117,6 @@ impl MainWorker {
"op_domain_to_ascii",
deno_web::op_domain_to_ascii,
);
ops::errors::init(js_runtime);
ops::fs_events::init(js_runtime);
ops::fs::init(js_runtime);
ops::io::init(js_runtime);
@ -136,11 +125,10 @@ impl MainWorker {
ops::permissions::init(js_runtime);
ops::plugin::init(js_runtime);
ops::process::init(js_runtime);
ops::runtime_compiler::init(js_runtime);
ops::signal::init(js_runtime);
ops::tls::init(js_runtime);
ops::tty::init(js_runtime);
ops::websocket::init(js_runtime);
ops::websocket::init(js_runtime, options.ca_filepath.as_deref());
}
{
let op_state = js_runtime.op_state();
@ -157,10 +145,32 @@ impl MainWorker {
t.add("stderr", Box::new(stream));
}
}
worker
.execute("bootstrap.mainRuntime()")
}
pub fn bootstrap(&mut self, options: &WorkerOptions) {
let runtime_options = json!({
"args": options.args,
"applySourceMaps": options.apply_source_maps,
"debugFlag": options.debug_flag,
"denoVersion": version::deno(),
"noColor": !colors::use_color(),
"pid": std::process::id(),
"ppid": ops::runtime::ppid(),
"target": env!("TARGET"),
"tsVersion": version::TYPESCRIPT,
"unstableFlag": options.unstable,
"v8Version": deno_core::v8_version(),
});
let script = format!(
"bootstrap.mainRuntime({})",
serde_json::to_string_pretty(&runtime_options).unwrap()
);
self
.execute(&script)
.expect("Failed to execute bootstrap script");
worker
}
/// Same as execute2() but the filename defaults to "$CWD/__anonymous__".
@ -231,23 +241,28 @@ impl Drop for MainWorker {
#[cfg(test)]
mod tests {
use super::*;
use crate::flags::DenoSubcommand;
use crate::flags::Flags;
use crate::program_state::ProgramState;
fn create_test_worker() -> MainWorker {
let main_module =
ModuleSpecifier::resolve_url_or_path("./hello.js").unwrap();
let flags = Flags {
subcommand: DenoSubcommand::Run {
script: main_module.to_string(),
},
..Default::default()
let permissions = Permissions::default();
let options = WorkerOptions {
apply_source_maps: false,
args: vec![],
debug_flag: false,
unstable: false,
ca_filepath: None,
seed: None,
js_error_create_fn: None,
create_web_worker_cb: Arc::new(|_| unreachable!()),
attach_inspector: false,
maybe_inspector_server: None,
should_break_on_first_statement: false,
module_loader: Rc::new(deno_core::FsModuleLoader),
};
let permissions = Permissions::from_flags(&flags);
let program_state =
ProgramState::mock(vec!["deno".to_string()], Some(flags));
MainWorker::new(&program_state, main_module, permissions)
MainWorker::from_options(main_module, permissions, &options)
}
#[tokio::test]
@ -273,26 +288,7 @@ mod tests {
let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.join("tests/circular1.ts");
let module_specifier =
ModuleSpecifier::resolve_url_or_path(&p.to_string_lossy()).unwrap();
let mut worker = create_test_worker();
let result = worker.execute_module(&module_specifier).await;
if let Err(err) = result {
eprintln!("execute_mod err {:?}", err);
}
if let Err(e) = worker.run_event_loop().await {
panic!("Future got unexpected error: {:?}", e);
}
}
#[tokio::test]
async fn execute_006_url_imports() {
let _http_server_guard = test_util::http_server();
let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.join("cli/tests/006_url_imports.ts");
.join("tests/circular1.js");
let module_specifier =
ModuleSpecifier::resolve_url_or_path(&p.to_string_lossy()).unwrap();
let mut worker = create_test_worker();
@ -323,7 +319,7 @@ mod tests {
let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.join("cli/tests/002_hello.ts");
.join("cli/tests/001_hello.js");
let module_specifier =
ModuleSpecifier::resolve_url_or_path(&p.to_string_lossy()).unwrap();
let result = worker.execute_module(&module_specifier).await;

View file

@ -51,6 +51,7 @@ pub use crate::modules::ModuleLoadId;
pub use crate::modules::ModuleLoader;
pub use crate::modules::ModuleSource;
pub use crate::modules::ModuleSourceFuture;
pub use crate::modules::NoopModuleLoader;
pub use crate::modules::RecursiveModuleLoad;
pub use crate::normalize_path::normalize_path;
pub use crate::ops::json_op_async;

View file

@ -105,7 +105,7 @@ pub trait ModuleLoader {
/// Placeholder structure used when creating
/// a runtime that doesn't support module loading.
pub(crate) struct NoopModuleLoader;
pub struct NoopModuleLoader;
impl ModuleLoader for NoopModuleLoader {
fn resolve(

View file

@ -107,7 +107,7 @@ pub(crate) struct JsRuntimeState {
HashMap<v8::Global<v8::Promise>, v8::Global<v8::Value>>,
pending_dyn_mod_evaluate: HashMap<ModuleLoadId, DynImportModEvaluate>,
pending_mod_evaluate: Option<ModEvaluate>,
pub(crate) js_error_create_fn: Box<JsErrorCreateFn>,
pub(crate) js_error_create_fn: Rc<JsErrorCreateFn>,
pub(crate) shared: SharedQueue,
pub(crate) pending_ops: FuturesUnordered<PendingOpFuture>,
pub(crate) pending_unref_ops: FuturesUnordered<PendingOpFuture>,
@ -168,7 +168,7 @@ pub struct RuntimeOptions {
/// Allows a callback to be set whenever a V8 exception is made. This allows
/// the caller to wrap the JsError into an error. By default this callback
/// is set to `JsError::create()`.
pub js_error_create_fn: Option<Box<JsErrorCreateFn>>,
pub js_error_create_fn: Option<Rc<JsErrorCreateFn>>,
/// Allows to map error type to a string "class" used to represent
/// error in JavaScript.
@ -257,7 +257,7 @@ impl JsRuntime {
let js_error_create_fn = options
.js_error_create_fn
.unwrap_or_else(|| Box::new(JsError::create));
.unwrap_or_else(|| Rc::new(JsError::create));
let mut op_state = OpState::default();
if let Some(get_error_class_fn) = options.get_error_class_fn {