diff --git a/build/travis/unit-tests.sh b/build/travis/unit-tests.sh index 1564b08baa7ae..ddf1806215d08 100644 --- a/build/travis/unit-tests.sh +++ b/build/travis/unit-tests.sh @@ -22,7 +22,7 @@ psql -d joomla_ut -a -f "$BASE/tests/unit/schema/postgresql.sql" # - ./build/travis/php-apache.sh # Enable additional PHP extensions -if [[ $INSTALL_MEMCACHE == "yes" ]]; then phpenv config-add "$BASE/build/travis/phpenv/memcached.ini"; fi +if [[ $INSTALL_MEMCACHE == "yes" ]]; then phpenv config-add "$BASE/build/travis/phpenv/memcache.ini"; fi if [[ $INSTALL_MEMCACHED == "yes" ]]; then phpenv config-add "$BASE/build/travis/phpenv/memcached.ini"; fi if [[ $INSTALL_APC == "yes" ]]; then phpenv config-add "$BASE/build/travis/phpenv/apc-$TRAVIS_PHP_VERSION.ini"; fi if [[ $INSTALL_APCU == "yes" && $TRAVIS_PHP_VERSION = 5.* ]]; then printf "\n" | pecl install apcu-4.0.10 && phpenv config-add "$BASE/build/travis/phpenv/apcu-$TRAVIS_PHP_VERSION.ini"; fi diff --git a/libraries/joomla/cache/storage.php b/libraries/joomla/cache/storage.php index 8d13425449a30..07747c9c196a0 100644 --- a/libraries/joomla/cache/storage.php +++ b/libraries/joomla/cache/storage.php @@ -253,6 +253,18 @@ public function clean($group, $mode = null) return true; } + /** + * Flush all existing items in storage. + * + * @return boolean + * + * @since __DEPLOY_VERSION__ + */ + public function flush() + { + return true; + } + /** * Garbage collect expired cache data * diff --git a/libraries/joomla/cache/storage/memcache.php b/libraries/joomla/cache/storage/memcache.php index 74c6a56c5406e..c11411773e8b7 100644 --- a/libraries/joomla/cache/storage/memcache.php +++ b/libraries/joomla/cache/storage/memcache.php @@ -25,14 +25,6 @@ class JCacheStorageMemcache extends JCacheStorage */ protected static $_db = null; - /** - * Persistent session flag - * - * @var boolean - * @since 11.1 - */ - protected $_persistent = false; - /** * Payload compression level * @@ -52,7 +44,9 @@ public function __construct($options = array()) { parent::__construct($options); - if (static::isSupported() && static::$_db === null) + $this->_compress = JFactory::getConfig()->get('memcache_compress', false) ? MEMCACHE_COMPRESSED : 0; + + if (static::$_db === null) { $this->getConnection(); } @@ -68,28 +62,59 @@ public function __construct($options = array()) */ protected function getConnection() { - $config = JFactory::getConfig(); - $this->_persistent = $config->get('memcache_persist', true); - $this->_compress = $config->get('memcache_compress', false) == false ? 0 : MEMCACHE_COMPRESSED; + if (!static::isSupported()) + { + throw new RuntimeException('Memcache Extension is not available'); + } + + $config = JFactory::getConfig(); + + $host = $config->get('memcache_server_host', 'localhost'); + $port = $config->get('memcache_server_port', 11211); // Create the memcache connection static::$_db = new Memcache; - static::$_db->addserver($config->get('memcache_server_host', 'localhost'), $config->get('memcache_server_port', 11211), $this->_persistent); - $memcachetest = @static::$_db->connect($server['host'], $server['port']); + if ($config->get('memcache_persist', true)) + { + $result = @static::$_db->pconnect($host, $port); + } + else + { + $result = @static::$_db->connect($host, $port); + } - if ($memcachetest == false) + if (!$result) { - throw new RuntimeException('Could not connect to memcache server', 404); + static::$_db = null; + + throw new RuntimeException('Could not connect to memcache server'); } + } + + /** + * Get a cache_id string from an id/group pair + * + * @param string $id The cache data id + * @param string $group The cache data group + * + * @return string The cache_id string + * + * @since 11.1 + */ + protected function _getCacheId($id, $group) + { + $prefix = JCache::getPlatformPrefix(); + $length = strlen($prefix); + $cache_id = parent::_getCacheId($id, $group); - // Memcahed has no list keys, we do our own accounting, initialise key index - if (static::$_db->get($this->_hash . '-index') === false) + if ($length) { - static::$_db->set($this->_hash . '-index', array(), $this->_compress, 0); + // Memcache use suffix instead of prefix + $cache_id = substr($cache_id, $length) . strrev($prefix); } - return; + return $cache_id; } /** @@ -122,7 +147,7 @@ public function getAll() $data = array(); - if (!empty($keys)) + if (is_array($keys)) { foreach ($keys as $key) { @@ -178,7 +203,7 @@ public function store($id, $group, $data) $index = static::$_db->get($this->_hash . '-index'); - if ($index === false) + if (!is_array($index)) { $index = array(); } @@ -188,14 +213,10 @@ public function store($id, $group, $data) $tmparr->size = strlen($data); $index[] = $tmparr; - static::$_db->replace($this->_hash . '-index', $index, 0, 0); + static::$_db->set($this->_hash . '-index', $index, 0, 0); $this->unlockindex(); - // Prevent double writes, write only if it doesn't exist else replace - if (!static::$_db->replace($cache_id, $data, $this->_compress, $this->_lifetime)) - { - static::$_db->set($cache_id, $data, $this->_compress, $this->_lifetime); - } + static::$_db->set($cache_id, $data, $this->_compress, $this->_lifetime); return true; } @@ -221,22 +242,19 @@ public function remove($id, $group) $index = static::$_db->get($this->_hash . '-index'); - if ($index === false) - { - $index = array(); - } - - foreach ($index as $key => $value) + if (is_array($index)) { - if ($value->name == $cache_id) + foreach ($index as $key => $value) { - unset($index[$key]); + if ($value->name == $cache_id) + { + unset($index[$key]); + static::$_db->set($this->_hash . '-index', $index, 0, 0); + break; + } } - - break; } - static::$_db->replace($this->_hash . '-index', $index, 0, 0); $this->unlockindex(); return static::$_db->delete($cache_id); @@ -264,51 +282,54 @@ public function clean($group, $mode = null) $index = static::$_db->get($this->_hash . '-index'); - if ($index === false) + if (is_array($index)) { - $index = array(); - } + $prefix = $this->_hash . '-cache-' . $group . '-'; - $secret = $this->_hash; - - foreach ($index as $key => $value) - { - if (strpos($value->name, $secret . '-cache-' . $group . '-') === 0 xor $mode != 'group') + foreach ($index as $key => $value) { - static::$_db->delete($value->name, 0); - unset($index[$key]); + if (strpos($value->name, $prefix) === 0 xor $mode != 'group') + { + static::$_db->delete($value->name); + unset($index[$key]); + } } + + static::$_db->set($this->_hash . '-index', $index, 0, 0); } - static::$_db->replace($this->_hash . '-index', $index, 0, 0); $this->unlockindex(); return true; } /** - * Test to see if the storage handler is available. + * Flush all existing items in storage. * * @return boolean * - * @since 12.1 + * @since __DEPLOY_VERSION__ */ - public static function isSupported() + public function flush() { - // First check if the PHP requirements are met - $supported = extension_loaded('memcache') && class_exists('Memcache'); - - if (!$supported) + if (!$this->lockindex()) { return false; } - // Now check if we can connect to the specified Memcache server - $config = JFactory::getConfig(); - - $memcache = new Memcache; + return static::$_db->flush(); + } - return @$memcache->connect($config->get('memcache_server_host', 'localhost'), $config->get('memcache_server_port', 11211)); + /** + * Test to see if the storage handler is available. + * + * @return boolean + * + * @since 12.1 + */ + public static function isSupported() + { + return extension_loaded('memcache') && class_exists('Memcache'); } /** @@ -324,53 +345,34 @@ public static function isSupported() */ public function lock($id, $group, $locktime) { - $returning = new stdClass; + $returning = new stdClass; $returning->locklooped = false; $looptime = $locktime * 10; $cache_id = $this->_getCacheId($id, $group); - if (!$this->lockindex()) - { - return false; - } - - $index = static::$_db->get($this->_hash . '-index'); - - if ($index === false) - { - $index = array(); - } - - $tmparr = new stdClass; - $tmparr->name = $cache_id; - $tmparr->size = 1; - - $index[] = $tmparr; - static::$_db->replace($this->_hash . '-index', $index, 0, 0); - $this->unlockindex(); - - $data_lock = static::$_db->add($cache_id . '_lock', 1, false, $locktime); + $data_lock = static::$_db->add($cache_id . '_lock', 1, 0, $locktime); if ($data_lock === false) { $lock_counter = 0; - // Loop until you find that the lock has been released. That implies that data get from other thread has finished + // Loop until you find that the lock has been released. + // That implies that data get from other thread has finished. while ($data_lock === false) { if ($lock_counter > $looptime) { - $returning->locked = false; - $returning->locklooped = true; break; } usleep(100); - $data_lock = static::$_db->add($cache_id . '_lock', 1, false, $locktime); + $data_lock = static::$_db->add($cache_id . '_lock', 1, 0, $locktime); $lock_counter++; } + + $returning->locklooped = true; } $returning->locked = $data_lock; @@ -391,32 +393,6 @@ public function lock($id, $group, $locktime) public function unlock($id, $group = null) { $cache_id = $this->_getCacheId($id, $group) . '_lock'; - - if (!$this->lockindex()) - { - return false; - } - - $index = static::$_db->get($this->_hash . '-index'); - - if ($index === false) - { - $index = array(); - } - - foreach ($index as $key => $value) - { - if ($value->name == $cache_id) - { - unset($index[$key]); - } - - break; - } - - static::$_db->replace($this->_hash . '-index', $index, 0, 0); - $this->unlockindex(); - return static::$_db->delete($cache_id); } @@ -430,7 +406,7 @@ public function unlock($id, $group = null) protected function lockindex() { $looptime = 300; - $data_lock = static::$_db->add($this->_hash . '-index_lock', 1, false, 30); + $data_lock = static::$_db->add($this->_hash . '-index_lock', 1, 0, 30); if ($data_lock === false) { @@ -442,11 +418,10 @@ protected function lockindex() if ($lock_counter > $looptime) { return false; - break; } usleep(100); - $data_lock = static::$_db->add($this->_hash . '-index_lock', 1, false, 30); + $data_lock = static::$_db->add($this->_hash . '-index_lock', 1, 0, 30); $lock_counter++; } } diff --git a/libraries/joomla/cache/storage/memcached.php b/libraries/joomla/cache/storage/memcached.php index 966dac936647b..f8a1c701d84f5 100644 --- a/libraries/joomla/cache/storage/memcached.php +++ b/libraries/joomla/cache/storage/memcached.php @@ -25,14 +25,6 @@ class JCacheStorageMemcached extends JCacheStorage */ protected static $_db = null; - /** - * Persistent session flag - * - * @var boolean - * @since 12.1 - */ - protected $_persistent = false; - /** * Payload compression level * @@ -52,7 +44,9 @@ public function __construct($options = array()) { parent::__construct($options); - if (static::isSupported() && static::$_db === null) + $this->_compress = JFactory::getConfig()->get('memcached_compress', false) ? Memcached::OPT_COMPRESSION : 0; + + if (static::$_db === null) { $this->getConnection(); } @@ -68,34 +62,76 @@ public function __construct($options = array()) */ protected function getConnection() { - $config = JFactory::getConfig(); - $this->_persistent = $config->get('memcached_persist', true); - $this->_compress = $config->get('memcached_compress', false) == false ? 0 : Memcached::OPT_COMPRESSION; + if (!static::isSupported()) + { + throw new RuntimeException('Memcached Extension is not available'); + } + + $config = JFactory::getConfig(); + + $host = $config->get('memcache_server_host', 'localhost'); + $port = $config->get('memcache_server_port', 11211); + // Create the memcache connection - if ($this->_persistent) + if ($config->get('memcached_persist', true)) { - static::$_db = new Memcached(JFactory::getSession()->getId()); + static::$_db = new Memcached($this->_hash); + $servers = static::$_db->getServerList(); + + if ($servers && ($servers[0]['host'] != $host || $servers[0]['port'] != $port)) + { + static::$_db->resetServerList(); + $servers = array(); + } + + if (!$servers) + { + static::$_db->addServer($host, $port); + } } else { static::$_db = new Memcached; + static::$_db->addServer($host, $port); } - $memcachedtest = static::$_db->addServer($config->get('memcached_server_host', 'localhost'), $config->get('memcached_server_port', 11211)); + static::$_db->setOption(Memcached::OPT_COMPRESSION, $this->_compress); + + $stats = static::$_db->getStats(); + $result = !empty($stats["$host:$port"]) && $stats["$host:$port"]['pid'] > 0; - if ($memcachedtest == false) + if (!$result) { - throw new RuntimeException('Could not connect to memcached server', 404); + static::$_db = null; + + throw new RuntimeException('Could not connect to memcached server'); } + } - static::$_db->setOption(Memcached::OPT_COMPRESSION, $this->_compress); + /** + * Get a cache_id string from an id/group pair + * + * @param string $id The cache data id + * @param string $group The cache data group + * + * @return string The cache_id string + * + * @since 11.1 + */ + protected function _getCacheId($id, $group) + { + $prefix = JCache::getPlatformPrefix(); + $length = strlen($prefix); + $cache_id = parent::_getCacheId($id, $group); - // Memcached has no list keys, we do our own accounting, initialise key index - if (static::$_db->get($this->_hash . '-index') === false) + if ($length) { - static::$_db->set($this->_hash . '-index', array(), 0); + // Memcached use suffix instead of prefix + $cache_id = substr($cache_id, $length) . strrev($prefix); } + + return $cache_id; } /** @@ -111,9 +147,7 @@ protected function getConnection() */ public function get($id, $group, $checkTime = true) { - $cache_id = $this->_getCacheId($id, $group); - - return static::$_db->get($cache_id); + return static::$_db->get($this->_getCacheId($id, $group)); } /** @@ -130,7 +164,7 @@ public function getAll() $data = array(); - if (!empty($keys) && is_array($keys)) + if (is_array($keys)) { foreach ($keys as $key) { @@ -186,7 +220,7 @@ public function store($id, $group, $data) $index = static::$_db->get($this->_hash . '-index'); - if ($index === false) + if (!is_array($index)) { $index = array(); } @@ -196,14 +230,10 @@ public function store($id, $group, $data) $tmparr->size = strlen($data); $index[] = $tmparr; - static::$_db->replace($this->_hash . '-index', $index, 0); + static::$_db->set($this->_hash . '-index', $index, 0); $this->unlockindex(); - // Prevent double writes, write only if it doesn't exist else replace - if (!static::$_db->replace($cache_id, $data, $this->_lifetime)) - { - static::$_db->set($cache_id, $data, $this->_lifetime); - } + static::$_db->set($cache_id, $data, $this->_lifetime); return true; } @@ -229,22 +259,19 @@ public function remove($id, $group) $index = static::$_db->get($this->_hash . '-index'); - if ($index === false) + if (is_array($index)) { - $index = array(); - } - - foreach ($index as $key => $value) - { - if ($value->name == $cache_id) + foreach ($index as $key => $value) { - unset($index[$key]); + if ($value->name == $cache_id) + { + unset($index[$key]); + static::$_db->set($this->_hash . '-index', $index, 0); + break; + } } - - break; } - static::$_db->replace($this->_hash . '-index', $index, 0); $this->unlockindex(); return static::$_db->delete($cache_id); @@ -272,28 +299,44 @@ public function clean($group, $mode = null) $index = static::$_db->get($this->_hash . '-index'); - if ($index === false) + if (is_array($index)) { - $index = array(); - } + $prefix = $this->_hash . '-cache-' . $group . '-'; - $secret = $this->_hash; - - foreach ($index as $key => $value) - { - if (strpos($value->name, $secret . '-cache-' . $group . '-') === 0 xor $mode != 'group') + foreach ($index as $key => $value) { - static::$_db->delete($value->name, 0); - unset($index[$key]); + if (strpos($value->name, $prefix) === 0 xor $mode != 'group') + { + static::$_db->delete($value->name); + unset($index[$key]); + } } + + static::$_db->set($this->_hash . '-index', $index, 0); } - static::$_db->replace($this->_hash . '-index', $index, 0); $this->unlockindex(); return true; } + /** + * Flush all existing items in storage. + * + * @return boolean + * + * @since __DEPLOY_VERSION__ + */ + public function flush() + { + if (!$this->lockindex()) + { + return false; + } + + return static::$_db->flush(); + } + /** * Test to see if the storage handler is available. * @@ -307,17 +350,7 @@ public static function isSupported() * GAE and HHVM have both had instances where Memcached the class was defined but no extension was loaded. * If the class is there, we can assume support. */ - if (!class_exists('Memcached')) - { - return false; - } - - // Now check if we can connect to the specified Memcached server - $config = JFactory::getConfig(); - - $memcached = new Memcached; - - return @$memcached->addServer($config->get('memcached_server_host', 'localhost'), $config->get('memcached_server_port', 11211)); + return class_exists('Memcached'); } /** @@ -340,27 +373,6 @@ public function lock($id, $group, $locktime) $cache_id = $this->_getCacheId($id, $group); - if (!$this->lockindex()) - { - return false; - } - - $index = static::$_db->get($this->_hash . '-index'); - - if ($index === false) - { - $index = array(); - } - - $tmparr = new stdClass; - $tmparr->name = $cache_id; - $tmparr->size = 1; - - $index[] = $tmparr; - static::$_db->replace($this->_hash . '-index', $index, 0); - - $this->unlockindex(); - $data_lock = static::$_db->add($cache_id . '_lock', 1, $locktime); if ($data_lock === false) @@ -368,13 +380,11 @@ public function lock($id, $group, $locktime) $lock_counter = 0; // Loop until you find that the lock has been released. - // That implies that data get from other thread has finished + // That implies that data get from other thread has finished. while ($data_lock === false) { if ($lock_counter > $looptime) { - $returning->locked = false; - $returning->locklooped = true; break; } @@ -382,6 +392,8 @@ public function lock($id, $group, $locktime) $data_lock = static::$_db->add($cache_id . '_lock', 1, $locktime); $lock_counter++; } + + $returning->locklooped = true; } $returning->locked = $data_lock; @@ -402,32 +414,6 @@ public function lock($id, $group, $locktime) public function unlock($id, $group = null) { $cache_id = $this->_getCacheId($id, $group) . '_lock'; - - if (!$this->lockindex()) - { - return false; - } - - $index = static::$_db->get($this->_hash . '-index'); - - if ($index === false) - { - $index = array(); - } - - foreach ($index as $key => $value) - { - if ($value->name == $cache_id) - { - unset($index[$key]); - } - - break; - } - - static::$_db->replace($this->_hash . '-index', $index, 0); - $this->unlockindex(); - return static::$_db->delete($cache_id); } @@ -453,7 +439,6 @@ protected function lockindex() if ($lock_counter > $looptime) { return false; - break; } usleep(100); diff --git a/tests/unit/core/case/cache.php b/tests/unit/core/case/cache.php index 9c3eef9b677cb..51d856e2d08d4 100644 --- a/tests/unit/core/case/cache.php +++ b/tests/unit/core/case/cache.php @@ -62,7 +62,9 @@ protected function tearDown() if ($this->handler instanceof JCacheStorage) { + // Deprecated, temporary have to stay because flush method is not implemented in all storages. $this->handler->clean($this->group); + $this->handler->flush(); } parent::tearDown();