2004-01-24 22:58:19 +00:00
|
|
|
<?php
|
2018-01-01 13:10:16 +00:00
|
|
|
// phpcs:ignoreFile -- It's an external lib and it isn't. Let's not bother.
|
2010-08-14 17:42:40 +00:00
|
|
|
/**
|
2012-05-02 08:51:15 +00:00
|
|
|
* Memcached client for PHP.
|
|
|
|
|
*
|
2010-08-14 17:42:40 +00:00
|
|
|
* +---------------------------------------------------------------------------+
|
|
|
|
|
* | memcached client, PHP |
|
|
|
|
|
* +---------------------------------------------------------------------------+
|
|
|
|
|
* | Copyright (c) 2003 Ryan T. Dean <rtdean@cytherianage.net> |
|
|
|
|
|
* | All rights reserved. |
|
|
|
|
|
* | |
|
|
|
|
|
* | Redistribution and use in source and binary forms, with or without |
|
|
|
|
|
* | modification, are permitted provided that the following conditions |
|
|
|
|
|
* | are met: |
|
|
|
|
|
* | |
|
|
|
|
|
* | 1. Redistributions of source code must retain the above copyright |
|
|
|
|
|
* | notice, this list of conditions and the following disclaimer. |
|
|
|
|
|
* | 2. Redistributions in binary form must reproduce the above copyright |
|
|
|
|
|
* | notice, this list of conditions and the following disclaimer in the |
|
|
|
|
|
* | documentation and/or other materials provided with the distribution. |
|
|
|
|
|
* | |
|
|
|
|
|
* | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
|
|
|
|
|
* | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
|
|
|
|
|
* | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
|
|
|
|
|
* | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
|
|
|
|
|
* | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
|
|
|
|
|
* | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
|
|
|
|
* | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
|
|
|
|
* | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
|
|
|
|
* | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
|
|
|
|
|
* | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
|
|
|
|
* +---------------------------------------------------------------------------+
|
|
|
|
|
* | Author: Ryan T. Dean <rtdean@cytherianage.net> |
|
|
|
|
|
* | Heavily influenced by the Perl memcached client by Brad Fitzpatrick. |
|
|
|
|
|
* | Permission granted by Brad Fitzpatrick for relicense of ported Perl |
|
|
|
|
|
* | client logic under 2-clause BSD license. |
|
|
|
|
|
* +---------------------------------------------------------------------------+
|
|
|
|
|
*
|
|
|
|
|
* @file
|
|
|
|
|
* $TCAnet$
|
|
|
|
|
*/
|
2004-01-24 22:58:19 +00:00
|
|
|
|
|
|
|
|
/**
|
2015-11-10 03:20:08 +00:00
|
|
|
* This is a PHP client for memcached - a distributed memory cache daemon.
|
|
|
|
|
*
|
2004-01-24 22:58:19 +00:00
|
|
|
* More information is available at http://www.danga.com/memcached/
|
|
|
|
|
*
|
|
|
|
|
* Usage example:
|
|
|
|
|
*
|
2015-11-10 03:20:08 +00:00
|
|
|
* $mc = new MemcachedClient(array(
|
|
|
|
|
* 'servers' => array(
|
|
|
|
|
* '127.0.0.1:10000',
|
|
|
|
|
* array( '192.0.0.1:10010', 2 ),
|
|
|
|
|
* '127.0.0.1:10020'
|
|
|
|
|
* ),
|
|
|
|
|
* 'debug' => false,
|
|
|
|
|
* 'compress_threshold' => 10240,
|
|
|
|
|
* 'persistent' => true
|
|
|
|
|
* ));
|
2004-01-24 22:58:19 +00:00
|
|
|
*
|
2015-11-10 03:20:08 +00:00
|
|
|
* $mc->add( 'key', array( 'some', 'array' ) );
|
|
|
|
|
* $mc->replace( 'key', 'some random string' );
|
|
|
|
|
* $val = $mc->get( 'key' );
|
2004-01-24 22:58:19 +00:00
|
|
|
*
|
2015-11-10 03:20:08 +00:00
|
|
|
* @author Ryan T. Dean <rtdean@cytherianage.net>
|
2004-01-24 22:58:19 +00:00
|
|
|
* @version 0.1.2
|
|
|
|
|
*/
|
|
|
|
|
|
2014-11-19 06:42:33 +00:00
|
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
|
use Psr\Log\NullLogger;
|
|
|
|
|
|
2015-11-10 03:20:08 +00:00
|
|
|
// {{{ class MemcachedClient
|
2004-01-24 22:58:19 +00:00
|
|
|
/**
|
|
|
|
|
* memcached client class implemented using (p)fsockopen()
|
|
|
|
|
*
|
|
|
|
|
* @author Ryan T. Dean <rtdean@cytherianage.net>
|
WARNING: HUGE COMMIT
Doxygen documentation update:
* Changed alls @addtogroup to @ingroup. @addtogroup adds the comment to the group description, but doesn't add the file, class, function, ... to the group like @ingroup does. See for example http://svn.wikimedia.org/doc/group__SpecialPage.html where it's impossible to see related files, classes, ... that should belong to that group.
* Added @file to file description, it seems that it should be explicitely decalred for file descriptions, otherwise doxygen will think that the comment document the first class, variabled, function, ... that is in that file.
* Removed some empty comments
* Removed some ?>
Added following groups:
* ExternalStorage
* JobQueue
* MaintenanceLanguage
One more thing: there are still a lot of warnings when generating the doc.
2008-05-20 17:13:28 +00:00
|
|
|
* @ingroup Cache
|
2004-01-24 22:58:19 +00:00
|
|
|
*/
|
2015-11-10 03:20:08 +00:00
|
|
|
class MemcachedClient {
|
2009-12-13 17:57:21 +00:00
|
|
|
// {{{ properties
|
|
|
|
|
// {{{ public
|
2004-01-24 22:58:19 +00:00
|
|
|
|
2009-11-12 17:21:09 +00:00
|
|
|
// {{{ constants
|
|
|
|
|
// {{{ flags
|
2008-03-18 09:54:43 +00:00
|
|
|
|
2009-11-12 17:21:09 +00:00
|
|
|
/**
|
|
|
|
|
* Flag: indicates data is serialized
|
|
|
|
|
*/
|
|
|
|
|
const SERIALIZED = 1;
|
2008-03-18 09:54:43 +00:00
|
|
|
|
2009-11-12 17:21:09 +00:00
|
|
|
/**
|
|
|
|
|
* Flag: indicates data is compressed
|
|
|
|
|
*/
|
|
|
|
|
const COMPRESSED = 2;
|
2008-03-18 09:54:43 +00:00
|
|
|
|
2015-03-05 00:00:10 +00:00
|
|
|
/**
|
|
|
|
|
* Flag: indicates data is an integer
|
|
|
|
|
*/
|
|
|
|
|
const INTVAL = 4;
|
|
|
|
|
|
2009-11-12 17:21:09 +00:00
|
|
|
// }}}
|
2008-03-18 09:54:43 +00:00
|
|
|
|
2009-11-12 17:21:09 +00:00
|
|
|
/**
|
|
|
|
|
* Minimum savings to store data compressed
|
|
|
|
|
*/
|
|
|
|
|
const COMPRESSION_SAVINGS = 0.20;
|
2008-03-18 09:54:43 +00:00
|
|
|
|
2009-11-12 17:21:09 +00:00
|
|
|
// }}}
|
2008-03-18 09:54:43 +00:00
|
|
|
|
2009-11-12 16:19:16 +00:00
|
|
|
/**
|
|
|
|
|
* Command statistics
|
|
|
|
|
*
|
2014-04-05 19:54:24 +00:00
|
|
|
* @var array
|
|
|
|
|
* @access public
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2014-08-17 20:07:37 +00:00
|
|
|
public $stats;
|
2009-11-12 16:19:16 +00:00
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ private
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Cached Sockets that are connected
|
|
|
|
|
*
|
2014-04-05 19:54:24 +00:00
|
|
|
* @var array
|
|
|
|
|
* @access private
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2014-08-17 20:07:37 +00:00
|
|
|
public $_cache_sock;
|
2009-11-12 16:19:16 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Current debug status; 0 - none to 9 - profiling
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @var bool
|
2014-04-05 19:54:24 +00:00
|
|
|
* @access private
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2014-08-17 20:07:37 +00:00
|
|
|
public $_debug;
|
2009-11-12 16:19:16 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Dead hosts, assoc array, 'host'=>'unixtime when ok to check again'
|
|
|
|
|
*
|
2014-04-05 19:54:24 +00:00
|
|
|
* @var array
|
|
|
|
|
* @access private
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2014-08-17 20:07:37 +00:00
|
|
|
public $_host_dead;
|
2009-11-12 16:19:16 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Is compression available?
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @var bool
|
2014-04-05 19:54:24 +00:00
|
|
|
* @access private
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2014-08-17 20:07:37 +00:00
|
|
|
public $_have_zlib;
|
2009-11-12 16:19:16 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Do we want to use compression?
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @var bool
|
2014-04-05 19:54:24 +00:00
|
|
|
* @access private
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2014-08-17 20:07:37 +00:00
|
|
|
public $_compress_enable;
|
2009-11-12 16:19:16 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* At how many bytes should we compress?
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @var int
|
2014-04-05 19:54:24 +00:00
|
|
|
* @access private
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2014-08-17 20:07:37 +00:00
|
|
|
public $_compress_threshold;
|
2009-11-12 16:19:16 +00:00
|
|
|
|
|
|
|
|
/**
|
* Rewrote ObjectCache.php to conform to the modern coding style, and to be less convoluted about how CACHE_ANYTHING and CACHE_ACCEL are resolved. Moved most functionality to static members of a new ObjectCache class.
* Moved the global functions to GlobalFunctions.php, where they are now just convenience wrappers. Made them return non-references. Updated callers (none found in extensions).
* Added an advanced configuration method, $wgObjectCaches, which allows a lot more detail in the object cache configuration than $wgMainCacheType.
* Made all object cache classes derive from BagOStuff.
* Split the MWMemcached class into a generic client class and a MediaWiki-specific wrapper class. The wrapper class presents a simple BagOStuff interface to calling code, hiding memcached client internals, and will simplify the task of supporting the PECL extension.
* Added some extra constructor parameters to MWMemcached, configurable via $wgObjectCaches.
* Removed the *_multi() methods from BagOStuff, my grepping indicates that they are not used.
* Rewrote FakeMemCachedClient as a BagOStuff subclass, called EmptyBagOStuff.
* Added an optional "server" parameter to SQLBagOStuff. This allows the server holding the objectcache table to be different from the server holding the core DB.
* Added MultiWriteBagOStuff: a cache class which writes to multiple locations, and reads from them in a defined fallback sequence. This can be used to extend the cache space by adding disk-backed storage to existing in-memory caches.
* Made MWMemcached::get() return false on failure instead of null, to match the BagOStuff documentation and the other BagOStuff subclasses. Anything that was relying on it returning null would have already been broken with SqlBagOStuff.
* Fixed a bug in the memcached client causing keys with spaces or line breaks in them to break the memcached protocol, injecting arbitrary commands or parameters. Since the PECL client apparently also has this flaw, I implemented the fix in the wrapper class.
* Renamed BagOStuff::set_debug() to setDebug(), since we aren't emulating the memcached client anymore
* Fixed spelling error in MWMemcached: persistant -> persistent
2011-03-03 09:37:37 +00:00
|
|
|
* Are we using persistent links?
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @var bool
|
2014-04-05 19:54:24 +00:00
|
|
|
* @access private
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2014-08-17 20:07:37 +00:00
|
|
|
public $_persistent;
|
2009-11-12 16:19:16 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* If only using one server; contains ip:port to connect to
|
|
|
|
|
*
|
2014-04-05 19:54:24 +00:00
|
|
|
* @var string
|
|
|
|
|
* @access private
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2014-08-17 20:07:37 +00:00
|
|
|
public $_single_sock;
|
2009-11-12 16:19:16 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Array containing ip:port or array(ip:port, weight)
|
|
|
|
|
*
|
2014-04-05 19:54:24 +00:00
|
|
|
* @var array
|
|
|
|
|
* @access private
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2014-08-17 20:07:37 +00:00
|
|
|
public $_servers;
|
2009-11-12 16:19:16 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Our bit buckets
|
|
|
|
|
*
|
2014-04-05 19:54:24 +00:00
|
|
|
* @var array
|
|
|
|
|
* @access private
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2014-08-17 20:07:37 +00:00
|
|
|
public $_buckets;
|
2009-11-12 16:19:16 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Total # of bit buckets we have
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @var int
|
2014-04-05 19:54:24 +00:00
|
|
|
* @access private
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2014-08-17 20:07:37 +00:00
|
|
|
public $_bucketcount;
|
2009-11-12 16:19:16 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* # of total servers we have
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @var int
|
2014-04-05 19:54:24 +00:00
|
|
|
* @access private
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2014-08-17 20:07:37 +00:00
|
|
|
public $_active;
|
2009-11-12 16:19:16 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Stream timeout in seconds. Applies for example to fread()
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @var int
|
2014-04-05 19:54:24 +00:00
|
|
|
* @access private
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2014-08-17 20:07:37 +00:00
|
|
|
public $_timeout_seconds;
|
2009-11-12 16:19:16 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Stream timeout in microseconds
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @var int
|
2014-04-05 19:54:24 +00:00
|
|
|
* @access private
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2014-08-17 20:07:37 +00:00
|
|
|
public $_timeout_microseconds;
|
2009-11-12 16:19:16 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Connect timeout in seconds
|
|
|
|
|
*/
|
2014-08-17 20:07:37 +00:00
|
|
|
public $_connect_timeout;
|
2009-11-12 16:19:16 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Number of connection attempts for each server
|
|
|
|
|
*/
|
2014-08-17 20:07:37 +00:00
|
|
|
public $_connect_attempts;
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2014-11-19 06:42:33 +00:00
|
|
|
/**
|
|
|
|
|
* @var LoggerInterface
|
|
|
|
|
*/
|
|
|
|
|
private $_logger;
|
|
|
|
|
|
2009-11-12 16:19:16 +00:00
|
|
|
// }}}
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ methods
|
|
|
|
|
// {{{ public functions
|
|
|
|
|
// {{{ memcached()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Memcache initializer
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param array $args Associative array of settings
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2014-04-05 19:54:24 +00:00
|
|
|
* @return mixed
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
public function __construct( $args ) {
|
2017-10-06 22:17:58 +00:00
|
|
|
$this->set_servers( $args['servers'] ?? array() );
|
|
|
|
|
$this->_debug = $args['debug'] ?? false;
|
2009-11-12 16:19:16 +00:00
|
|
|
$this->stats = array();
|
2017-10-06 22:17:58 +00:00
|
|
|
$this->_compress_threshold = $args['compress_threshold'] ?? 0;
|
|
|
|
|
$this->_persistent = $args['persistent'] ?? false;
|
2009-11-12 16:19:16 +00:00
|
|
|
$this->_compress_enable = true;
|
2009-12-13 17:57:21 +00:00
|
|
|
$this->_have_zlib = function_exists( 'gzcompress' );
|
2009-11-12 16:19:16 +00:00
|
|
|
|
|
|
|
|
$this->_cache_sock = array();
|
|
|
|
|
$this->_host_dead = array();
|
|
|
|
|
|
|
|
|
|
$this->_timeout_seconds = 0;
|
2017-10-06 22:17:58 +00:00
|
|
|
$this->_timeout_microseconds = $args['timeout'] ?? 500000;
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2017-10-06 22:17:58 +00:00
|
|
|
$this->_connect_timeout = $args['connect_timeout'] ?? 0.1;
|
2009-11-12 16:19:16 +00:00
|
|
|
$this->_connect_attempts = 2;
|
2014-11-19 06:42:33 +00:00
|
|
|
|
2017-10-06 22:17:58 +00:00
|
|
|
$this->_logger = $args['logger'] ?? new NullLogger();
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ add()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Adds a key/value to the memcache server if one isn't already set with
|
|
|
|
|
* that key
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $key Key to set with data
|
|
|
|
|
* @param mixed $val Value to store
|
|
|
|
|
* @param int $exp (optional) Expiration time. This can be a number of seconds
|
2010-10-18 06:12:55 +00:00
|
|
|
* to cache for (up to 30 days inclusive). Any timespans of 30 days + 1 second or
|
|
|
|
|
* longer must be the timestamp of the time at which the mapping should expire. It
|
2012-11-15 09:47:54 +00:00
|
|
|
* is safe to use timestamps in all cases, regardless of expiration
|
2010-10-18 06:12:55 +00:00
|
|
|
* eg: strtotime("+3 hour")
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return bool
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
public function add( $key, $val, $exp = 0 ) {
|
|
|
|
|
return $this->_set( 'add', $key, $val, $exp );
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ decr()
|
|
|
|
|
|
|
|
|
|
/**
|
2010-11-26 19:16:28 +00:00
|
|
|
* Decrease a value stored on the memcache server
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $key Key to decrease
|
|
|
|
|
* @param int $amt (optional) amount to decrease
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return mixed False on failure, value on success
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
public function decr( $key, $amt = 1 ) {
|
|
|
|
|
return $this->_incrdecr( 'decr', $key, $amt );
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ delete()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Deletes a key from the server, optionally after $time
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $key Key to delete
|
|
|
|
|
* @param int $time (optional) how long to wait before deleting
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return bool True on success, false on failure
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
public function delete( $key, $time = 0 ) {
|
|
|
|
|
if ( !$this->_active ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
return false;
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2009-12-13 17:57:21 +00:00
|
|
|
$sock = $this->get_sock( $key );
|
|
|
|
|
if ( !is_resource( $sock ) ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
return false;
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2009-12-13 17:57:21 +00:00
|
|
|
$key = is_array( $key ) ? $key[1] : $key;
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2010-07-04 20:43:58 +00:00
|
|
|
if ( isset( $this->stats['delete'] ) ) {
|
|
|
|
|
$this->stats['delete']++;
|
|
|
|
|
} else {
|
|
|
|
|
$this->stats['delete'] = 1;
|
|
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
$cmd = "delete $key $time\r\n";
|
2013-04-20 20:28:52 +00:00
|
|
|
if ( !$this->_fwrite( $sock, $cmd ) ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
2012-08-03 07:03:03 +00:00
|
|
|
$res = $this->_fgets( $sock );
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2009-12-13 17:57:21 +00:00
|
|
|
if ( $this->_debug ) {
|
2016-02-07 22:01:30 +00:00
|
|
|
$this->_debugprint( sprintf( "MemCache: delete %s (%s)", $key, $res ) );
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2012-04-27 23:41:20 +00:00
|
|
|
if ( $res == "DELETED" || $res == "NOT_FOUND" ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
return true;
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2012-04-27 23:41:20 +00:00
|
|
|
|
2009-11-12 16:19:16 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-12 02:26:21 +00:00
|
|
|
/**
|
|
|
|
|
* Changes the TTL on a key from the server to $time
|
|
|
|
|
*
|
2017-12-28 15:06:10 +00:00
|
|
|
* @param string $key
|
2016-08-12 02:26:21 +00:00
|
|
|
* @param int $time TTL in seconds
|
|
|
|
|
*
|
|
|
|
|
* @return bool True on success, false on failure
|
|
|
|
|
*/
|
|
|
|
|
public function touch( $key, $time = 0 ) {
|
|
|
|
|
if ( !$this->_active ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$sock = $this->get_sock( $key );
|
|
|
|
|
if ( !is_resource( $sock ) ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$key = is_array( $key ) ? $key[1] : $key;
|
|
|
|
|
|
|
|
|
|
if ( isset( $this->stats['touch'] ) ) {
|
|
|
|
|
$this->stats['touch']++;
|
|
|
|
|
} else {
|
|
|
|
|
$this->stats['touch'] = 1;
|
|
|
|
|
}
|
|
|
|
|
$cmd = "touch $key $time\r\n";
|
|
|
|
|
if ( !$this->_fwrite( $sock, $cmd ) ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
$res = $this->_fgets( $sock );
|
|
|
|
|
|
|
|
|
|
if ( $this->_debug ) {
|
|
|
|
|
$this->_debugprint( sprintf( "MemCache: touch %s (%s)", $key, $res ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $res == "TOUCHED" ) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-17 22:52:54 +00:00
|
|
|
/**
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $key
|
|
|
|
|
* @param int $timeout
|
2012-03-17 22:52:54 +00:00
|
|
|
* @return bool
|
|
|
|
|
*/
|
2011-09-21 20:25:58 +00:00
|
|
|
public function lock( $key, $timeout = 0 ) {
|
|
|
|
|
/* stub */
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-17 22:52:54 +00:00
|
|
|
/**
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $key
|
2012-03-17 22:52:54 +00:00
|
|
|
* @return bool
|
|
|
|
|
*/
|
2011-09-21 20:25:58 +00:00
|
|
|
public function unlock( $key ) {
|
|
|
|
|
/* stub */
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2009-11-12 16:19:16 +00:00
|
|
|
// }}}
|
|
|
|
|
// {{{ disconnect_all()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Disconnects all connected sockets
|
|
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
public function disconnect_all() {
|
|
|
|
|
foreach ( $this->_cache_sock as $sock ) {
|
|
|
|
|
fclose( $sock );
|
|
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
|
|
|
|
$this->_cache_sock = array();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ enable_compress()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Enable / Disable compression
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param bool $enable True to enable, false to disable
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
public function enable_compress( $enable ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
$this->_compress_enable = $enable;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ forget_dead_hosts()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Forget about all of the dead hosts
|
|
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
public function forget_dead_hosts() {
|
2009-11-12 16:19:16 +00:00
|
|
|
$this->_host_dead = array();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ get()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Retrieves the value associated with the key from the memcache server
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param array|string $key key to retrieve
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param float $casToken [optional]
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return mixed
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2012-10-01 14:05:22 +00:00
|
|
|
public function get( $key, &$casToken = null ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
if ( $this->_debug ) {
|
2016-02-07 22:01:30 +00:00
|
|
|
$this->_debugprint( "get($key)" );
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
|
2014-04-23 14:18:24 +00:00
|
|
|
if ( !is_array( $key ) && strval( $key ) === '' ) {
|
|
|
|
|
$this->_debugprint( "Skipping key which equals to an empty string" );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2009-12-13 17:57:21 +00:00
|
|
|
if ( !$this->_active ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2009-12-13 17:57:21 +00:00
|
|
|
$sock = $this->get_sock( $key );
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2009-12-13 17:57:21 +00:00
|
|
|
if ( !is_resource( $sock ) ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-19 20:01:33 +00:00
|
|
|
$key = is_array( $key ) ? $key[1] : $key;
|
2010-07-04 20:43:58 +00:00
|
|
|
if ( isset( $this->stats['get'] ) ) {
|
|
|
|
|
$this->stats['get']++;
|
|
|
|
|
} else {
|
|
|
|
|
$this->stats['get'] = 1;
|
|
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2012-10-01 14:05:22 +00:00
|
|
|
$cmd = "gets $key\r\n";
|
2012-08-03 07:03:03 +00:00
|
|
|
if ( !$this->_fwrite( $sock, $cmd ) ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$val = array();
|
2012-10-01 14:05:22 +00:00
|
|
|
$this->_load_items( $sock, $val, $casToken );
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2009-12-13 17:57:21 +00:00
|
|
|
if ( $this->_debug ) {
|
|
|
|
|
foreach ( $val as $k => $v ) {
|
2016-02-07 22:01:30 +00:00
|
|
|
$this->_debugprint( sprintf( "MemCache: sock %s got %s", serialize( $sock ), $k ) );
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
|
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2011-03-06 23:29:29 +00:00
|
|
|
$value = false;
|
* Rewrote ObjectCache.php to conform to the modern coding style, and to be less convoluted about how CACHE_ANYTHING and CACHE_ACCEL are resolved. Moved most functionality to static members of a new ObjectCache class.
* Moved the global functions to GlobalFunctions.php, where they are now just convenience wrappers. Made them return non-references. Updated callers (none found in extensions).
* Added an advanced configuration method, $wgObjectCaches, which allows a lot more detail in the object cache configuration than $wgMainCacheType.
* Made all object cache classes derive from BagOStuff.
* Split the MWMemcached class into a generic client class and a MediaWiki-specific wrapper class. The wrapper class presents a simple BagOStuff interface to calling code, hiding memcached client internals, and will simplify the task of supporting the PECL extension.
* Added some extra constructor parameters to MWMemcached, configurable via $wgObjectCaches.
* Removed the *_multi() methods from BagOStuff, my grepping indicates that they are not used.
* Rewrote FakeMemCachedClient as a BagOStuff subclass, called EmptyBagOStuff.
* Added an optional "server" parameter to SQLBagOStuff. This allows the server holding the objectcache table to be different from the server holding the core DB.
* Added MultiWriteBagOStuff: a cache class which writes to multiple locations, and reads from them in a defined fallback sequence. This can be used to extend the cache space by adding disk-backed storage to existing in-memory caches.
* Made MWMemcached::get() return false on failure instead of null, to match the BagOStuff documentation and the other BagOStuff subclasses. Anything that was relying on it returning null would have already been broken with SqlBagOStuff.
* Fixed a bug in the memcached client causing keys with spaces or line breaks in them to break the memcached protocol, injecting arbitrary commands or parameters. Since the PECL client apparently also has this flaw, I implemented the fix in the wrapper class.
* Renamed BagOStuff::set_debug() to setDebug(), since we aren't emulating the memcached client anymore
* Fixed spelling error in MWMemcached: persistant -> persistent
2011-03-03 09:37:37 +00:00
|
|
|
if ( isset( $val[$key] ) ) {
|
2011-03-06 23:29:29 +00:00
|
|
|
$value = $val[$key];
|
* Rewrote ObjectCache.php to conform to the modern coding style, and to be less convoluted about how CACHE_ANYTHING and CACHE_ACCEL are resolved. Moved most functionality to static members of a new ObjectCache class.
* Moved the global functions to GlobalFunctions.php, where they are now just convenience wrappers. Made them return non-references. Updated callers (none found in extensions).
* Added an advanced configuration method, $wgObjectCaches, which allows a lot more detail in the object cache configuration than $wgMainCacheType.
* Made all object cache classes derive from BagOStuff.
* Split the MWMemcached class into a generic client class and a MediaWiki-specific wrapper class. The wrapper class presents a simple BagOStuff interface to calling code, hiding memcached client internals, and will simplify the task of supporting the PECL extension.
* Added some extra constructor parameters to MWMemcached, configurable via $wgObjectCaches.
* Removed the *_multi() methods from BagOStuff, my grepping indicates that they are not used.
* Rewrote FakeMemCachedClient as a BagOStuff subclass, called EmptyBagOStuff.
* Added an optional "server" parameter to SQLBagOStuff. This allows the server holding the objectcache table to be different from the server holding the core DB.
* Added MultiWriteBagOStuff: a cache class which writes to multiple locations, and reads from them in a defined fallback sequence. This can be used to extend the cache space by adding disk-backed storage to existing in-memory caches.
* Made MWMemcached::get() return false on failure instead of null, to match the BagOStuff documentation and the other BagOStuff subclasses. Anything that was relying on it returning null would have already been broken with SqlBagOStuff.
* Fixed a bug in the memcached client causing keys with spaces or line breaks in them to break the memcached protocol, injecting arbitrary commands or parameters. Since the PECL client apparently also has this flaw, I implemented the fix in the wrapper class.
* Renamed BagOStuff::set_debug() to setDebug(), since we aren't emulating the memcached client anymore
* Fixed spelling error in MWMemcached: persistant -> persistent
2011-03-03 09:37:37 +00:00
|
|
|
}
|
2011-03-06 23:29:29 +00:00
|
|
|
return $value;
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ get_multi()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get multiple keys from the server(s)
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param array $keys Keys to retrieve
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return array
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
public function get_multi( $keys ) {
|
|
|
|
|
if ( !$this->_active ) {
|
2016-05-06 18:25:56 +00:00
|
|
|
return array();
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2010-07-04 20:43:58 +00:00
|
|
|
if ( isset( $this->stats['get_multi'] ) ) {
|
|
|
|
|
$this->stats['get_multi']++;
|
|
|
|
|
} else {
|
|
|
|
|
$this->stats['get_multi'] = 1;
|
|
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
$sock_keys = array();
|
2012-03-17 22:52:54 +00:00
|
|
|
$socks = array();
|
2009-12-13 17:57:21 +00:00
|
|
|
foreach ( $keys as $key ) {
|
|
|
|
|
$sock = $this->get_sock( $key );
|
|
|
|
|
if ( !is_resource( $sock ) ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$key = is_array( $key ) ? $key[1] : $key;
|
|
|
|
|
if ( !isset( $sock_keys[$sock] ) ) {
|
2013-03-24 10:01:51 +00:00
|
|
|
$sock_keys[intval( $sock )] = array();
|
2009-11-12 16:19:16 +00:00
|
|
|
$socks[] = $sock;
|
|
|
|
|
}
|
2013-03-24 10:01:51 +00:00
|
|
|
$sock_keys[intval( $sock )][] = $key;
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
|
2012-03-17 22:52:54 +00:00
|
|
|
$gather = array();
|
2009-11-12 16:19:16 +00:00
|
|
|
// Send out the requests
|
2009-12-13 17:57:21 +00:00
|
|
|
foreach ( $socks as $sock ) {
|
2012-10-01 14:05:22 +00:00
|
|
|
$cmd = 'gets';
|
2013-03-24 10:01:51 +00:00
|
|
|
foreach ( $sock_keys[intval( $sock )] as $key ) {
|
2009-12-13 17:57:21 +00:00
|
|
|
$cmd .= ' ' . $key;
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
$cmd .= "\r\n";
|
|
|
|
|
|
2012-08-03 07:03:03 +00:00
|
|
|
if ( $this->_fwrite( $sock, $cmd ) ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
$gather[] = $sock;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse responses
|
|
|
|
|
$val = array();
|
2009-12-13 17:57:21 +00:00
|
|
|
foreach ( $gather as $sock ) {
|
2012-10-01 14:05:22 +00:00
|
|
|
$this->_load_items( $sock, $val, $casToken );
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
|
2009-12-13 17:57:21 +00:00
|
|
|
if ( $this->_debug ) {
|
|
|
|
|
foreach ( $val as $k => $v ) {
|
2016-02-07 22:01:30 +00:00
|
|
|
$this->_debugprint( sprintf( "MemCache: got %s", $k ) );
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
|
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
|
|
|
|
return $val;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ incr()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Increments $key (optionally) by $amt
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $key Key to increment
|
|
|
|
|
* @param int $amt (optional) amount to increment
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return int|null Null if the key does not exist yet (this does NOT
|
2010-10-18 06:12:55 +00:00
|
|
|
* create new mappings if the key does not exist). If the key does
|
|
|
|
|
* exist, this returns the new value for that key.
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
public function incr( $key, $amt = 1 ) {
|
|
|
|
|
return $this->_incrdecr( 'incr', $key, $amt );
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ replace()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Overwrites an existing value for key; only works if key is already set
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $key Key to set value as
|
|
|
|
|
* @param mixed $value Value to store
|
|
|
|
|
* @param int $exp (optional) Expiration time. This can be a number of seconds
|
2010-10-18 06:12:55 +00:00
|
|
|
* to cache for (up to 30 days inclusive). Any timespans of 30 days + 1 second or
|
|
|
|
|
* longer must be the timestamp of the time at which the mapping should expire. It
|
|
|
|
|
* is safe to use timestamps in all cases, regardless of exipration
|
|
|
|
|
* eg: strtotime("+3 hour")
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return bool
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
public function replace( $key, $value, $exp = 0 ) {
|
|
|
|
|
return $this->_set( 'replace', $key, $value, $exp );
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ run_command()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Passes through $cmd to the memcache server connected by $sock; returns
|
|
|
|
|
* output as an array (null array if no output)
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param Resource $sock Socket to send command on
|
|
|
|
|
* @param string $cmd Command to run
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return array Output array
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2010-05-25 18:34:15 +00:00
|
|
|
public function run_command( $sock, $cmd ) {
|
2009-12-13 17:57:21 +00:00
|
|
|
if ( !is_resource( $sock ) ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
return array();
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2012-08-03 07:03:03 +00:00
|
|
|
if ( !$this->_fwrite( $sock, $cmd ) ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
return array();
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2012-03-17 22:52:54 +00:00
|
|
|
$ret = array();
|
2009-12-13 17:57:21 +00:00
|
|
|
while ( true ) {
|
2012-08-03 07:03:03 +00:00
|
|
|
$res = $this->_fgets( $sock );
|
2009-11-12 16:19:16 +00:00
|
|
|
$ret[] = $res;
|
2009-12-13 17:57:21 +00:00
|
|
|
if ( preg_match( '/^END/', $res ) ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
break;
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
|
|
|
|
if ( strlen( $res ) == 0 ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
break;
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
return $ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ set()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Unconditionally sets a key to a given value in the memcache. Returns true
|
|
|
|
|
* if set successfully.
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $key Key to set value as
|
|
|
|
|
* @param mixed $value Value to set
|
|
|
|
|
* @param int $exp (optional) Expiration time. This can be a number of seconds
|
2010-10-18 06:12:55 +00:00
|
|
|
* to cache for (up to 30 days inclusive). Any timespans of 30 days + 1 second or
|
|
|
|
|
* longer must be the timestamp of the time at which the mapping should expire. It
|
|
|
|
|
* is safe to use timestamps in all cases, regardless of exipration
|
|
|
|
|
* eg: strtotime("+3 hour")
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return bool True on success
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
public function set( $key, $value, $exp = 0 ) {
|
|
|
|
|
return $this->_set( 'set', $key, $value, $exp );
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
|
2012-10-01 14:05:22 +00:00
|
|
|
// }}}
|
|
|
|
|
// {{{ cas()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sets a key to a given value in the memcache if the current value still corresponds
|
|
|
|
|
* to a known, given value. Returns true if set successfully.
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param float $casToken Current known value
|
|
|
|
|
* @param string $key Key to set value as
|
|
|
|
|
* @param mixed $value Value to set
|
|
|
|
|
* @param int $exp (optional) Expiration time. This can be a number of seconds
|
2012-10-01 14:05:22 +00:00
|
|
|
* to cache for (up to 30 days inclusive). Any timespans of 30 days + 1 second or
|
|
|
|
|
* longer must be the timestamp of the time at which the mapping should expire. It
|
|
|
|
|
* is safe to use timestamps in all cases, regardless of exipration
|
|
|
|
|
* eg: strtotime("+3 hour")
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return bool True on success
|
2012-10-01 14:05:22 +00:00
|
|
|
*/
|
|
|
|
|
public function cas( $casToken, $key, $value, $exp = 0 ) {
|
|
|
|
|
return $this->_set( 'cas', $key, $value, $exp, $casToken );
|
|
|
|
|
}
|
|
|
|
|
|
2009-11-12 16:19:16 +00:00
|
|
|
// }}}
|
|
|
|
|
// {{{ set_compress_threshold()
|
|
|
|
|
|
|
|
|
|
/**
|
2015-11-10 03:20:08 +00:00
|
|
|
* Set the compression threshold
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param int $thresh Threshold to compress if larger than
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
public function set_compress_threshold( $thresh ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
$this->_compress_threshold = $thresh;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ set_debug()
|
|
|
|
|
|
|
|
|
|
/**
|
2015-11-10 03:20:08 +00:00
|
|
|
* Set the debug flag
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2015-11-10 03:20:08 +00:00
|
|
|
* @see __construct()
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param bool $dbg True for debugging, false otherwise
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
public function set_debug( $dbg ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
$this->_debug = $dbg;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ set_servers()
|
|
|
|
|
|
|
|
|
|
/**
|
2015-11-10 03:20:08 +00:00
|
|
|
* Set the server list to distribute key gets and puts between
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2015-11-10 03:20:08 +00:00
|
|
|
* @see __construct()
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param array $list Array of servers to connect to
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
public function set_servers( $list ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
$this->_servers = $list;
|
2009-12-13 17:57:21 +00:00
|
|
|
$this->_active = count( $list );
|
2009-11-12 16:19:16 +00:00
|
|
|
$this->_buckets = null;
|
|
|
|
|
$this->_bucketcount = 0;
|
|
|
|
|
|
|
|
|
|
$this->_single_sock = null;
|
2009-12-13 17:57:21 +00:00
|
|
|
if ( $this->_active == 1 ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
$this->_single_sock = $this->_servers[0];
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sets the timeout for new connections
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param int $seconds Number of seconds
|
|
|
|
|
* @param int $microseconds Number of microseconds
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
public function set_timeout( $seconds, $microseconds ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
$this->_timeout_seconds = $seconds;
|
|
|
|
|
$this->_timeout_microseconds = $microseconds;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ private methods
|
|
|
|
|
// {{{ _close_sock()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Close the specified socket
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $sock Socket to close
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2014-04-05 19:54:24 +00:00
|
|
|
* @access private
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
function _close_sock( $sock ) {
|
|
|
|
|
$host = array_search( $sock, $this->_cache_sock );
|
|
|
|
|
fclose( $this->_cache_sock[$host] );
|
|
|
|
|
unset( $this->_cache_sock[$host] );
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ _connect_sock()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Connects $sock to $host, timing out after $timeout
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param int $sock Socket to connect
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $host Host:IP to connect to
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return bool
|
2014-04-05 19:54:24 +00:00
|
|
|
* @access private
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
function _connect_sock( &$sock, $host ) {
|
2014-03-20 18:59:20 +00:00
|
|
|
list( $ip, $port ) = preg_split( '/:(?=\d)/', $host );
|
2009-11-12 16:19:16 +00:00
|
|
|
$sock = false;
|
|
|
|
|
$timeout = $this->_connect_timeout;
|
|
|
|
|
$errno = $errstr = null;
|
2013-04-20 20:28:52 +00:00
|
|
|
for ( $i = 0; !$sock && $i < $this->_connect_attempts; $i++ ) {
|
2018-02-10 07:52:26 +00:00
|
|
|
Wikimedia\suppressWarnings();
|
* Rewrote ObjectCache.php to conform to the modern coding style, and to be less convoluted about how CACHE_ANYTHING and CACHE_ACCEL are resolved. Moved most functionality to static members of a new ObjectCache class.
* Moved the global functions to GlobalFunctions.php, where they are now just convenience wrappers. Made them return non-references. Updated callers (none found in extensions).
* Added an advanced configuration method, $wgObjectCaches, which allows a lot more detail in the object cache configuration than $wgMainCacheType.
* Made all object cache classes derive from BagOStuff.
* Split the MWMemcached class into a generic client class and a MediaWiki-specific wrapper class. The wrapper class presents a simple BagOStuff interface to calling code, hiding memcached client internals, and will simplify the task of supporting the PECL extension.
* Added some extra constructor parameters to MWMemcached, configurable via $wgObjectCaches.
* Removed the *_multi() methods from BagOStuff, my grepping indicates that they are not used.
* Rewrote FakeMemCachedClient as a BagOStuff subclass, called EmptyBagOStuff.
* Added an optional "server" parameter to SQLBagOStuff. This allows the server holding the objectcache table to be different from the server holding the core DB.
* Added MultiWriteBagOStuff: a cache class which writes to multiple locations, and reads from them in a defined fallback sequence. This can be used to extend the cache space by adding disk-backed storage to existing in-memory caches.
* Made MWMemcached::get() return false on failure instead of null, to match the BagOStuff documentation and the other BagOStuff subclasses. Anything that was relying on it returning null would have already been broken with SqlBagOStuff.
* Fixed a bug in the memcached client causing keys with spaces or line breaks in them to break the memcached protocol, injecting arbitrary commands or parameters. Since the PECL client apparently also has this flaw, I implemented the fix in the wrapper class.
* Renamed BagOStuff::set_debug() to setDebug(), since we aren't emulating the memcached client anymore
* Fixed spelling error in MWMemcached: persistant -> persistent
2011-03-03 09:37:37 +00:00
|
|
|
if ( $this->_persistent == 1 ) {
|
2010-07-04 20:43:58 +00:00
|
|
|
$sock = pfsockopen( $ip, $port, $errno, $errstr, $timeout );
|
2009-12-13 17:57:21 +00:00
|
|
|
} else {
|
2010-07-04 20:43:58 +00:00
|
|
|
$sock = fsockopen( $ip, $port, $errno, $errstr, $timeout );
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
2018-02-10 07:52:26 +00:00
|
|
|
Wikimedia\restoreWarnings();
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
2009-12-13 17:57:21 +00:00
|
|
|
if ( !$sock ) {
|
2016-02-07 22:01:30 +00:00
|
|
|
$this->_error_log( "Error connecting to $host: $errstr" );
|
2012-08-03 07:03:03 +00:00
|
|
|
$this->_dead_host( $host );
|
2009-11-12 16:19:16 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialise timeout
|
2009-12-13 17:57:21 +00:00
|
|
|
stream_set_timeout( $sock, $this->_timeout_seconds, $this->_timeout_microseconds );
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2012-08-03 07:03:03 +00:00
|
|
|
// If the connection was persistent, flush the read buffer in case there
|
|
|
|
|
// was a previous incomplete request on this connection
|
|
|
|
|
if ( $this->_persistent ) {
|
|
|
|
|
$this->_flush_read_buffer( $sock );
|
|
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ _dead_sock()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Marks a host as dead until 30-40 seconds in the future
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $sock Socket to mark as dead
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2014-04-05 19:54:24 +00:00
|
|
|
* @access private
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
function _dead_sock( $sock ) {
|
|
|
|
|
$host = array_search( $sock, $this->_cache_sock );
|
|
|
|
|
$this->_dead_host( $host );
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
|
2012-03-17 22:52:54 +00:00
|
|
|
/**
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $host
|
2012-03-17 22:52:54 +00:00
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
function _dead_host( $host ) {
|
2016-02-17 19:54:59 +00:00
|
|
|
$ip = explode( ':', $host )[0];
|
2009-12-13 17:57:21 +00:00
|
|
|
$this->_host_dead[$ip] = time() + 30 + intval( rand( 0, 10 ) );
|
2009-11-12 16:19:16 +00:00
|
|
|
$this->_host_dead[$host] = $this->_host_dead[$ip];
|
2009-12-13 17:57:21 +00:00
|
|
|
unset( $this->_cache_sock[$host] );
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ get_sock()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* get_sock
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $key Key to retrieve value for;
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return Resource|bool Resource on success, false on failure
|
2010-05-25 18:34:15 +00:00
|
|
|
* @access private
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
function get_sock( $key ) {
|
|
|
|
|
if ( !$this->_active ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
return false;
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2009-12-13 17:57:21 +00:00
|
|
|
if ( $this->_single_sock !== null ) {
|
|
|
|
|
return $this->sock_to_host( $this->_single_sock );
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
|
2009-12-13 17:57:21 +00:00
|
|
|
$hv = is_array( $key ) ? intval( $key[0] ) : $this->_hashfunc( $key );
|
|
|
|
|
if ( $this->_buckets === null ) {
|
2012-03-17 22:52:54 +00:00
|
|
|
$bu = array();
|
2009-12-13 17:57:21 +00:00
|
|
|
foreach ( $this->_servers as $v ) {
|
|
|
|
|
if ( is_array( $v ) ) {
|
2013-04-20 20:28:52 +00:00
|
|
|
for ( $i = 0; $i < $v[1]; $i++ ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
$bu[] = $v[0];
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
|
|
|
|
} else {
|
2009-11-12 16:19:16 +00:00
|
|
|
$bu[] = $v;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$this->_buckets = $bu;
|
2009-12-13 17:57:21 +00:00
|
|
|
$this->_bucketcount = count( $bu );
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
|
2009-12-13 17:57:21 +00:00
|
|
|
$realkey = is_array( $key ) ? $key[1] : $key;
|
2013-04-20 20:28:52 +00:00
|
|
|
for ( $tries = 0; $tries < 20; $tries++ ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
$host = $this->_buckets[$hv % $this->_bucketcount];
|
2009-12-13 17:57:21 +00:00
|
|
|
$sock = $this->sock_to_host( $host );
|
|
|
|
|
if ( is_resource( $sock ) ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
return $sock;
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
$hv = $this->_hashfunc( $hv . $realkey );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ _hashfunc()
|
|
|
|
|
|
|
|
|
|
/**
|
2010-05-25 18:34:15 +00:00
|
|
|
* Creates a hash integer based on the $key
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $key Key to hash
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return int Hash value
|
2010-05-25 18:34:15 +00:00
|
|
|
* @access private
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
function _hashfunc( $key ) {
|
2012-05-11 05:45:23 +00:00
|
|
|
# Hash function must be in [0,0x7ffffff]
|
2009-11-12 16:19:16 +00:00
|
|
|
# We take the first 31 bits of the MD5 hash, which unlike the hash
|
|
|
|
|
# function used in a previous version of this client, works
|
2009-12-13 17:57:21 +00:00
|
|
|
return hexdec( substr( md5( $key ), 0, 8 ) ) & 0x7fffffff;
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ _incrdecr()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Perform increment/decriment on $key
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $cmd Command to perform
|
|
|
|
|
* @param string|array $key Key to perform it on
|
|
|
|
|
* @param int $amt Amount to adjust
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return int New value of $key
|
2010-05-25 18:34:15 +00:00
|
|
|
* @access private
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
function _incrdecr( $cmd, $key, $amt = 1 ) {
|
|
|
|
|
if ( !$this->_active ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
return null;
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2009-12-13 17:57:21 +00:00
|
|
|
$sock = $this->get_sock( $key );
|
|
|
|
|
if ( !is_resource( $sock ) ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
return null;
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2009-12-13 17:57:21 +00:00
|
|
|
$key = is_array( $key ) ? $key[1] : $key;
|
2010-07-04 20:43:58 +00:00
|
|
|
if ( isset( $this->stats[$cmd] ) ) {
|
|
|
|
|
$this->stats[$cmd]++;
|
|
|
|
|
} else {
|
|
|
|
|
$this->stats[$cmd] = 1;
|
|
|
|
|
}
|
2012-08-03 07:03:03 +00:00
|
|
|
if ( !$this->_fwrite( $sock, "$cmd $key $amt\r\n" ) ) {
|
2012-03-17 22:52:54 +00:00
|
|
|
return null;
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2012-08-03 07:03:03 +00:00
|
|
|
$line = $this->_fgets( $sock );
|
2009-11-12 16:19:16 +00:00
|
|
|
$match = array();
|
2009-12-13 17:57:21 +00:00
|
|
|
if ( !preg_match( '/^(\d+)/', $line, $match ) ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
return null;
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
return $match[1];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ _load_items()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Load items into $ret from $sock
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param Resource $sock Socket to read from
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param array $ret returned values
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param float $casToken [optional]
|
|
|
|
|
* @return bool True for success, false for failure
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2010-05-25 18:34:15 +00:00
|
|
|
* @access private
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2012-10-01 14:05:22 +00:00
|
|
|
function _load_items( $sock, &$ret, &$casToken = null ) {
|
2013-02-04 16:54:53 +00:00
|
|
|
$results = array();
|
|
|
|
|
|
2009-12-13 17:57:21 +00:00
|
|
|
while ( 1 ) {
|
2012-08-03 07:03:03 +00:00
|
|
|
$decl = $this->_fgets( $sock );
|
2013-03-07 17:54:12 +00:00
|
|
|
|
2013-04-20 20:28:52 +00:00
|
|
|
if ( $decl === false ) {
|
2013-03-07 17:54:12 +00:00
|
|
|
/*
|
|
|
|
|
* If nothing can be read, something is wrong because we know exactly when
|
|
|
|
|
* to stop reading (right after "END") and we return right after that.
|
|
|
|
|
*/
|
2012-08-03 07:03:03 +00:00
|
|
|
return false;
|
2012-10-01 14:05:22 +00:00
|
|
|
} elseif ( preg_match( '/^VALUE (\S+) (\d+) (\d+) (\d+)$/', $decl, $match ) ) {
|
2013-03-07 17:54:12 +00:00
|
|
|
/*
|
|
|
|
|
* Read all data returned. This can be either one or multiple values.
|
|
|
|
|
* Save all that data (in an array) to be processed later: we'll first
|
|
|
|
|
* want to continue reading until "END" before doing anything else,
|
|
|
|
|
* to make sure that we don't leave our client in a state where it's
|
|
|
|
|
* output is not yet fully read.
|
|
|
|
|
*/
|
2013-02-04 16:54:53 +00:00
|
|
|
$results[] = array(
|
|
|
|
|
$match[1], // rkey
|
|
|
|
|
$match[2], // flags
|
|
|
|
|
$match[3], // len
|
|
|
|
|
$match[4], // casToken
|
|
|
|
|
$this->_fread( $sock, $match[3] + 2 ), // data
|
|
|
|
|
);
|
|
|
|
|
} elseif ( $decl == "END" ) {
|
|
|
|
|
if ( count( $results ) == 0 ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2013-03-07 17:54:12 +00:00
|
|
|
/**
|
|
|
|
|
* All data has been read, time to process the data and build
|
|
|
|
|
* meaningful return values.
|
|
|
|
|
*/
|
2013-02-04 16:54:53 +00:00
|
|
|
foreach ( $results as $vars ) {
|
|
|
|
|
list( $rkey, $flags, $len, $casToken, $data ) = $vars;
|
|
|
|
|
|
|
|
|
|
if ( $data === false || substr( $data, -2 ) !== "\r\n" ) {
|
|
|
|
|
$this->_handle_error( $sock,
|
|
|
|
|
'line ending missing from data block from $1' );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
$data = substr( $data, 0, -2 );
|
|
|
|
|
$ret[$rkey] = $data;
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2013-02-04 16:54:53 +00:00
|
|
|
if ( $this->_have_zlib && $flags & self::COMPRESSED ) {
|
|
|
|
|
$ret[$rkey] = gzuncompress( $ret[$rkey] );
|
|
|
|
|
}
|
|
|
|
|
|
2013-03-07 17:54:12 +00:00
|
|
|
/*
|
|
|
|
|
* This unserialize is the exact reason that we only want to
|
|
|
|
|
* process data after having read until "END" (instead of doing
|
|
|
|
|
* this right away): "unserialize" can trigger outside code:
|
|
|
|
|
* in the event that $ret[$rkey] is a serialized object,
|
|
|
|
|
* unserializing it will trigger __wakeup() if present. If that
|
|
|
|
|
* function attempted to read from memcached (while we did not
|
|
|
|
|
* yet read "END"), these 2 calls would collide.
|
|
|
|
|
*/
|
2013-02-04 16:54:53 +00:00
|
|
|
if ( $flags & self::SERIALIZED ) {
|
|
|
|
|
$ret[$rkey] = unserialize( $ret[$rkey] );
|
2015-03-05 00:00:10 +00:00
|
|
|
} elseif ( $flags & self::INTVAL ) {
|
|
|
|
|
$ret[$rkey] = intval( $ret[$rkey] );
|
2013-02-04 16:54:53 +00:00
|
|
|
}
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2013-02-04 16:54:53 +00:00
|
|
|
return true;
|
2009-12-13 17:57:21 +00:00
|
|
|
} else {
|
2012-08-03 07:03:03 +00:00
|
|
|
$this->_handle_error( $sock, 'Error parsing response from $1' );
|
|
|
|
|
return false;
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ _set()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Performs the requested storage operation to the memcache server
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $cmd Command to perform
|
|
|
|
|
* @param string $key Key to act on
|
|
|
|
|
* @param mixed $val What we need to store
|
|
|
|
|
* @param int $exp (optional) Expiration time. This can be a number of seconds
|
2010-10-18 06:12:55 +00:00
|
|
|
* to cache for (up to 30 days inclusive). Any timespans of 30 days + 1 second or
|
|
|
|
|
* longer must be the timestamp of the time at which the mapping should expire. It
|
|
|
|
|
* is safe to use timestamps in all cases, regardless of exipration
|
|
|
|
|
* eg: strtotime("+3 hour")
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param float $casToken [optional]
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return bool
|
2010-05-25 18:34:15 +00:00
|
|
|
* @access private
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2012-10-01 14:05:22 +00:00
|
|
|
function _set( $cmd, $key, $val, $exp, $casToken = null ) {
|
2009-12-13 17:57:21 +00:00
|
|
|
if ( !$this->_active ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
return false;
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2009-12-13 17:57:21 +00:00
|
|
|
$sock = $this->get_sock( $key );
|
|
|
|
|
if ( !is_resource( $sock ) ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
return false;
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2010-07-04 20:43:58 +00:00
|
|
|
if ( isset( $this->stats[$cmd] ) ) {
|
|
|
|
|
$this->stats[$cmd]++;
|
|
|
|
|
} else {
|
|
|
|
|
$this->stats[$cmd] = 1;
|
|
|
|
|
}
|
2011-11-19 20:01:33 +00:00
|
|
|
|
2009-11-12 16:19:16 +00:00
|
|
|
$flags = 0;
|
|
|
|
|
|
2015-03-05 00:00:10 +00:00
|
|
|
if ( is_int( $val ) ) {
|
|
|
|
|
$flags |= self::INTVAL;
|
|
|
|
|
} elseif ( !is_scalar( $val ) ) {
|
2009-12-13 17:57:21 +00:00
|
|
|
$val = serialize( $val );
|
2009-11-20 19:14:24 +00:00
|
|
|
$flags |= self::SERIALIZED;
|
2009-12-13 17:57:21 +00:00
|
|
|
if ( $this->_debug ) {
|
2016-02-07 22:01:30 +00:00
|
|
|
$this->_debugprint( sprintf( "client: serializing data as it is not scalar" ) );
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
|
2009-12-13 17:57:21 +00:00
|
|
|
$len = strlen( $val );
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2013-12-01 20:39:00 +00:00
|
|
|
if ( $this->_have_zlib && $this->_compress_enable
|
|
|
|
|
&& $this->_compress_threshold && $len >= $this->_compress_threshold
|
|
|
|
|
) {
|
2009-12-13 17:57:21 +00:00
|
|
|
$c_val = gzcompress( $val, 9 );
|
|
|
|
|
$c_len = strlen( $c_val );
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2009-12-13 17:57:21 +00:00
|
|
|
if ( $c_len < $len * ( 1 - self::COMPRESSION_SAVINGS ) ) {
|
|
|
|
|
if ( $this->_debug ) {
|
2016-02-07 22:01:30 +00:00
|
|
|
$this->_debugprint( sprintf( "client: compressing data; was %d bytes is now %d bytes", $len, $c_len ) );
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
$val = $c_val;
|
|
|
|
|
$len = $c_len;
|
2009-11-20 19:14:24 +00:00
|
|
|
$flags |= self::COMPRESSED;
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
}
|
2012-10-01 14:05:22 +00:00
|
|
|
|
|
|
|
|
$command = "$cmd $key $flags $exp $len";
|
|
|
|
|
if ( $casToken ) {
|
|
|
|
|
$command .= " $casToken";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( !$this->_fwrite( $sock, "$command\r\n$val\r\n" ) ) {
|
2012-03-17 22:52:54 +00:00
|
|
|
return false;
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2012-08-03 07:03:03 +00:00
|
|
|
$line = $this->_fgets( $sock );
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2009-12-13 17:57:21 +00:00
|
|
|
if ( $this->_debug ) {
|
2016-02-07 22:01:30 +00:00
|
|
|
$this->_debugprint( sprintf( "%s %s (%s)", $cmd, $key, $line ) );
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
2017-05-24 17:50:05 +00:00
|
|
|
if ( $line === "STORED" ) {
|
|
|
|
|
return true;
|
|
|
|
|
} elseif ( $line === "NOT_STORED" && $cmd === "set" ) {
|
|
|
|
|
// "Not stored" is always used as the mcrouter response with AllAsyncRoute
|
2009-11-12 16:19:16 +00:00
|
|
|
return true;
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2017-05-24 17:50:05 +00:00
|
|
|
|
2009-11-12 16:19:16 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// {{{ sock_to_host()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the socket for the host
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $host Host:IP to get socket for
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return Resource|bool IO Stream or false
|
2010-05-25 18:34:15 +00:00
|
|
|
* @access private
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
function sock_to_host( $host ) {
|
|
|
|
|
if ( isset( $this->_cache_sock[$host] ) ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
return $this->_cache_sock[$host];
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
|
|
|
|
$sock = null;
|
|
|
|
|
$now = time();
|
2009-12-13 17:57:21 +00:00
|
|
|
list( $ip, /* $port */) = explode( ':', $host );
|
|
|
|
|
if ( isset( $this->_host_dead[$host] ) && $this->_host_dead[$host] > $now ||
|
|
|
|
|
isset( $this->_host_dead[$ip] ) && $this->_host_dead[$ip] > $now
|
|
|
|
|
) {
|
2009-11-12 16:19:16 +00:00
|
|
|
return null;
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2009-12-13 17:57:21 +00:00
|
|
|
if ( !$this->_connect_sock( $sock, $host ) ) {
|
2012-03-17 22:52:54 +00:00
|
|
|
return null;
|
2009-12-13 17:57:21 +00:00
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
|
|
|
|
// Do not buffer writes
|
2009-12-13 17:57:21 +00:00
|
|
|
stream_set_write_buffer( $sock, 0 );
|
2009-11-12 16:19:16 +00:00
|
|
|
|
|
|
|
|
$this->_cache_sock[$host] = $sock;
|
|
|
|
|
|
|
|
|
|
return $this->_cache_sock[$host];
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-17 22:52:54 +00:00
|
|
|
/**
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $text
|
2012-08-03 07:03:03 +00:00
|
|
|
*/
|
|
|
|
|
function _debugprint( $text ) {
|
2014-11-19 06:42:33 +00:00
|
|
|
$this->_logger->debug( $text );
|
2012-08-03 07:03:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $text
|
2012-03-17 22:52:54 +00:00
|
|
|
*/
|
2012-08-03 07:03:03 +00:00
|
|
|
function _error_log( $text ) {
|
2014-11-19 06:42:33 +00:00
|
|
|
$this->_logger->error( "Memcached error: $text" );
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-08-03 07:03:03 +00:00
|
|
|
* Write to a stream. If there is an error, mark the socket dead.
|
2009-11-12 16:19:16 +00:00
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param Resource $sock The socket
|
|
|
|
|
* @param string $buf The string to write
|
2012-08-03 07:03:03 +00:00
|
|
|
* @return bool True on success, false on failure
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2012-08-03 07:03:03 +00:00
|
|
|
function _fwrite( $sock, $buf ) {
|
|
|
|
|
$bytesWritten = 0;
|
|
|
|
|
$bufSize = strlen( $buf );
|
2013-04-27 12:02:08 +00:00
|
|
|
while ( $bytesWritten < $bufSize ) {
|
2012-08-03 07:03:03 +00:00
|
|
|
$result = fwrite( $sock, $buf );
|
|
|
|
|
$data = stream_get_meta_data( $sock );
|
|
|
|
|
if ( $data['timed_out'] ) {
|
|
|
|
|
$this->_handle_error( $sock, 'timeout writing to $1' );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// Contrary to the documentation, fwrite() returns zero on error in PHP 5.3.
|
|
|
|
|
if ( $result === false || $result === 0 ) {
|
|
|
|
|
$this->_handle_error( $sock, 'error writing to $1' );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
$bytesWritten += $result;
|
|
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2012-08-03 07:03:03 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle an I/O error. Mark the socket dead and log an error.
|
2014-04-18 23:19:46 +00:00
|
|
|
*
|
|
|
|
|
* @param Resource $sock
|
|
|
|
|
* @param string $msg
|
2012-08-03 07:03:03 +00:00
|
|
|
*/
|
|
|
|
|
function _handle_error( $sock, $msg ) {
|
|
|
|
|
$peer = stream_socket_get_name( $sock, true /** remote **/ );
|
|
|
|
|
if ( strval( $peer ) === '' ) {
|
|
|
|
|
$peer = array_search( $sock, $this->_cache_sock );
|
|
|
|
|
if ( $peer === false ) {
|
|
|
|
|
$peer = '[unknown host]';
|
|
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
2012-08-03 07:03:03 +00:00
|
|
|
$msg = str_replace( '$1', $peer, $msg );
|
2016-02-07 22:01:30 +00:00
|
|
|
$this->_error_log( "$msg" );
|
2012-08-03 07:03:03 +00:00
|
|
|
$this->_dead_sock( $sock );
|
|
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
2012-08-03 07:03:03 +00:00
|
|
|
/**
|
2012-10-19 20:10:42 +00:00
|
|
|
* Read the specified number of bytes from a stream. If there is an error,
|
2012-08-03 07:03:03 +00:00
|
|
|
* mark the socket dead.
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param Resource $sock The socket
|
|
|
|
|
* @param int $len The number of bytes to read
|
|
|
|
|
* @return string|bool The string on success, false on failure.
|
2012-08-03 07:03:03 +00:00
|
|
|
*/
|
|
|
|
|
function _fread( $sock, $len ) {
|
|
|
|
|
$buf = '';
|
|
|
|
|
while ( $len > 0 ) {
|
|
|
|
|
$result = fread( $sock, $len );
|
|
|
|
|
$data = stream_get_meta_data( $sock );
|
|
|
|
|
if ( $data['timed_out'] ) {
|
|
|
|
|
$this->_handle_error( $sock, 'timeout reading from $1' );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if ( $result === false ) {
|
|
|
|
|
$this->_handle_error( $sock, 'error reading buffer from $1' );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if ( $result === '' ) {
|
|
|
|
|
// This will happen if the remote end of the socket is shut down
|
|
|
|
|
$this->_handle_error( $sock, 'unexpected end of file reading from $1' );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
$len -= strlen( $result );
|
|
|
|
|
$buf .= $result;
|
|
|
|
|
}
|
|
|
|
|
return $buf;
|
|
|
|
|
}
|
2009-11-12 16:19:16 +00:00
|
|
|
|
|
|
|
|
/**
|
2012-08-03 07:03:03 +00:00
|
|
|
* Read a line from a stream. If there is an error, mark the socket dead.
|
|
|
|
|
* The \r\n line ending is stripped from the response.
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param Resource $sock The socket
|
|
|
|
|
* @return string|bool The string on success, false on failure
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2012-08-03 07:03:03 +00:00
|
|
|
function _fgets( $sock ) {
|
|
|
|
|
$result = fgets( $sock );
|
|
|
|
|
// fgets() may return a partial line if there is a select timeout after
|
2012-10-19 20:10:42 +00:00
|
|
|
// a successful recv(), so we have to check for a timeout even if we
|
2012-08-03 07:03:03 +00:00
|
|
|
// got a string response.
|
|
|
|
|
$data = stream_get_meta_data( $sock );
|
|
|
|
|
if ( $data['timed_out'] ) {
|
|
|
|
|
$this->_handle_error( $sock, 'timeout reading line from $1' );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if ( $result === false ) {
|
|
|
|
|
$this->_handle_error( $sock, 'error reading line from $1' );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if ( substr( $result, -2 ) === "\r\n" ) {
|
|
|
|
|
$result = substr( $result, 0, -2 );
|
|
|
|
|
} elseif ( substr( $result, -1 ) === "\n" ) {
|
|
|
|
|
$result = substr( $result, 0, -1 );
|
2009-11-12 16:19:16 +00:00
|
|
|
} else {
|
2012-08-03 07:03:03 +00:00
|
|
|
$this->_handle_error( $sock, 'line ending missing in response from $1' );
|
|
|
|
|
return false;
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
2012-08-03 07:03:03 +00:00
|
|
|
return $result;
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Flush the read buffer of a stream
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param Resource $f
|
2009-11-12 16:19:16 +00:00
|
|
|
*/
|
2009-12-13 17:57:21 +00:00
|
|
|
function _flush_read_buffer( $f ) {
|
|
|
|
|
if ( !is_resource( $f ) ) {
|
2009-11-12 16:19:16 +00:00
|
|
|
return;
|
|
|
|
|
}
|
2012-10-12 00:12:27 +00:00
|
|
|
$r = array( $f );
|
|
|
|
|
$w = null;
|
|
|
|
|
$e = null;
|
|
|
|
|
$n = stream_select( $r, $w, $e, 0, 0 );
|
2009-12-13 17:57:21 +00:00
|
|
|
while ( $n == 1 && !feof( $f ) ) {
|
|
|
|
|
fread( $f, 1024 );
|
2012-10-12 00:12:27 +00:00
|
|
|
$r = array( $f );
|
|
|
|
|
$w = null;
|
|
|
|
|
$e = null;
|
|
|
|
|
$n = stream_select( $r, $w, $e, 0, 0 );
|
2009-11-12 16:19:16 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
// }}}
|
|
|
|
|
// }}}
|
2004-01-24 22:58:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// }}}
|