Repository navigation
Conversation
…he nothing for a failed read The two listing loops in resolver.rs took an error from the directory iterator as the end of the directory and cached the names read so far as the whole directory. RealFS::readdir returned the error, and its callers cached it with no generation. RealFS::read_listing is now the only reader. A read that fails is tried once more on a handle opened again by path. If it fails again, nothing is stored: a fresh slot stays unknown and a stale listing keeps its names. commit_listing is the only writer of EntriesOption::Entries. A resolution that crossed a failed read fails with 'Cannot read directory "<dir>": <ERRNO> while resolving "<specifier>"'.
…tores The directory walk interned the path of its input once the first directory opened. A read that failed after that left the path in DirnameStore, once for each attempt. The walk now interns the path after the directory is listed. RealFS keeps what failed reads interned for each directory, and not only for the last one, until a read of that directory succeeds.
Collaborator
Author
|
Status Reproduced on 1.4.3 (release build) and on a debug build of main, linux-x64. An LD_PRELOAD library makes one The fix and the tests are in this PR (#44540). It is a draft until the release-build numbers are in the body. |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
import "dep"then loadsdep/index.js, not themainof its package.json, with exit 0.Err(_) => breakin the two read loops (src/resolver/resolver.rs:3392and:4652). The two readers insrc/resolver/lib.rscache the error, and it never expires.Fix
RealFS::read_listingis the only reader andRealFS::commit_listingthe only writer. A listing is stored only after the read reaches the end.Cannot read directory "<dir>": EIO while resolving "dep".test/js/bun/resolve/resolve.test.ts(11 new tests, 10 fail on 1.4.3), and the hot, watch,--filterandBun.buildsuites.Background
DirInfoderived from it (package.json, tsconfig.json, node_modules).lstatwhen a listing fails. That is a second lookup path, and it reports nothing.Downsides
DirInfois still "no such entry" for that lookup.Notes
How to reproduce
There is no fault injection for
getdents64in the tree, so the tests build an LD_PRELOAD library. bun issuesgetdents64through libcsyscall(). For one directory, the firstgetdents64of a handle returns the real records without one name, and each latergetdents64of that handle fails with EIO.bun run entry.ts, the read ofnode_modules/depfails each timeindex.js, exit 0error: Cannot read directory ".../node_modules/dep": EIO while resolving "dep", exit 1index.jsfor the first import and for each laterrequire,Bun.resolveSync,require.resolvereal.jsfor all four"main": "lib/real.js", the read ofdep/libfails onceindex.jsreal.jsbun build entry.ts --target=browser,./src/xwithsrc/x.tsandsrc/x.js, the read ofsrcfails each timex.js, exit 0--target=bunCannot read directory ".../src": EIOandCould not resolve: "./src/x", exit 1Bun.buildthree times, the read ofsrcfails during the secondx.tsbun run --filter '*' hello, one failed read of the workspace rootNo workspace packages matched the filter "*", exit 1What changed, by function
RealFS::readdiris the one loop.RealFS::read_listingwraps it: it names the listing, reads, and on an error goes to the coldread_listing_again.read_listing_againopens the directory again by path and reads once more. The first handle is not read again, because its position is not known. When the caller owns the handle, the new handle replaces it. A non-void iterator (thebun testscanner) gets no second read, because it already saw entries.RealFS::read_failedis the rule for a read that failed twice. ENOENT and ENOTDIR (the directory went away while it was open) end like a failed open. Any other error stores nothing. A stale listing keeps its names and its generation. The handle is closed when the call opened it.EntriesMap::putandmark_not_foundare private to the module.commit_listing,opaque_listingandmark_dir_not_foundare the named writers thatresolver.rsuses.dir_info_cached_miss: a read error of ENOENT or ENOTDIR ends like its failed open. EACCES or EPERM on an ancestor gives the empty listing that windows: run Bun inside an AppContainer (lowbox token) #33119 gives an ancestor that does not open. Any other error returnsErrand leaves both cache keys unknown.DirnameStore.RealFS.failed_listingskeeps the entries that failed reads interned, for each directory, until a read of that directory succeeds.EntryStore,FilenameStoreandDirnameStoreonly grow, so a read that fails again and again must not add to them.Resolver.dir_read_failurerecords the directory and the errno.dir_info_cached_miss,dir_info_for_resolutionandload_as_fileset it.resolve_and_auto_installtests it once after the resolution. If it is set, a cold function clears it and resolves once more, because a lookup outside a resolution can also leave a record. If the read fails again, the result isFailurewith the message above, added withadd_resolve_erroras in Don't panic when auto-install can't read the top-level directory #31938.Numbers
Counted on a debug build of this branch with gdb breakpoints and the shim, linux-x64.
Bun.resolveSync("dep"), fault staysEntryslots,DirnameStorestrings andFilenameStorestrings for each failed resolution after the first--hot,--watchRelease builds of the merge base and of this branch are in progress. The instruction count for a warm resolution, the size of the binary and the stack frames of the two walk functions follow before this leaves draft.
Suites run on the debug build
test/js/bun/resolve/resolve.test.ts: 106 pass, 2 skip.test/js/bun/resolve/import-meta.test.js,resolve-error.test.ts,test/js/bun/util/filesystem_router.test.ts,test/cli/hot/hot.test.ts,test/cli/watch/watch.test.ts: all pass.test/cli/run/filter-workspace.test.ts: 88 pass, 1 fail ("run in parallel", which needs the output of two scripts to interleave in time; it passes alone).test/cli/run/env.test.ts: 106 pass, 1 fail (a compiled executable test, 5 s timeout).test/bundler/bun-build-api.test.ts: 67 pass, 3 fail (three bytecode tests, 5 s timeout each).test/cli/test/bun-test.test.ts: 102 pass, 2 fail (two deep-nesting print tests, 5 s timeout each).test/internal/source-lints/: 170 pass.test/js/bun/resolve/resolver-permission-denied-ancestor.test.tsskips as root here.cargo check -p bun_resolverforx86_64-pc-windows-msvc,aarch64-apple-darwin,x86_64-unknown-freebsd,aarch64-unknown-linux-musl.The host was under heavy load, so the 5 s timeouts are not a signal. None of those tests reads a directory through the changed code in a way the others do not.
Not in this change
getdents64is in fs.rm: continue past vanished entries, retry transient errors, report the failing path #41480. Until it lands, one EINTR is healed by the second read.dir_info_cached_miss,read_directory_error).bun run <file>printsModule not foundfor an entry point whose directory cannot be read, without the errno.bun run --filterprintserror: Unexpectedfor EIO.bun testscanner only debug-logs a directory it cannot read (bun test: report directories the scanner cannot read, scan each directory once #41465).DirInfobuilt while the stale listing of its parent cannot be refreshed has no real path for a symlinked directory.bun pm packandbun publish(pack, publish: exit 1 when a read of a package directory fails #44400),bun pm licenses, the system certificate loader, install prune.Related open PRs
read_failedand in the callers ofread_listing.Two questions
dir_read_failureand the check, and a read that keeps failing is then only "not found" for that resolution.