diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig index e0e976180856e..005a6717a194b 100644 --- a/src/bun.js/event_loop.zig +++ b/src/bun.js/event_loop.zig @@ -1017,8 +1017,8 @@ pub const EventLoop = struct { transform_task.deinit(); }, @field(Task.Tag, @typeName(HotReloadTask)) => { - var transform_task: *HotReloadTask = task.get(HotReloadTask).?; - transform_task.*.run(); + const transform_task: *HotReloadTask = task.get(HotReloadTask).?; + transform_task.run(); transform_task.deinit(); // special case: we return return 0; diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index 71794e686e015..45e003084a532 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -624,8 +624,8 @@ pub const WebWorker = @import("./web_worker.zig").WebWorker; pub const ImportWatcher = union(enum) { none: void, - hot: *HotReloader.Watcher, - watch: *WatchReloader.Watcher, + hot: *Watcher, + watch: *Watcher, pub fn start(this: ImportWatcher) !void { switch (this) { @@ -4025,14 +4025,13 @@ pub const VirtualMachine = struct { } }; +pub const Watcher = GenericWatcher.NewWatcher; 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; pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime reload_immediately: bool) type { return struct { - pub const Watcher = GenericWatcher.NewWatcher(*@This()); const Reloader = @This(); ctx: *Ctx, @@ -4049,7 +4048,7 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime }; clear_screen = clear_screen_flag; - const watcher = @This().Watcher.init(reloader, fs, bun.default_allocator) catch |err| { + const watcher = Watcher.init(Reloader, reloader, fs, bun.default_allocator) catch |err| { bun.handleErrorReturnTrace(err, @errorReturnTrace()); Output.panic("Failed to enable File Watcher: {s}", .{@errorName(err)}); }; @@ -4082,19 +4081,15 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime pub var clear_screen = false; pub const HotReloadTask = struct { - reloader: *Reloader, count: u8 = 0, hashes: [8]u32 = [_]u32{0} ** 8, concurrent_task: JSC.ConcurrentTask = undefined, + reloader: *Reloader, pub fn append(this: *HotReloadTask, id: u32) void { if (this.count == 8) { this.enqueue(); - const reloader = this.reloader; - this.* = .{ - .reloader = reloader, - .count = 0, - }; + this.count = 0; } this.hashes[this.count] = id; @@ -4134,16 +4129,19 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime _ = this.reloader.pending_count.fetchAdd(1, .monotonic); BunDebugger__willHotReload(); - var that = bun.default_allocator.create(HotReloadTask) catch unreachable; - - that.* = this.*; + const that = bun.new(HotReloadTask, .{ + .reloader = this.reloader, + .count = this.count, + .hashes = this.hashes, + .concurrent_task = undefined, + }); + that.concurrent_task = .{ .task = Task.init(that), .auto_delete = false }; + that.reloader.enqueueTaskConcurrent(&that.concurrent_task); this.count = 0; - that.concurrent_task.task = Task.init(that); - this.reloader.enqueueTaskConcurrent(&that.concurrent_task); } pub fn deinit(this: *HotReloadTask) void { - bun.default_allocator.destroy(this); + bun.destroy(this); } }; @@ -4164,7 +4162,8 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime if (comptime @TypeOf(this.bun_watcher) == ImportWatcher) { this.bun_watcher = if (reload_immediately) - .{ .watch = @This().Watcher.init( + .{ .watch = Watcher.init( + Reloader, reloader, this.bundler.fs, bun.default_allocator, @@ -4173,7 +4172,8 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime Output.panic("Failed to enable File Watcher: {s}", .{@errorName(err)}); } } else - .{ .hot = @This().Watcher.init( + .{ .hot = Watcher.init( + Reloader, reloader, this.bundler.fs, bun.default_allocator, @@ -4183,12 +4183,13 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime } }; if (reload_immediately) { - this.bundler.resolver.watcher = Resolver.ResolveWatcher(*@This().Watcher, onMaybeWatchDirectory).init(this.bun_watcher.watch); + this.bundler.resolver.watcher = Resolver.ResolveWatcher(*Watcher, Watcher.onMaybeWatchDirectory).init(this.bun_watcher.watch); } else { - this.bundler.resolver.watcher = Resolver.ResolveWatcher(*@This().Watcher, onMaybeWatchDirectory).init(this.bun_watcher.hot); + this.bundler.resolver.watcher = Resolver.ResolveWatcher(*Watcher, Watcher.onMaybeWatchDirectory).init(this.bun_watcher.hot); } } else { - this.bun_watcher = @This().Watcher.init( + this.bun_watcher = Watcher.init( + Reloader, reloader, this.bundler.fs, bun.default_allocator, @@ -4196,7 +4197,7 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime bun.handleErrorReturnTrace(err, @errorReturnTrace()); Output.panic("Failed to enable File Watcher: {s}", .{@errorName(err)}); }; - this.bundler.resolver.watcher = Resolver.ResolveWatcher(*@This().Watcher, onMaybeWatchDirectory).init(this.bun_watcher.?); + this.bundler.resolver.watcher = Resolver.ResolveWatcher(*Watcher, Watcher.onMaybeWatchDirectory).init(this.bun_watcher.?); } clear_screen = !this.bundler.env.hasSetNoClearTerminalOnReload(!Output.enable_ansi_colors); @@ -4204,15 +4205,6 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime reloader.getContext().start() catch @panic("Failed to start File Watcher"); } - pub fn onMaybeWatchDirectory(watch: *@This().Watcher, file_path: string, dir_fd: StoredFileDescriptorType) void { - // We don't want to watch: - // - Directories outside the root directory - // - Directories inside node_modules - if (std.mem.indexOf(u8, file_path, "node_modules") == null and std.mem.indexOf(u8, file_path, watch.fs.top_level_dir) != null) { - _ = watch.addDirectory(dir_fd, file_path, GenericWatcher.getHash(file_path), false); - } - } - fn putTombstone(this: *@This(), key: []const u8, value: *bun.fs.FileSystem.RealFS.EntriesOption) void { this.tombstones.put(bun.default_allocator, key, value) catch unreachable; } @@ -4231,7 +4223,7 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime } } - pub fn getContext(this: *@This()) *@This().Watcher { + pub fn getContext(this: *@This()) *Watcher { if (comptime @TypeOf(this.ctx.bun_watcher) == ImportWatcher) { if (reload_immediately) { return this.ctx.bun_watcher.watch; @@ -4245,7 +4237,7 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime } } - pub fn onFileUpdate( + pub noinline fn onFileUpdate( this: *@This(), events: []GenericWatcher.WatchEvent, changed_files: []?[:0]u8, @@ -4262,16 +4254,9 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime defer ctx.flushEvictions(); defer Output.flush(); - const bundler = if (@TypeOf(this.ctx.bundler) == *bun.Bundler) - this.ctx.bundler - else - &this.ctx.bundler; - - var fs: *Fs.FileSystem = bundler.fs; - var rfs: *Fs.FileSystem.RealFS = &fs.fs; - var resolver = &bundler.resolver; + const fs: *Fs.FileSystem = &Fs.FileSystem.instance; + const rfs: *Fs.FileSystem.RealFS = &fs.fs; var _on_file_update_path_buf: bun.PathBuffer = undefined; - var current_task: HotReloadTask = .{ .reloader = this, }; @@ -4314,7 +4299,7 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime // on windows we receive file events for all items affected by a directory change // so we only need to clear the directory cache. all other effects will be handled // by the file events - _ = resolver.bustDirCache(strings.pathWithoutTrailingSlashOne(file_path)); + _ = this.ctx.bustDirCache(strings.pathWithoutTrailingSlashOne(file_path)); continue; } var affected_buf: [128][]const u8 = undefined; @@ -4364,7 +4349,7 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime } } - _ = resolver.bustDirCache(strings.pathWithoutTrailingSlashOne(file_path)); + _ = this.ctx.bustDirCache(strings.pathWithoutTrailingSlashOne(file_path)); if (entries_option) |dir_ent| { var last_file_hash: GenericWatcher.HashType = std.math.maxInt(GenericWatcher.HashType); @@ -4376,7 +4361,7 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime bun.asByteSlice(changed_name_.?); if (changed_name.len == 0 or changed_name[0] == '~' or changed_name[0] == '.') continue; - const loader = (bundler.options.loaders.get(Fs.PathName.init(changed_name).ext) orelse .file); + const loader = (this.ctx.getLoaders().get(Fs.PathName.init(changed_name).ext) orelse .file); var prev_entry_id: usize = std.math.maxInt(usize); if (loader != .file) { var path_string: bun.PathString = undefined; diff --git a/src/bun.js/node/path_watcher.zig b/src/bun.js/node/path_watcher.zig index dbbc6b6798700..9664278b05d5b 100644 --- a/src/bun.js/node/path_watcher.zig +++ b/src/bun.js/node/path_watcher.zig @@ -25,10 +25,11 @@ const FSWatcher = bun.JSC.Node.FSWatcher; const Event = FSWatcher.Event; const StringOrBytesToDecode = FSWatcher.FSWatchTaskWindows.StringOrBytesToDecode; +const Watcher = GenericWatcher.NewWatcher; + pub const PathWatcherManager = struct { const options = @import("../../options.zig"); const log = Output.scoped(.PathWatcherManager, false); - pub const Watcher = GenericWatcher.NewWatcher(*PathWatcherManager); main_watcher: *Watcher, watchers: bun.BabyList(?*PathWatcher) = .{}, @@ -148,7 +149,7 @@ pub const PathWatcherManager = struct { .current_fd_task = bun.FDHashMap(*DirectoryRegisterTask).init(bun.default_allocator), .watchers = watchers, .main_watcher = try Watcher.init( - // PathWatcherManager, + PathWatcherManager, this, vm.bundler.fs, bun.default_allocator, diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 580c97f04803a..a0dd164a32644 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -336,7 +336,7 @@ pub const BundleV2 = struct { framework: ?kit.Framework, graph: Graph, linker: LinkerContext, - bun_watcher: ?*Watcher.Watcher, + bun_watcher: ?*bun.JSC.Watcher, plugins: ?*JSC.API.JSBundler.Plugin, completion: ?*JSBundleCompletionTask, source_code_length: usize, diff --git a/src/crash_handler.zig b/src/crash_handler.zig index 39dc6d68c9b15..d7ac9e87924fe 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -168,6 +168,10 @@ pub fn crashHandler( ) noreturn { @setCold(true); + if (bun.Environment.isWindows) { + @breakpoint(); + } + if (bun.Environment.isDebug) bun.Output.disableScopedDebugWriter(); diff --git a/src/watcher.zig b/src/watcher.zig index b8bfbc2574d19..8741858570ad7 100644 --- a/src/watcher.zig +++ b/src/watcher.zig @@ -535,10 +535,11 @@ pub fn getHash(filepath: string) HashType { return @as(HashType, @truncate(bun.hash(filepath))); } -// TODO: this should not be a function with a generic context. every function -// besides `watchLoop` does not refer to context. -pub fn NewWatcher(comptime ContextType: type) type { - return struct { +// TODO: Rename to `Watcher` and make a top-level struct. +// `if(true)` is to reduce git diff from when it was changed +// from a comptime function to a basic struct. +pub const NewWatcher = if (true) + struct { const Watcher = @This(); watchlist: WatchList, @@ -551,7 +552,10 @@ pub fn NewWatcher(comptime ContextType: type) type { watch_events: [128]WatchEvent = undefined, changed_filepaths: [128]?[:0]u8 = [_]?[:0]u8{null} ** 128, - ctx: ContextType, + ctx: *anyopaque, + onFileUpdate: *const fn (this: *anyopaque, events: []WatchEvent, changed_files: []?[:0]u8, watchlist: WatchList) void, + onError: *const fn (this: *anyopaque, err: bun.sys.Error) void, + fs: *bun.fs.FileSystem, allocator: std.mem.Allocator, watchloop_handle: ?std.Thread.Id = null, @@ -565,7 +569,16 @@ pub fn NewWatcher(comptime ContextType: type) type { const no_watch_item: WatchItemIndex = std.math.maxInt(WatchItemIndex); - pub fn init(ctx: ContextType, fs: *bun.fs.FileSystem, allocator: std.mem.Allocator) !*Watcher { + pub fn init(comptime T: type, ctx: *T, fs: *bun.fs.FileSystem, allocator: std.mem.Allocator) !*Watcher { + const wrapped = struct { + fn onFileUpdateWrapped(ctx_opaque: *anyopaque, events: []WatchEvent, changed_files: []?[:0]u8, watchlist: WatchList) void { + T.onFileUpdate(@alignCast(@ptrCast(ctx_opaque)), events, changed_files, watchlist); + } + fn onErrorWrapped(ctx_opaque: *anyopaque, err: bun.sys.Error) void { + T.onError(@alignCast(@ptrCast(ctx_opaque)), err); + } + }; + const watcher = try allocator.create(Watcher); errdefer allocator.destroy(watcher); @@ -573,10 +586,13 @@ pub fn NewWatcher(comptime ContextType: type) type { .fs = fs, .allocator = allocator, .watched_count = 0, - .ctx = ctx, .watchlist = WatchList{}, .mutex = .{}, .cwd = fs.top_level_dir, + + .ctx = ctx, + .onFileUpdate = &wrapped.onFileUpdateWrapped, + .onError = &wrapped.onErrorWrapped, }; try PlatformWatcher.init(&watcher.platform, fs.top_level_dir); @@ -624,7 +640,7 @@ pub fn NewWatcher(comptime ContextType: type) type { this.watchloop_handle = null; this.platform.stop(); if (this.running) { - this.ctx.onError(err); + this.onError(this.ctx, err); } }, .result => {}, @@ -745,7 +761,7 @@ pub fn NewWatcher(comptime ContextType: type) type { this.mutex.lock(); defer this.mutex.unlock(); if (this.running) { - this.ctx.onFileUpdate(watchevents, this.changed_filepaths[0..watchevents.len], this.watchlist); + this.onFileUpdate(this.ctx, watchevents, this.changed_filepaths[0..watchevents.len], this.watchlist); } else { break; } @@ -821,7 +837,7 @@ pub fn NewWatcher(comptime ContextType: type) type { this.mutex.lock(); defer this.mutex.unlock(); if (this.running) { - this.ctx.onFileUpdate(all_events[0 .. last_event_index + 1], this.changed_filepaths[0 .. name_off + 1], this.watchlist); + this.onFileUpdate(this.ctx, all_events[0 .. last_event_index + 1], this.changed_filepaths[0 .. name_off + 1], this.watchlist); } else { break; } @@ -915,9 +931,9 @@ pub fn NewWatcher(comptime ContextType: type) type { if (all_events.len == 0) continue :restart; all_events = all_events[0 .. last_event_index + 1]; - log("calling onFileUpdate (all_events.len = {d})", .{all_events.len}); + Output.println("calling onFileUpdate (all_events.len = {d})", .{all_events.len}); - this.ctx.onFileUpdate(all_events, this.changed_filepaths[0 .. last_event_index + 1], this.watchlist); + this.onFileUpdate(this.ctx, all_events, this.changed_filepaths[0 .. last_event_index + 1], this.watchlist); } } @@ -1280,7 +1296,15 @@ pub fn NewWatcher(comptime ContextType: type) type { } pub fn getResolveWatcher(watcher: *Watcher) bun.resolver.AnyResolveWatcher { - return bun.resolver.ResolveWatcher(*@This(), @typeInfo(ContextType).Pointer.child.onMaybeWatchDirectory).init(watcher); + return bun.resolver.ResolveWatcher(*@This(), onMaybeWatchDirectory).init(watcher); + } + + pub fn onMaybeWatchDirectory(watch: *Watcher, file_path: string, dir_fd: bun.StoredFileDescriptorType) void { + // We don't want to watch: + // - Directories outside the root directory + // - Directories inside node_modules + if (std.mem.indexOf(u8, file_path, "node_modules") == null and std.mem.indexOf(u8, file_path, watch.fs.top_level_dir) != null) { + _ = watch.addDirectory(dir_fd, file_path, getHash(file_path), false); + } } }; -}