aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Memcache/Factory.php
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/Memcache/Factory.php')
-rw-r--r--lib/private/Memcache/Factory.php108
1 files changed, 64 insertions, 44 deletions
diff --git a/lib/private/Memcache/Factory.php b/lib/private/Memcache/Factory.php
index ab8fcea4e6a..b54189937fc 100644
--- a/lib/private/Memcache/Factory.php
+++ b/lib/private/Memcache/Factory.php
@@ -1,36 +1,13 @@
<?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 Closure;
use OCP\Cache\CappedMemoryCache;
use OCP\ICache;
use OCP\ICacheFactory;
@@ -41,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;
@@ -65,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;
@@ -84,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
@@ -110,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
*
@@ -117,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;
@@ -139,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');
@@ -161,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');