fix: Add a factory for Memcached object instead of a static var

Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
This commit is contained in:
Côme Chilliet 2026-03-26 23:15:49 +01:00
parent ac8cd6846d
commit 2aeac5ed7c
No known key found for this signature in database
GPG key ID: A3E2F658B28C760A
4 changed files with 100 additions and 72 deletions

View file

@ -1939,6 +1939,7 @@ return array(
'OC\\Memcache\\Factory' => $baseDir . '/lib/private/Memcache/Factory.php',
'OC\\Memcache\\LoggerWrapperCache' => $baseDir . '/lib/private/Memcache/LoggerWrapperCache.php',
'OC\\Memcache\\Memcached' => $baseDir . '/lib/private/Memcache/Memcached.php',
'OC\\Memcache\\MemcachedFactory' => $baseDir . '/lib/private/Memcache/MemcachedFactory.php',
'OC\\Memcache\\NullCache' => $baseDir . '/lib/private/Memcache/NullCache.php',
'OC\\Memcache\\ProfilerWrapperCache' => $baseDir . '/lib/private/Memcache/ProfilerWrapperCache.php',
'OC\\Memcache\\Redis' => $baseDir . '/lib/private/Memcache/Redis.php',

View file

@ -1980,6 +1980,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Memcache\\Factory' => __DIR__ . '/../../..' . '/lib/private/Memcache/Factory.php',
'OC\\Memcache\\LoggerWrapperCache' => __DIR__ . '/../../..' . '/lib/private/Memcache/LoggerWrapperCache.php',
'OC\\Memcache\\Memcached' => __DIR__ . '/../../..' . '/lib/private/Memcache/Memcached.php',
'OC\\Memcache\\MemcachedFactory' => __DIR__ . '/../../..' . '/lib/private/Memcache/MemcachedFactory.php',
'OC\\Memcache\\NullCache' => __DIR__ . '/../../..' . '/lib/private/Memcache/NullCache.php',
'OC\\Memcache\\ProfilerWrapperCache' => __DIR__ . '/../../..' . '/lib/private/Memcache/ProfilerWrapperCache.php',
'OC\\Memcache\\Redis' => __DIR__ . '/../../..' . '/lib/private/Memcache/Redis.php',

View file

@ -8,73 +8,18 @@
namespace OC\Memcache;
use OC\SystemConfig;
use OCP\HintException;
use OCP\IConfig;
use OCP\IMemcache;
use OCP\Server;
class Memcached extends Cache implements IMemcache {
use CASTrait;
/**
* @var \Memcached $cache
*/
private static $cache = null;
private \Memcached $cache;
use CADTrait;
public function __construct($prefix = '') {
parent::__construct($prefix);
if (is_null(self::$cache)) {
self::$cache = new \Memcached();
$defaultOptions = [
\Memcached::OPT_CONNECT_TIMEOUT => 50,
\Memcached::OPT_RETRY_TIMEOUT => 50,
\Memcached::OPT_SEND_TIMEOUT => 50,
\Memcached::OPT_RECV_TIMEOUT => 50,
\Memcached::OPT_POLL_TIMEOUT => 50,
// Enable compression
\Memcached::OPT_COMPRESSION => true,
// Turn on consistent hashing
\Memcached::OPT_LIBKETAMA_COMPATIBLE => true,
// Enable Binary Protocol
\Memcached::OPT_BINARY_PROTOCOL => true,
];
/**
* By default enable igbinary serializer if available
*
* Psalm checks depend on if igbinary is installed or not with memcached
* @psalm-suppress RedundantCondition
* @psalm-suppress TypeDoesNotContainType
*/
if (\Memcached::HAVE_IGBINARY) {
$defaultOptions[\Memcached::OPT_SERIALIZER]
= \Memcached::SERIALIZER_IGBINARY;
}
$options = Server::get(IConfig::class)->getSystemValue('memcached_options', []);
if (is_array($options)) {
$options = $options + $defaultOptions;
self::$cache->setOptions($options);
} else {
throw new HintException("Expected 'memcached_options' config to be an array, got $options");
}
$servers = Server::get(SystemConfig::class)->getValue('memcached_servers');
if (!$servers) {
$server = Server::get(SystemConfig::class)->getValue('memcached_server');
if ($server) {
$servers = [$server];
} else {
$servers = [['localhost', 11211]];
}
}
self::$cache->addServers($servers);
}
$this->cache = \OCP\Server::get(MemcachedFactory::class)->getInstance();
}
/**
@ -86,8 +31,8 @@ class Memcached extends Cache implements IMemcache {
#[\Override]
public function get($key) {
$result = self::$cache->get($this->getNameSpace() . $key);
if ($result === false && self::$cache->getResultCode() === \Memcached::RES_NOTFOUND) {
$result = $this->cache->get($this->getNameSpace() . $key);
if ($result === false && $this->cache->getResultCode() === \Memcached::RES_NOTFOUND) {
return null;
} else {
return $result;
@ -97,29 +42,29 @@ class Memcached extends Cache implements IMemcache {
#[\Override]
public function set($key, $value, $ttl = 0) {
if ($ttl > 0) {
$result = self::$cache->set($this->getNameSpace() . $key, $value, $ttl);
$result = $this->cache->set($this->getNameSpace() . $key, $value, $ttl);
} else {
$result = self::$cache->set($this->getNameSpace() . $key, $value);
$result = $this->cache->set($this->getNameSpace() . $key, $value);
}
return $result || $this->isSuccess();
}
#[\Override]
public function hasKey($key) {
self::$cache->get($this->getNameSpace() . $key);
return self::$cache->getResultCode() === \Memcached::RES_SUCCESS;
$this->cache->get($this->getNameSpace() . $key);
return $this->cache->getResultCode() === \Memcached::RES_SUCCESS;
}
#[\Override]
public function remove($key) {
$result = self::$cache->delete($this->getNameSpace() . $key);
return $result || $this->isSuccess() || self::$cache->getResultCode() === \Memcached::RES_NOTFOUND;
$result = $this->cache->delete($this->getNameSpace() . $key);
return $result || $this->isSuccess() || $this->cache->getResultCode() === \Memcached::RES_NOTFOUND;
}
#[\Override]
public function clear($prefix = '') {
// Newer Memcached doesn't like getAllKeys(), flush everything
self::$cache->flush();
$this->cache->flush();
return true;
}
@ -133,7 +78,7 @@ class Memcached extends Cache implements IMemcache {
*/
#[\Override]
public function add($key, $value, $ttl = 0) {
$result = self::$cache->add($this->getPrefix() . $key, $value, $ttl);
$result = $this->cache->add($this->getPrefix() . $key, $value, $ttl);
return $result || $this->isSuccess();
}
@ -147,9 +92,9 @@ class Memcached extends Cache implements IMemcache {
#[\Override]
public function inc($key, $step = 1) {
$this->add($key, 0);
$result = self::$cache->increment($this->getPrefix() . $key, $step);
$result = $this->cache->increment($this->getPrefix() . $key, $step);
if (self::$cache->getResultCode() !== \Memcached::RES_SUCCESS) {
if ($this->cache->getResultCode() !== \Memcached::RES_SUCCESS) {
return false;
}
@ -165,9 +110,9 @@ class Memcached extends Cache implements IMemcache {
*/
#[\Override]
public function dec($key, $step = 1) {
$result = self::$cache->decrement($this->getPrefix() . $key, $step);
$result = $this->cache->decrement($this->getPrefix() . $key, $step);
if (self::$cache->getResultCode() !== \Memcached::RES_SUCCESS) {
if ($this->cache->getResultCode() !== \Memcached::RES_SUCCESS) {
return false;
}
@ -180,6 +125,6 @@ class Memcached extends Cache implements IMemcache {
}
private function isSuccess(): bool {
return self::$cache->getResultCode() === \Memcached::RES_SUCCESS;
return $this->cache->getResultCode() === \Memcached::RES_SUCCESS;
}
}

View file

@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
/**
* 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 OC\SystemConfig;
use OCP\HintException;
use OCP\IConfig;
use OCP\Server;
/**
* Factory for \Memcached instance, to create a singleton
*/
class MemcachedFactory {
private ?\Memcached $instance = null;
public function getInstance(): \Memcached {
if ($this->instance === null) {
$this->init();
}
return $this->instance;
}
/** @psalm-assert \Memcached $this->instance */
public function init(): void {
$this->instance = new \Memcached();
$defaultOptions = [
\Memcached::OPT_CONNECT_TIMEOUT => 50,
\Memcached::OPT_RETRY_TIMEOUT => 50,
\Memcached::OPT_SEND_TIMEOUT => 50,
\Memcached::OPT_RECV_TIMEOUT => 50,
\Memcached::OPT_POLL_TIMEOUT => 50,
// Enable compression
\Memcached::OPT_COMPRESSION => true,
// Turn on consistent hashing
\Memcached::OPT_LIBKETAMA_COMPATIBLE => true,
// Enable Binary Protocol
\Memcached::OPT_BINARY_PROTOCOL => true,
];
/**
* By default enable igbinary serializer if available
*
* Psalm checks depend on if igbinary is installed or not with memcached
* @psalm-suppress RedundantCondition
* @psalm-suppress TypeDoesNotContainType
*/
if (\Memcached::HAVE_IGBINARY) {
$defaultOptions[\Memcached::OPT_SERIALIZER]
= \Memcached::SERIALIZER_IGBINARY;
}
$options = Server::get(IConfig::class)->getSystemValue('memcached_options', []);
if (is_array($options)) {
$options = $options + $defaultOptions;
$this->instance->setOptions($options);
} else {
throw new HintException("Expected 'memcached_options' config to be an array, got $options");
}
$servers = Server::get(SystemConfig::class)->getValue('memcached_servers');
if (!$servers) {
$server = Server::get(SystemConfig::class)->getValue('memcached_server');
if ($server) {
$servers = [$server];
} else {
$servers = [['localhost', 11211]];
}
}
$this->instance->addServers($servers);
}
}