diff --git a/src/Symfony/Component/Cache/CHANGELOG.md b/src/Symfony/Component/Cache/CHANGELOG.md index 9ff816b70b5b2..787942f85c115 100644 --- a/src/Symfony/Component/Cache/CHANGELOG.md +++ b/src/Symfony/Component/Cache/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 5.4 --- + * Make `LockRegistry` use semaphores when possible * Deprecate `DoctrineProvider` because this class has been added to the `doctrine/cache` package 5.3 diff --git a/src/Symfony/Component/Cache/LockRegistry.php b/src/Symfony/Component/Cache/LockRegistry.php index ecedac8c07e87..629cb5ed69708 100644 --- a/src/Symfony/Component/Cache/LockRegistry.php +++ b/src/Symfony/Component/Cache/LockRegistry.php @@ -27,7 +27,7 @@ final class LockRegistry { private static $openedFiles = []; - private static $lockedFiles; + private static $lockedKeys; /** * The number of items in this list controls the max number of concurrent processes. @@ -75,32 +75,40 @@ public static function setFiles(array $files): array fclose($file); } } - self::$openedFiles = self::$lockedFiles = []; + self::$openedFiles = self::$lockedKeys = []; return $previousFiles; } public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata = null, LoggerInterface $logger = null) { - if ('\\' === \DIRECTORY_SEPARATOR && null === self::$lockedFiles) { + if ('\\' === \DIRECTORY_SEPARATOR && null === self::$lockedKeys) { // disable locking on Windows by default - self::$files = self::$lockedFiles = []; + self::$files = self::$lockedKeys = []; } - $key = self::$files ? abs(crc32($item->getKey())) % \count(self::$files) : -1; + $key = unpack('i', md5($item->getKey(), true))[1]; - if ($key < 0 || (self::$lockedFiles[$key] ?? false) || !$lock = self::open($key)) { + if (!\function_exists('sem_get')) { + $key = self::$files ? abs($key) % \count(self::$files) : null; + } + + if (null === $key || (self::$lockedKeys[$key] ?? false) || !$lock = self::open($key)) { return $callback($item, $save); } while (true) { try { // race to get the lock in non-blocking mode - $locked = flock($lock, \LOCK_EX | \LOCK_NB, $wouldBlock); + if ($wouldBlock = \function_exists('sem_get')) { + $locked = @sem_acquire($lock, true); + } else { + $locked = flock($lock, \LOCK_EX | \LOCK_NB, $wouldBlock); + } if ($locked || !$wouldBlock) { $logger && $logger->info(sprintf('Lock %s, now computing item "{key}"', $locked ? 'acquired' : 'not supported'), ['key' => $item->getKey()]); - self::$lockedFiles[$key] = true; + self::$lockedKeys[$key] = true; $value = $callback($item, $save); @@ -115,12 +123,23 @@ public static function compute(callable $callback, ItemInterface $item, bool &$s return $value; } + // if we failed the race, retry locking in blocking mode to wait for the winner $logger && $logger->info('Item "{key}" is locked, waiting for it to be released', ['key' => $item->getKey()]); - flock($lock, \LOCK_SH); + + if (\function_exists('sem_get')) { + $lock = sem_get($key); + @sem_acquire($lock); + } else { + flock($lock, \LOCK_SH); + } } finally { - flock($lock, \LOCK_UN); - unset(self::$lockedFiles[$key]); + if (\function_exists('sem_get')) { + sem_remove($lock); + } else { + flock($lock, \LOCK_UN); + } + unset(self::$lockedKeys[$key]); } static $signalingException, $signalingCallback; $signalingException = $signalingException ?? unserialize("O:9:\"Exception\":1:{s:16:\"\0Exception\0trace\";a:0:{}}"); @@ -145,6 +164,10 @@ public static function compute(callable $callback, ItemInterface $item, bool &$s private static function open(int $key) { + if (\function_exists('sem_get')) { + return sem_get($key); + } + if (null !== $h = self::$openedFiles[$key] ?? null) { return $h; }