Skip to content

Commit

Permalink
chore: make watcher use an anyopaque pointer context (#14224)
Browse files Browse the repository at this point in the history
  • Loading branch information
paperdave authored Sep 28, 2024
1 parent af82a44 commit 9ab5198
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 65 deletions.
4 changes: 2 additions & 2 deletions src/bun.js/event_loop.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
77 changes: 31 additions & 46 deletions src/bun.js/javascript.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand All @@ -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)});
};
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
};

Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -4183,36 +4183,28 @@ 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,
) catch |err| {
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);

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;
}
Expand All @@ -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;
Expand All @@ -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,
Expand All @@ -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,
};
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down
5 changes: 3 additions & 2 deletions src/bun.js/node/path_watcher.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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) = .{},
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/bundler/bundle_v2.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions src/crash_handler.zig
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ pub fn crashHandler(
) noreturn {
@setCold(true);

if (bun.Environment.isWindows) {
@breakpoint();
}

if (bun.Environment.isDebug)
bun.Output.disableScopedDebugWriter();

Expand Down
52 changes: 38 additions & 14 deletions src/watcher.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -565,18 +569,30 @@ 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);

watcher.* = Watcher{
.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);
Expand Down Expand Up @@ -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 => {},
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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);
}
}
};
}

0 comments on commit 9ab5198

Please sign in to comment.