Compare commits

...

2 commits

Author SHA1 Message Date
6301c078c8
fix: move file type guessing out of project_manager thread
Performing hundreds of thousands of file type guessing operations can
block the project manager for seconds leading to slow exits. With this
change we move the file type guessing into the tree walker thread leaving
the project manager to respond to other requests including shutdown messages.
2025-10-02 17:43:12 +02:00
e7dcb2947b
refactor: add handler callbacks to walk_tree 2025-10-02 17:42:24 +02:00
3 changed files with 44 additions and 18 deletions

View file

@ -421,22 +421,44 @@ pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []co
return @min(max, matches.items.len); return @min(max, matches.items.len);
} }
pub fn walk_tree_entry( fn walk_tree_entry_callback(parent: tp.pid_ref, root_path: []const u8, file_path: []const u8, mtime_high: i64, mtime_low: i64) error{Exit}!void {
self: *Self,
file_path: []const u8,
mtime: i128,
) OutOfMemoryError!void {
const file_type: []const u8, const file_icon: []const u8, const file_color: u24 = guess_file_type(file_path); const file_type: []const u8, const file_icon: []const u8, const file_color: u24 = guess_file_type(file_path);
try parent.send(.{ "walk_tree_entry", root_path, file_path, mtime_high, mtime_low, file_type, file_icon, file_color });
}
pub fn walk_tree_entry(self: *Self, m: tp.message) OutOfMemoryError!void {
var file_path: []const u8 = undefined;
var mtime_high: i64 = 0;
var mtime_low: i64 = 0;
var file_type: []const u8 = undefined;
var file_icon: []const u8 = undefined;
var file_color: u32 = 0;
if (!(cbor.match(m.buf, .{
tp.string,
tp.string,
tp.extract(&file_path),
tp.extract(&mtime_high),
tp.extract(&mtime_low),
tp.extract(&file_type),
tp.extract(&file_icon),
tp.extract(&file_color),
}) catch return)) return;
const mtime = (@as(i128, @intCast(mtime_high)) << 64) | @as(i128, @intCast(mtime_low));
self.longest_file_path = @max(self.longest_file_path, file_path.len); self.longest_file_path = @max(self.longest_file_path, file_path.len);
(try self.pending.addOne(self.allocator)).* = .{ (try self.pending.addOne(self.allocator)).* = .{
.path = try self.allocator.dupe(u8, file_path), .path = try self.allocator.dupe(u8, file_path),
.type = file_type, .type = file_type,
.icon = file_icon, .icon = file_icon,
.color = file_color, .color = @intCast(file_color),
.mtime = mtime, .mtime = mtime,
}; };
} }
fn walk_tree_done_callback(parent: tp.pid_ref, root_path: []const u8) error{Exit}!void {
try parent.send(.{ "walk_tree_done", root_path });
}
pub fn walk_tree_done(self: *Self, parent: tp.pid_ref) OutOfMemoryError!void { pub fn walk_tree_done(self: *Self, parent: tp.pid_ref) OutOfMemoryError!void {
self.state.walk_tree = .done; self.state.walk_tree = .done;
if (self.walker) |pid| pid.deinit(); if (self.walker) |pid| pid.deinit();
@ -1987,7 +2009,7 @@ pub fn query_git(self: *Self) void {
fn start_walker(self: *Self) void { fn start_walker(self: *Self) void {
self.state.walk_tree = .running; self.state.walk_tree = .running;
self.walker = walk_tree.start(self.allocator, self.name) catch blk: { self.walker = walk_tree.start(self.allocator, self.name, walk_tree_entry_callback, walk_tree_done_callback) catch blk: {
self.state.walk_tree = .failed; self.state.walk_tree = .failed;
break :blk null; break :blk null;
}; };

View file

@ -318,8 +318,6 @@ const Process = struct {
var method: []const u8 = undefined; var method: []const u8 = undefined;
var cbor_id: []const u8 = undefined; var cbor_id: []const u8 = undefined;
var params_cb: []const u8 = undefined; var params_cb: []const u8 = undefined;
var high: i64 = 0;
var low: i64 = 0;
var max: usize = 0; var max: usize = 0;
var row: usize = 0; var row: usize = 0;
var col: usize = 0; var col: usize = 0;
@ -338,10 +336,9 @@ const Process = struct {
var eol_mode: Buffer.EolModeTag = @intFromEnum(Buffer.EolMode.lf); var eol_mode: Buffer.EolModeTag = @intFromEnum(Buffer.EolMode.lf);
if (try cbor.match(m.buf, .{ "walk_tree_entry", tp.extract(&project_directory), tp.extract(&path), tp.extract(&high), tp.extract(&low) })) { if (try cbor.match(m.buf, .{ "walk_tree_entry", tp.extract(&project_directory), tp.more })) {
const mtime = (@as(i128, @intCast(high)) << 64) | @as(i128, @intCast(low));
if (self.projects.get(project_directory)) |project| if (self.projects.get(project_directory)) |project|
project.walk_tree_entry(path, mtime) catch |e| self.logger.err("walk_tree_entry", e); project.walk_tree_entry(m) catch |e| self.logger.err("walk_tree_entry", e);
} else if (try cbor.match(m.buf, .{ "walk_tree_done", tp.extract(&project_directory) })) { } else if (try cbor.match(m.buf, .{ "walk_tree_done", tp.extract(&project_directory) })) {
if (self.projects.get(project_directory)) |project| if (self.projects.get(project_directory)) |project|
project.walk_tree_done(self.parent.ref()) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed; project.walk_tree_done(self.parent.ref()) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed;

View file

@ -7,7 +7,10 @@ const module_name = @typeName(@This());
const SpawnError = (OutOfMemoryError || error{ThespianSpawnFailed}); const SpawnError = (OutOfMemoryError || error{ThespianSpawnFailed});
const OutOfMemoryError = error{OutOfMemory}; const OutOfMemoryError = error{OutOfMemory};
pub fn start(a_: std.mem.Allocator, root_path_: []const u8) (SpawnError || std.fs.Dir.OpenError)!tp.pid { pub const EntryCallBack = *const fn (parent: tp.pid_ref, root_path: []const u8, path: []const u8, mtime_high: i64, mtime_low: i64) error{Exit}!void;
pub const DoneCallBack = *const fn (parent: tp.pid_ref, root_path: []const u8) error{Exit}!void;
pub fn start(a_: std.mem.Allocator, root_path_: []const u8, entry_handler: EntryCallBack, done_handler: DoneCallBack) (SpawnError || std.fs.Dir.OpenError)!tp.pid {
return struct { return struct {
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
root_path: []const u8, root_path: []const u8,
@ -15,11 +18,13 @@ pub fn start(a_: std.mem.Allocator, root_path_: []const u8) (SpawnError || std.f
receiver: Receiver, receiver: Receiver,
dir: std.fs.Dir, dir: std.fs.Dir,
walker: FilteredWalker, walker: FilteredWalker,
entry_handler: EntryCallBack,
done_handler: DoneCallBack,
const tree_walker = @This(); const tree_walker = @This();
const Receiver = tp.Receiver(*tree_walker); const Receiver = tp.Receiver(*tree_walker);
fn spawn_link(allocator: std.mem.Allocator, root_path: []const u8) (SpawnError || std.fs.Dir.OpenError)!tp.pid { fn spawn_link(allocator: std.mem.Allocator, root_path: []const u8, entry_handler_: EntryCallBack, done_handler_: DoneCallBack) (SpawnError || std.fs.Dir.OpenError)!tp.pid {
const self = try allocator.create(tree_walker); const self = try allocator.create(tree_walker);
errdefer allocator.destroy(self); errdefer allocator.destroy(self);
self.* = .{ self.* = .{
@ -29,6 +34,8 @@ pub fn start(a_: std.mem.Allocator, root_path_: []const u8) (SpawnError || std.f
.receiver = .init(tree_walker.receive, self), .receiver = .init(tree_walker.receive, self),
.dir = try std.fs.cwd().openDir(self.root_path, .{ .iterate = true }), .dir = try std.fs.cwd().openDir(self.root_path, .{ .iterate = true }),
.walker = try .init(self.dir, self.allocator), .walker = try .init(self.dir, self.allocator),
.entry_handler = entry_handler_,
.done_handler = done_handler_,
}; };
return tp.spawn_link(allocator, self, tree_walker.start, module_name ++ ".tree_walker"); return tp.spawn_link(allocator, self, tree_walker.start, module_name ++ ".tree_walker");
} }
@ -65,21 +72,21 @@ pub fn start(a_: std.mem.Allocator, root_path_: []const u8) (SpawnError || std.f
fn next(self: *tree_walker) !void { fn next(self: *tree_walker) !void {
if (try self.walker.next()) |path| { if (try self.walker.next()) |path| {
const stat = self.dir.statFile(path) catch { const stat = self.dir.statFile(path) catch {
try self.parent.send(.{ "walk_tree_entry", self.root_path, path, 0, 0 }); try self.entry_handler(self.parent.ref(), self.root_path, path, 0, 0);
return tp.self_pid().send(.{"next"}); return tp.self_pid().send(.{"next"});
}; };
const mtime = stat.mtime; const mtime = stat.mtime;
const high: i64 = @intCast(mtime >> 64); const high: i64 = @intCast(mtime >> 64);
const low: i64 = @truncate(mtime); const low: i64 = @truncate(mtime);
std.debug.assert(mtime == (@as(i128, @intCast(high)) << 64) | @as(i128, @intCast(low))); std.debug.assert(mtime == (@as(i128, @intCast(high)) << 64) | @as(i128, @intCast(low)));
try self.parent.send(.{ "walk_tree_entry", self.root_path, path, high, low }); try self.entry_handler(self.parent.ref(), self.root_path, path, high, low);
return tp.self_pid().send(.{"next"}); return tp.self_pid().send(.{"next"});
} else { } else {
self.parent.send(.{ "walk_tree_done", self.root_path }) catch {}; self.done_handler(self.parent.ref(), self.root_path) catch {};
return tp.exit_normal(); return tp.exit_normal();
} }
} }
}.spawn_link(a_, root_path_); }.spawn_link(a_, root_path_, entry_handler, done_handler);
} }
const filtered_dirs = [_][]const u8{ const filtered_dirs = [_][]const u8{