fix(cli/worker): Print error stacks from the origin Worker (#7987)

Fixes #4728
This commit is contained in:
Nayeem Rahman 2020-10-20 05:05:42 +01:00 committed by GitHub
parent 57e95032c8
commit 7aba07cc77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 172 additions and 46 deletions

View File

@ -1,5 +1,6 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use crate::colors;
use crate::fmt_errors::JsError;
use crate::ops::io::get_stdio;
use crate::permissions::Permissions;
@ -8,7 +9,9 @@ use crate::tokio_util::create_basic_runtime;
use crate::worker::WebWorker;
use crate::worker::WebWorkerHandle;
use crate::worker::WorkerEvent;
use deno_core::error::generic_error;
use deno_core::error::AnyError;
use deno_core::futures::channel::mpsc;
use deno_core::futures::future::FutureExt;
use deno_core::serde_json;
use deno_core::serde_json::json;
@ -25,7 +28,15 @@ use std::rc::Rc;
use std::sync::Arc;
use std::thread::JoinHandle;
pub fn init(rt: &mut deno_core::JsRuntime) {
#[derive(Deserialize)]
struct HostUnhandledErrorArgs {
message: String,
}
pub fn init(
rt: &mut deno_core::JsRuntime,
sender: Option<mpsc::Sender<WorkerEvent>>,
) {
{
let op_state = rt.op_state();
let mut state = op_state.borrow_mut();
@ -40,6 +51,21 @@ pub fn init(rt: &mut deno_core::JsRuntime) {
);
super::reg_json_sync(rt, "op_host_post_message", op_host_post_message);
super::reg_json_async(rt, "op_host_get_message", op_host_get_message);
super::reg_json_sync(
rt,
"op_host_unhandled_error",
move |_state, args, _zero_copy| {
if let Some(mut sender) = sender.clone() {
let args: HostUnhandledErrorArgs = serde_json::from_value(args)?;
sender
.try_send(WorkerEvent::Error(generic_error(args.message)))
.expect("Failed to propagate error event to parent worker");
Ok(json!(true))
} else {
Err(generic_error("Cannot be called from main worker."))
}
},
);
}
pub type WorkersTable = HashMap<u32, (JoinHandle<()>, WebWorkerHandle)>;
@ -162,6 +188,12 @@ fn run_worker_thread(
}
if let Err(e) = result {
eprintln!(
"{}: Uncaught (in worker \"{}\") {}",
colors::red_bold("error"),
name,
e.to_string().trim_start_matches("Uncaught "),
);
sender
.try_send(WorkerEvent::TerminalError(e))
.expect("Failed to post message to host");

View File

@ -3,6 +3,7 @@
((window) => {
const core = window.Deno.core;
const { Window } = window.__bootstrap.globalInterfaces;
const { log } = window.__bootstrap.util;
function createWorker(
@ -142,7 +143,14 @@
if (type === "terminalError") {
this.#terminated = true;
if (!this.#handleError(event.error)) {
throw Error(event.error.message);
if (globalThis instanceof Window) {
throw new Error("Unhandled error event reached main worker.");
} else {
core.jsonOpSync(
"op_host_unhandled_error",
{ message: event.error.message },
);
}
}
continue;
}
@ -154,7 +162,14 @@
if (type === "error") {
if (!this.#handleError(event.error)) {
throw Error(event.error.message);
if (globalThis instanceof Window) {
throw new Error("Unhandled error event reached main worker.");
} else {
core.jsonOpSync(
"op_host_unhandled_error",
{ message: event.error.message },
);
}
}
continue;
}

View File

@ -7,8 +7,8 @@ delete Object.prototype.__proto__;
((window) => {
const core = Deno.core;
const util = window.__bootstrap.util;
const { illegalConstructorKey } = window.__bootstrap.webUtil;
const eventTarget = window.__bootstrap.eventTarget;
const globalInterfaces = window.__bootstrap.globalInterfaces;
const dispatchMinimal = window.__bootstrap.dispatchMinimal;
const build = window.__bootstrap.build;
const version = window.__bootstrap.version;
@ -192,42 +192,6 @@ delete Object.prototype.__proto__;
core.registerErrorClass("URIError", URIError);
}
class Window extends EventTarget {
constructor(key) {
if (key !== illegalConstructorKey) {
throw new TypeError("Illegal constructor.");
}
}
get [Symbol.toStringTag]() {
return "Window";
}
}
class WorkerGlobalScope extends EventTarget {
constructor(key) {
if (key != illegalConstructorKey) {
throw new TypeError("Illegal constructor.");
}
}
get [Symbol.toStringTag]() {
return "WorkerGlobalScope";
}
}
class DedicatedWorkerGlobalScope extends WorkerGlobalScope {
constructor(key) {
if (key != illegalConstructorKey) {
throw new TypeError("Illegal constructor.");
}
}
get [Symbol.toStringTag]() {
return "DedicatedWorkerGlobalScope";
}
}
// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope
const windowOrWorkerGlobalScope = {
Blob: util.nonEnumerable(fetch.Blob),
@ -277,7 +241,7 @@ delete Object.prototype.__proto__;
};
const mainRuntimeGlobalProperties = {
Window: util.nonEnumerable(Window),
Window: globalInterfaces.windowConstructorDescriptor,
window: util.readOnly(globalThis),
self: util.readOnly(globalThis),
// TODO(bartlomieju): from MDN docs (https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope)
@ -292,8 +256,9 @@ delete Object.prototype.__proto__;
};
const workerRuntimeGlobalProperties = {
WorkerGlobalScope: util.nonEnumerable(WorkerGlobalScope),
DedicatedWorkerGlobalScope: util.nonEnumerable(DedicatedWorkerGlobalScope),
WorkerGlobalScope: globalInterfaces.workerGlobalScopeConstructorDescriptor,
DedicatedWorkerGlobalScope:
globalInterfaces.dedicatedWorkerGlobalScopeConstructorDescriptor,
self: util.readOnly(globalThis),
onmessage: util.writable(onmessage),
onerror: util.writable(onerror),

View File

@ -0,0 +1,5 @@
const worker = new Worker(
new URL("subdir/worker_error.ts", import.meta.url).href,
{ type: "module", name: "bar" },
);
setTimeout(() => worker.terminate(), 30000);

View File

@ -0,0 +1,5 @@
[WILDCARD]error: Uncaught (in worker "bar") Error: foo[WILDCARD]
at foo ([WILDCARD])
at [WILDCARD]
error: Uncaught Error: Unhandled error event reached main worker.
at Worker.#poll ([WILDCARD])

View File

@ -0,0 +1,5 @@
const worker = new Worker(
new URL("073_worker_error.ts", import.meta.url).href,
{ type: "module", name: "baz" },
);
setTimeout(() => worker.terminate(), 30000);

View File

@ -0,0 +1,5 @@
[WILDCARD]error: Uncaught (in worker "bar") Error: foo[WILDCARD]
at foo ([WILDCARD])
at [WILDCARD]
error: Uncaught Error: Unhandled error event reached main worker.
at Worker.#poll ([WILDCARD])

View File

@ -2136,6 +2136,18 @@ fn _066_prompt() {
util::test_pty(args, output, input);
}
itest!(_073_worker_error {
args: "run -A 073_worker_error.ts",
output: "073_worker_error.ts.out",
exit_code: 1,
});
itest!(_074_worker_nested_error {
args: "run -A 074_worker_nested_error.ts",
output: "074_worker_nested_error.ts.out",
exit_code: 1,
});
itest!(js_import_detect {
args: "run --quiet --reload js_import_detect.ts",
output: "js_import_detect.ts.out",

View File

@ -0,0 +1,5 @@
function foo() {
throw new Error("foo");
}
foo();

View File

@ -1,5 +1,6 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use crate::colors;
use crate::fmt_errors::JsError;
use crate::inspector::DenoInspector;
use crate::inspector::InspectorSession;
@ -275,7 +276,7 @@ impl MainWorker {
ops::runtime::init(js_runtime, main_module);
ops::fetch::init(js_runtime, program_state.flags.ca_file.as_deref());
ops::timers::init(js_runtime);
ops::worker_host::init(js_runtime);
ops::worker_host::init(js_runtime, None);
ops::random::init(js_runtime, program_state.flags.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);
@ -443,11 +444,11 @@ impl WebWorker {
op_state.put::<Permissions>(permissions);
}
ops::web_worker::init(js_runtime, sender, handle);
ops::web_worker::init(js_runtime, sender.clone(), handle);
ops::runtime::init(js_runtime, main_module);
ops::fetch::init(js_runtime, program_state.flags.ca_file.as_deref());
ops::timers::init(js_runtime);
ops::worker_host::init(js_runtime);
ops::worker_host::init(js_runtime, Some(sender));
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(
@ -510,6 +511,12 @@ impl WebWorker {
}
if let Err(e) = r {
eprintln!(
"{}: Uncaught (in worker \"{}\") {}",
colors::red_bold("error"),
worker.name.to_string(),
e.to_string().trim_start_matches("Uncaught "),
);
let mut sender = worker.internal_channels.sender.clone();
sender
.try_send(WorkerEvent::Error(e))

View File

@ -0,0 +1,66 @@
((window) => {
const { EventTarget } = window;
const illegalConstructorKey = Symbol("illegalConstuctorKey");
class Window extends EventTarget {
constructor(key) {
if (key !== illegalConstructorKey) {
throw new TypeError("Illegal constructor.");
}
}
get [Symbol.toStringTag]() {
return "Window";
}
}
class WorkerGlobalScope extends EventTarget {
constructor(key) {
if (key != illegalConstructorKey) {
throw new TypeError("Illegal constructor.");
}
}
get [Symbol.toStringTag]() {
return "WorkerGlobalScope";
}
}
class DedicatedWorkerGlobalScope extends WorkerGlobalScope {
constructor(key) {
if (key != illegalConstructorKey) {
throw new TypeError("Illegal constructor.");
}
}
get [Symbol.toStringTag]() {
return "DedicatedWorkerGlobalScope";
}
}
window.__bootstrap = (window.__bootstrap || {});
window.__bootstrap.globalInterfaces = {
DedicatedWorkerGlobalScope,
Window,
WorkerGlobalScope,
dedicatedWorkerGlobalScopeConstructorDescriptor: {
configurable: true,
enumerable: false,
value: DedicatedWorkerGlobalScope,
writable: true,
},
windowConstructorDescriptor: {
configurable: true,
enumerable: false,
value: Window,
writable: true,
},
workerGlobalScopeConstructorDescriptor: {
configurable: true,
enumerable: false,
value: WorkerGlobalScope,
writable: true,
},
};
})(this);

View File

@ -52,6 +52,10 @@ pub fn init(isolate: &mut JsRuntime) {
"deno:op_crates/web/02_abort_signal.js",
include_str!("02_abort_signal.js"),
),
(
"deno:op_crates/web/03_global_interfaces.js",
include_str!("03_global_interfaces.js"),
),
(
"deno:op_crates/web/08_text_encoding.js",
include_str!("08_text_encoding.js"),