perf(share): gather provider sessions in parallel - #157
Conversation
|
🔍 Preview deployed: https://c072734d.toolpath.pages.dev |
There was a problem hiding this comment.
Reasoning from Claude Code on the changes in this file:
The why: the listing cache in cmd_share needs the exact same stat-level enumeration the sync engine uses (that's the whole invariant — one enumeration path, so cache stamps and sync stamps can never
disagree). The tuple structs are private to sources.rs, and rather than making the structs themselves pub(crate) — exposing their internals — the cache work added three small constructor functions
returning impl ArtifactSource, only for the three providers the cache consumes (claude/codex/opencode; that's why gemini/cursor/pi/copilot lines are untouched).The line-53 edits you're looking at are then source_for delegating through those new functions instead of invoking the structs directly. Strictly optional — same module, it could still call
ClaudeSource(...) — but it keeps exactly one construction site per provider, so a future change to how a source is built can't silently diverge between the sync engine and the cache. Three lines of
diff noise bought for that guarantee; I think it's the right trade, but if you'd rather source_for stay untouched, reverting those three lines is behavior-neutral.
The unified picker (share; bare resume once it lands) enumerated the seven providers sequentially, so the slowest scan stacked on top of the rest. gather_artifacts now fans out to scoped threads — wall time is max-of-providers, not the sum. Claude stays on the calling thread (ClaudeConvo caches its chain index in a RefCell); concatenation keeps the old provider order so ranking tie-breaks are unchanged. path-cli 0.16.2.
40d60e9 to
9c5c61c
Compare
| std::thread::scope(|s| { | ||
| let mut handles = Vec::new(); | ||
|
|
||
| macro_rules! spawn_collect { |
There was a problem hiding this comment.
I think a match over a loop of ArtifactType would probably be more immediately readable. While this makes adding a new line much simpler, understanding the actual logic requires a new indirection and reading a macro definition.
This would also make the spawn and join shapes match, decreasing mental overhead for bookending.
e.g.
let mut types = /* all the types here */
if let Some(exclusive_harness) = harness_filter {
types = Vec::from([exclusive_harness]);
}
for t in types {
handles.push(s.spawn(match t {
ArtifactType::Gemini => move || {
collect_gemini(mgr, canonical_cwd, canonical_project.as_deref()) }
....
}))
}Remember that collect_gemini, etc. are just helpers for use here and you can make them return an easier to use return type, etc.
Layer 2 of the picker-performance stack (on #156, under #159 → #154).
The unified session picker (
path share, barepath resumein #154) enumerated the seven providers sequentially, so the slowest scan — a big codex or claude history — stacked on top of all the others.gather_artifactsnow fans the provider scans out to scoped threads: wall time is max-of-providers instead of the sum.ClaudeConvocaches its chain index in aRefCell, so it cannot cross threads. Codex dominates the wall clock in practice, so this costs nothing.Measured (with #156 beneath): gather drops from ~8.5 s to ~5 s debug on a 421-rollout tree; release lands ~2.5 s cold. #159 above this takes warm opens to milliseconds.
path-cli 0.16.1 → 0.16.2. Version slots: #138/#145 also claim numbers in this range — whoever lands second renumbers.