跳转到内容

ripgrep ignore-aware 并行遍历

ripgrep 的“递归搜索”首先是一个策略化文件遍历器:它同时理解 gitignore、hidden、遵循 symlink、glob、文件类型和权限错误,再把合格的 DirEntry 交给搜索层。

先给答案:ripgrep 的“递归搜索”首先是一个策略化文件遍历器:它同时理解 gitignore、hidden、遵…

Section titled “先给答案:ripgrep 的“递归搜索”首先是一个策略化文件遍历器:它同时理解 gitignore、hidden、遵…”

ripgrep 的“递归搜索”首先是一个策略化文件遍历器:它同时理解 gitignore、hidden、遵循 symlink、glob、文件类型和权限错误,再把合格的 DirEntry 交给搜索层。 正文沿“Walker 结构 -> 规则优先级 -> 并行 ownership”展开:先确认入口和状态归属,再跟踪控制流或数据流的推进,最后落到对外可观察的结果。

主要失效边界集中在“–no-ignore 仍漏文件、白名单规则不生效、follow symlink 后循环”这些场景。它们破坏的是容量、顺序、并发或生命周期前提;排查时应先确认状态是否仍由正确对象持有,再核对推进条件和清理路径。

WalkBuilder
├─ overrides / types / hidden / gitignore options
├─ max_filesize / follow_links / same_file_system
├─ thread count / channel
└─ build / run
|
Worker::run -> run_one(Work)
|
DirEntry / Error events

坐标:WalkBuilder 在 crates/ignore/src/walk.rs:488;运行入口 run() 在 :1428;目录读取在 :1616;Worker 在 :1721;worker 主循环在 :1761,单个 work 处理在 :1769;core 构造 walker 在 crates/core/flags/hiargs.rs:885。

遍历器不是遇到 .gitignore 就简单跳过:规则有来源、深度、白名单/反向规则和 override。目录级忽略可以减少进入子树的成本,但反向规则可能要求继续访问目录,因此 walker 必须保留足够上下文。

每个 worker 拥有自己的目录读取状态和规则上下文;通过 channel 把 DirEntry 或 error 发给消费者。共享的不是整个 walker,而是不可变配置和事件通道,从而减少锁竞争。

替代方案:先把所有路径收集出来,再交给 matcher 过滤。

为什么不行:被忽略的大目录可能包含数十万文件;晚过滤已经付出了目录读取、stat、路径分配和 channel 成本。

证据:WalkBuilder 将 hidden、gitignore、glob、type 和 max filesize 作为遍历策略,worker 在产生 entry 的过程中执行裁剪。

权限错误、损坏 symlink、读取失败不一定应终止整个搜索。walker 将错误作为事件交给上层,由 core 根据 --no-messages、是否标准输入和 exit policy 决定输出与退出。

场景 现象 原因 规避
--no-ignore 仍漏文件 还有 hidden/type/size 过滤 ignore 不是唯一策略 分别检查各类过滤 flag
白名单规则不生效 父目录已被裁剪 进入目录前无法发现反向规则 从规则根目录执行并保留 parent context
follow symlink 后循环 遍历不结束 图结构不再是树 使用 same-file-system/visited 策略
worker 数量盲目增加 变慢 IO、锁和 printer 成为瓶颈 按磁盘和 workload 基准调节

面试锚点

  • ignore-aware walker 为什么不能用简单递归?
  • 过滤为什么必须尽量前移?
  • 并行 worker 如何避免共享可变遍历状态?