aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Memcache
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/Memcache')
-rw-r--r--lib/private/Memcache/APCu.php69
-rw-r--r--lib/private/Memcache/ArrayCache.php27
-rw-r--r--lib/private/Memcache/CADTrait.php39
-rw-r--r--lib/private/Memcache/CASTrait.php22
-rw-r--r--lib/private/Memcache/Cache.php28
-rw-r--r--lib/private/Memcache/Factory.php136
-rw-r--r--lib/private/Memcache/LoggerWrapperCache.php44
-rw-r--r--lib/private/Memcache/Memcached.php36
-rw-r--r--lib/private/Memcache/NullCache.php32
-rw-r--r--lib/private/Memcache/ProfilerWrapperCache.php48
-rw-r--r--lib/private/Memcache/Redis.php170
-rw-r--r--lib/private/Memcache/WithLocalCache.php58
12 files changed, 351 insertions, 358 deletions
diff --git a/lib/private/Memcache/APCu.php b/lib/private/Memcache/APCu.php
index 957926ab848..937f8a863ab 100644
--- a/lib/private/Memcache/APCu.php
+++ b/lib/private/Memcache/APCu.php
@@ -1,29 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Andreas Fischer <bantu@owncloud.com>
- * @author Bart Visscher <bartv@thisnet.nl>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Memcache;
@@ -46,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);
}
@@ -77,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);
}
@@ -88,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);
}
/**
@@ -114,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;
@@ -155,10 +115,7 @@ class APCu extends Cache implements IMemcache {
return false;
} elseif (!\OC::$server->get(IniGetWrapper::class)->getBool('apc.enable_cli') && \OC::$CLI) {
return false;
- } elseif (
- version_compare(phpversion('apc') ?: '0.0.0', '4.0.6') === -1 &&
- version_compare(phpversion('apcu') ?: '0.0.0', '5.1.0') === -1
- ) {
+ } elseif (version_compare(phpversion('apcu') ?: '0.0.0', '5.1.0') === -1) {
return false;
} else {
return true;
diff --git a/lib/private/Memcache/ArrayCache.php b/lib/private/Memcache/ArrayCache.php
index 13597a068b3..9b3540b771f 100644
--- a/lib/private/Memcache/ArrayCache.php
+++ b/lib/private/Memcache/ArrayCache.php
@@ -1,26 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Memcache;
@@ -75,7 +58,7 @@ class ArrayCache extends Cache implements IMemcache {
}
foreach ($this->cachedData as $key => $value) {
- if (strpos($key, $prefix) === 0) {
+ if (str_starts_with($key, $prefix)) {
$this->remove($key);
}
}
diff --git a/lib/private/Memcache/CADTrait.php b/lib/private/Memcache/CADTrait.php
index a0843fc7731..d0f6611c4f3 100644
--- a/lib/private/Memcache/CADTrait.php
+++ b/lib/private/Memcache/CADTrait.php
@@ -1,23 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Robin Appelman <robin@icewind.nl>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Memcache;
@@ -50,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 13688651043..8c2d2a46b19 100644
--- a/lib/private/Memcache/CASTrait.php
+++ b/lib/private/Memcache/CASTrait.php
@@ -1,23 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Robin Appelman <robin@icewind.nl>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Memcache;
diff --git a/lib/private/Memcache/Cache.php b/lib/private/Memcache/Cache.php
index 1d54a705098..774769b25fe 100644
--- a/lib/private/Memcache/Cache.php
+++ b/lib/private/Memcache/Cache.php
@@ -1,29 +1,15 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Robin McCorkell <robin@mccorkell.me.uk>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Memcache;
+/**
+ * @template-implements \ArrayAccess<string,mixed>
+ */
abstract class Cache implements \ArrayAccess, \OCP\ICache {
/**
* @var string $prefix
diff --git a/lib/private/Memcache/Factory.php b/lib/private/Memcache/Factory.php
index 604f764c03c..b54189937fc 100644
--- a/lib/private/Memcache/Factory.php
+++ b/lib/private/Memcache/Factory.php
@@ -1,46 +1,24 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Markus Goetz <markus@woboq.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Richard Steinmetz <richard@steinmetz.cloud>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Robin McCorkell <robin@mccorkell.me.uk>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Stefan Weil <sw@weilnetz.de>
- * @author Vincent Petry <vincent@nextcloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Memcache;
-use OCP\Profiler\IProfiler;
+use Closure;
+use OCP\Cache\CappedMemoryCache;
use OCP\ICache;
use OCP\ICacheFactory;
use OCP\IMemcache;
+use OCP\Profiler\IProfiler;
use Psr\Log\LoggerInterface;
class Factory implements ICacheFactory {
public const NULL_CACHE = NullCache::class;
- private string $globalPrefix;
+ private ?string $globalPrefix = null;
private LoggerInterface $logger;
@@ -64,42 +42,65 @@ 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 = '') {
- $this->logger = $logger;
+ 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;
}
+ $localCacheClass = ltrim($localCacheClass, '\\');
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 fallback since the fallback might not be suitable for storing lock
+ // don't fall back since the fallback might not be suitable for storing lock
$lockingCacheClass = self::NULL_CACHE;
}
+ $lockingCacheClass = ltrim($lockingCacheClass, '\\');
$this->localCacheClass = $localCacheClass;
$this->distributedCacheClass = $distributedCacheClass;
@@ -107,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
*
@@ -114,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);
- if ($this->profiler->isEnabled() && $this->lockingCacheClass === '\OC\Memcache\Redis') {
+ $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;
@@ -136,9 +149,14 @@ 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);
- if ($this->profiler->isEnabled() && $this->distributedCacheClass === '\OC\Memcache\Redis') {
+ $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');
$this->profiler->add($cache);
@@ -158,9 +176,14 @@ 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);
- if ($this->profiler->isEnabled() && $this->localCacheClass === '\OC\Memcache\Redis') {
+ $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');
$this->profiler->add($cache);
@@ -179,16 +202,11 @@ class Factory implements ICacheFactory {
* @return bool
*/
public function isAvailable(): bool {
- return ($this->distributedCacheClass !== self::NULL_CACHE);
+ return $this->distributedCacheClass !== self::NULL_CACHE;
}
- /**
- * @see \OC\Memcache\Factory::createLocal()
- * @param string $prefix
- * @return ICache
- */
- public function createLowLatency(string $prefix = ''): ICache {
- return $this->createLocal($prefix);
+ public function createInMemory(int $capacity = 512): ICache {
+ return new CappedMemoryCache($capacity);
}
/**
@@ -197,6 +215,6 @@ class Factory implements ICacheFactory {
* @return bool
*/
public function isLocalCacheAvailable(): bool {
- return ($this->localCacheClass !== self::NULL_CACHE);
+ return $this->localCacheClass !== self::NULL_CACHE;
}
}
diff --git a/lib/private/Memcache/LoggerWrapperCache.php b/lib/private/Memcache/LoggerWrapperCache.php
index 55c0e76db79..c2a06731910 100644
--- a/lib/private/Memcache/LoggerWrapperCache.php
+++ b/lib/private/Memcache/LoggerWrapperCache.php
@@ -2,25 +2,8 @@
declare(strict_types = 1);
/**
- * @copyright 2022 Carl Schwan <carl@carlschwan.eu>
- *
- * @author Carl Schwan <carl@carlschwan.eu>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Memcache;
@@ -75,7 +58,7 @@ class LoggerWrapperCache extends Cache implements IMemcacheTTL {
FILE_APPEND
);
- return $this->wrappedCache->set($key, $value, $$ttl);
+ return $this->wrappedCache->set($key, $value, $ttl);
}
/** @inheritDoc */
@@ -167,10 +150,29 @@ class LoggerWrapperCache extends Cache implements IMemcacheTTL {
}
/** @inheritDoc */
- public function setTTL($key, $ttl) {
+ 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);
}
+ public function getTTL(string $key): int|false {
+ return $this->wrappedCache->getTTL($key);
+ }
+
+ public function compareSetTTL(string $key, mixed $value, int $ttl): bool {
+ return $this->wrappedCache->compareSetTTL($key, $value, $ttl);
+ }
+
public static function isAvailable(): bool {
return true;
}
diff --git a/lib/private/Memcache/Memcached.php b/lib/private/Memcache/Memcached.php
index 7d512d4d1ae..d8b624a978a 100644
--- a/lib/private/Memcache/Memcached.php
+++ b/lib/private/Memcache/Memcached.php
@@ -1,33 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Andreas Fischer <bantu@owncloud.com>
- * @author Bart Visscher <bartv@thisnet.nl>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Jörn Friedrich Dreyer <jfd@butonic.de>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Robin McCorkell <robin@mccorkell.me.uk>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @author Victor Dubiniuk <dubiniuk@owncloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Memcache;
@@ -73,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 fc41595dfe1..eac1e6ddadc 100644
--- a/lib/private/Memcache/NullCache.php
+++ b/lib/private/Memcache/NullCache.php
@@ -1,28 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Robin McCorkell <robin@mccorkell.me.uk>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @author Vincent Petry <vincent@nextcloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Memcache;
@@ -63,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 6e76989dddd..97d9d828a32 100644
--- a/lib/private/Memcache/ProfilerWrapperCache.php
+++ b/lib/private/Memcache/ProfilerWrapperCache.php
@@ -2,25 +2,8 @@
declare(strict_types = 1);
/**
- * @copyright 2022 Carl Schwan <carl@carlschwan.eu>
- *
- * @author Carl Schwan <carl@carlschwan.eu>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Memcache;
@@ -32,9 +15,10 @@ use OCP\IMemcacheTTL;
/**
* Cache wrapper that logs profiling information
+ * @template-implements \ArrayAccess<string,mixed>
*/
class ProfilerWrapperCache extends AbstractDataCollector implements IMemcacheTTL, \ArrayAccess {
- /** @var Redis $wrappedCache*/
+ /** @var Redis $wrappedCache */
protected $wrappedCache;
/** @var string $prefix */
@@ -183,10 +167,30 @@ class ProfilerWrapperCache extends AbstractDataCollector implements IMemcacheTTL
}
/** @inheritDoc */
- public function setTTL($key, $ttl) {
+ 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);
}
+ public function getTTL(string $key): int|false {
+ return $this->wrappedCache->getTTL($key);
+ }
+
+ public function compareSetTTL(string $key, mixed $value, int $ttl): bool {
+ return $this->wrappedCache->compareSetTTL($key, $value, $ttl);
+ }
+
public function offsetExists($offset): bool {
return $this->hasKey($offset);
}
@@ -207,7 +211,7 @@ class ProfilerWrapperCache extends AbstractDataCollector implements IMemcacheTTL
$this->remove($offset);
}
- public function collect(Request $request, Response $response, \Throwable $exception = null): void {
+ public function collect(Request $request, Response $response, ?\Throwable $exception = null): void {
// Nothing to do here $data is already ready
}
diff --git a/lib/private/Memcache/Redis.php b/lib/private/Memcache/Redis.php
index f4094e0bef6..f8c51570c4f 100644
--- a/lib/private/Memcache/Redis.php
+++ b/lib/private/Memcache/Redis.php
@@ -1,37 +1,41 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Jörn Friedrich Dreyer <jfd@butonic.de>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Robin McCorkell <robin@mccorkell.me.uk>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Stefan Weil <sw@weilnetz.de>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Memcache;
use OCP\IMemcacheTTL;
class Redis extends Cache implements IMemcacheTTL {
+ /** name => [script, sha1] */
+ public const LUA_SCRIPTS = [
+ 'dec' => [
+ 'if redis.call("exists", KEYS[1]) == 1 then return redis.call("decrby", KEYS[1], ARGV[1]) else return "NEX" end',
+ '720b40cb66cef1579f2ef16ec69b3da8c85510e9',
+ ],
+ 'cas' => [
+ 'if redis.call("get", KEYS[1]) == ARGV[1] then redis.call("set", KEYS[1], ARGV[2]) return 1 else return 0 end',
+ '94eac401502554c02b811e3199baddde62d976d4',
+ ],
+ 'cad' => [
+ '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 MAX_TTL = 30 * 24 * 60 * 60; // 1 month
+
/**
* @var \Redis|\RedisCluster $cache
*/
@@ -47,26 +51,28 @@ class Redis extends Cache implements IMemcacheTTL {
*/
public function getCache() {
if (is_null(self::$cache)) {
- self::$cache = \OC::$server->getGetRedisFactory()->getInstance();
+ self::$cache = \OC::$server->get('RedisFactory')->getInstance();
}
return self::$cache;
}
public function get($key) {
$result = $this->getCache()->get($this->getPrefix() . $key);
- if ($result === false && !$this->getCache()->exists($this->getPrefix() . $key)) {
+ if ($result === false) {
return null;
- } else {
- return json_decode($result, true);
}
+
+ return self::decodeValue($result);
}
public function set($key, $value, $ttl = 0) {
- if ($ttl > 0) {
- return $this->getCache()->setex($this->getPrefix() . $key, $ttl, json_encode($value));
- } else {
- return $this->getCache()->set($this->getPrefix() . $key, json_encode($value));
+ $value = self::encodeValue($value);
+ if ($ttl === 0) {
+ // having infinite TTL can lead to leaked keys as the prefix changes with version upgrades
+ $ttl = self::DEFAULT_TTL;
}
+ $ttl = min($ttl, self::MAX_TTL);
+ return $this->getCache()->setex($this->getPrefix() . $key, $ttl, $value);
}
public function hasKey($key) {
@@ -74,7 +80,7 @@ class Redis extends Cache implements IMemcacheTTL {
}
public function remove($key) {
- if ($this->getCache()->del($this->getPrefix() . $key)) {
+ if ($this->getCache()->unlink($this->getPrefix() . $key)) {
return true;
} else {
return false;
@@ -82,6 +88,7 @@ class Redis extends Cache implements IMemcacheTTL {
}
public function clear($prefix = '') {
+ // TODO: this is slow and would fail with Redis cluster
$prefix = $this->getPrefix() . $prefix . '*';
$keys = $this->getCache()->keys($prefix);
$deleted = $this->getCache()->del($keys);
@@ -98,17 +105,17 @@ class Redis extends Cache implements IMemcacheTTL {
* @return bool
*/
public function add($key, $value, $ttl = 0) {
- // don't encode ints for inc/dec
- if (!is_int($value)) {
- $value = json_encode($value);
+ $value = self::encodeValue($value);
+ if ($ttl === 0) {
+ // having infinite TTL can lead to leaked keys as the prefix changes with version upgrades
+ $ttl = self::DEFAULT_TTL;
}
+ $ttl = min($ttl, self::MAX_TTL);
$args = ['nx'];
- if ($ttl !== 0 && is_int($ttl)) {
- $args['ex'] = $ttl;
- }
+ $args['ex'] = $ttl;
- return $this->getCache()->set($this->getPrefix() . $key, (string)$value, $args);
+ return $this->getCache()->set($this->getPrefix() . $key, $value, $args);
}
/**
@@ -130,10 +137,8 @@ class Redis extends Cache implements IMemcacheTTL {
* @return int | bool
*/
public function dec($key, $step = 1) {
- if (!$this->hasKey($key)) {
- return false;
- }
- return $this->getCache()->decrBy($this->getPrefix() . $key, $step);
+ $res = $this->evalLua('dec', [$key], [$step]);
+ return ($res === 'NEX') ? false : $res;
}
/**
@@ -145,18 +150,10 @@ class Redis extends Cache implements IMemcacheTTL {
* @return bool
*/
public function cas($key, $old, $new) {
- if (!is_int($new)) {
- $new = json_encode($new);
- }
- $this->getCache()->watch($this->getPrefix() . $key);
- if ($this->get($key) === $old) {
- $result = $this->getCache()->multi()
- ->set($this->getPrefix() . $key, $new)
- ->exec();
- return $result !== false;
- }
- $this->getCache()->unwatch();
- return false;
+ $old = self::encodeValue($old);
+ $new = self::encodeValue($new);
+
+ return $this->evalLua('cas', [$key], [$old, $new]) > 0;
}
/**
@@ -167,22 +164,59 @@ class Redis extends Cache implements IMemcacheTTL {
* @return bool
*/
public function cad($key, $old) {
- $this->getCache()->watch($this->getPrefix() . $key);
- if ($this->get($key) === $old) {
- $result = $this->getCache()->multi()
- ->del($this->getPrefix() . $key)
- ->exec();
- return $result !== false;
- }
- $this->getCache()->unwatch();
- return false;
+ $old = self::encodeValue($old);
+
+ 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
+ $ttl = self::DEFAULT_TTL;
+ }
+ $ttl = min($ttl, self::MAX_TTL);
$this->getCache()->expire($this->getPrefix() . $key, $ttl);
}
+ public function getTTL(string $key): int|false {
+ $ttl = $this->getCache()->ttl($this->getPrefix() . $key);
+ return $ttl > 0 ? (int)$ttl : false;
+ }
+
+ public function compareSetTTL(string $key, mixed $value, int $ttl): bool {
+ $value = self::encodeValue($value);
+
+ return $this->evalLua('caSetTtl', [$key], [$value, $ttl]) > 0;
+ }
+
public static function isAvailable(): bool {
- return \OC::$server->getGetRedisFactory()->isAvailable();
+ return \OC::$server->get('RedisFactory')->isAvailable();
+ }
+
+ protected function evalLua(string $scriptName, array $keys, array $args) {
+ $keys = array_map(fn ($key) => $this->getPrefix() . $key, $keys);
+ $args = array_merge($keys, $args);
+ $script = self::LUA_SCRIPTS[$scriptName];
+
+ $result = $this->getCache()->evalSha($script[1], $args, count($keys));
+ if ($result === false) {
+ $result = $this->getCache()->eval($script[0], $args, count($keys));
+ }
+
+ return $result;
+ }
+
+ protected static function encodeValue(mixed $value): string {
+ 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);
}
}
diff --git a/lib/private/Memcache/WithLocalCache.php b/lib/private/Memcache/WithLocalCache.php
new file mode 100644
index 00000000000..0fc5d310801
--- /dev/null
+++ b/lib/private/Memcache/WithLocalCache.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Memcache;
+
+use OCP\Cache\CappedMemoryCache;
+use OCP\ICache;
+
+/**
+ * Wrap a cache instance with an extra later of local, in-memory caching
+ */
+class WithLocalCache implements ICache {
+ private ICache $inner;
+ private CappedMemoryCache $cached;
+
+ public function __construct(ICache $inner, int $localCapacity = 512) {
+ $this->inner = $inner;
+ $this->cached = new CappedMemoryCache($localCapacity);
+ }
+
+ public function get($key) {
+ if (isset($this->cached[$key])) {
+ return $this->cached[$key];
+ } else {
+ $value = $this->inner->get($key);
+ if (!is_null($value)) {
+ $this->cached[$key] = $value;
+ }
+ return $value;
+ }
+ }
+
+ public function set($key, $value, $ttl = 0) {
+ $this->cached[$key] = $value;
+ return $this->inner->set($key, $value, $ttl);
+ }
+
+ public function hasKey($key) {
+ return isset($this->cached[$key]) || $this->inner->hasKey($key);
+ }
+
+ public function remove($key) {
+ unset($this->cached[$key]);
+ return $this->inner->remove($key);
+ }
+
+ public function clear($prefix = '') {
+ $this->cached->clear();
+ return $this->inner->clear($prefix);
+ }
+
+ public static function isAvailable(): bool {
+ return false;
+ }
+}