diff options
-rw-r--r-- | src/bun.js/api/server.zig | 4 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.cpp | 18 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.h | 3 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.cpp | 2 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.zig | 8 | ||||
-rw-r--r-- | src/bun.js/bindings/headers-cpp.h | 2 | ||||
-rw-r--r-- | src/bun.js/bindings/headers.h | 3 | ||||
-rw-r--r-- | src/bun.js/bindings/headers.zig | 1 | ||||
-rw-r--r-- | src/bun.js/event_loop.zig | 7 | ||||
-rw-r--r-- | src/bun.js/javascript.zig | 270 | ||||
-rw-r--r-- | src/bun_js.zig | 3 | ||||
-rw-r--r-- | src/cli.zig | 3 | ||||
-rw-r--r-- | src/http.zig | 7 | ||||
-rw-r--r-- | src/resolver/resolver.zig | 28 | ||||
-rw-r--r-- | src/watcher.zig | 15 |
15 files changed, 340 insertions, 34 deletions
diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index 23c961480..d93ca7ac6 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -2139,12 +2139,12 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { // only reload those two if (new_config.onRequest != .zero) { - this.config.onRequest = new_config.onRequest; this.config.onRequest.unprotect(); + this.config.onRequest = new_config.onRequest; } if (new_config.onError != .zero) { - this.config.onError = new_config.onError; this.config.onError.unprotect(); + this.config.onError = new_config.onError; } return this.thisObject.asObjectRef(); diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 5b716ef79..f9ee0117a 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -2738,6 +2738,24 @@ DEFINE_VISIT_CHILDREN(GlobalObject); // DEFINE_VISIT_CHILDREN(Zig::GlobalObject); +void GlobalObject::reload() +{ + JSModuleLoader* moduleLoader = this->moduleLoader(); + JSC::JSMap* registry = jsCast<JSC::JSMap*>(moduleLoader->get( + this, + Identifier::fromString(this->vm(), "registry"_s))); + + registry->clear(this->vm()); + this->requireMap()->clear(this->vm()); + this->vm().heap.collectSync(); +} + +extern "C" void JSC__JSGlobalObject__reload(JSC__JSGlobalObject* arg0) +{ + Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(arg0); + globalObject->reload(); +} + JSC::Identifier GlobalObject::moduleLoaderResolve(JSGlobalObject* globalObject, JSModuleLoader* loader, JSValue key, JSValue referrer, JSValue origin) diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index 550d4d853..4a003fc60 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -364,6 +364,9 @@ public: BunPlugin::OnResolve onResolvePlugins[BunPluginTargetMax + 1] {}; BunPluginTarget defaultBunPluginTarget = BunPluginTargetBun; + + void reload(); + JSC::Structure* pendingVirtualModuleResultStructure() { return m_pendingVirtualModuleResultStructure.get(this); } // When a napi module initializes on dlopen, we need to know what the value is diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index fda55c3b4..59a3c43c2 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -3095,4 +3095,4 @@ JSC__JSValue JSC__JSValue__fastGet_(JSC__JSValue JSValue0, JSC__JSGlobalObject* return JSValue::encode( value.getObject()->getIfPropertyExists(globalObject, builtinNameMap(globalObject, arg2))); -}
\ No newline at end of file +} diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index d39abf337..db48a858f 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -1860,6 +1860,13 @@ pub const JSGlobalObject = extern struct { this.vm().throwError(this, err); } + pub fn reload(this: *JSC.JSGlobalObject) void { + this.vm().drainMicrotasks(); + this.vm().collectAsync(); + + return cppFn("reload", .{this}); + } + pub const BunPluginTarget = enum(u8) { bun = 0, node = 1, @@ -2124,6 +2131,7 @@ pub const JSGlobalObject = extern struct { } pub const Extern = [_][]const u8{ + "reload", "bunVM", "putCachedObject", "getCachedObject", diff --git a/src/bun.js/bindings/headers-cpp.h b/src/bun.js/bindings/headers-cpp.h index e0073666e..2dd59b457 100644 --- a/src/bun.js/bindings/headers-cpp.h +++ b/src/bun.js/bindings/headers-cpp.h @@ -1,4 +1,4 @@ -//-- AUTOGENERATED FILE -- 1665017239 +//-- AUTOGENERATED FILE -- 1665034748 // clang-format off #pragma once diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h index fdcd1878b..e0c818497 100644 --- a/src/bun.js/bindings/headers.h +++ b/src/bun.js/bindings/headers.h @@ -1,5 +1,5 @@ // clang-format off -//-- AUTOGENERATED FILE -- 1665017239 +//-- AUTOGENERATED FILE -- 1665034748 #pragma once #include <stddef.h> @@ -411,6 +411,7 @@ CPP_DECL JSC__ObjectPrototype* JSC__JSGlobalObject__objectPrototype(JSC__JSGloba CPP_DECL JSC__JSPromisePrototype* JSC__JSGlobalObject__promisePrototype(JSC__JSGlobalObject* arg0); CPP_DECL JSC__JSValue JSC__JSGlobalObject__putCachedObject(JSC__JSGlobalObject* arg0, const ZigString* arg1, JSC__JSValue JSValue2); CPP_DECL JSC__RegExpPrototype* JSC__JSGlobalObject__regExpPrototype(JSC__JSGlobalObject* arg0); +CPP_DECL void JSC__JSGlobalObject__reload(JSC__JSGlobalObject* arg0); CPP_DECL JSC__SetIteratorPrototype* JSC__JSGlobalObject__setIteratorPrototype(JSC__JSGlobalObject* arg0); CPP_DECL bool JSC__JSGlobalObject__startRemoteInspector(JSC__JSGlobalObject* arg0, unsigned char* arg1, uint16_t arg2); CPP_DECL JSC__StringPrototype* JSC__JSGlobalObject__stringPrototype(JSC__JSGlobalObject* arg0); diff --git a/src/bun.js/bindings/headers.zig b/src/bun.js/bindings/headers.zig index 7b7c2f567..ba7aa0b0f 100644 --- a/src/bun.js/bindings/headers.zig +++ b/src/bun.js/bindings/headers.zig @@ -214,6 +214,7 @@ pub extern fn JSC__JSGlobalObject__objectPrototype(arg0: ?*JSC__JSGlobalObject) pub extern fn JSC__JSGlobalObject__promisePrototype(arg0: ?*JSC__JSGlobalObject) ?*bindings.JSPromisePrototype; pub extern fn JSC__JSGlobalObject__putCachedObject(arg0: ?*JSC__JSGlobalObject, arg1: [*c]const ZigString, JSValue2: JSC__JSValue) JSC__JSValue; pub extern fn JSC__JSGlobalObject__regExpPrototype(arg0: ?*JSC__JSGlobalObject) ?*bindings.RegExpPrototype; +pub extern fn JSC__JSGlobalObject__reload(arg0: ?*JSC__JSGlobalObject) void; pub extern fn JSC__JSGlobalObject__setIteratorPrototype(arg0: ?*JSC__JSGlobalObject) ?*bindings.SetIteratorPrototype; pub extern fn JSC__JSGlobalObject__startRemoteInspector(arg0: ?*JSC__JSGlobalObject, arg1: [*c]u8, arg2: u16) bool; pub extern fn JSC__JSGlobalObject__stringPrototype(arg0: ?*JSC__JSGlobalObject) ?*bindings.StringPrototype; diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig index 0ce2394c7..462136414 100644 --- a/src/bun.js/event_loop.zig +++ b/src/bun.js/event_loop.zig @@ -182,6 +182,7 @@ pub const CppTask = opaque { }; const ThreadSafeFunction = JSC.napi.ThreadSafeFunction; const MicrotaskForDefaultGlobalObject = JSC.MicrotaskForDefaultGlobalObject; +const HotReloadTask = JSC.HotReloader.HotReloadTask; // const PromiseTask = JSInternalPromise.Completion.PromiseTask; pub const Task = TaggedPointerUnion(.{ FetchTasklet, @@ -195,6 +196,7 @@ pub const Task = TaggedPointerUnion(.{ napi_async_work, ThreadSafeFunction, CppTask, + HotReloadTask, // PromiseTask, // TimeoutTasklet, }); @@ -272,6 +274,11 @@ pub const EventLoop = struct { transform_task.*.runFromJS(); transform_task.deinit(); }, + .HotReloadTask => { + var transform_task: *HotReloadTask = task.get(HotReloadTask).?; + transform_task.*.run(); + transform_task.deinit(); + }, @field(Task.Tag, typeBaseName(@typeName(AnyTask))) => { var any: *AnyTask = task.get(AnyTask).?; any.run(); diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index 020399040..c337372e5 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -305,7 +305,8 @@ pub const VirtualMachine = struct { has_loaded_constructors: bool = false, node_modules: ?*NodeModuleBundle = null, bundler: Bundler, - watcher: ?*http.Watcher = null, + bun_dev_watcher: ?*http.Watcher = null, + bun_watcher: ?*JSC.Watcher = null, console: *ZigConsoleClient, log: *logger.Log, event_listeners: EventListenerMixin.Map, @@ -379,6 +380,12 @@ pub const VirtualMachine = struct { poller: JSC.Poller = JSC.Poller{}, us_loop_reference_count: usize = 0, is_us_loop_entered: bool = false, + pending_internal_promise: *JSC.JSInternalPromise = undefined, + pub fn reload(this: *VirtualMachine) void { + Output.debug("Reloading...", .{}); + this.global.reload(); + this.pending_internal_promise = this.loadEntryPoint(this.main) catch @panic("Failed to reload"); + } pub fn io(this: *VirtualMachine) *IO { if (this.io_ == null) { @@ -516,6 +523,10 @@ pub const VirtualMachine = struct { return slice; } + pub fn isWatcherEnabled(this: *VirtualMachine) bool { + return this.bun_dev_watcher != null or this.bun_watcher != null; + } + pub fn init( allocator: std.mem.Allocator, _args: Api.TransformOptions, @@ -1483,15 +1494,17 @@ pub const VirtualMachine = struct { } promise = JSModuleLoader.loadAndEvaluateModule(this.global, &ZigString.init(std.mem.span(main_file_name))); + this.pending_internal_promise = promise; } else { promise = JSModuleLoader.loadAndEvaluateModule(this.global, &ZigString.init(this.main)); + this.pending_internal_promise = promise; } this.waitForPromise(promise); this.eventLoop().autoTick(); - return promise; + return this.pending_internal_promise; } pub fn loadMacroEntryPoint(this: *VirtualMachine, entry_path: string, function_name: string, specifier: string, hash: i32) !*JSInternalPromise { @@ -2852,7 +2865,13 @@ pub const ModuleLoader = struct { var fd: ?StoredFileDescriptorType = null; var package_json: ?*PackageJSON = null; - if (jsc_vm.watcher) |watcher| { + 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; @@ -2979,14 +2998,30 @@ pub const ModuleLoader = struct { return error.PrintingErrorWriteFailed; } - if (jsc_vm.has_loaded) { - return jsc_vm.refCountedResolvedSource(printer.ctx.written, specifier, path.text, null); - } - if (comptime Environment.dump_source) { try dumpSource(specifier, &printer); } + if (jsc_vm.isWatcherEnabled()) { + const resolved_source = jsc_vm.refCountedResolvedSource(printer.ctx.written, 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; + } + return ResolvedSource{ .allocator = null, .source_code = ZigString.init(try default_allocator.dupe(u8, printer.ctx.getWritten())), @@ -3363,3 +3398,224 @@ const FetchFlags = enum { return this != .transpile; } }; + +pub const Watcher = @import("../watcher.zig").NewWatcher(*HotReloader); + +pub const HotReloader = struct { + const watcher = @import("../watcher.zig"); + + onAccept: std.ArrayHashMapUnmanaged(Watcher.HashType, bun.BabyList(OnAcceptCallback), bun.ArrayIdentityContext, false) = .{}, + vm: *JSC.VirtualMachine, + + pub const HotReloadTask = struct { + reloader: *HotReloader, + count: u8 = 0, + hashes: [8]u32 = [_]u32{0} ** 8, + concurrent_task: JSC.ConcurrentTask = undefined, + + pub fn append(this: *HotReloadTask, id: u32) void { + if (this.count == 8) { + this.enqueue(); + var reloader = this.reloader; + this.* = .{ + .reloader = reloader, + .count = 0, + }; + } + + this.hashes[this.count] = id; + this.count += 1; + } + + pub fn run(this: *HotReloadTask) void { + this.reloader.vm.reload(); + } + + pub fn enqueue(this: *HotReloadTask) void { + if (this.count == 0) + return; + var that = bun.default_allocator.create(HotReloadTask) catch unreachable; + + that.* = this.*; + this.count = 0; + that.concurrent_task.task = Task.init(that); + that.reloader.vm.eventLoop().enqueueTaskConcurrent(&that.concurrent_task); + } + + pub fn deinit(this: *HotReloadTask) void { + bun.default_allocator.destroy(this); + } + }; + + fn NewCallback(comptime FunctionSignature: type) type { + return union(enum) { + javascript_callback: JSC.Strong, + zig_callback: struct { + ptr: *anyopaque, + function: FunctionSignature, + }, + }; + } + + pub const OnAcceptCallback = NewCallback(fn ( + vm: *JSC.VirtualMachine, + specifier: []const u8, + ) void); + + pub fn enableHotModuleReloading(this: *VirtualMachine) void { + if (this.bun_watcher != null) + return; + + var reloader = bun.default_allocator.create(HotReloader) catch @panic("OOM"); + reloader.* = .{ + .vm = this, + }; + this.bun_watcher = JSC.Watcher.init( + reloader, + this.bundler.fs, + bun.default_allocator, + ) catch @panic("Failed to enable File Watcher"); + + this.bundler.resolver.watcher = Resolver.ResolveWatcher(*Watcher, onMaybeWatchDirectory).init(this.bun_watcher.?); + + this.bun_watcher.?.start() catch @panic("Failed to start File Watcher"); + } + + pub fn onMaybeWatchDirectory(watch: *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, Watcher.getHash(file_path), false) catch {}; + } + } + + pub fn onFileUpdate( + this: *HotReloader, + events: []watcher.WatchEvent, + changed_files: []?[:0]u8, + watchlist: watcher.Watchlist, + ) void { + var slice = watchlist.slice(); + const file_paths = slice.items(.file_path); + var counts = slice.items(.count); + const kinds = slice.items(.kind); + const hashes = slice.items(.hash); + var file_descriptors = slice.items(.fd); + var ctx = this.vm.bun_watcher.?; + defer ctx.flushEvictions(); + defer Output.flush(); + + var bundler = &this.vm.bundler; + var fs: *Fs.FileSystem = bundler.fs; + var rfs: *Fs.FileSystem.RealFS = &fs.fs; + var resolver = &bundler.resolver; + var _on_file_update_path_buf: [bun.MAX_PATH_BYTES]u8 = undefined; + + var current_task: HotReloadTask = .{ + .reloader = this, + }; + defer current_task.enqueue(); + + for (events) |event| { + const file_path = file_paths[event.index]; + const update_count = counts[event.index] + 1; + counts[event.index] = update_count; + const kind = kinds[event.index]; + + // so it's consistent with the rest + // if we use .extname we might run into an issue with whether or not the "." is included. + // const path = Fs.PathName.init(file_path); + const id = hashes[event.index]; + + if (comptime Environment.isDebug) { + Output.prettyErrorln("[watcher] {s}: -- {}", .{ @tagName(kind), event.op }); + } + + switch (kind) { + .file => { + if (event.op.delete or event.op.rename) { + ctx.removeAtIndex( + event.index, + 0, + &.{}, + .file, + ); + } + + if (comptime bun.FeatureFlags.verbose_watcher) { + Output.prettyErrorln("<r><d>File changed: {s}<r>", .{fs.relativeTo(file_path)}); + } + + if (event.op.write) { + current_task.append(id); + } + }, + .directory => { + const affected = event.names(changed_files); + var entries_option: ?*Fs.FileSystem.RealFS.EntriesOption = null; + if (affected.len > 0) { + entries_option = rfs.entries.get(file_path); + } + + rfs.bustEntriesCache(file_path); + resolver.dir_cache.remove(file_path); + + if (entries_option) |dir_ent| { + var last_file_hash: Watcher.HashType = std.math.maxInt(Watcher.HashType); + for (affected) |changed_name_ptr| { + const changed_name: []const u8 = std.mem.span((changed_name_ptr orelse continue)); + 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); + if (loader.isJavaScriptLikeOrJSON() or loader == .css) { + var path_string: bun.PathString = undefined; + var file_hash: Watcher.HashType = last_file_hash; + const abs_path: string = brk: { + if (dir_ent.entries.get(changed_name)) |file_ent| { + // reset the file descriptor + file_ent.entry.cache.fd = 0; + file_ent.entry.need_stat = true; + path_string = file_ent.entry.abs_path; + file_hash = Watcher.getHash(path_string.slice()); + for (hashes) |hash, entry_id| { + if (hash == file_hash) { + file_descriptors[entry_id] = 0; + break; + } + } + + break :brk path_string.slice(); + } else { + var file_path_without_trailing_slash = std.mem.trimRight(u8, file_path, std.fs.path.sep_str); + @memcpy(&_on_file_update_path_buf, file_path_without_trailing_slash.ptr, file_path_without_trailing_slash.len); + _on_file_update_path_buf[file_path_without_trailing_slash.len] = std.fs.path.sep; + + @memcpy(_on_file_update_path_buf[file_path_without_trailing_slash.len + 1 ..].ptr, changed_name.ptr, changed_name.len); + const path_slice = _on_file_update_path_buf[0 .. file_path_without_trailing_slash.len + changed_name.len + 1]; + file_hash = Watcher.getHash(path_slice); + break :brk path_slice; + } + }; + + // skip consecutive duplicates + if (last_file_hash == file_hash) continue; + last_file_hash = file_hash; + + Output.prettyErrorln("<r> <d>File change: {s}<r>", .{fs.relativeTo(abs_path)}); + } + } + } + + // if (event.op.delete or event.op.rename) + // ctx.watcher.removeAtIndex(event.index, hashes[event.index], parent_hashes, .directory); + if (comptime false) { + Output.prettyErrorln("<r>📁 <d>Dir change: {s}<r>", .{fs.relativeTo(file_path)}); + } else { + Output.prettyErrorln("<r> <d>Dir change: {s}<r>", .{fs.relativeTo(file_path)}); + } + }, + } + } + } +}; diff --git a/src/bun_js.zig b/src/bun_js.zig index 65a172b52..dc65cc391 100644 --- a/src/bun_js.zig +++ b/src/bun_js.zig @@ -115,6 +115,9 @@ pub const Run = struct { } pub fn start(this: *Run) void { + if (this.ctx.debug.hot_reload) { + JSC.HotReloader.enableHotModuleReloading(this.vm); + } var promise = this.vm.loadEntryPoint(this.entry_path) catch return; if (promise.status(this.vm.global.vm()) == .Rejected) { diff --git a/src/cli.zig b/src/cli.zig index 23082e2ae..39bc8797a 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -189,6 +189,7 @@ pub const Arguments = struct { clap.parseParam("-l, --loader <STR>... Parse files with .ext:loader, e.g. --loader .js:jsx. Valid loaders: jsx, js, json, tsx, ts, css") catch unreachable, 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("--silent Don't repeat the command for bun run") catch unreachable, clap.parseParam("<POS>... ") catch unreachable, }; @@ -403,6 +404,7 @@ pub const Arguments = struct { opts.generate_node_module_bundle = cmd == .BunCommand; opts.inject = args.options("--inject"); opts.extension_order = args.options("--extension-order"); + ctx.debug.hot_reload = args.flag("--hot"); ctx.passthrough = args.remaining(); opts.no_summary = args.flag("--no-summary"); @@ -781,6 +783,7 @@ pub const Command = struct { dump_limits: bool = false, fallback_only: bool = false, silent: bool = false, + hot_reload: bool = false, // technical debt macros: ?MacroMap = null, diff --git a/src/http.zig b/src/http.zig index 49303d415..fedb30a65 100644 --- a/src/http.zig +++ b/src/http.zig @@ -1486,7 +1486,7 @@ pub const RequestContext = struct { const boot = vm.bundler.options.framework.?.server.path; std.debug.assert(boot.len > 0); errdefer vm.deinit(); - vm.watcher = handler.watcher; + vm.bun_dev_watcher = handler.watcher; { vm.bundler.configureRouter(false) catch |err| { handler.handleJSError(.configure_router, err) catch {}; @@ -3886,10 +3886,7 @@ pub const Server = struct { server.watcher = try Watcher.init(server, server.bundler.fs, server.allocator); if (comptime FeatureFlags.watch_directories and !Environment.isTest) { - server.bundler.resolver.watcher = ResolveWatcher(Watcher){ - .context = server.watcher, - .callback = onMaybeWatchDirectory, - }; + server.bundler.resolver.watcher = ResolveWatcher(*Watcher, onMaybeWatchDirectory).init(server.watcher); } } diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index 56ce3135e..2c14089ee 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -314,13 +314,26 @@ var bin_folders_lock: Mutex = Mutex.init(); var bin_folders_loaded: bool = false; const Timer = @import("../system_timer.zig").Timer; -pub fn ResolveWatcher(comptime Context: type) type { - return struct { - context: *Context, - callback: fn (*Context, dir_path: string, dir_fd: StoredFileDescriptorType) void = undefined, - pub fn watch(this: @This(), dir_path: string, fd: StoredFileDescriptorType) void { - return this.callback(this.context, dir_path, fd); +pub const AnyResolveWatcher = struct { + context: *anyopaque, + callback: fn (*anyopaque, dir_path: string, dir_fd: StoredFileDescriptorType) void = undefined, + + pub fn watch(this: @This(), dir_path: string, fd: StoredFileDescriptorType) void { + return this.callback(this.context, dir_path, fd); + } +}; + +pub fn ResolveWatcher(comptime Context: type, comptime onWatch: anytype) type { + return struct { + pub fn init(context: Context) AnyResolveWatcher { + return AnyResolveWatcher{ + .context = context, + .callback = watch, + }; + } + pub fn watch(this: *anyopaque, dir_path: string, fd: StoredFileDescriptorType) void { + onWatch(bun.cast(Context, this), dir_path, fd); } }; } @@ -341,7 +354,7 @@ pub const Resolver = struct { debug_logs: ?DebugLogs = null, elapsed: u64 = 0, // tracing - watcher: ?ResolveWatcher(HTTPWatcher) = null, + watcher: ?AnyResolveWatcher = null, caches: CacheSet, @@ -2538,7 +2551,6 @@ pub const Resolver = struct { if (comptime FeatureFlags.watch_directories) { // For existent directories which don't find a match // Start watching it automatically, - // onStartWatchingDirectory fn decides whether to actually watch. if (r.watcher) |watcher| { watcher.watch(entries.dir, entries.fd); } diff --git a/src/watcher.zig b/src/watcher.zig index 138edca44..0663303bc 100644 --- a/src/watcher.zig +++ b/src/watcher.zig @@ -287,10 +287,6 @@ pub const WatchEvent = struct { pub const Watchlist = std.MultiArrayList(WatchItem); -// This implementation only works on macOS, for now. -// The Internet seems to suggest basically always using FSEvents instead of kqueue -// It seems like the main concern is max open file descriptors -// Since we adjust the ulimit already, I think we can avoid that. pub fn NewWatcher(comptime ContextType: type) type { return struct { const Watcher = @This(); @@ -425,23 +421,24 @@ pub fn NewWatcher(comptime ContextType: type) type { std.debug.assert(DarwinWatcher.fd > 0); const KEvent = std.c.Kevent; - var changelist_array: [1]KEvent = std.mem.zeroes([1]KEvent); + var changelist_array: [128]KEvent = std.mem.zeroes([128]KEvent); var changelist = &changelist_array; while (true) { defer Output.flush(); - _ = std.os.system.kevent( + const count_ = std.os.system.kevent( DarwinWatcher.fd, @as([*]KEvent, changelist), 0, @as([*]KEvent, changelist), - 1, + 128, null, ); - var watchevents = this.watch_events[0..1]; - for (changelist) |event, i| { + var changes = changelist[0..@intCast(usize, @maximum(0, count_))]; + var watchevents = this.watch_events[0..changes.len]; + for (changes) |event, i| { watchevents[i].fromKEvent(event); } |