Skip to content

FileCacheStorage uses copy() instead of rename(), corrupting cache files on parallel cold-cache runs #9876

Description

@dragosprotung

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"]}
Image

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

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions