diff options
Diffstat (limited to 'lib/private/Memcache')
-rw-r--r-- | lib/private/Memcache/APCu.php | 37 | ||||
-rw-r--r-- | lib/private/Memcache/ArrayCache.php | 1 | ||||
-rw-r--r-- | lib/private/Memcache/CADTrait.php | 18 | ||||
-rw-r--r-- | lib/private/Memcache/CASTrait.php | 1 | ||||
-rw-r--r-- | lib/private/Memcache/Cache.php | 1 | ||||
-rw-r--r-- | lib/private/Memcache/Factory.php | 77 | ||||
-rw-r--r-- | lib/private/Memcache/LoggerWrapperCache.php | 11 | ||||
-rw-r--r-- | lib/private/Memcache/Memcached.php | 5 | ||||
-rw-r--r-- | lib/private/Memcache/NullCache.php | 6 | ||||
-rw-r--r-- | lib/private/Memcache/ProfilerWrapperCache.php | 14 | ||||
-rw-r--r-- | lib/private/Memcache/Redis.php | 16 |
11 files changed, 137 insertions, 50 deletions
diff --git a/lib/private/Memcache/APCu.php b/lib/private/Memcache/APCu.php index 7f6a73354ee..937f8a863ab 100644 --- a/lib/private/Memcache/APCu.php +++ b/lib/private/Memcache/APCu.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -25,6 +26,9 @@ class APCu extends Cache implements IMemcache { } public function set($key, $value, $ttl = 0) { + if ($ttl === 0) { + $ttl = self::DEFAULT_TTL; + } return apcu_store($this->getPrefix() . $key, $value, $ttl); } @@ -56,6 +60,9 @@ class APCu extends Cache implements IMemcache { * @return bool */ public function add($key, $value, $ttl = 0) { + if ($ttl === 0) { + $ttl = self::DEFAULT_TTL; + } return apcu_add($this->getPrefix() . $key, $value, $ttl); } @@ -67,22 +74,8 @@ class APCu extends Cache implements IMemcache { * @return int | bool */ public function inc($key, $step = 1) { - $this->add($key, 0); - /** - * TODO - hack around a PHP 7 specific issue in APCu - * - * on PHP 7 the apcu_inc method on a non-existing object will increment - * "0" and result in "1" as value - therefore we check for existence - * first - * - * on PHP 5.6 this is not the case - * - * see https://github.com/krakjoe/apcu/issues/183#issuecomment-244038221 - * for details - */ - return apcu_exists($this->getPrefix() . $key) - ? apcu_inc($this->getPrefix() . $key, $step) - : false; + $success = null; + return apcu_inc($this->getPrefix() . $key, $step, $success, self::DEFAULT_TTL); } /** @@ -93,18 +86,6 @@ class APCu extends Cache implements IMemcache { * @return int | bool */ public function dec($key, $step = 1) { - /** - * TODO - hack around a PHP 7 specific issue in APCu - * - * on PHP 7 the apcu_dec method on a non-existing object will decrement - * "0" and result in "-1" as value - therefore we check for existence - * first - * - * on PHP 5.6 this is not the case - * - * see https://github.com/krakjoe/apcu/issues/183#issuecomment-244038221 - * for details - */ return apcu_exists($this->getPrefix() . $key) ? apcu_dec($this->getPrefix() . $key, $step) : false; diff --git a/lib/private/Memcache/ArrayCache.php b/lib/private/Memcache/ArrayCache.php index 4cac60c272c..9b3540b771f 100644 --- a/lib/private/Memcache/ArrayCache.php +++ b/lib/private/Memcache/ArrayCache.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/Memcache/CADTrait.php b/lib/private/Memcache/CADTrait.php index bb010e238dc..d0f6611c4f3 100644 --- a/lib/private/Memcache/CADTrait.php +++ b/lib/private/Memcache/CADTrait.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -35,4 +36,21 @@ trait CADTrait { return false; } } + + public function ncad(string $key, mixed $old): bool { + //no native cad, emulate with locking + if ($this->add($key . '_lock', true)) { + $value = $this->get($key); + if ($value !== null && $value !== $old) { + $this->remove($key); + $this->remove($key . '_lock'); + return true; + } else { + $this->remove($key . '_lock'); + return false; + } + } else { + return false; + } + } } diff --git a/lib/private/Memcache/CASTrait.php b/lib/private/Memcache/CASTrait.php index 945f1539f99..8c2d2a46b19 100644 --- a/lib/private/Memcache/CASTrait.php +++ b/lib/private/Memcache/CASTrait.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/Memcache/Cache.php b/lib/private/Memcache/Cache.php index 2a2a6e2a23f..774769b25fe 100644 --- a/lib/private/Memcache/Cache.php +++ b/lib/private/Memcache/Cache.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. 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'); diff --git a/lib/private/Memcache/LoggerWrapperCache.php b/lib/private/Memcache/LoggerWrapperCache.php index 11497e2a5d8..c2a06731910 100644 --- a/lib/private/Memcache/LoggerWrapperCache.php +++ b/lib/private/Memcache/LoggerWrapperCache.php @@ -150,6 +150,17 @@ class LoggerWrapperCache extends Cache implements IMemcacheTTL { } /** @inheritDoc */ + public function ncad(string $key, mixed $old): bool { + file_put_contents( + $this->logFile, + $this->getNameSpace() . '::ncad::' . $key . "\n", + FILE_APPEND + ); + + return $this->wrappedCache->cad($key, $old); + } + + /** @inheritDoc */ public function setTTL(string $key, int $ttl) { $this->wrappedCache->setTTL($key, $ttl); } diff --git a/lib/private/Memcache/Memcached.php b/lib/private/Memcache/Memcached.php index 620013feda6..d8b624a978a 100644 --- a/lib/private/Memcache/Memcached.php +++ b/lib/private/Memcache/Memcached.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -48,8 +49,8 @@ class Memcached extends Cache implements IMemcache { * @psalm-suppress TypeDoesNotContainType */ if (\Memcached::HAVE_IGBINARY) { - $defaultOptions[\Memcached::OPT_SERIALIZER] = - \Memcached::SERIALIZER_IGBINARY; + $defaultOptions[\Memcached::OPT_SERIALIZER] + = \Memcached::SERIALIZER_IGBINARY; } $options = \OC::$server->getConfig()->getSystemValue('memcached_options', []); if (is_array($options)) { diff --git a/lib/private/Memcache/NullCache.php b/lib/private/Memcache/NullCache.php index ab5c491913a..eac1e6ddadc 100644 --- a/lib/private/Memcache/NullCache.php +++ b/lib/private/Memcache/NullCache.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -43,6 +44,11 @@ class NullCache extends Cache implements \OCP\IMemcache { return true; } + public function ncad(string $key, mixed $old): bool { + return true; + } + + public function clear($prefix = '') { return true; } diff --git a/lib/private/Memcache/ProfilerWrapperCache.php b/lib/private/Memcache/ProfilerWrapperCache.php index 84e3d880a0c..97d9d828a32 100644 --- a/lib/private/Memcache/ProfilerWrapperCache.php +++ b/lib/private/Memcache/ProfilerWrapperCache.php @@ -18,7 +18,7 @@ use OCP\IMemcacheTTL; * @template-implements \ArrayAccess<string,mixed> */ class ProfilerWrapperCache extends AbstractDataCollector implements IMemcacheTTL, \ArrayAccess { - /** @var Redis $wrappedCache*/ + /** @var Redis $wrappedCache */ protected $wrappedCache; /** @var string $prefix */ @@ -167,6 +167,18 @@ class ProfilerWrapperCache extends AbstractDataCollector implements IMemcacheTTL } /** @inheritDoc */ + public function ncad(string $key, mixed $old): bool { + $start = microtime(true); + $ret = $this->wrappedCache->ncad($key, $old); + $this->data['queries'][] = [ + 'start' => $start, + 'end' => microtime(true), + 'op' => $this->getPrefix() . '::ncad::' . $key, + ]; + return $ret; + } + + /** @inheritDoc */ public function setTTL(string $key, int $ttl) { $this->wrappedCache->setTTL($key, $ttl); } diff --git a/lib/private/Memcache/Redis.php b/lib/private/Memcache/Redis.php index cbafadc3b1b..f8c51570c4f 100644 --- a/lib/private/Memcache/Redis.php +++ b/lib/private/Memcache/Redis.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -23,13 +24,16 @@ class Redis extends Cache implements IMemcacheTTL { 'if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end', 'cf0e94b2e9ffc7e04395cf88f7583fc309985910', ], + 'ncad' => [ + 'if redis.call("get", KEYS[1]) ~= ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end', + '75526f8048b13ce94a41b58eee59c664b4990ab2', + ], 'caSetTtl' => [ 'if redis.call("get", KEYS[1]) == ARGV[1] then redis.call("expire", KEYS[1], ARGV[2]) return 1 else return 0 end', 'fa4acbc946d23ef41d7d3910880b60e6e4972d72', ], ]; - private const DEFAULT_TTL = 24 * 60 * 60; // 1 day private const MAX_TTL = 30 * 24 * 60 * 60; // 1 month /** @@ -165,6 +169,12 @@ class Redis extends Cache implements IMemcacheTTL { return $this->evalLua('cad', [$key], [$old]) > 0; } + public function ncad(string $key, mixed $old): bool { + $old = self::encodeValue($old); + + return $this->evalLua('ncad', [$key], [$old]) > 0; + } + public function setTTL($key, $ttl) { if ($ttl === 0) { // having infinite TTL can lead to leaked keys as the prefix changes with version upgrades @@ -203,10 +213,10 @@ class Redis extends Cache implements IMemcacheTTL { } protected static function encodeValue(mixed $value): string { - return is_int($value) ? (string) $value : json_encode($value); + return is_int($value) ? (string)$value : json_encode($value); } protected static function decodeValue(string $value): mixed { - return is_numeric($value) ? (int) $value : json_decode($value, true); + return is_numeric($value) ? (int)$value : json_decode($value, true); } } |