FileCacheStorage uses copy() instead of rename(), corrupting cache files on parallel cold-cache runs
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 78/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- php
- Domain
- backend, performance
Research direction
Start with FileCacheStorage::save(), especially the temporary-file write and destination update, then inspect ParallelFileProcessor.php:181 to understand the reported worker failure. Reproduce with a removed cache directory and parallel processing, and verify that replacing the non-atomic cache update prevents truncated PHP files and the resulting parse errors.
Written by the indexing model from the issue text.
Description
Bug Report
| Subject | Details |
|---|---|
| Rector version | 2.6.5 |
On a cold cache, parallel runs intermittently abort with:
[ERROR] Could not process some files, due to:
"Child process error: ".
and the worker JSON on the progress line carries bogus parse errors, varying per run:
{"fatal_errors":["syntax error, unexpected string content \"c343c467a1cdb4d496cd51fa46a79d...\""]}
{"fatal_errors":["Unclosed '(' on line 9"]}
Cause
FileCacheStorage::save() writes a temp file and then copies it over the destination:
FileSystem::write($tmpPath, sprintf("<?php declare(strict_types = 1);\n\nreturn %s;", $exported), null);
$copySuccess = @copy($tmpPath, $filePath);
@unlink($tmpPath);
copy() truncates the destination and streams into it, so it is not atomic. On a cold cache every
analysed file writes a cache entry while the parent process and the workers read those same files; a
reader that lands on a partially copied file loads a truncated PHP file and dies with a parse error
whose message depends on where the truncation falls. The worker's non-zero exit is then surfaced as
Child process error: with empty stderr (ParallelFileProcessor.php:181), which is why no file is
reported and the run fails at 100% with every file already processed.
Reproduction
3183 files, 32-core Linux, PHP 8.5.10, config using
->withCache(cacheDirectory: ..., cacheClass: FileCacheStorage::class):
| Scenario | Result |
|---|---|
cold cache (rm -rf <cacheDirectory>) before each run |
2/3 runs fail; up to 8/8 under load |
| warm cache, same command | 6/6 pass |
--debug (single process) |
always passes |
withParallel(maxNumberOfProcess: 1), cold cache |
still fails — parent and worker both touch the cache |
Fix
Replacing
$copySuccess = @copy($tmpPath, $filePath);
with
$copySuccess = @\rename($tmpPath, $filePath);
fixes the issue
- Dominant language
- PHP
- Stars
- 10.4k
- Forks
- 742
- PR merge metrics
- No merged PRs in 30d
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from rectorphp/rector
All issues in rectorphp/rector
Similar issues
-
sync-en
Difficulty 1/5 1-3 hours Newbie friendliness 85/100
-
sync-en
Difficulty 1/5 1-3 hours Newbie friendliness 85/100
-
Перевод устарел
Difficulty 1/5 1-3 hours Newbie friendliness 78/100
-
[6.x]: "Cannot use object of type stdClass as array" loading Users index (regression of #19182) Open
Difficulty 1/5 Under an hour Newbie friendliness 90/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100