Skip to content

fix(LocalStorage): Clear realpath cache on fopen failure and re-try access - #63101

Open
DerDreschner wants to merge 1 commit into
masterfrom
fix/add-retry-on-realpath-cache-miss
Open

fix(LocalStorage): Clear realpath cache on fopen failure and re-try access#63101
DerDreschner wants to merge 1 commit into
masterfrom
fix/add-retry-on-realpath-cache-miss

Conversation

@DerDreschner

@DerDreschner DerDreschner commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

PHP resolves the path of every fopen() through its realpath cache, which stores per path component whether that component is a directory. That cache is per process, and entries live for realpath_cache_ttl (120s by default). If one process has a path cached as a file and another process replaces it with a directory, the first process keeps refusing to descend into it and fopen() fails as if the file were missing - while stat()/file_exists() bypass that cache and keep reporting the file. The file looks perfectly present and is still unreadable, for up to two minutes.

That is why the filesdrop integration tests fail intermittently: the suite recycles the same user and paths, one scenario stores drop/Alice/folder as a file, the next needs it as a directory, and the dev server runs several worker processes (PHP_CLI_SERVER_WORKERS=2), so the worker serving the download is usually not the one that created the directory (example run: …). It is not CI-specific: php-fpm workers keep the same cache, so a user who deletes a file X, creates a folder X, and downloads X/y.txt via another worker gets a 503 for up to realpath_cache_ttl.

This is being fixed by this PR, which in this case walks up the parents, clears their cache entry and re-tries to access the path.

Checklist

AI (if applicable)

  • The content of this PR was partly or fully generated using AI

@DerDreschner DerDreschner self-assigned this Aug 10, 2026
@DerDreschner
DerDreschner requested a review from a team as a code owner August 10, 2026 01:50
@DerDreschner
DerDreschner requested review from provokateurin and salmart-dev and removed request for a team August 10, 2026 01:50
@DerDreschner DerDreschner added the 3. to review Waiting for reviews label Aug 10, 2026
@DerDreschner
DerDreschner requested a review from come-nc August 10, 2026 01:50
@DerDreschner

Copy link
Copy Markdown
Contributor Author

/backport to stable34

@DerDreschner

Copy link
Copy Markdown
Contributor Author

/backport to stable33

@DerDreschner

Copy link
Copy Markdown
Contributor Author

/backport to stable32

@DerDreschner

Copy link
Copy Markdown
Contributor Author

/backport to stable31

@DerDreschner

Copy link
Copy Markdown
Contributor Author

/backport to stable30

@DerDreschner

Copy link
Copy Markdown
Contributor Author

/backport to stable29

@DerDreschner
DerDreschner enabled auto-merge August 10, 2026 01:51
@DerDreschner

Copy link
Copy Markdown
Contributor Author

Backport down to stable29 is to stabilize our CI runs.

@joshtrichards joshtrichards left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Some of the comments may be better represented by (or at least in) the regression tests. 🤷

Comment thread lib/private/Files/Storage/Local.php Outdated
Comment thread lib/private/Files/Storage/Local.php Outdated
// realpath_cache_ttl expires, this process still has it cached as a file, and
// because fopen() resolves through that cache, it refuses to look inside and
// fails as if the file did not exist. Dropping the stale entries and opening
// again is what fixes that. The retry deliberately stays unsuppressed, so the

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm all for making troubleshooting clues not invisible, but this is an API contract change. Not sure...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hmm, kinda... The alternative would be to disable the realpath cache entirely by setting realpath_cache_size=0... At least for the test runners... But our own code relies on the current state reported by the filesystem ops to be correct, so, I think it's correct to fix it in the code itself as the issue can occur via CLI or web as well... Although I see that this is different from what PHP's own fopen() does...

Comment thread lib/private/Files/Storage/Local.php Outdated
$this->unlink($path);
}
$result = @fopen($sourcePath, $mode);
if ($result === false) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Any non-cache-related failures worth ignoring? (Not even sure it's possible).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's not possible, fopen() only returns false on any error and it's only worth looking into the error once cache is being reset and the retry happens... Which is then being logged as warning. Ignoring anything here would require the error handler to replace before/after the second fopen(), so, quite hacky.

@joshtrichards

Copy link
Copy Markdown
Member

+1 to an official php.net docs clarification in this area. ;-)

I wander.if this is relevant for any other Local (or elsewhere?) operations and/or anywhere else like the Storage-less Updater app?

Comment thread lib/private/Files/Storage/Local.php Outdated
@DerDreschner
DerDreschner force-pushed the fix/add-retry-on-realpath-cache-miss branch from deec1b8 to ceca32a Compare August 10, 2026 08:02
…ccess

Assisted-by: ClaudeCode:claude-opus-5
Signed-off-by: David Dreschner <david.dreschner@nextcloud.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants