Skip to content

Commit 25a21ab

Browse files
Report uncaught exceptions in the request handler to stderr (#44)
More precisely, this will log the error message and stack if the promise passed to `FetchEvent#respondWith` is rejected. One common scenario in which this happens is when passing the return value of an async function to `respondWith`, and that async function then throws an uncaught exception. E.g., this previously failed silently (besides sending a response with a `500` http status and an empty body): ```js addEventListener("fetch", event => event.respondWith(handleRequest(event))); async function handleRequest(event) { throw new Error("The developer should really see this"); } ``` With this change, it will log the following to `stderr`: ``` Error while running request handler: The developer should really see this Stack: handleRequest@<stdin>:4:9 @<stdin>:1:54 ```
1 parent b66d9a4 commit 25a21ab

File tree

4 files changed

+48
-5
lines changed

4 files changed

+48
-5
lines changed

Cargo.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

c-dependencies/js-compute-runtime/js-compute-builtins.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3549,6 +3549,10 @@ static PersistentRooted<JSObject*> INSTANCE;
35493549
bool response_promise_catch_handler(JSContext* cx, unsigned argc, Value* vp) {
35503550
CallArgs args = CallArgsFromVp(argc, vp);
35513551
RootedObject event(cx, &js::GetFunctionNativeReserved(&args.callee(), 0).toObject());
3552+
RootedObject promise(cx, &js::GetFunctionNativeReserved(&args.callee(), 1).toObject());
3553+
3554+
fprintf(stderr, "Error while running request handler: ");
3555+
dump_promise_rejection(cx, args.get(0), promise, stderr);
35523556

35533557
// TODO: verify that this is the right behavior.
35543558
// Steps 9.1-2
@@ -3590,6 +3594,7 @@ static PersistentRooted<JSObject*> INSTANCE;
35903594
if (!catch_fun) return false;
35913595
RootedObject catch_handler(cx, JS_GetFunctionObject(catch_fun));
35923596
js::SetFunctionNativeReserved(catch_handler, 0, JS::ObjectValue(*self));
3597+
js::SetFunctionNativeReserved(catch_handler, 1, JS::ObjectValue(*response_promise));
35933598

35943599
// Step 10 (continued in `response_promise_then_handler` above)
35953600
JSFunction* then_fun = js::NewFunctionWithReserved(cx, response_promise_then_handler, 1, 0,
@@ -4588,6 +4593,41 @@ bool print_stack(JSContext* cx, HandleObject stack, FILE* fp) {
45884593
return true;
45894594
}
45904595

4596+
void dump_promise_rejection(JSContext* cx, HandleValue reason, HandleObject promise, FILE* fp) {
4597+
bool reported = false;
4598+
RootedObject stack(cx);
4599+
4600+
if (reason.isObject()) {
4601+
RootedObject err(cx, &reason.toObject());
4602+
JSErrorReport* report = JS_ErrorFromException(cx, err);
4603+
if (report) {
4604+
fprintf(stderr, "%s\n", report->message().c_str());
4605+
reported = true;
4606+
}
4607+
4608+
stack = JS::ExceptionStackOrNull(err);
4609+
}
4610+
4611+
// If the rejection reason isn't an `Error` object, we just dump the value as-is.
4612+
if (!reported) {
4613+
dump_value(cx, reason, stderr);
4614+
}
4615+
4616+
// If the rejection reason isn't an `Error` object, we can't get an exception stack from it.
4617+
// In that case, fall back to getting the stack from the promise resolution site.
4618+
// These should be identical in many cases, such as for exceptions thrown in async functions,
4619+
// but for some reason the resolution site stack seems to sometimes be wrong, so we only fall
4620+
// back to it as a last resort.
4621+
if (!stack) {
4622+
stack = JS::GetPromiseResolutionSite(promise);
4623+
}
4624+
4625+
if (stack) {
4626+
fprintf(stderr, "Stack:\n");
4627+
print_stack(cx, stack, stderr);
4628+
}
4629+
}
4630+
45914631
bool print_stack(JSContext* cx, FILE* fp) {
45924632
RootedObject stackp(cx);
45934633
if (!JS::CaptureCurrentStack(cx, &stackp))

c-dependencies/js-compute-runtime/js-compute-builtins.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ JS::UniqueChars encode(JSContext* cx, JS::HandleValue val, size_t* encoded_len);
5353

5454
bool debug_logging_enabled();
5555
bool dump_value(JSContext* cx, JS::Value value, FILE* fp);
56+
void dump_promise_rejection(JSContext* cx, JS::HandleValue reason, JS::HandleObject promise,
57+
FILE* fp);
5658
bool print_stack(JSContext* cx, FILE* fp);
5759
bool print_stack(JSContext* cx, JS::HandleObject stack, FILE* fp);
5860

c-dependencies/js-compute-runtime/js-compute-runtime.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,8 @@ static bool report_unhandled_promise_rejections(JSContext* cx) {
180180
promise = &promise_val.toObject();
181181
// Note: we unconditionally print these, since they almost always indicate serious bugs.
182182
fprintf(stderr, "Promise rejected but never handled: ");
183-
dump_value(cx, JS::GetPromiseResult(promise), stderr);
183+
RootedValue result(cx, JS::GetPromiseResult(promise));
184+
dump_promise_rejection(cx, result, promise, stderr);
184185
}
185186

186187
return true;

0 commit comments

Comments
 (0)