diff options
Diffstat (limited to 'lib/private/Memcache/Factory.php')
-rw-r--r-- | lib/private/Memcache/Factory.php | 77 |
1 files changed, 61 insertions, 16 deletions
diff --git a/lib/private/Memcache/Factory.php b/lib/private/Memcache/Factory.php index c0f4f787200..b54189937fc 100644 --- a/lib/private/Memcache/Factory.php +++ b/lib/private/Memcache/Factory.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -6,6 +7,7 @@ */ namespace OC\Memcache; +use Closure; use OCP\Cache\CappedMemoryCache; use OCP\ICache; use OCP\ICacheFactory; @@ -16,7 +18,7 @@ use Psr\Log\LoggerInterface; class Factory implements ICacheFactory { public const NULL_CACHE = NullCache::class; - private string $globalPrefix; + private ?string $globalPrefix = null; private LoggerInterface $logger; @@ -40,17 +42,23 @@ class Factory implements ICacheFactory { private IProfiler $profiler; /** - * @param string $globalPrefix + * @param Closure $globalPrefixClosure * @param LoggerInterface $logger * @param ?class-string<ICache> $localCacheClass * @param ?class-string<ICache> $distributedCacheClass * @param ?class-string<IMemcache> $lockingCacheClass * @param string $logFile */ - public function __construct(string $globalPrefix, LoggerInterface $logger, IProfiler $profiler, - ?string $localCacheClass = null, ?string $distributedCacheClass = null, ?string $lockingCacheClass = null, string $logFile = '') { + public function __construct( + private Closure $globalPrefixClosure, + LoggerInterface $logger, + IProfiler $profiler, + ?string $localCacheClass = null, + ?string $distributedCacheClass = null, + ?string $lockingCacheClass = null, + string $logFile = '', + ) { $this->logFile = $logFile; - $this->globalPrefix = $globalPrefix; if (!$localCacheClass) { $localCacheClass = self::NULL_CACHE; @@ -59,19 +67,34 @@ class Factory implements ICacheFactory { if (!$distributedCacheClass) { $distributedCacheClass = $localCacheClass; } + $distributedCacheClass = ltrim($distributedCacheClass, '\\'); $missingCacheMessage = 'Memcache {class} not available for {use} cache'; $missingCacheHint = 'Is the matching PHP module installed and enabled?'; if (!class_exists($localCacheClass) || !$localCacheClass::isAvailable()) { - throw new \OCP\HintException(strtr($missingCacheMessage, [ - '{class}' => $localCacheClass, '{use}' => 'local' - ]), $missingCacheHint); + if (\OC::$CLI && !defined('PHPUNIT_RUN') && $localCacheClass === APCu::class) { + // CLI should not fail if APCu is not available but fallback to NullCache. + // This can be the case if APCu is used without apc.enable_cli=1. + // APCu however cannot be shared between PHP instances (CLI and web) anyway. + $localCacheClass = self::NULL_CACHE; + } else { + throw new \OCP\HintException(strtr($missingCacheMessage, [ + '{class}' => $localCacheClass, '{use}' => 'local' + ]), $missingCacheHint); + } } if (!class_exists($distributedCacheClass) || !$distributedCacheClass::isAvailable()) { - throw new \OCP\HintException(strtr($missingCacheMessage, [ - '{class}' => $distributedCacheClass, '{use}' => 'distributed' - ]), $missingCacheHint); + if (\OC::$CLI && !defined('PHPUNIT_RUN') && $distributedCacheClass === APCu::class) { + // CLI should not fail if APCu is not available but fallback to NullCache. + // This can be the case if APCu is used without apc.enable_cli=1. + // APCu however cannot be shared between Nextcloud (PHP) instances anyway. + $distributedCacheClass = self::NULL_CACHE; + } else { + throw new \OCP\HintException(strtr($missingCacheMessage, [ + '{class}' => $distributedCacheClass, '{use}' => 'distributed' + ]), $missingCacheHint); + } } if (!($lockingCacheClass && class_exists($lockingCacheClass) && $lockingCacheClass::isAvailable())) { // don't fall back since the fallback might not be suitable for storing lock @@ -85,6 +108,13 @@ class Factory implements ICacheFactory { $this->profiler = $profiler; } + private function getGlobalPrefix(): ?string { + if (is_null($this->globalPrefix)) { + $this->globalPrefix = ($this->globalPrefixClosure)(); + } + return $this->globalPrefix; + } + /** * create a cache instance for storing locks * @@ -92,16 +122,21 @@ class Factory implements ICacheFactory { * @return IMemcache */ public function createLocking(string $prefix = ''): IMemcache { + $globalPrefix = $this->getGlobalPrefix(); + if (is_null($globalPrefix)) { + return new ArrayCache($prefix); + } + assert($this->lockingCacheClass !== null); - $cache = new $this->lockingCacheClass($this->globalPrefix . '/' . $prefix); + $cache = new $this->lockingCacheClass($globalPrefix . '/' . $prefix); if ($this->lockingCacheClass === Redis::class && $this->profiler->isEnabled()) { // We only support the profiler with Redis $cache = new ProfilerWrapperCache($cache, 'Locking'); $this->profiler->add($cache); } - if ($this->lockingCacheClass === Redis::class && - $this->logFile !== '' && is_writable(dirname($this->logFile)) && (!file_exists($this->logFile) || is_writable($this->logFile))) { + if ($this->lockingCacheClass === Redis::class + && $this->logFile !== '' && is_writable(dirname($this->logFile)) && (!file_exists($this->logFile) || is_writable($this->logFile))) { $cache = new LoggerWrapperCache($cache, $this->logFile); } return $cache; @@ -114,8 +149,13 @@ class Factory implements ICacheFactory { * @return ICache */ public function createDistributed(string $prefix = ''): ICache { + $globalPrefix = $this->getGlobalPrefix(); + if (is_null($globalPrefix)) { + return new ArrayCache($prefix); + } + assert($this->distributedCacheClass !== null); - $cache = new $this->distributedCacheClass($this->globalPrefix . '/' . $prefix); + $cache = new $this->distributedCacheClass($globalPrefix . '/' . $prefix); if ($this->distributedCacheClass === Redis::class && $this->profiler->isEnabled()) { // We only support the profiler with Redis $cache = new ProfilerWrapperCache($cache, 'Distributed'); @@ -136,8 +176,13 @@ class Factory implements ICacheFactory { * @return ICache */ public function createLocal(string $prefix = ''): ICache { + $globalPrefix = $this->getGlobalPrefix(); + if (is_null($globalPrefix)) { + return new ArrayCache($prefix); + } + assert($this->localCacheClass !== null); - $cache = new $this->localCacheClass($this->globalPrefix . '/' . $prefix); + $cache = new $this->localCacheClass($globalPrefix . '/' . $prefix); if ($this->localCacheClass === Redis::class && $this->profiler->isEnabled()) { // We only support the profiler with Redis $cache = new ProfilerWrapperCache($cache, 'Local'); |