Skip to content

linux: try a directory that cannot be listed once, and give each path once - #645

Merged
ronleizrowice-ant merged 3 commits into
mainfrom
perf/linux-unlistable-once
Oct 5, 2026
Merged

ronleizrowice-ant merged 3 commits into
mainfrom
perf/linux-unlistable-once

Conversation

@ronleizrowice-ant

@ronleizrowice-ant ronleizrowice-ant commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

With many ** read rules over a tree that holds directories the user cannot list, a wrap's cost grew with rules × such directories. On a slow mount that is most of the time.

What was slow

  • The patterns of one configuration share their listings (linux: make the read-deny glob walk about six times faster #627), but only the successful ones. Every pattern tried each unlistable directory again.
  • Every ** pattern over one base returns those directories, to be denied whole. The resolved list was a plain concatenation, and the Linux wrapper resolves and stats every entry it is given. The duplicates were dropped only afterwards.

What changes

  • A failed listing that has the directory denied whole is kept in the shared map, for as long as a successful one: one attempt of one wrap. A later pattern takes the branch it takes today for that error. A failure that would have cleared meanwhile hides more.
  • An absence (ENOENT) is not kept. It is the one failure that denies nothing, so each pattern asks for itself. The first commit here kept it; the second takes that back.
  • The resolved list holds each path once, where it first came up. Exact strings only.
  • By the way: that list is no longer built with an argument spread, which threw RangeError from about 125,000 matches of one pattern on Node.

Who sees a difference

getFsReadConfig().denyOnly and allowWithinDeny have no duplicates. The wrapped command is the same.

Measured

471 directories, 50 of them mode 000; every filesystem call the library makes under the tree delayed by 1 ms; one wrap:

** rules main here
5 2.7 s, 310 failing calls 1.8 s, 110
20 6.2 s, 1,090 1.9 s, 140
100 25.1 s, 5,250 2.5 s, 300

At 100 rules: 5,020 paths, 70 of them distinct, become 70. Six rules over 400,150 files with nothing unlistable: 1.0 s on both. (Measured again on main with #620 to #622, which add an lstat or two per rule.)

Testing

  • The wrapped command at 1, 5, 20 and 100 rules, on main and here, with the build's path, random names and the proxy port taken out: one and the same. Run under real bubblewrap: the matching file is refused, a plain one is read.
  • Two new cases. One has, beside a directory that cannot be listed, one that is absent when the first pattern asks and there afterwards: the second pattern still finds its match in it. Four mutations, each failing a test, a kept absence among them; both cases fail on the base's source. Linux full suite: nothing that passes on main fails here.

… once

The patterns of one configuration share their listings, but only the ones
that succeeded, so every pattern tried an unlistable directory again. And
every `**` pattern over one base returns those directories to be denied
whole, so the list the Linux wrapper looks at, entry by entry, held each
of them once per pattern.

A failed listing is now kept for as long as a successful one, and the
resolved list holds each path once. The wrapped command is the same.
The commit before this one kept every failed listing for the patterns
that follow. An absence is the one failure that denies nothing, so a
pattern handed another's absence missed what it matches in a directory
that is there when it would have asked.

Only the failures that have a directory denied whole are kept.
Base automatically changed from fix/linux-wrap-restart-reads-again to main October 3, 2026 12:33
`resolveReadPathEntries` collects into this branch's set what main's
`withOtherReadings` returns for each of `spelledOf(paths)`.
ronleizrowice-ant added a commit that referenced this pull request Oct 4, 2026
#645 keeps a failed listing for the patterns that follow. #575 lets a
second name for a directory have its try after a listing that failed.
Here a failure is kept only in a map the caller handed in, where the deny
patterns of one configuration meet; a walk's own map keeps none. Between
deny patterns nothing is lost: the first failure has the directory denied
whole. One join case says so.

@antdres antdres left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/stamp

@ronleizrowice-ant
ronleizrowice-ant merged commit 6daa5ce into main Oct 5, 2026
16 checks passed
@ronleizrowice-ant
ronleizrowice-ant deleted the perf/linux-unlistable-once branch October 5, 2026 16:06
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.

2 participants