aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bun.js/event_loop.zig29
-rw-r--r--src/bun.js/javascript.zig7
-rw-r--r--src/bun.js/module_loader.zig69
-rw-r--r--src/bun.zig76
-rw-r--r--src/bun_js.zig38
-rw-r--r--src/bundler.zig7
-rw-r--r--src/cli.zig16
-rw-r--r--src/cli/test_command.zig16
-rw-r--r--src/install/install.zig4
-rw-r--r--src/install/lockfile.zig3
-rw-r--r--src/output.zig7
-rw-r--r--src/panic_handler.zig9
-rw-r--r--src/report.zig9
13 files changed, 244 insertions, 46 deletions
diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig
index 8dbf5abfd..6928cd2b8 100644
--- a/src/bun.js/event_loop.zig
+++ b/src/bun.js/event_loop.zig
@@ -363,6 +363,7 @@ pub const EventLoop = struct {
waker: ?AsyncIO.Waker = null,
start_server_on_next_tick: bool = false,
defer_count: std.atomic.Atomic(usize) = std.atomic.Atomic(usize).init(0),
+ forever_timer: ?*uws.Timer = null,
pub const Queue = std.fifo.LinearFifo(Task, .Dynamic);
@@ -498,6 +499,34 @@ pub const EventLoop = struct {
}
}
+ pub fn tickPossiblyForever(this: *EventLoop) void {
+ var ctx = this.virtual_machine;
+ var loop = ctx.uws_event_loop.?;
+
+ const pending_unref = ctx.pending_unref_counter;
+ if (pending_unref > 0) {
+ ctx.pending_unref_counter = 0;
+ loop.unrefCount(pending_unref);
+ }
+
+ if (loop.num_polls == 0 or loop.active == 0) {
+ if (this.forever_timer == null) {
+ var t = uws.Timer.create(loop, this);
+ t.set(this, &noopForeverTimer, 1000 * 60 * 4, 1000 * 60 * 4);
+ this.forever_timer = t;
+ }
+ }
+
+ loop.tick();
+ this.processGCTimer();
+ this.tickConcurrent();
+ this.tick();
+ }
+
+ fn noopForeverTimer(_: *uws.Timer) callconv(.C) void {
+ // do nothing
+ }
+
pub fn autoTickActive(this: *EventLoop) void {
var loop = this.virtual_machine.uws_event_loop.?;
diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig
index 3841a07e2..39cead99f 100644
--- a/src/bun.js/javascript.zig
+++ b/src/bun.js/javascript.zig
@@ -364,6 +364,8 @@ pub const VirtualMachine = struct {
preload: []const string = &[_][]const u8{},
unhandled_pending_rejection_to_capture: ?*JSC.JSValue = null,
+ hot_reload: bun.CLI.Command.HotReload = .none,
+
/// hide bun:wrap from stack traces
/// bun:wrap is very noisy
hide_bun_stackframes: bool = true,
@@ -552,6 +554,11 @@ pub const VirtualMachine = struct {
pub fn reload(this: *VirtualMachine) void {
Output.debug("Reloading...", .{});
+ if (this.hot_reload == .watch) {
+ Output.flush();
+ bun.reloadProcess(bun.default_allocator, !strings.eqlComptime(this.bundler.env.map.get("BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD") orelse "0", "true"));
+ }
+
this.global.reload();
this.pending_internal_promise = this.reloadEntryPoint(this.main) catch @panic("Failed to reload");
}
diff --git a/src/bun.js/module_loader.zig b/src/bun.js/module_loader.zig
index adfa88cb1..e9a4bb2c2 100644
--- a/src/bun.js/module_loader.zig
+++ b/src/bun.js/module_loader.zig
@@ -931,13 +931,14 @@ pub const ModuleLoader = struct {
jsc_vm.bundler.options.macro_remap;
var fallback_source: logger.Source = undefined;
-
+ var input_file_fd: StoredFileDescriptorType = 0;
var parse_options = Bundler.ParseOptions{
.allocator = allocator,
.path = path,
.loader = loader,
.dirname_fd = 0,
.file_descriptor = fd,
+ .file_fd_ptr = &input_file_fd,
.file_hash = hash,
.macro_remappings = macro_remappings,
.jsx = jsc_vm.bundler.options.jsx,
@@ -962,6 +963,24 @@ pub const ModuleLoader = struct {
null,
disable_transpilying,
) orelse {
+ if (comptime !disable_transpilying) {
+ if (jsc_vm.isWatcherEnabled()) {
+ if (input_file_fd != 0) {
+ if (jsc_vm.bun_watcher != null and !is_node_override and std.fs.path.isAbsolute(path.text) and !strings.contains(path.text, "node_modules")) {
+ jsc_vm.bun_watcher.?.addFile(
+ input_file_fd,
+ path.text,
+ hash,
+ loader,
+ 0,
+ package_json,
+ true,
+ ) catch {};
+ }
+ }
+ }
+ }
+
return error.ParseError;
};
@@ -984,6 +1003,24 @@ pub const ModuleLoader = struct {
return wasm_result;
}
+ if (comptime !disable_transpilying) {
+ if (jsc_vm.isWatcherEnabled()) {
+ if (input_file_fd != 0) {
+ if (jsc_vm.bun_watcher != null and !is_node_override and std.fs.path.isAbsolute(path.text) and !strings.contains(path.text, "node_modules")) {
+ jsc_vm.bun_watcher.?.addFile(
+ input_file_fd,
+ path.text,
+ hash,
+ loader,
+ 0,
+ package_json,
+ true,
+ ) catch {};
+ }
+ }
+ }
+ }
+
if (jsc_vm.bundler.log.errors > 0) {
return error.ParseError;
}
@@ -1026,22 +1063,6 @@ pub const ModuleLoader = struct {
return error.UnexpectedPendingResolution;
}
- if (jsc_vm.isWatcherEnabled()) {
- if (parse_result.input_fd) |fd_| {
- if (jsc_vm.bun_watcher != null and !is_node_override and std.fs.path.isAbsolute(path.text) and !strings.contains(path.text, "node_modules")) {
- jsc_vm.bun_watcher.?.addFile(
- fd_,
- path.text,
- hash,
- loader,
- 0,
- package_json,
- true,
- ) catch {};
- }
- }
- }
-
if (parse_result.source.contents_is_recycled) {
// this shared buffer is about to become owned by the AsyncModule struct
jsc_vm.bundler.resolver.caches.fs.resetSharedBuffer(
@@ -1110,20 +1131,6 @@ pub const ModuleLoader = struct {
if (jsc_vm.isWatcherEnabled()) {
const resolved_source = jsc_vm.refCountedResolvedSource(printer.ctx.written, display_specifier, path.text, null);
- if (parse_result.input_fd) |fd_| {
- if (jsc_vm.bun_watcher != null and !is_node_override and std.fs.path.isAbsolute(path.text) and !strings.contains(path.text, "node_modules")) {
- jsc_vm.bun_watcher.?.addFile(
- fd_,
- path.text,
- hash,
- loader,
- 0,
- package_json,
- true,
- ) catch {};
- }
- }
-
return resolved_source;
}
diff --git a/src/bun.zig b/src/bun.zig
index d3b9a04c5..4a3dad9aa 100644
--- a/src/bun.zig
+++ b/src/bun.zig
@@ -1016,3 +1016,79 @@ pub fn asByteSlice(buffer: anytype) []const u8 {
},
};
}
+
+/// Reload Bun's process
+///
+/// This clones envp, argv, and gets the current executable path
+///
+/// Overwrites the current process with the new process
+///
+/// Must be able to allocate memory. malloc is not signal safe, but it's
+/// best-effort. Not much we can do if it fails.
+pub fn reloadProcess(
+ allocator: std.mem.Allocator,
+ clear_terminal: bool,
+) void {
+ const PosixSpawn = @import("./bun.js/api/bun/spawn.zig").PosixSpawn;
+
+ var dupe_argv = allocator.allocSentinel(?[*:0]const u8, std.os.argv.len, null) catch unreachable;
+ for (std.os.argv, dupe_argv) |src, *dest| {
+ dest.* = (allocator.dupeZ(u8, sliceTo(src, 0)) catch unreachable).ptr;
+ }
+
+ var environ_slice = std.mem.span(std.c.environ);
+ var environ = allocator.allocSentinel(?[*:0]const u8, environ_slice.len, null) catch unreachable;
+ for (environ_slice, environ) |src, *dest| {
+ if (src == null) {
+ dest.* = null;
+ } else {
+ dest.* = (allocator.dupeZ(u8, sliceTo(src.?, 0)) catch unreachable).ptr;
+ }
+ }
+
+ // we must clone selfExePath incase the argv[0] was not an absolute path (what appears in the terminal)
+ const exec_path = (allocator.dupeZ(u8, std.fs.selfExePathAlloc(allocator) catch unreachable) catch unreachable).ptr;
+
+ // we clone argv so that the memory address isn't the same as the libc one
+ const argv = @ptrCast([*:null]?[*:0]const u8, dupe_argv.ptr);
+
+ // we clone envp so that the memory address of environment variables isn't the same as the libc one
+ const envp = @ptrCast([*:null]?[*:0]const u8, environ.ptr);
+
+ // Clear the terminal
+ if (clear_terminal) {
+ Output.resetTerminalAll();
+ }
+
+ // macOS doesn't have CLOEXEC, so we must go through posix_spawn
+ if (comptime Environment.isMac) {
+ var actions = PosixSpawn.Actions.init() catch unreachable;
+ actions.inherit(0) catch unreachable;
+ actions.inherit(1) catch unreachable;
+ actions.inherit(2) catch unreachable;
+ var attrs = PosixSpawn.Attr.init() catch unreachable;
+ attrs.set(
+ C.POSIX_SPAWN_CLOEXEC_DEFAULT |
+ // Apple Extension: If this bit is set, rather
+ // than returning to the caller, posix_spawn(2)
+ // and posix_spawnp(2) will behave as a more
+ // featureful execve(2).
+ C.POSIX_SPAWN_SETEXEC |
+ C.POSIX_SPAWN_SETSIGDEF | C.POSIX_SPAWN_SETSIGMASK,
+ ) catch unreachable;
+ switch (PosixSpawn.spawnZ(exec_path, actions, attrs, @ptrCast([*:null]?[*:0]const u8, argv), @ptrCast([*:null]?[*:0]const u8, envp))) {
+ .err => |err| {
+ Output.panic("Unexpected error while reloading: {d} {s}", .{ err.errno, @tagName(err.getErrno()) });
+ },
+ .result => |_| {},
+ }
+ } else {
+ const err = std.os.execveZ(
+ exec_path,
+ argv,
+ envp,
+ );
+ Output.panic("Unexpected error while reloading: {s}", .{@errorName(err)});
+ }
+}
+pub var auto_reload_on_crash = false;
diff --git a/src/bun_js.zig b/src/bun_js.zig
index f13568390..51764231e 100644
--- a/src/bun_js.zig
+++ b/src/bun_js.zig
@@ -147,13 +147,20 @@ pub const Run = struct {
pub fn start(this: *Run) void {
var vm = this.vm;
- if (this.ctx.debug.hot_reload) {
+ vm.hot_reload = this.ctx.debug.hot_reload;
+ if (this.ctx.debug.hot_reload != .none) {
JSC.HotReloader.enableHotModuleReloading(vm);
}
if (vm.loadEntryPoint(this.entry_path)) |promise| {
if (promise.status(vm.global.vm()) == .Rejected) {
vm.runErrorHandler(promise.result(vm.global.vm()), null);
- Global.exit(1);
+
+ if (vm.hot_reload != .none) {
+ vm.eventLoop().tick();
+ vm.eventLoop().tickPossiblyForever();
+ } else {
+ Global.exit(1);
+ }
}
_ = promise.result(vm.global.vm());
@@ -178,7 +185,13 @@ pub const Run = struct {
} else {
Output.prettyErrorln("Error occurred loading entry point: {s}", .{@errorName(err)});
}
- Global.exit(1);
+
+ if (vm.hot_reload != .none) {
+ vm.eventLoop().tick();
+ vm.eventLoop().tickPossiblyForever();
+ } else {
+ Global.exit(1);
+ }
}
// don't run the GC if we don't actually need to
@@ -199,17 +212,26 @@ pub const Run = struct {
vm.onUnhandledError(this.vm.global, this.vm.pending_internal_promise.result(vm.global.vm()));
}
- while (vm.eventLoop().tasks.count > 0 or vm.active_tasks > 0 or vm.uws_event_loop.?.active > 0) {
- vm.tick();
+ while (true) {
+ while (vm.eventLoop().tasks.count > 0 or vm.active_tasks > 0 or vm.uws_event_loop.?.active > 0) {
+ vm.tick();
+
+ // Report exceptions in hot-reloaded modules
+ if (this.vm.pending_internal_promise.status(vm.global.vm()) == .Rejected and prev_promise != this.vm.pending_internal_promise) {
+ prev_promise = this.vm.pending_internal_promise;
+ vm.onUnhandledError(this.vm.global, this.vm.pending_internal_promise.result(vm.global.vm()));
+ continue;
+ }
+
+ vm.eventLoop().autoTickActive();
+ }
- // Report exceptions in hot-reloaded modules
if (this.vm.pending_internal_promise.status(vm.global.vm()) == .Rejected and prev_promise != this.vm.pending_internal_promise) {
prev_promise = this.vm.pending_internal_promise;
vm.onUnhandledError(this.vm.global, this.vm.pending_internal_promise.result(vm.global.vm()));
- continue;
}
- vm.eventLoop().autoTickActive();
+ vm.eventLoop().tickPossiblyForever();
}
if (this.vm.pending_internal_promise.status(vm.global.vm()) == .Rejected and prev_promise != this.vm.pending_internal_promise) {
diff --git a/src/bundler.zig b/src/bundler.zig
index a43d29c54..e1701ecd7 100644
--- a/src/bundler.zig
+++ b/src/bundler.zig
@@ -1324,6 +1324,10 @@ pub const Bundler = struct {
dirname_fd: StoredFileDescriptorType,
file_descriptor: ?StoredFileDescriptorType = null,
file_hash: ?u32 = null,
+
+ /// On exception, we might still want to watch the file.
+ file_fd_ptr: ?*StoredFileDescriptorType = null,
+
path: Fs.Path,
loader: options.Loader,
jsx: options.JSX.Pragma,
@@ -1396,6 +1400,9 @@ pub const Bundler = struct {
return null;
};
input_fd = entry.fd;
+ if (this_parse.file_fd_ptr) |file_fd_ptr| {
+ file_fd_ptr.* = entry.fd;
+ }
break :brk logger.Source.initRecycledFile(Fs.File{ .path = path, .contents = entry.contents }, bundler.allocator) catch return null;
};
diff --git a/src/cli.zig b/src/cli.zig
index 1418d1b2e..530dc5e30 100644
--- a/src/cli.zig
+++ b/src/cli.zig
@@ -192,6 +192,7 @@ pub const Arguments = struct {
clap.parseParam("-u, --origin <STR> Rewrite import URLs to start with --origin. Default: \"\"") catch unreachable,
clap.parseParam("-p, --port <STR> Port to serve bun's dev server on. Default: \"3000\"") catch unreachable,
clap.parseParam("--hot Enable auto reload in bun's JavaScript runtime") catch unreachable,
+ clap.parseParam("--watch Automatically restart bun's JavaScript runtime on file change") catch unreachable,
clap.parseParam("--no-install Disable auto install in bun's JavaScript runtime") catch unreachable,
clap.parseParam("-i Automatically install dependencies and use global cache in bun's runtime, equivalent to --install=fallback") catch unreachable,
clap.parseParam("--install <STR> Install dependencies automatically when no node_modules are present, default: \"auto\". \"force\" to ignore node_modules, fallback to install any missing") catch unreachable,
@@ -440,7 +441,12 @@ pub const Arguments = struct {
// we never actually supported inject.
// opts.inject = args.options("--inject");
opts.extension_order = args.options("--extension-order");
- ctx.debug.hot_reload = args.flag("--hot");
+ if (args.flag("--hot")) {
+ ctx.debug.hot_reload = .hot;
+ } else if (args.flag("--watch")) {
+ ctx.debug.hot_reload = .watch;
+ bun.auto_reload_on_crash = true;
+ }
ctx.passthrough = args.remaining();
opts.no_summary = args.flag("--no-summary");
@@ -864,7 +870,7 @@ pub const Command = struct {
dump_limits: bool = false,
fallback_only: bool = false,
silent: bool = false,
- hot_reload: bool = false,
+ hot_reload: HotReload = HotReload.none,
global_cache: options.GlobalCache = .auto,
offline_mode_setting: ?Bunfig.OfflineMode = null,
run_in_bun: bool = false,
@@ -878,6 +884,12 @@ pub const Command = struct {
test_directory: []const u8 = "",
};
+ pub const HotReload = enum {
+ none,
+ hot,
+ watch,
+ };
+
pub const TestOptions = struct {
update_snapshots: bool = false,
repeat_count: u32 = 0,
diff --git a/src/cli/test_command.zig b/src/cli/test_command.zig
index 11f57b0ad..bdcb0ee55 100644
--- a/src/cli/test_command.zig
+++ b/src/cli/test_command.zig
@@ -431,6 +431,9 @@ pub const TestCommand = struct {
const test_files = try scanner.results.toOwnedSlice();
if (test_files.len > 0) {
+ vm.hot_reload = ctx.debug.hot_reload;
+ if (vm.hot_reload != .none)
+ JSC.HotReloader.enableHotModuleReloading(vm);
// vm.bundler.fs.fs.readDirectory(_dir: string, _handle: ?std.fs.Dir)
runAllTests(reporter, vm, test_files, ctx.allocator);
}
@@ -550,6 +553,19 @@ pub const TestCommand = struct {
Output.prettyError("\n", .{});
Output.flush();
+ if (vm.hot_reload == .watch) {
+ vm.eventLoop().tickPossiblyForever();
+
+ while (true) {
+ while (vm.eventLoop().tasks.count > 0 or vm.active_tasks > 0 or vm.uws_event_loop.?.active > 0) {
+ vm.tick();
+ vm.eventLoop().autoTickActive();
+ }
+
+ vm.eventLoop().tickPossiblyForever();
+ }
+ }
+
if (reporter.summary.fail > 0) {
Global.exit(1);
}
diff --git a/src/install/install.zig b/src/install/install.zig
index 75a7e28aa..b6d61e00a 100644
--- a/src/install/install.zig
+++ b/src/install/install.zig
@@ -2748,10 +2748,8 @@ pub const PackageManager = struct {
var tmpfile = FileSystem.RealFS.Tmpfile{};
var secret: [32]u8 = undefined;
std.mem.writeIntNative(u64, secret[0..8], @intCast(u64, std.time.milliTimestamp()));
- var state = std.rand.Xoodoo.init(secret);
- const rng = state.random();
var base64_bytes: [64]u8 = undefined;
- rng.bytes(&base64_bytes);
+ std.crypto.random.bytes(&base64_bytes);
const tmpname__ = std.fmt.bufPrint(tmpname_buf[8..], "{s}", .{std.fmt.fmtSliceHexLower(&base64_bytes)}) catch unreachable;
tmpname_buf[tmpname__.len + 8] = 0;
diff --git a/src/install/lockfile.zig b/src/install/lockfile.zig
index f7e872d4b..35e018b7e 100644
--- a/src/install/lockfile.zig
+++ b/src/install/lockfile.zig
@@ -1380,9 +1380,8 @@ pub fn saveToDisk(this: *Lockfile, filename: stringZ) void {
var tmpfile = FileSystem.RealFS.Tmpfile{};
var secret: [32]u8 = undefined;
std.mem.writeIntNative(u64, secret[0..8], @intCast(u64, std.time.milliTimestamp()));
- var rng = std.rand.Xoodoo.init(secret);
var base64_bytes: [64]u8 = undefined;
- rng.random().bytes(&base64_bytes);
+ std.crypto.random.bytes(&base64_bytes);
const tmpname__ = std.fmt.bufPrint(tmpname_buf[8..], "{s}", .{std.fmt.fmtSliceHexLower(&base64_bytes)}) catch unreachable;
tmpname_buf[tmpname__.len + 8] = 0;
diff --git a/src/output.zig b/src/output.zig
index 72f8e57ed..96bf1b8a7 100644
--- a/src/output.zig
+++ b/src/output.zig
@@ -232,6 +232,13 @@ pub fn resetTerminal() void {
}
}
+pub fn resetTerminalAll() void {
+ if (enable_ansi_colors_stderr)
+ _ = source.error_stream.write("\x1b[H\x1b[2J") catch 0;
+ if (enable_ansi_colors_stdout)
+ _ = source.stream.write("\x1b[H\x1b[2J") catch 0;
+}
+
/// Write buffered stdout & stderr to the terminal.
/// Must be called before the process exits or the buffered output will be lost.
/// Bun automatically calls this function in Global.exit().
diff --git a/src/panic_handler.zig b/src/panic_handler.zig
index ff17ed7d1..4e2ca05f9 100644
--- a/src/panic_handler.zig
+++ b/src/panic_handler.zig
@@ -36,6 +36,15 @@ pub fn NewPanicHandler(comptime panic_func: fn ([]const u8, ?*std.builtin.StackT
Output.disableBuffering();
+ if (bun.auto_reload_on_crash) {
+ // attempt to prevent a double panic
+ bun.auto_reload_on_crash = false;
+
+ Output.prettyErrorln("<d>--- Bun is auto-restarting due to crash <d>[time: <b>{d}<r><d>] ---<r>", .{@max(std.time.milliTimestamp(), 0)});
+ Output.flush();
+ bun.reloadProcess(bun.default_allocator, false);
+ }
+
// // We want to always inline the panic handler so it doesn't show up in the stacktrace.
@call(.always_inline, panic_func, .{ msg, error_return_type, addr });
}
diff --git a/src/report.zig b/src/report.zig
index b3724e1d2..ee0366263 100644
--- a/src/report.zig
+++ b/src/report.zig
@@ -558,5 +558,14 @@ pub noinline fn globalError(err: anyerror) noreturn {
std.debug.writeStackTrace(trace.*, Output.errorWriter(), default_allocator, debug_info, std.debug.detectTTYConfig(std.io.getStdErr())) catch break :print_stacktrace;
}
+ if (bun.auto_reload_on_crash) {
+ // attempt to prevent a double panic
+ bun.auto_reload_on_crash = false;
+
+ Output.prettyErrorln("<d>---<r> Bun is auto-restarting due to crash <d>[time: <b>{d}<r><d>] ---<r>", .{@max(std.time.milliTimestamp(), 0)});
+ Output.flush();
+ bun.reloadProcess(bun.default_allocator, false);
+ }
+
Global.exit(1);
}