xff v0.3.7

xff - Parallel Traversal Design

Detailed spec for parallel directory traversal and --sort (issue #43).
Refines design.md §"Cross-cutting concerns" (Determinism). Where the two
differ, this document is authoritative for traversal/ordering specifically.
Status: Draft · 2026-06-27 · Org: MBO Works

Purpose

The directory walk is the hot path and it is IO-bound: most time is spent
blocked in readdir/lstat, not in CPU. Running it across several workers
hides that latency and is the single biggest performance lever for xff,
especially on deep trees and high-latency (network) filesystems.

Parallelism must not cost correctness. Three things are trivially correct in the
current sequential walk and must stay correct under concurrency: deterministic
output (when asked for), the -prune/-quit/-depth control semantics, and the
exit-code model. The VFS layer is already contractually safe to call from many
threads (vfs::FileSystem), so the foundation is ready.

v1 scope (what shipped first)

The first implementation (engine PR for #43) takes three deliberate simplifications
of the design below; each has a noted follow-up:

Architecture

A bounded worker pool over directories, with a single emission-ordering
layer
between the workers and the output sink:

--sort modes

Four modes, a spectrum of rising ordering cost. name stays an alias for dir
(back-compat with the current SortOrder::kName).

--sort Buffer Deterministic order
none none no
dir ~none within a directory
subtree bounded within a subtree
tree all matches fully

The key distinction between dir/subtree and tree: neither dir nor
subtree ever inlines a subdirectory's contents at a globally-sorted position -
that is precisely what tree buys, and why only tree must buffer everything.

Parallelism control

A single knob, -j N (long form --jobs), caps total concurrency for both
the directory walk and concurrent -exec/-capture children - one mental model.
-j 1 forces the sequential walk. -j all (--jobs=all) means every detected
core (hardware_concurrency()), regardless of the active mode's default.

Under -j > 1 the serial -exec ... ; / -execdir ... ; form launches its child
on a bounded runner (at most N outstanding) instead of running it synchronously.
Because the child's exit status is not yet known when the action returns, the action
reports success on launch, so its truth value cannot gate a predicate to its right
(e.g. -exec a \; -exec b \; or -exec test \; -print). This matches find's exit
model: find's ; form is a predicate whose nonzero exit makes only the action false
and never raises find's own exit status (verified against BSD and GNU find), so
nothing observable is lost at the exit-code level - the children are still reaped at
the end-of-walk drain. Use -j 1 for find's strict synchronous, status-gating
semantics. The + batch forms are unaffected: they always accumulate and flush once
at end-of-walk, and a nonzero exit there does raise the exit status, as in find.

When -j is omitted the default is mode-scoped - xff wears the persona it is
invoked as (the --mode / argv[0] mechanism, design-config.md and #54/#59):

Persona Workers (no -j) Default --sort
find all cores none
fd all cores none
rg all cores none
modern max(1, min(cores-1, 15)) dir

Concurrency correctness

ThreadSanitizer

As soon as the walk is multithreaded we add a TSan run. TSan is mutually
exclusive with ASan, so it is a separate --config=tsan in .bazelrc
(-fsanitize=thread copt/linkopt, TSAN_OPTIONS=halt_on_error=1, the symbolizer,
mirroring the asan block) and its own clang-tsan CI matrix cell wired into
the done gate. It lands in the same PR that introduces threads (it is a no-op on
single-threaded code). The asan config also runs UBSan as of #138.

Phasing (chained PRs)

Built as a chain of stacked PRs (each off its predecessor's tip), so each step is
small and conflict-free:

  1. Worker pool + --sort=none - parallel walk behind the existing sink, plus
    the clang-tsan config and CI cell (threads arrive here, so TSan does too).
    The sequential walk stays available via -j 1.
  2. Ordering layer + --sort=dir - generalize SortOrder::kName to kDir
    over the parallel walk (per-directory sorted listing blocks).
  3. --sort=subtree - the bounded-buffer contiguous-subtree mode (kSubtree).
  4. --sort=tree - the collect-all global ordering (kTree).
  5. -j / --jobs + mode-scoped defaults - the flag and the per-persona
    worker-count and default---sort wiring.

Open follow-ups: an optional stderr soft-warn for --sort=tree past a large
match count (no hard cap; subtree already covers bounded-memory ordering).