refactor: arean allocate temporaries in kqueue backend

This commit is contained in:
CJ van den Berg 2026-03-08 21:24:33 +01:00
parent 0548953460
commit 3ba8b2bac5
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
2 changed files with 21 additions and 29 deletions

View file

@ -795,43 +795,35 @@ const KQueueBackend = struct {
var dir = std.fs.openDirAbsolute(dir_path, .{ .iterate = true }) catch return; var dir = std.fs.openDirAbsolute(dir_path, .{ .iterate = true }) catch return;
defer dir.close(); defer dir.close();
// Arena for all temporaries freed in one shot at the end.
var arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit();
const tmp = arena.allocator();
// Collect current files and subdirectories (no lock, reading filesystem only). // Collect current files and subdirectories (no lock, reading filesystem only).
var current_files: std.StringHashMapUnmanaged(void) = .empty; var current_files: std.StringHashMapUnmanaged(void) = .empty;
defer {
var it = current_files.iterator();
while (it.next()) |e| allocator.free(e.key_ptr.*);
current_files.deinit(allocator);
}
var current_dirs: std.ArrayListUnmanaged([]u8) = .empty; var current_dirs: std.ArrayListUnmanaged([]u8) = .empty;
defer {
for (current_dirs.items) |d| allocator.free(d);
current_dirs.deinit(allocator);
}
var iter = dir.iterate(); var iter = dir.iterate();
while (try iter.next()) |entry| { while (try iter.next()) |entry| {
switch (entry.kind) { switch (entry.kind) {
.file => { .file => {
const name = try allocator.dupe(u8, entry.name); const name = try tmp.dupe(u8, entry.name);
try current_files.put(allocator, name, {}); try current_files.put(tmp, name, {});
}, },
.directory => { .directory => {
const name = try allocator.dupe(u8, entry.name); const name = try tmp.dupe(u8, entry.name);
try current_dirs.append(allocator, name); try current_dirs.append(tmp, name);
}, },
else => {}, else => {},
} }
} }
// Diff against snapshot under the lock; collect events to emit after releasing it. // Diff against snapshot under the lock; collect events to emit after releasing it.
// to_create / to_delete hold borrowed pointers into the snapshot (which uses
// allocator, not tmp); only the list metadata itself uses tmp.
var to_create: std.ArrayListUnmanaged([]const u8) = .empty; var to_create: std.ArrayListUnmanaged([]const u8) = .empty;
defer to_create.deinit(allocator);
var to_delete: std.ArrayListUnmanaged([]const u8) = .empty; var to_delete: std.ArrayListUnmanaged([]const u8) = .empty;
defer to_delete.deinit(allocator); var new_dirs: std.ArrayListUnmanaged([]const u8) = .empty;
var new_dirs: std.ArrayListUnmanaged([]u8) = .empty;
defer {
for (new_dirs.items) |p| allocator.free(p);
new_dirs.deinit(allocator);
}
self.snapshots_mutex.lock(); self.snapshots_mutex.lock();
{ {
@ -839,11 +831,8 @@ const KQueueBackend = struct {
var path_buf: [std.fs.max_path_bytes]u8 = undefined; var path_buf: [std.fs.max_path_bytes]u8 = undefined;
const full_path = std.fmt.bufPrint(&path_buf, "{s}/{s}", .{ dir_path, name }) catch continue; const full_path = std.fmt.bufPrint(&path_buf, "{s}/{s}", .{ dir_path, name }) catch continue;
if (!self.snapshots.contains(full_path)) { if (!self.snapshots.contains(full_path)) {
const owned = allocator.dupe(u8, full_path) catch continue; const owned = tmp.dupe(u8, full_path) catch continue;
new_dirs.append(allocator, owned) catch { new_dirs.append(tmp, owned) catch continue;
allocator.free(owned);
continue;
};
} }
} }
@ -866,13 +855,13 @@ const KQueueBackend = struct {
self.snapshots_mutex.unlock(); self.snapshots_mutex.unlock();
return e; return e;
}; };
try to_create.append(allocator, owned); try to_create.append(tmp, owned);
} }
var sit = snapshot.iterator(); var sit = snapshot.iterator();
while (sit.next()) |entry| { while (sit.next()) |entry| {
if (current_files.contains(entry.key_ptr.*)) continue; if (current_files.contains(entry.key_ptr.*)) continue;
try to_delete.append(allocator, entry.key_ptr.*); try to_delete.append(tmp, entry.key_ptr.*);
} }
for (to_delete.items) |name| _ = snapshot.fetchRemove(name); for (to_delete.items) |name| _ = snapshot.fetchRemove(name);
} }
@ -891,7 +880,7 @@ const KQueueBackend = struct {
}; };
self.deregister_file_watch(allocator, full_path); self.deregister_file_watch(allocator, full_path);
try self.handler.change(full_path, EventType.deleted, .file); try self.handler.change(full_path, EventType.deleted, .file);
allocator.free(name); allocator.free(name); // snapshot key, owned by allocator
} }
for (to_create.items) |name| { for (to_create.items) |name| {
var path_buf: [std.fs.max_path_bytes]u8 = undefined; var path_buf: [std.fs.max_path_bytes]u8 = undefined;
@ -899,6 +888,7 @@ const KQueueBackend = struct {
self.register_file_watch(allocator, full_path); self.register_file_watch(allocator, full_path);
try self.handler.change(full_path, EventType.created, .file); try self.handler.change(full_path, EventType.created, .file);
} }
// arena.deinit() frees current_files, current_dirs, new_dirs, and list metadata
} }
fn register_file_watch(self: *@This(), allocator: std.mem.Allocator, path: []const u8) void { fn register_file_watch(self: *@This(), allocator: std.mem.Allocator, path: []const u8) void {

View file

@ -274,11 +274,13 @@ test "deleting a file emits a 'deleted' event" {
defer watcher.deinit(); defer watcher.deinit();
try watcher.watch(tmp); try watcher.watch(tmp);
// Create the file after the watcher is active so the backend can cache its type. // Create the file after the watcher is active so the backend can cache its type,
// then drain before deleting so the file lands in any snapshot before it disappears.
{ {
const f = try std.fs.createFileAbsolute(file_path, .{}); const f = try std.fs.createFileAbsolute(file_path, .{});
f.close(); f.close();
} }
try drainEvents(&watcher);
try std.fs.deleteFileAbsolute(file_path); try std.fs.deleteFileAbsolute(file_path);