aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bun.js/api/server.zig6
-rw-r--r--src/bun.js/javascript.zig139
-rw-r--r--src/bun.js/module_loader.zig59
-rw-r--r--src/bun.js/rare_data.zig26
-rw-r--r--src/bun_js.zig6
-rw-r--r--src/cli/test_command.zig9
-rw-r--r--src/watcher.zig1
7 files changed, 190 insertions, 56 deletions
diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig
index 85d4dadb5..390f8ef96 100644
--- a/src/bun.js/api/server.zig
+++ b/src/bun.js/api/server.zig
@@ -5254,6 +5254,10 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp
var listener = this.listener orelse return;
this.listener = null;
this.unref();
+
+ if (!ssl_enabled_)
+ this.vm.removeListeningSocketForWatchMode(@intCast(listener.socket().fd()));
+
if (!abrupt) {
listener.close();
} else if (!this.flags.terminated) {
@@ -5428,6 +5432,8 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp
this.listener = socket;
this.vm.event_loop_handle = uws.Loop.get();
+ if (!ssl_enabled_)
+ this.vm.addListeningSocketForWatchMode(@intCast(socket.?.socket().fd()));
}
pub fn ref(this: *ThisServer) void {
diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig
index e2e44e63a..60364706e 100644
--- a/src/bun.js/javascript.zig
+++ b/src/bun.js/javascript.zig
@@ -401,6 +401,50 @@ pub const ExitHandler = struct {
pub const WebWorker = @import("./web_worker.zig").WebWorker;
+pub const ImportWatcher = union(enum) {
+ none: void,
+ hot: *HotReloader.Watcher,
+ watch: *WatchReloader.Watcher,
+
+ pub fn start(this: ImportWatcher) !void {
+ switch (this) {
+ inline .hot => |watcher| try watcher.start(),
+ inline .watch => |watcher| try watcher.start(),
+ else => {},
+ }
+ }
+
+ pub inline fn watchlist(this: ImportWatcher) Watcher.WatchListArray {
+ return switch (this) {
+ inline .hot, .watch => |wacher| wacher.watchlist,
+ else => .{},
+ };
+ }
+
+ pub inline fn indexOf(this: ImportWatcher, hash: Watcher.HashType) ?u32 {
+ return switch (this) {
+ inline .hot, .watch => |wacher| wacher.indexOf(hash),
+ else => null,
+ };
+ }
+
+ pub inline fn addFile(
+ this: ImportWatcher,
+ fd: StoredFileDescriptorType,
+ file_path: string,
+ hash: Watcher.HashType,
+ loader: options.Loader,
+ dir_fd: StoredFileDescriptorType,
+ package_json: ?*PackageJSON,
+ comptime copy_file_path: bool,
+ ) !void {
+ switch (this) {
+ inline .hot, .watch => |wacher| try wacher.addFile(fd, file_path, hash, loader, dir_fd, package_json, copy_file_path),
+ else => {},
+ }
+ }
+};
+
/// TODO: rename this to ScriptExecutionContext
/// This is the shared global state for a single JS instance execution
/// Today, Bun is one VM per thread, so the name "VirtualMachine" sort of makes sense
@@ -410,8 +454,7 @@ pub const VirtualMachine = struct {
allocator: std.mem.Allocator,
has_loaded_constructors: bool = false,
bundler: Bundler,
- bun_dev_watcher: ?*http.Watcher = null,
- bun_watcher: ?*JSC.Watcher = null,
+ bun_watcher: ImportWatcher = .{ .none = {} },
console: *ZigConsoleClient,
log: *logger.Log,
main: string = "",
@@ -1006,7 +1049,7 @@ pub const VirtualMachine = struct {
}
pub fn isWatcherEnabled(this: *VirtualMachine) bool {
- return this.bun_dev_watcher != null or this.bun_watcher != null;
+ return this.bun_watcher != .none;
}
/// Instead of storing timestamp as a i128, we store it as a u64.
@@ -1987,7 +2030,7 @@ pub const VirtualMachine = struct {
try this.entry_point.generate(
this.allocator,
- this.bun_watcher != null,
+ this.bun_watcher != .none,
Fs.PathName.init(entry_path),
main_file_name,
);
@@ -2044,7 +2087,7 @@ pub const VirtualMachine = struct {
defer JSValue.fromCell(promise).unprotect();
// pending_internal_promise can change if hot module reloading is enabled
- if (this.bun_watcher != null) {
+ if (this.isWatcherEnabled()) {
this.eventLoop().performGC();
switch (this.pending_internal_promise.status(this.global.vm())) {
JSC.JSPromise.Status.Pending => {
@@ -2099,7 +2142,7 @@ pub const VirtualMachine = struct {
var promise = try this.reloadEntryPoint(entry_path);
// pending_internal_promise can change if hot module reloading is enabled
- if (this.bun_watcher != null) {
+ if (this.isWatcherEnabled()) {
this.eventLoop().performGC();
switch (this.pending_internal_promise.status(this.global.vm())) {
JSC.JSPromise.Status.Pending => {
@@ -2125,6 +2168,21 @@ pub const VirtualMachine = struct {
return this.pending_internal_promise;
}
+ pub fn addListeningSocketForWatchMode(this: *VirtualMachine, socket: bun.FileDescriptor) void {
+ if (this.hot_reload != .watch) {
+ return;
+ }
+
+ this.rareData().addListeningSocketForWatchMode(socket);
+ }
+ pub fn removeListeningSocketForWatchMode(this: *VirtualMachine, socket: bun.FileDescriptor) void {
+ if (this.hot_reload != .watch) {
+ return;
+ }
+
+ this.rareData().removeListeningSocketForWatchMode(socket);
+ }
+
pub fn loadMacroEntryPoint(this: *VirtualMachine, entry_path: string, function_name: string, specifier: string, hash: i32) !*JSInternalPromise {
var entry_point_entry = try this.macro_entry_points.getOrPut(hash);
@@ -2809,6 +2867,7 @@ pub const VirtualMachine = struct {
};
pub const HotReloader = NewHotReloader(VirtualMachine, JSC.EventLoop, false);
+pub const WatchReloader = NewHotReloader(VirtualMachine, JSC.EventLoop, true);
pub const Watcher = HotReloader.Watcher;
extern fn BunDebugger__willHotReload() void;
@@ -2835,6 +2894,8 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime
this.eventLoop().enqueueTaskConcurrent(task);
}
+ pub var clear_screen = false;
+
pub const HotReloadTask = struct {
reloader: *Reloader,
count: u8 = 0,
@@ -2864,7 +2925,11 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime
return;
if (comptime reload_immediately) {
- bun.reloadProcess(bun.default_allocator, Output.enable_ansi_colors);
+ Output.flush();
+ if (comptime Ctx == ImportWatcher) {
+ this.reloader.ctx.rareData().closeAllListenSocketsForWatchMode();
+ }
+ bun.reloadProcess(bun.default_allocator, clear_screen);
unreachable;
}
@@ -2898,23 +2963,51 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime
) void);
pub fn enableHotModuleReloading(this: *Ctx) void {
- if (this.bun_watcher != null)
- return;
+ if (comptime @TypeOf(this.bun_watcher) == ImportWatcher) {
+ if (this.bun_watcher != .none)
+ return;
+ } else {
+ if (this.bun_watcher != null)
+ return;
+ }
var reloader = bun.default_allocator.create(Reloader) catch @panic("OOM");
reloader.* = .{
.ctx = this,
.verbose = if (@hasField(Ctx, "log")) this.log.level.atLeast(.info) else false,
};
- this.bun_watcher = @This().Watcher.init(
- reloader,
- this.bundler.fs,
- bun.default_allocator,
- ) catch @panic("Failed to enable File Watcher");
- this.bundler.resolver.watcher = Resolver.ResolveWatcher(*@This().Watcher, onMaybeWatchDirectory).init(this.bun_watcher.?);
+ if (comptime @TypeOf(this.bun_watcher) == ImportWatcher) {
+ this.bun_watcher = if (reload_immediately)
+ .{ .watch = @This().Watcher.init(
+ reloader,
+ this.bundler.fs,
+ bun.default_allocator,
+ ) catch @panic("Failed to enable File Watcher") }
+ else
+ .{ .hot = @This().Watcher.init(
+ reloader,
+ this.bundler.fs,
+ bun.default_allocator,
+ ) catch @panic("Failed to enable File Watcher") };
+
+ if (reload_immediately) {
+ this.bundler.resolver.watcher = Resolver.ResolveWatcher(*@This().Watcher, onMaybeWatchDirectory).init(this.bun_watcher.watch);
+ } else {
+ this.bundler.resolver.watcher = Resolver.ResolveWatcher(*@This().Watcher, onMaybeWatchDirectory).init(this.bun_watcher.hot);
+ }
+ } else {
+ this.bun_watcher = @This().Watcher.init(
+ reloader,
+ this.bundler.fs,
+ bun.default_allocator,
+ ) catch @panic("Failed to enable File Watcher");
+ this.bundler.resolver.watcher = Resolver.ResolveWatcher(*@This().Watcher, onMaybeWatchDirectory).init(this.bun_watcher.?);
+ }
+
+ clear_screen = Output.enable_ansi_colors and !strings.eqlComptime(this.bundler.env.map.get("BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD") orelse "0", "true");
- this.bun_watcher.?.start() catch @panic("Failed to start File Watcher");
+ reloader.getContext().start() catch @panic("Failed to start File Watcher");
}
pub fn onMaybeWatchDirectory(watch: *@This().Watcher, file_path: string, dir_fd: StoredFileDescriptorType) void {
@@ -2941,6 +3034,18 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime
Output.prettyErrorln("<r>Watcher crashed: <red><b>{s}<r>", .{@errorName(err)});
}
+ pub fn getContext(this: *@This()) *@This().Watcher {
+ if (comptime @TypeOf(this.ctx.bun_watcher) == ImportWatcher) {
+ if (reload_immediately) {
+ return this.ctx.bun_watcher.watch;
+ } else {
+ return this.ctx.bun_watcher.hot;
+ }
+ } else {
+ return this.ctx.bun_watcher.?;
+ }
+ }
+
pub fn onFileUpdate(
this: *@This(),
events: []watcher.WatchEvent,
@@ -2954,7 +3059,7 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime
const hashes = slice.items(.hash);
const parents = slice.items(.parent_hash);
var file_descriptors = slice.items(.fd);
- var ctx = this.ctx.bun_watcher.?;
+ var ctx = this.getContext();
defer ctx.flushEvictions();
defer Output.flush();
diff --git a/src/bun.js/module_loader.zig b/src/bun.js/module_loader.zig
index cf86cb460..db8df00c2 100644
--- a/src/bun.js/module_loader.zig
+++ b/src/bun.js/module_loader.zig
@@ -363,18 +363,15 @@ pub const RuntimeTranspilerStore = struct {
var package_json: ?*PackageJSON = null;
const hash = JSC.Watcher.getHash(path.text);
- if (vm.bun_dev_watcher) |watcher| {
- if (watcher.indexOf(hash)) |index| {
- const _fd = watcher.watchlist.items(.fd)[index];
- fd = if (_fd > 0) _fd else null;
- package_json = watcher.watchlist.items(.package_json)[index];
- }
- } else if (vm.bun_watcher) |watcher| {
- if (watcher.indexOf(hash)) |index| {
- const _fd = watcher.watchlist.items(.fd)[index];
- fd = if (_fd > 0) _fd else null;
- package_json = watcher.watchlist.items(.package_json)[index];
- }
+ switch (vm.bun_watcher) {
+ .hot, .watch => {
+ if (vm.bun_watcher.indexOf(hash)) |index| {
+ const _fd = vm.bun_watcher.watchlist().items(.fd)[index];
+ fd = if (_fd > 0) _fd else null;
+ package_json = vm.bun_watcher.watchlist().items(.package_json)[index];
+ }
+ },
+ else => {},
}
// this should be a cheap lookup because 24 bytes == 8 * 3 so it's read 3 machine words
@@ -438,9 +435,9 @@ pub const RuntimeTranspilerStore = struct {
) orelse {
if (vm.isWatcherEnabled()) {
if (input_file_fd != 0) {
- if (vm.bun_watcher != null and !is_node_override and std.fs.path.isAbsolute(path.text) and !strings.contains(path.text, "node_modules")) {
+ if (!is_node_override and std.fs.path.isAbsolute(path.text) and !strings.contains(path.text, "node_modules")) {
should_close_input_file_fd = false;
- vm.bun_watcher.?.addFile(
+ vm.bun_watcher.addFile(
input_file_fd,
path.text,
hash,
@@ -459,11 +456,11 @@ pub const RuntimeTranspilerStore = struct {
if (vm.isWatcherEnabled()) {
if (input_file_fd != 0) {
- if (vm.bun_watcher != null and !is_node_override and
+ if (!is_node_override and
std.fs.path.isAbsolute(path.text) and !strings.contains(path.text, "node_modules"))
{
should_close_input_file_fd = false;
- vm.bun_watcher.?.addFile(
+ vm.bun_watcher.addFile(
input_file_fd,
path.text,
hash,
@@ -1244,8 +1241,8 @@ pub const ModuleLoader = struct {
var resolved_source = jsc_vm.refCountedResolvedSource(printer.ctx.written, bun.String.init(specifier), path.text, null, false);
if (parse_result.input_fd) |fd_| {
- if (jsc_vm.bun_watcher != null and std.fs.path.isAbsolute(path.text) and !strings.contains(path.text, "node_modules")) {
- jsc_vm.bun_watcher.?.addFile(
+ if (std.fs.path.isAbsolute(path.text) and !strings.contains(path.text, "node_modules")) {
+ jsc_vm.bun_watcher.addFile(
fd_,
path.text,
this.hash,
@@ -1379,18 +1376,10 @@ pub const ModuleLoader = struct {
var fd: ?StoredFileDescriptorType = null;
var package_json: ?*PackageJSON = null;
- if (jsc_vm.bun_dev_watcher) |watcher| {
- if (watcher.indexOf(hash)) |index| {
- const _fd = watcher.watchlist.items(.fd)[index];
- fd = if (_fd > 0) _fd else null;
- package_json = watcher.watchlist.items(.package_json)[index];
- }
- } else if (jsc_vm.bun_watcher) |watcher| {
- if (watcher.indexOf(hash)) |index| {
- const _fd = watcher.watchlist.items(.fd)[index];
- fd = if (_fd > 0) _fd else null;
- package_json = watcher.watchlist.items(.package_json)[index];
- }
+ if (jsc_vm.bun_watcher.indexOf(hash)) |index| {
+ const _fd = jsc_vm.bun_watcher.watchlist().items(.fd)[index];
+ fd = if (_fd > 0) _fd else null;
+ package_json = jsc_vm.bun_watcher.watchlist().items(.package_json)[index];
}
var old = jsc_vm.bundler.log;
@@ -1470,9 +1459,9 @@ pub const ModuleLoader = struct {
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")) {
+ if (!is_node_override and std.fs.path.isAbsolute(path.text) and !strings.contains(path.text, "node_modules")) {
should_close_input_file_fd = false;
- jsc_vm.bun_watcher.?.addFile(
+ jsc_vm.bun_watcher.addFile(
input_file_fd,
path.text,
hash,
@@ -1513,9 +1502,9 @@ pub const ModuleLoader = struct {
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")) {
+ if (!is_node_override and std.fs.path.isAbsolute(path.text) and !strings.contains(path.text, "node_modules")) {
should_close_input_file_fd = false;
- jsc_vm.bun_watcher.?.addFile(
+ jsc_vm.bun_watcher.addFile(
input_file_fd,
path.text,
hash,
@@ -1715,7 +1704,7 @@ pub const ModuleLoader = struct {
// const hash = http.Watcher.getHash(path.text);
// if (jsc_vm.watcher) |watcher| {
// if (watcher.indexOf(hash)) |index| {
- // const _fd = watcher.watchlist.items(.fd)[index];
+ // const _fd = watcher.watchlist().items(.fd)[index];
// fd = if (_fd > 0) _fd else null;
// }
// }
diff --git a/src/bun.js/rare_data.zig b/src/bun.js/rare_data.zig
index 44e482049..c9d742d96 100644
--- a/src/bun.js/rare_data.zig
+++ b/src/bun.js/rare_data.zig
@@ -39,6 +39,32 @@ mime_types: ?bun.HTTP.MimeType.Map = null,
node_fs_stat_watcher_scheduler: ?*StatWatcherScheduler = null,
+listening_sockets_for_watch_mode: std.ArrayListUnmanaged(bun.FileDescriptor) = .{},
+listening_sockets_for_watch_mode_lock: bun.Lock = bun.Lock.init(),
+
+pub fn addListeningSocketForWatchMode(this: *RareData, socket: bun.FileDescriptor) void {
+ this.listening_sockets_for_watch_mode_lock.lock();
+ defer this.listening_sockets_for_watch_mode_lock.unlock();
+ this.listening_sockets_for_watch_mode.append(bun.default_allocator, socket) catch {};
+}
+
+pub fn removeListeningSocketForWatchMode(this: *RareData, socket: bun.FileDescriptor) void {
+ this.listening_sockets_for_watch_mode_lock.lock();
+ defer this.listening_sockets_for_watch_mode_lock.unlock();
+ if (std.mem.indexOfScalar(bun.FileDescriptor, this.listening_sockets_for_watch_mode.items, socket)) |i| {
+ _ = this.listening_sockets_for_watch_mode.swapRemove(i);
+ }
+}
+
+pub fn closeAllListenSocketsForWatchMode(this: *RareData) void {
+ this.listening_sockets_for_watch_mode_lock.lock();
+ defer this.listening_sockets_for_watch_mode_lock.unlock();
+ for (this.listening_sockets_for_watch_mode.items) |socket| {
+ _ = Syscall.close(socket);
+ }
+ this.listening_sockets_for_watch_mode = .{};
+}
+
pub fn hotMap(this: *RareData, allocator: std.mem.Allocator) *HotMap {
if (this.hot_map == null) {
this.hot_map = HotMap.init(allocator);
diff --git a/src/bun_js.zig b/src/bun_js.zig
index 29ecf28b8..6b807cf46 100644
--- a/src/bun_js.zig
+++ b/src/bun_js.zig
@@ -243,8 +243,10 @@ pub const Run = struct {
vm.hot_reload = this.ctx.debug.hot_reload;
vm.onUnhandledRejection = &onUnhandledRejectionBeforeClose;
- if (this.ctx.debug.hot_reload != .none) {
- JSC.HotReloader.enableHotModuleReloading(vm);
+ switch (this.ctx.debug.hot_reload) {
+ .hot => JSC.HotReloader.enableHotModuleReloading(vm),
+ .watch => JSC.WatchReloader.enableHotModuleReloading(vm),
+ else => {},
}
if (strings.eqlComptime(this.entry_path, ".") and vm.bundler.fs.top_level_dir.len > 0) {
diff --git a/src/cli/test_command.zig b/src/cli/test_command.zig
index 75ac87e5d..dccbdb205 100644
--- a/src/cli/test_command.zig
+++ b/src/cli/test_command.zig
@@ -685,8 +685,13 @@ 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);
+
+ switch (vm.hot_reload) {
+ .hot => JSC.HotReloader.enableHotModuleReloading(vm),
+ .watch => JSC.WatchReloader.enableHotModuleReloading(vm),
+ else => {},
+ }
+
// vm.bundler.fs.fs.readDirectory(_dir: string, _handle: ?std.fs.Dir)
runAllTests(reporter, vm, test_files, ctx.allocator);
}
diff --git a/src/watcher.zig b/src/watcher.zig
index 5521414e3..98cf5aa29 100644
--- a/src/watcher.zig
+++ b/src/watcher.zig
@@ -373,6 +373,7 @@ pub fn NewWatcher(comptime ContextType: type) type {
close_descriptors: bool = false,
pub const HashType = u32;
+ pub const WatchListArray = Watchlist;
var evict_list: [WATCHER_MAX_LIST]WatchItemIndex = undefined;