-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Summary
Clicking the "Clear Cache" button in the admin panel returns a 500 Internal Server Error,
preventing cache from being cleared.
Environment
- PHP with Symfony Cache (symfony/cache ^7.2)
- Application using set_error_handler() to convert PHP warnings to ErrorException
Root Cause
In Application::warningHandler(), all PHP E_WARNING errors are converted to ErrorException:
set_error_handler($this->warningHandler(...), E_WARNING);
private function warningHandler(int $errno, string $errstr, ...): never {
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
CacheManager::clearCacheDir() calls rmdir() and unlink() without error suppression.
When the Symfony FilesystemAdapter clears the cache pool (cacheItemPool->clear()), it
deletes cache files but not all subdirectories. When clearCacheDir() subsequently tries
to rmdir() a directory that is still non-empty (due to concurrent writes from other
PHP processes writing new cache entries during the clear operation), rmdir() emits a
PHP warning, which warningHandler() immediately converts to an ErrorException.
This exception propagates up through ClearCacheRequestHandler and is caught by
ExceptionMiddleware, which returns a 500 response.
Log evidence of the failure:
ErrorException: rmdir(/home/.../var/cache/@): Directory not empty
#0 [internal function]: Application->warningHandler()
#1 CacheManager.php(44): rmdir()
#2 CacheManager.php(28): CacheManager->clearCacheDir()
#3 ClearCacheRequestHandler.php(26): CacheManager->clearCache()
Affected File
src/Shared/Infrastructure/CacheManager.php
private function clearCacheDir(): void
{
foreach ($iterator as $path) {
if ($path->isDir()) {
rmdir($path->getPathname()); // <-- triggers warning if not empty
} elseif ($path->getFilename() != '.gitkeep') {
unlink($path->getPathname()); // <-- triggers warning if already deleted
}
}
}
Proposed Fix
Suppress warnings on filesystem operations, exactly as Symfony's own FilesystemAdapter
does internally in FilesystemCommonTrait::doUnlink() with @Unlink():
private function clearCacheDir(): void
{
foreach ($iterator as $path) {
if ($path->isDir()) {
@rmdir($path->getPathname());
} elseif ($path->getFilename() != '.gitkeep') {
@unlink($path->getPathname());
}
}
}
The @ operator suppresses the PHP warning before warningHandler() can intercept it,
allowing the loop to continue gracefully when a directory is non-empty or a file was
already deleted by a concurrent process.
Steps to Reproduce
- Have any module writing to the Symfony FilesystemAdapter cache (e.g. embedding cache,
IP geolocation cache, or any PSR-16 cache usage) with ongoing traffic. - Navigate to Admin → Settings.
- Click "Clear Cache".
- Observe HTTP 500 response.
Expected Behavior
Cache is cleared successfully and the UI shows a success notification.
Actual Behavior
HTTP 500 is returned. No cache is cleared.
Notes
- The bug is latent in the original code and becomes reliably triggered once any
service actively writes to the cache with sufficient concurrency. - No changes to Application::warningHandler() are required; the fix is isolated
to CacheManager::clearCacheDir().