From 0e4933e6d248ab383e6221e045fec2eaf3d17082 Mon Sep 17 00:00:00 2001 From: Robin McCorkell Date: Wed, 14 Jan 2015 18:25:00 +0000 Subject: [PATCH] Refactor \OC\Memcache\Factory Caches divided up into two groups: distributed and local. 'Low latency' is an alias for local caches, while the standard `create()` call tries to get distributed caches first, then local caches. Memcache backend is set in `config.php`, with the keys `memcache.local` and `memcache.distributed`. If not set, `memcache.distributed` defaults to the value of `memcache.local`. --- config/config.sample.php | 30 +++++++-- lib/base.php | 4 +- lib/private/memcache/factory.php | 93 +++++++++++++++----------- lib/private/server.php | 6 +- tests/lib/memcache/factory.php | 110 +++++++++++++++++++++++++++++++ 5 files changed, 199 insertions(+), 44 deletions(-) create mode 100644 tests/lib/memcache/factory.php diff --git a/config/config.sample.php b/config/config.sample.php index 4b902b306b..94284e28dd 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -782,10 +782,33 @@ $CONFIG = array( 'cipher' => 'AES-256-CFB', +/** + * Memory caching backend configuration + * + * Available cache backends: + * - \OC\Memcache\APC Alternative PHP Cache backend + * - \OC\Memcache\APCu APC user backend + * - \OC\Memcache\ArrayCache In-memory array-based backend (not recommended) + * - \OC\Memcache\Memcached Memcached backend + * - \OC\Memcache\Redis Redis backend + * - \OC\Memcache\XCache XCache backend + */ + +/** + * Memory caching backend for locally stored data + * Used for host-specific data, e.g. file paths + */ +'memcache.local' => '\OC\Memcache\APCu', + +/** + * Memory caching backend for distributed data + * Used for installation-specific data, e.g. database caching + * If unset, defaults to the value of memcache.local + */ +'memcache.distributed' => '\OC\Memcache\Memcached', + /** * Connection details for redis to use for memory caching. - * Redis is only used if other memory cache options (xcache, apc, apcu) are - * not available. */ 'redis' => array( 'host' => 'localhost', // can also be a unix domain socket: '/tmp/redis.sock' @@ -795,8 +818,6 @@ $CONFIG = array( /** * Server details for one or more memcached servers to use for memory caching. - * Memcache is only used if other memory cache options (xcache, apc, apcu, - * redis) are not available. */ 'memcached_servers' => array( // hostname, port and optional weight. Also see: @@ -806,6 +827,7 @@ $CONFIG = array( //array('other.host.local', 11211), ), + /** * Location of the cache folder, defaults to ``data/$user/cache`` where * ``$user`` is the current user. When specified, the format will change to diff --git a/lib/base.php b/lib/base.php index 84616090ec..e957d6be08 100644 --- a/lib/base.php +++ b/lib/base.php @@ -726,8 +726,8 @@ class OC { $instanceId = \OC::$server->getSystemConfig()->getValue('instanceid', null); if ($instanceId) { try { - $memcacheFactory = new \OC\Memcache\Factory($instanceId); - self::$loader->setMemoryCache($memcacheFactory->createLowLatency('Autoloader')); + $memcacheFactory = \OC::$server->getMemCacheFactory(); + self::$loader->setMemoryCache($memcacheFactory->createLocal('Autoloader')); } catch (\Exception $ex) { } } diff --git a/lib/private/memcache/factory.php b/lib/private/memcache/factory.php index e8a91c5226..f70f8c7c27 100644 --- a/lib/private/memcache/factory.php +++ b/lib/private/memcache/factory.php @@ -11,77 +11,96 @@ namespace OC\Memcache; use \OCP\ICacheFactory; class Factory implements ICacheFactory { + const NULL_CACHE = '\\OC\\Memcache\\Null'; + /** * @var string $globalPrefix */ private $globalPrefix; /** - * @param string $globalPrefix + * @var string $localCacheClass */ - public function __construct($globalPrefix) { + private $localCacheClass; + + /** + * @var string $distributedCacheClass + */ + private $distributedCacheClass; + + /** + * @param string $globalPrefix + * @param string|null $localCacheClass + * @param string|null $distributedCacheClass + */ + public function __construct($globalPrefix, + $localCacheClass = null, $distributedCacheClass = null) + { $this->globalPrefix = $globalPrefix; + + if (!($localCacheClass && $localCacheClass::isAvailable())) { + $localCacheClass = self::NULL_CACHE; + } + if (!($distributedCacheClass && $distributedCacheClass::isAvailable())) { + $distributedCacheClass = $localCacheClass; + } + $this->localCacheClass = $localCacheClass; + $this->distributedCacheClass = $distributedCacheClass; } /** - * get a cache instance, or Null backend if no backend available + * create a distributed cache instance * * @param string $prefix * @return \OC\Memcache\Cache */ - function create($prefix = '') { - $prefix = $this->globalPrefix . '/' . $prefix; - if (XCache::isAvailable()) { - return new XCache($prefix); - } elseif (APCu::isAvailable()) { - return new APCu($prefix); - } elseif (APC::isAvailable()) { - return new APC($prefix); - } elseif (Redis::isAvailable()) { - return new Redis($prefix); - } elseif (Memcached::isAvailable()) { - return new Memcached($prefix); - } else { - return new ArrayCache($prefix); - } + public function createDistributed($prefix = '') { + return new $this->distributedCacheClass($this->globalPrefix . '/' . $prefix); } /** - * check if there is a memcache backend available + * create a local cache instance + * + * @param string $prefix + * @return \OC\Memcache\Cache + */ + public function createLocal($prefix = '') { + return new $this->localCacheClass($this->globalPrefix . '/' . $prefix); + } + + /** + * @see \OC\Memcache\Factory::createDistributed() + * @param string $prefix + * @return \OC\Memcache\Cache + */ + public function create($prefix = '') { + return $this->createDistributed($prefix); + } + + /** + * check memcache availability * * @return bool */ public function isAvailable() { - return XCache::isAvailable() || APCu::isAvailable() || APC::isAvailable() || Redis::isAvailable() || Memcached::isAvailable(); + return ($this->distributedCacheClass !== self::NULL_CACHE); } /** - * get a in-server cache instance, will return null if no backend is available - * + * @see \OC\Memcache\Factory::createLocal() * @param string $prefix - * @return null|Cache + * @return \OC\Memcache\Cache|null */ public function createLowLatency($prefix = '') { - $prefix = $this->globalPrefix . '/' . $prefix; - if (XCache::isAvailable()) { - return new XCache($prefix); - } elseif (APCu::isAvailable()) { - return new APCu($prefix); - } elseif (APC::isAvailable()) { - return new APC($prefix); - } else { - return null; - } + return $this->createLocal($prefix); } /** - * check if there is a in-server backend available + * check local memcache availability * * @return bool */ public function isAvailableLowLatency() { - return XCache::isAvailable() || APCu::isAvailable() || APC::isAvailable(); + return ($this->localCacheClass !== self::NULL_CACHE); } - - } diff --git a/lib/private/server.php b/lib/private/server.php index 18d996537e..896abf04a4 100644 --- a/lib/private/server.php +++ b/lib/private/server.php @@ -155,8 +155,12 @@ class Server extends SimpleContainer implements IServerContainer { return new UserCache(); }); $this->registerService('MemCacheFactory', function ($c) { + $config = $c->getConfig(); $instanceId = \OC_Util::getInstanceId(); - return new \OC\Memcache\Factory($instanceId); + return new \OC\Memcache\Factory($instanceId, + $config->getSystemValue('memcache.local', null), + $config->getSystemValue('memcache.distributed', null) + ); }); $this->registerService('ActivityManager', function ($c) { return new ActivityManager(); diff --git a/tests/lib/memcache/factory.php b/tests/lib/memcache/factory.php new file mode 100644 index 0000000000..4ce032abbe --- /dev/null +++ b/tests/lib/memcache/factory.php @@ -0,0 +1,110 @@ + + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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 + * + */ +namespace Test\Memcache; + +class Test_Factory_Available_Cache1 { + public function __construct($prefix = '') { + } + + public static function isAvailable() { + return true; + } +} + +class Test_Factory_Available_Cache2 { + public function __construct($prefix = '') { + } + + public static function isAvailable() { + return true; + } +} + +class Test_Factory_Unavailable_Cache1 { + public function __construct($prefix = '') { + } + + public static function isAvailable() { + return false; + } +} + +class Test_Factory_Unavailable_Cache2 { + public function __construct($prefix = '') { + } + + public static function isAvailable() { + return false; + } +} + +class Test_Factory extends \Test\TestCase { + const AVAILABLE1 = '\\Test\\Memcache\\Test_Factory_Available_Cache1'; + const AVAILABLE2 = '\\Test\\Memcache\\Test_Factory_Available_Cache2'; + const UNAVAILABLE1 = '\\Test\\Memcache\\Test_Factory_Unavailable_Cache1'; + const UNAVAILABLE2 = '\\Test\\Memcache\\Test_Factory_Unavailable_Cache2'; + + public function cacheAvailabilityProvider() { + return [ + [ + // local and distributed available + self::AVAILABLE1, self::AVAILABLE2, + self::AVAILABLE1, self::AVAILABLE2 + ], + [ + // local available, distributed unavailable + self::AVAILABLE1, self::UNAVAILABLE1, + self::AVAILABLE1, self::AVAILABLE1 + ], + [ + // local unavailable, distributed available + self::UNAVAILABLE1, self::AVAILABLE1, + \OC\Memcache\Factory::NULL_CACHE, self::AVAILABLE1 + ], + [ + // local and distributed unavailable + self::UNAVAILABLE1, self::UNAVAILABLE2, + \OC\Memcache\Factory::NULL_CACHE, \OC\Memcache\Factory::NULL_CACHE + ], + [ + // local and distributed null + null, null, + \OC\Memcache\Factory::NULL_CACHE, \OC\Memcache\Factory::NULL_CACHE + ], + [ + // local available, distributed null (most common scenario) + self::AVAILABLE1, null, + self::AVAILABLE1, self::AVAILABLE1 + ] + ]; + } + + /** + * @dataProvider cacheAvailabilityProvider + */ + public function testCacheAvailability($localCache, $distributedCache, + $expectedLocalCache, $expectedDistributedCache) + { + $factory = new \OC\Memcache\Factory('abc', $localCache, $distributedCache); + $this->assertTrue(is_a($factory->createLocal(), $expectedLocalCache)); + $this->assertTrue(is_a($factory->createDistributed(), $expectedDistributedCache)); + } +}