Skip to content

Commit

Permalink
fix(cli/worker): Print error stacks from the origin Worker (#7987)
Browse files Browse the repository at this point in the history
Fixes #4728
  • Loading branch information
nayeemrmn authored Oct 20, 2020
1 parent 57e9503 commit 7aba07c
Show file tree
Hide file tree
Showing 12 changed files with 172 additions and 46 deletions.
34 changes: 33 additions & 1 deletion cli/ops/worker_host.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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();
Expand All @@ -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)>;
Expand Down Expand Up @@ -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");
Expand Down
19 changes: 17 additions & 2 deletions cli/rt/11_workers.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

((window) => {
const core = window.Deno.core;
const { Window } = window.__bootstrap.globalInterfaces;
const { log } = window.__bootstrap.util;

function createWorker(
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand Down
45 changes: 5 additions & 40 deletions cli/rt/99_main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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)
Expand All @@ -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),
Expand Down
5 changes: 5 additions & 0 deletions cli/tests/073_worker_error.ts
Original file line number Diff line number Diff line change
@@ -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);
5 changes: 5 additions & 0 deletions cli/tests/073_worker_error.ts.out
Original file line number Diff line number Diff line change
@@ -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])
5 changes: 5 additions & 0 deletions cli/tests/074_worker_nested_error.ts
Original file line number Diff line number Diff line change
@@ -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);
5 changes: 5 additions & 0 deletions cli/tests/074_worker_nested_error.ts.out
Original file line number Diff line number Diff line change
@@ -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])
12 changes: 12 additions & 0 deletions cli/tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
5 changes: 5 additions & 0 deletions cli/tests/subdir/worker_error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function foo() {
throw new Error("foo");
}

foo();
13 changes: 10 additions & 3 deletions cli/worker.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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))
Expand Down
66 changes: 66 additions & 0 deletions op_crates/web/03_global_interfaces.js
Original file line number Diff line number Diff line change
@@ -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);
4 changes: 4 additions & 0 deletions op_crates/web/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down

0 comments on commit 7aba07c

Please sign in to comment.