[4.x] Fix Race Condition in Component Cache Writes#9833
[4.x] Fix Race Condition in Component Cache Writes#9833calebporzio merged 1 commit intolivewire:mainfrom
Conversation
Use File::replace() instead of File::put() for atomic writes in all cache write methods: - writeClassFile() - writeViewFile() - writeScriptFile() - writeStyleFile() - writeGlobalStyleFile() - writePlaceholderFile() - writeIslandFile() Without atomic writes, concurrent requests compiling the same component can corrupt the cache file, causing "Cannot use ::class on int" errors. File::replace() writes to a temp file then renames atomically. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
PR Review: #9833 — [4.x] Fix Race Condition in Component Cache WritesType: Bug fix What's happening (plain English)
The fix: swap Other approaches considered
Changes MadeNo changes made. The PR is clean as-is. Test Results
Code Review
SecurityNo security concerns identified. This is a safer pattern than what existed before. VerdictClean, minimal, correct fix for a real production race condition. The contributor's diagnosis is accurate and independently verified. Reviewed by Claude |
|
great. thanks! |
Change File::put() to File::replace() when writing compiled Blade views. In threaded servers like FrankenPHP, concurrent requests can race on compiling the same view: file_put_contents() truncates the file to 0 bytes before writing, so another thread doing include() during that window gets empty output. This causes Livewire's insertAttributesIntoHtmlRoot() to throw RootTagMissingFromViewException because there is no root HTML tag to find. File::replace() uses a temp file + rename, which is atomic on POSIX systems, ensuring readers always see either the old or new content. This is the same class of fix applied to Livewire's CacheManager in livewire/livewire#9833.
* Use atomic writes in BladeCompiler to prevent race condition Change File::put() to File::replace() when writing compiled Blade views. In threaded servers like FrankenPHP, concurrent requests can race on compiling the same view: file_put_contents() truncates the file to 0 bytes before writing, so another thread doing include() during that window gets empty output. This causes Livewire's insertAttributesIntoHtmlRoot() to throw RootTagMissingFromViewException because there is no root HTML tag to find. File::replace() uses a temp file + rename, which is atomic on POSIX systems, ensuring readers always see either the old or new content. This is the same class of fix applied to Livewire's CacheManager in livewire/livewire#9833. * Update BladeCompiler tests to expect replace() instead of put() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Use atomic writes in BladeCompiler to prevent race condition Change File::put() to File::replace() when writing compiled Blade views. In threaded servers like FrankenPHP, concurrent requests can race on compiling the same view: file_put_contents() truncates the file to 0 bytes before writing, so another thread doing include() during that window gets empty output. This causes Livewire's insertAttributesIntoHtmlRoot() to throw RootTagMissingFromViewException because there is no root HTML tag to find. File::replace() uses a temp file + rename, which is atomic on POSIX systems, ensuring readers always see either the old or new content. This is the same class of fix applied to Livewire's CacheManager in livewire/livewire#9833. * Update BladeCompiler tests to expect replace() instead of put() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…#58812) * Use atomic writes in BladeCompiler to prevent race condition Change File::put() to File::replace() when writing compiled Blade views. In threaded servers like FrankenPHP, concurrent requests can race on compiling the same view: file_put_contents() truncates the file to 0 bytes before writing, so another thread doing include() during that window gets empty output. This causes Livewire's insertAttributesIntoHtmlRoot() to throw RootTagMissingFromViewException because there is no root HTML tag to find. File::replace() uses a temp file + rename, which is atomic on POSIX systems, ensuring readers always see either the old or new content. This is the same class of fix applied to Livewire's CacheManager in livewire/livewire#9833. * Update BladeCompiler tests to expect replace() instead of put() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The Problem
When multiple requests hit an uncached Livewire component at the same time, you get:
This happens after fresh deployments when bots or users hit your site simultaneously.
Why It Happens
In
CacheManager.php, all write methods usedFile::put():This is not atomic. When two requests try to compile the same component:
1instead of the class instance$instance::classcrashesThe Fix
Use
File::replace()which writes to a temp file first, then renames atomically:Rename operations are atomic on POSIX systems - the file is either fully there or not.
This fix applies to all cache write methods:
writeClassFile()- the critical one causing the crashwriteViewFile()writeScriptFile()writeStyleFile()writeGlobalStyleFile()writePlaceholderFile()writeIslandFile()Real Example
This error occurred in production with a footer component:
The footer had
<livewire:footer-links />. After deployment, Googlebot hit multiple pages simultaneously, triggering the race condition.