Skip to content

perf: stop re-resolving every prefix of a missing path - #175

Open
ferdingler wants to merge 1 commit into
strands-agents:mainfrom
ferdingler:perf/missing-path-resolution
Open

ferdingler wants to merge 1 commit into
strands-agents:mainfrom
ferdingler:perf/missing-path-resolution

Conversation

@ferdingler

@ferdingler ferdingler commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Description

A long path made of components that don't exist was really slow to fail. About 32 KB of a/a/a/... took around 85 seconds in a debug build, well under the default input limit. Looking up which bind a path sits under re-resolved the whole path once per missing component, so the cost grew with the square of the length, and the VFS lock was held the whole time.

Now the lookup finds the deepest part of the path that exists in a handful of steps and appends the missing rest once. The answers don't change: ./.. handling, symlinks, binds, and the paths that get refused all behave as before. The only code change is in that one lookup; the rest is tests. We already fixed this in Box's copy of Shell (here), and this brings the fix upstream.

I added tests that pin the current answers for missing paths under a bind, .. across a missing component, symlinks in front of a missing tail, host symlinks that dangle or leave the bind, and paths with no bind. They pass on main too, and I broke the new search a couple of ways on purpose to check they catch it. I also added a timing test that compares 2,000 against 16,000 missing components in the same run, so it doesn't depend on how fast the runner is. On main the long case took about 85 seconds against 1.3 for the short one and the test failed. With the change the whole test takes about a tenth of a second. It only covers this path shape, though. It doesn't show the cost is linear for every kind of path, or for deep trees full of symlinks.

This touches the same file as #170, but a different function, so they shouldn't conflict.

Related Issues

None.

Documentation PR

None.

Type of Change

Other (please describe): Performance fix

Testing

How have you tested the change? Verify that the changes do not break functionality or introduce new warnings.

  • I ran the relevant test suites for the bindings I touched (cargo test --workspace --all-targets, pytest tests/python, npm test)
  • If I touched Rust, I ran cargo fmt and cargo clippy

Checklist

  • I have read the CONTRIBUTING document
  • I have reviewed and understand every line of code in this PR, including any generated by AI tools, and I can explain why it works
  • My change is focused and reasonably small; I have split unrelated work into separate PRs
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Finding the bind above a path walked up one component at a time and
resolved the whole prefix at each step, so a long path of missing
components cost time quadratic in its length while holding the VFS lock.
Nothing resolves past a bind and a shorter prefix resolves whenever a
longer one does, so a binary search for the longest resolving prefix
finds the same ancestor, and the missing tail is appended once.

Adds unit tests that pin the existing answers (missing tails under a
bind, '..' across a missing component, symlinks before a missing tail,
refused host symlinks, no bind ancestor) and a test that compares the
cost of 2,000 and 16,000 missing components.
@ferdingler
ferdingler requested a review from a team as a code owner October 21, 2026 05:00
@ferdingler
ferdingler requested review from notowen333 and removed request for a team October 21, 2026 05:00

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant