aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-01-05 16:23:45 -0800
committerGravatar GitHub <noreply@github.com> 2023-01-05 16:23:45 -0800
commita7d9f161079292dfb757fa412767754f26f9f4c4 (patch)
tree077a430904658ff1cd9da69ea28920b3b8db84e2
parentbbbb4835b42533ea9eec67b772b26fbbb1264efc (diff)
downloadbun-a7d9f161079292dfb757fa412767754f26f9f4c4.tar.gz
bun-a7d9f161079292dfb757fa412767754f26f9f4c4.tar.zst
bun-a7d9f161079292dfb757fa412767754f26f9f4c4.zip
Report unhandled promise rejection on exit and make exit code 1 instead of 0 (#1734)
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
-rw-r--r--src/bun_js.zig119
-rw-r--r--test/bun.js/exit-code-0.js1
-rw-r--r--test/bun.js/exit-code-1.js1
-rw-r--r--test/bun.js/exit-code-await-throw-1.js3
-rw-r--r--test/bun.js/exit-code-unhandled-throw.js3
-rw-r--r--test/bun.js/exit-code.test.ts35
6 files changed, 108 insertions, 54 deletions
diff --git a/src/bun_js.zig b/src/bun_js.zig
index c9b6cb490..92f78f404 100644
--- a/src/bun_js.zig
+++ b/src/bun_js.zig
@@ -35,12 +35,14 @@ const Arena = @import("./mimalloc_arena.zig").Arena;
const OpaqueWrap = JSC.OpaqueWrap;
const VirtualMachine = JSC.VirtualMachine;
+var run: Run = undefined;
pub const Run = struct {
file: std.fs.File,
ctx: Command.Context,
vm: *VirtualMachine,
entry_path: string,
arena: Arena = undefined,
+ any_unhandled: bool = false,
pub fn boot(ctx: Command.Context, file: std.fs.File, entry_path: string) !void {
JSC.markBinding(@src());
@@ -50,58 +52,61 @@ pub const Run = struct {
js_ast.Stmt.Data.Store.create(default_allocator);
var arena = try Arena.init();
- var run = Run{
+ run = .{
.vm = try VirtualMachine.init(arena.allocator(), ctx.args, null, ctx.log, null),
.file = file,
.arena = arena,
.ctx = ctx,
.entry_path = entry_path,
};
- run.vm.argv = ctx.passthrough;
- run.vm.arena = &run.arena;
- run.vm.allocator = arena.allocator();
-
- run.vm.bundler.options.install = ctx.install;
- run.vm.bundler.resolver.opts.install = ctx.install;
- run.vm.bundler.resolver.opts.global_cache = ctx.debug.global_cache;
- run.vm.bundler.resolver.opts.prefer_offline_install = (ctx.debug.offline_mode_setting orelse .online) == .offline;
- run.vm.bundler.resolver.opts.prefer_latest_install = (ctx.debug.offline_mode_setting orelse .online) == .latest;
- run.vm.bundler.options.global_cache = run.vm.bundler.resolver.opts.global_cache;
- run.vm.bundler.options.prefer_offline_install = run.vm.bundler.resolver.opts.prefer_offline_install;
- run.vm.bundler.options.prefer_latest_install = run.vm.bundler.resolver.opts.prefer_latest_install;
- run.vm.bundler.resolver.env_loader = run.vm.bundler.env;
+ var vm = run.vm;
+ var b = &vm.bundler;
+
+ vm.argv = ctx.passthrough;
+ vm.arena = &run.arena;
+ vm.allocator = arena.allocator();
+
+ b.options.install = ctx.install;
+ b.resolver.opts.install = ctx.install;
+ b.resolver.opts.global_cache = ctx.debug.global_cache;
+ b.resolver.opts.prefer_offline_install = (ctx.debug.offline_mode_setting orelse .online) == .offline;
+ b.resolver.opts.prefer_latest_install = (ctx.debug.offline_mode_setting orelse .online) == .latest;
+ b.options.global_cache = b.resolver.opts.global_cache;
+ b.options.prefer_offline_install = b.resolver.opts.prefer_offline_install;
+ b.options.prefer_latest_install = b.resolver.opts.prefer_latest_install;
+ b.resolver.env_loader = b.env;
if (ctx.debug.macros) |macros| {
- run.vm.bundler.options.macro_remap = macros;
+ b.options.macro_remap = macros;
}
- run.vm.bundler.configureRouter(false) catch {
+ b.configureRouter(false) catch {
if (Output.enable_ansi_colors_stderr) {
- run.vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true) catch {};
+ vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true) catch {};
} else {
- run.vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false) catch {};
+ vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false) catch {};
}
Output.prettyErrorln("\n", .{});
Global.exit(1);
};
- run.vm.bundler.configureDefines() catch {
+ b.configureDefines() catch {
if (Output.enable_ansi_colors_stderr) {
- run.vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true) catch {};
+ vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true) catch {};
} else {
- run.vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false) catch {};
+ vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false) catch {};
}
Output.prettyErrorln("\n", .{});
Global.exit(1);
};
AsyncHTTP.max_simultaneous_requests = 255;
- if (run.vm.bundler.env.map.get("BUN_CONFIG_MAX_HTTP_REQUESTS")) |max_http_requests| {
+ if (b.env.map.get("BUN_CONFIG_MAX_HTTP_REQUESTS")) |max_http_requests| {
load: {
AsyncHTTP.max_simultaneous_requests = std.fmt.parseInt(u16, max_http_requests, 10) catch {
- run.vm.log.addErrorFmt(
+ vm.log.addErrorFmt(
null,
logger.Loc.Empty,
- run.vm.allocator,
+ vm.allocator,
"BUN_CONFIG_MAX_HTTP_REQUESTS value \"{s}\" is not a valid integer between 1 and 65535",
.{max_http_requests},
) catch unreachable;
@@ -109,10 +114,10 @@ pub const Run = struct {
};
if (AsyncHTTP.max_simultaneous_requests == 0) {
- run.vm.log.addWarningFmt(
+ vm.log.addWarningFmt(
null,
logger.Loc.Empty,
- run.vm.allocator,
+ vm.allocator,
"BUN_CONFIG_MAX_HTTP_REQUESTS value must be a number between 1 and 65535",
.{},
) catch unreachable;
@@ -121,71 +126,77 @@ pub const Run = struct {
}
}
- run.vm.loadExtraEnv();
- run.vm.is_main_thread = true;
+ vm.loadExtraEnv();
+ vm.is_main_thread = true;
JSC.VirtualMachine.is_main_thread_vm = true;
var callback = OpaqueWrap(Run, Run.start);
- run.vm.global.vm().holdAPILock(&run, callback);
+ vm.global.vm().holdAPILock(&run, callback);
+ }
+
+ fn onUnhandledRejectionBeforeClose(this: *JSC.VirtualMachine, _: *JSC.JSGlobalObject, value: JSC.JSValue) void {
+ this.runErrorHandler(value, null);
+ run.any_unhandled = true;
}
pub fn start(this: *Run) void {
+ var vm = this.vm;
if (this.ctx.debug.hot_reload) {
- JSC.HotReloader.enableHotModuleReloading(this.vm);
+ JSC.HotReloader.enableHotModuleReloading(vm);
}
- var promise = this.vm.loadEntryPoint(this.entry_path) catch return;
+ var promise = vm.loadEntryPoint(this.entry_path) catch return;
- if (promise.status(this.vm.global.vm()) == .Rejected) {
- this.vm.runErrorHandler(promise.result(this.vm.global.vm()), null);
+ if (promise.status(vm.global.vm()) == .Rejected) {
+ vm.runErrorHandler(promise.result(vm.global.vm()), null);
Global.exit(1);
}
- _ = promise.result(this.vm.global.vm());
+ _ = promise.result(vm.global.vm());
- if (this.vm.log.msgs.items.len > 0) {
+ if (vm.log.msgs.items.len > 0) {
if (Output.enable_ansi_colors) {
- this.vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true) catch {};
+ vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true) catch {};
} else {
- this.vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false) catch {};
+ vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false) catch {};
}
Output.prettyErrorln("\n", .{});
Output.flush();
}
// don't run the GC if we don't actually need to
- if (this.vm.eventLoop().tasks.count > 0 or this.vm.active_tasks > 0 or
- this.vm.uws_event_loop.?.active > 0 or
- this.vm.eventLoop().tickConcurrentWithCount() > 0)
+ if (vm.eventLoop().tasks.count > 0 or vm.active_tasks > 0 or
+ vm.uws_event_loop.?.active > 0 or
+ vm.eventLoop().tickConcurrentWithCount() > 0)
{
- this.vm.global.vm().releaseWeakRefs();
- _ = this.vm.arena.gc(false);
- _ = this.vm.global.vm().runGC(false);
- this.vm.tick();
+ vm.global.vm().releaseWeakRefs();
+ _ = vm.arena.gc(false);
+ _ = vm.global.vm().runGC(false);
+ vm.tick();
}
{
- while (this.vm.eventLoop().tasks.count > 0 or this.vm.active_tasks > 0 or this.vm.uws_event_loop.?.active > 0) {
- this.vm.tick();
- this.vm.eventLoop().autoTickActive();
+ while (vm.eventLoop().tasks.count > 0 or vm.active_tasks > 0 or vm.uws_event_loop.?.active > 0) {
+ vm.tick();
+ vm.eventLoop().autoTickActive();
}
- if (this.vm.log.msgs.items.len > 0) {
+ if (vm.log.msgs.items.len > 0) {
if (Output.enable_ansi_colors) {
- this.vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true) catch {};
+ vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true) catch {};
} else {
- this.vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false) catch {};
+ vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false) catch {};
}
Output.prettyErrorln("\n", .{});
Output.flush();
}
}
- this.vm.global.handleRejectedPromises();
+ vm.onUnhandledRejection = &onUnhandledRejectionBeforeClose;
+ vm.global.handleRejectedPromises();
- this.vm.onExit();
+ vm.onExit();
if (!JSC.is_bindgen) JSC.napi.fixDeadCodeElimination();
-
- Global.exit(0);
+ Global.exit(@boolToInt(this.any_unhandled));
}
};
diff --git a/test/bun.js/exit-code-0.js b/test/bun.js/exit-code-0.js
new file mode 100644
index 000000000..dcbbff6c9
--- /dev/null
+++ b/test/bun.js/exit-code-0.js
@@ -0,0 +1 @@
+process.exit(0);
diff --git a/test/bun.js/exit-code-1.js b/test/bun.js/exit-code-1.js
new file mode 100644
index 000000000..6cee2e1e7
--- /dev/null
+++ b/test/bun.js/exit-code-1.js
@@ -0,0 +1 @@
+process.exit(1);
diff --git a/test/bun.js/exit-code-await-throw-1.js b/test/bun.js/exit-code-await-throw-1.js
new file mode 100644
index 000000000..6b8c42eab
--- /dev/null
+++ b/test/bun.js/exit-code-await-throw-1.js
@@ -0,0 +1,3 @@
+await (async function () {
+ throw 42;
+})();
diff --git a/test/bun.js/exit-code-unhandled-throw.js b/test/bun.js/exit-code-unhandled-throw.js
new file mode 100644
index 000000000..e8f5ca4cb
--- /dev/null
+++ b/test/bun.js/exit-code-unhandled-throw.js
@@ -0,0 +1,3 @@
+(async function () {
+ throw 42;
+})();
diff --git a/test/bun.js/exit-code.test.ts b/test/bun.js/exit-code.test.ts
new file mode 100644
index 000000000..b7d91f8e9
--- /dev/null
+++ b/test/bun.js/exit-code.test.ts
@@ -0,0 +1,35 @@
+import { describe, expect, it, test } from "bun:test";
+import { bunExe } from "bunExe";
+import { spawnSync } from "bun";
+
+it("process.exit(1) works", () => {
+ const { exitCode } = spawnSync([
+ bunExe(),
+ import.meta.dir + "/exit-code-1.js",
+ ]);
+ expect(exitCode).toBe(1);
+});
+
+it("await on a thrown value reports exit code 1", () => {
+ const { exitCode } = spawnSync([
+ bunExe(),
+ import.meta.dir + "/exit-code-await-throw-1.js",
+ ]);
+ expect(exitCode).toBe(1);
+});
+
+it("unhandled promise rejection reports exit code 1", () => {
+ const { exitCode } = spawnSync([
+ bunExe(),
+ import.meta.dir + "/exit-code-unhandled-throw.js",
+ ]);
+ expect(exitCode).toBe(1);
+});
+
+it("process.exit(0) works", () => {
+ const { exitCode } = spawnSync([
+ bunExe(),
+ import.meta.dir + "/exit-code-0.js",
+ ]);
+ expect(exitCode).toBe(0);
+});