commit d9269bfe5f0a6e8c089176269eff2f9d75c9dd64
parent 6319df6d084a88159255796eaffd6a8d75c1b5eb
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date: Sun, 20 Sep 2026 07:41:32 -0300
walk: free the directories a stopped scan never reached
A worker frees the path it is handed, but a scan called off mid-volume leaves the
rest of the queue untouched, and those paths were owned by nobody once `manage`
dropped the queue. Cancelling a walk of /usr after 40 ms stranded 910
allocations, 17 KB; the whole volume would strand far more, and a UI that offers
a cancel button pays it every time.
`drop` is the discard half of the contract `manage` now states. It runs on the
thread that called `scan`, where the paths were cloned, so it frees them from the
allocator that made them.
Diffstat:
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/walk/walk.odin b/walk/walk.odin
@@ -80,7 +80,7 @@ scan :: proc(root: string, t: ^scan.Tree, cfg := Config{}) -> Error {
}
seed := []Dir{{index = first, path = strings.clone(root, allocator)}}
- flow.manage(seed, states, walk_dir, hand_over)
+ flow.manage(seed, states, walk_dir, hand_over, drop)
for &s in states {
if s.err != nil {
@@ -108,6 +108,15 @@ hand_over :: proc(d: Dir, ok: bool, w: ^Worker, queue: ^[dynamic]Dir) -> bool {
return w.err == nil && !scan.cancelled(w.writer.tree)
}
+// A directory a stopped scan never reached. `walk_dir` frees what it is handed; this
+// is the other half, since cancelling mid-volume otherwise strands a path per
+// directory found but not yet walked. Runs on the thread that called `scan`, and
+// paths are cloned from that context, so the two allocators are the same one.
+@(private)
+drop :: proc(d: Dir) {
+ delete(d.path, context.allocator)
+}
+
// A directory waiting to be read, and the node already standing for it.
@(private)
Dir :: struct {