[Cache] Fix filesystem cache collision #39786
Closed
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.
NOTE: this isn't a theoretical case but a real issue I had in a production environment.
Current filesystem cache write implementation leads to cache collision under certain conditions.
I'll try to explain the conditions that lead to it and the reasoning behind each change step by step.
Once initialized,
Symfony/Component/Cache/Traits/FilesystemCommonTrait::$tmp
never changes. It means that if process is forked, both processes now use the same tmp file path to write to the cache. You don't really need that much concurrency to end up with a collision this way.The first fix iteration that comes to mind is removing the
if
condition here:symfony/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php
Lines 95 to 97 in cce5a42
That would work better, but there's still a possibility of a race condition.
As you know
uniqid()
is based on timestamp in microseconds. With enough luck and concurrency you may get exactly the same value from it in different processes/threads. Themore_entropy
argument is supposed to help with that, however it doesn't work very well if it had been used before forking.Check out this example:
Output:
As you can see the 2nd and the 3rd line have the same "more entropy" value. The problem is that under the hood it's using an LCG algorithm. Once its constants are initialized, they never change and the sequence of generated numbers is pre-determined.
Thus
uniqid()
was replaced withbin2hex(random_bytes(40))
.$this->tmp
) still leads to problems when used with coroutines (Swoole) and presumably - threads (I have no experience with multi-threading solutions in PHP, maybe they do some magic around it to prevent this kind of issue).As a result,
$tmp
property was replaced with a local variable.