Conversation
Every worker in a parallel run boots its own container, and each one
require()s the configuration_hash cache file while another worker may be
saving over it. copy() truncates the destination and streams into it, so a
reader can catch a prefix of the new file and die on it:
{"fatal_errors":["syntax error, unexpected string content ..."]}
[ERROR] Could not process some files, due to: "Child process error".
The interleaving that produces a torn read is not reproducible on demand,
but the precondition for it is, with no threads and no timing: open a
reader on the cache file, save over it, and read. copy() writes through
the inode the reader is holding, so the reader's file changes underneath
it; an atomic replace leaves that reader on the whole file it opened.
Fails on copy(), passes on rename().
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.
Targets
partial-cacheso it lands inside #8426 rather than alongside it. Test only — no production change.Addresses the one thing blocking that PR:
Fails on
main'scopy(), passes on this branch'srename(). No threads, no timing, no sleep.The test opens a read handle on a cache file, saves over the same key, and then reads from the handle it opened before the save — the way a parallel worker holds the
configuration_hashfile open whilerequire-ing it during container boot.copy()truncates the destination and streams into the very inode that reader is holding, so the reader's file changes underneath it.rename()publishes a new inode and leaves the reader on the complete file it opened.On
main:What it does not prove
It is not a reproduction of the crash. It pins the precondition — a writer mutating a file under a live reader — not a torn read. With a payload this small
copy()finishes before the read, so you get complete-but-wrong content rather than thesyntax error, unexpected string content/Child process errorfailure seen in the wild. Making an actual torn read deterministic needs the interleaving, which I don't think is achievable in a test.What it does give you is a guard that fails the moment
copy()comes back, which I read as the point of the request.Checks
vendor/bin/phpunit tests/Caching— 14/14 green on this branch, 1 failure onmaincomposer check-cs— cleanContext and the Linux reproduction that led here: #8426 (comment)