aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Memcache
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/Memcache')
-rw-r--r--lib/private/Memcache/APCu.php37
-rw-r--r--lib/private/Memcache/ArrayCache.php1
-rw-r--r--lib/private/Memcache/CADTrait.php18
-rw-r--r--lib/private/Memcache/CASTrait.php1
-rw-r--r--lib/private/Memcache/Cache.php1
-rw-r--r--lib/private/Memcache/Factory.php77
-rw-r--r--lib/private/Memcache/LoggerWrapperCache.php11
-rw-r--r--lib/private/Memcache/Memcached.php5
-rw-r--r--lib/private/Memcache/NullCache.php6
-rw-r--r--lib/private/Memcache/ProfilerWrapperCache.php14
-rw-r--r--lib/private/Memcache/Redis.php16
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);
}
}