diff options
author | 2023-09-13 17:31:59 -0700 | |
---|---|---|
committer | 2023-09-13 17:31:59 -0700 | |
commit | 9c9f4ed6ad39f92cb331e21a6b6dc0847e66510e (patch) | |
tree | 6bff76943ef302cfad33b13e03febdaccdcdeb2f /src | |
parent | 4f8edb825f48d06891de7a77131c6a434c06df88 (diff) | |
download | bun-9c9f4ed6ad39f92cb331e21a6b6dc0847e66510e.tar.gz bun-9c9f4ed6ad39f92cb331e21a6b6dc0847e66510e.tar.zst bun-9c9f4ed6ad39f92cb331e21a6b6dc0847e66510e.zip |
Make --watch instant (#5236)
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/bun.js/api/server.zig | 6 | ||||
-rw-r--r-- | src/bun.js/javascript.zig | 139 | ||||
-rw-r--r-- | src/bun.js/module_loader.zig | 59 | ||||
-rw-r--r-- | src/bun.js/rare_data.zig | 26 | ||||
-rw-r--r-- | src/bun_js.zig | 6 | ||||
-rw-r--r-- | src/cli/test_command.zig | 9 | ||||
-rw-r--r-- | src/watcher.zig | 1 |
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; |