2011-03-03 04:38:17 +00:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* Copyright © 2003-2004 Brion Vibber <brion@pobox.com>
|
2014-03-13 22:23:56 +00:00
|
|
|
* https://www.mediawiki.org/
|
2011-03-03 04:38:17 +00:00
|
|
|
*
|
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* 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 General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
|
|
|
*
|
|
|
|
|
* @file
|
|
|
|
|
* @ingroup Cache
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @defgroup Cache Cache
|
|
|
|
|
*/
|
|
|
|
|
|
2014-11-19 06:42:33 +00:00
|
|
|
use Psr\Log\LoggerAwareInterface;
|
|
|
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
|
use Psr\Log\NullLogger;
|
2016-10-12 05:36:03 +00:00
|
|
|
use Wikimedia\ScopedCallback;
|
2016-10-01 07:07:19 +00:00
|
|
|
use Wikimedia\WaitConditionLoop;
|
2014-11-19 06:42:33 +00:00
|
|
|
|
2011-03-03 04:38:17 +00:00
|
|
|
/**
|
2018-05-15 22:33:38 +00:00
|
|
|
* Class representing a cache/ephemeral data store
|
2011-03-03 04:38:17 +00:00
|
|
|
*
|
2018-05-15 22:33:38 +00:00
|
|
|
* This interface is intended to be more or less compatible with the PHP memcached client.
|
|
|
|
|
*
|
|
|
|
|
* Instances of this class should be created with an intended access scope, such as:
|
|
|
|
|
* - a) A single PHP thread on a server (e.g. stored in a PHP variable)
|
|
|
|
|
* - b) A single application server (e.g. stored in APC or sqlite)
|
|
|
|
|
* - c) All application servers in datacenter (e.g. stored in memcached or mysql)
|
|
|
|
|
* - d) All application servers in all datacenters (e.g. stored via mcrouter or dynomite)
|
|
|
|
|
*
|
|
|
|
|
* Callers should use the proper factory methods that yield BagOStuff instances. Site admins
|
|
|
|
|
* should make sure the configuration for those factory methods matches their access scope.
|
|
|
|
|
* BagOStuff subclasses have widely varying levels of support for replication features.
|
|
|
|
|
*
|
|
|
|
|
* For any given instance, methods like lock(), unlock(), merge(), and set() with WRITE_SYNC
|
|
|
|
|
* should semantically operate over its entire access scope; any nodes/threads in that scope
|
|
|
|
|
* should serialize appropriately when using them. Likewise, a call to get() with READ_LATEST
|
|
|
|
|
* from one node in its access scope should reflect the prior changes of any other node its access
|
|
|
|
|
* scope. Any get() should reflect the changes of any prior set() with WRITE_SYNC.
|
2011-03-03 04:38:17 +00:00
|
|
|
*
|
|
|
|
|
* @ingroup Cache
|
|
|
|
|
*/
|
2015-10-19 17:52:19 +00:00
|
|
|
abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface {
|
2015-08-21 21:03:48 +00:00
|
|
|
/** @var array[] Lock tracking */
|
2016-02-17 09:09:32 +00:00
|
|
|
protected $locks = [];
|
2017-08-20 11:20:59 +00:00
|
|
|
/** @var int ERR_* class constant */
|
2014-04-01 20:25:42 +00:00
|
|
|
protected $lastError = self::ERR_NONE;
|
2015-10-09 23:35:08 +00:00
|
|
|
/** @var string */
|
|
|
|
|
protected $keyspace = 'local';
|
2015-08-21 21:03:48 +00:00
|
|
|
/** @var LoggerInterface */
|
2014-11-19 06:42:33 +00:00
|
|
|
protected $logger;
|
2016-03-02 02:25:55 +00:00
|
|
|
/** @var callback|null */
|
|
|
|
|
protected $asyncHandler;
|
2017-08-20 11:20:59 +00:00
|
|
|
/** @var int Seconds */
|
2016-08-29 17:00:05 +00:00
|
|
|
protected $syncTimeout;
|
2016-03-02 02:25:55 +00:00
|
|
|
|
2015-08-21 21:03:48 +00:00
|
|
|
/** @var bool */
|
|
|
|
|
private $debugMode = false;
|
2016-03-02 02:25:55 +00:00
|
|
|
/** @var array */
|
|
|
|
|
private $duplicateKeyLookups = [];
|
|
|
|
|
/** @var bool */
|
|
|
|
|
private $reportDupes = false;
|
|
|
|
|
/** @var bool */
|
|
|
|
|
private $dupeTrackScheduled = false;
|
|
|
|
|
|
2016-08-29 17:00:05 +00:00
|
|
|
/** @var callable[] */
|
|
|
|
|
protected $busyCallbacks = [];
|
|
|
|
|
|
2018-05-31 06:14:09 +00:00
|
|
|
/** @var float|null */
|
|
|
|
|
private $wallClockOverride;
|
|
|
|
|
|
2017-08-20 11:20:59 +00:00
|
|
|
/** @var int[] Map of (ATTR_* class constant => QOS_* class constant) */
|
2016-08-02 10:57:52 +00:00
|
|
|
protected $attrMap = [];
|
|
|
|
|
|
2014-04-01 20:25:42 +00:00
|
|
|
/** Possible values for getLastError() */
|
2014-07-19 21:12:10 +00:00
|
|
|
const ERR_NONE = 0; // no error
|
2014-04-01 20:25:42 +00:00
|
|
|
const ERR_NO_RESPONSE = 1; // no response
|
|
|
|
|
const ERR_UNREACHABLE = 2; // can't connect
|
2014-07-19 21:12:10 +00:00
|
|
|
const ERR_UNEXPECTED = 3; // response gave some error
|
2014-04-01 20:25:42 +00:00
|
|
|
|
2015-05-27 18:52:44 +00:00
|
|
|
/** Bitfield constants for get()/getMulti() */
|
|
|
|
|
const READ_LATEST = 1; // use latest data for replicated stores
|
2015-10-03 07:43:40 +00:00
|
|
|
const READ_VERIFIED = 2; // promise that caller can tell when keys are stale
|
2015-10-18 22:57:42 +00:00
|
|
|
/** Bitfield constants for set()/merge() */
|
|
|
|
|
const WRITE_SYNC = 1; // synchronously write to all locations for replicated stores
|
2016-01-30 01:09:57 +00:00
|
|
|
const WRITE_CACHE_ONLY = 2; // Only change state of the in-memory cache
|
2015-05-27 18:52:44 +00:00
|
|
|
|
2016-03-02 02:25:55 +00:00
|
|
|
/**
|
|
|
|
|
* $params include:
|
|
|
|
|
* - logger: Psr\Log\LoggerInterface instance
|
|
|
|
|
* - keyspace: Default keyspace for $this->makeKey()
|
|
|
|
|
* - asyncHandler: Callable to use for scheduling tasks after the web request ends.
|
|
|
|
|
* In CLI mode, it should run the task immediately.
|
|
|
|
|
* - reportDupes: Whether to emit warning log messages for all keys that were
|
|
|
|
|
* requested more than once (requires an asyncHandler).
|
2016-08-29 17:00:05 +00:00
|
|
|
* - syncTimeout: How long to wait with WRITE_SYNC in seconds.
|
2016-03-02 02:25:55 +00:00
|
|
|
* @param array $params
|
|
|
|
|
*/
|
2016-02-17 09:09:32 +00:00
|
|
|
public function __construct( array $params = [] ) {
|
2014-11-19 06:42:33 +00:00
|
|
|
if ( isset( $params['logger'] ) ) {
|
|
|
|
|
$this->setLogger( $params['logger'] );
|
|
|
|
|
} else {
|
|
|
|
|
$this->setLogger( new NullLogger() );
|
|
|
|
|
}
|
2015-10-09 23:35:08 +00:00
|
|
|
|
|
|
|
|
if ( isset( $params['keyspace'] ) ) {
|
|
|
|
|
$this->keyspace = $params['keyspace'];
|
|
|
|
|
}
|
2016-03-02 02:25:55 +00:00
|
|
|
|
2017-10-06 22:17:58 +00:00
|
|
|
$this->asyncHandler = $params['asyncHandler'] ?? null;
|
2016-03-02 02:25:55 +00:00
|
|
|
|
|
|
|
|
if ( !empty( $params['reportDupes'] ) && is_callable( $this->asyncHandler ) ) {
|
|
|
|
|
$this->reportDupes = true;
|
|
|
|
|
}
|
2016-08-29 17:00:05 +00:00
|
|
|
|
2017-10-06 22:17:58 +00:00
|
|
|
$this->syncTimeout = $params['syncTimeout'] ?? 3;
|
2014-11-19 06:42:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param LoggerInterface $logger
|
|
|
|
|
* @return null
|
|
|
|
|
*/
|
|
|
|
|
public function setLogger( LoggerInterface $logger ) {
|
|
|
|
|
$this->logger = $logger;
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-28 15:59:57 +00:00
|
|
|
/**
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param bool $bool
|
2011-05-28 15:59:57 +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
|
|
|
public function setDebug( $bool ) {
|
2011-03-03 04:38:17 +00:00
|
|
|
$this->debugMode = $bool;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-03 19:37:28 +00:00
|
|
|
/**
|
|
|
|
|
* Get an item with the given key, regenerating and setting it if not found
|
|
|
|
|
*
|
|
|
|
|
* If the callback returns false, then nothing is stored.
|
|
|
|
|
*
|
|
|
|
|
* @param string $key
|
|
|
|
|
* @param int $ttl Time-to-live (seconds)
|
|
|
|
|
* @param callable $callback Callback that derives the new value
|
2017-08-20 11:20:59 +00:00
|
|
|
* @param int $flags Bitfield of BagOStuff::READ_* constants [optional]
|
2015-10-03 19:37:28 +00:00
|
|
|
* @return mixed The cached value if found or the result of $callback otherwise
|
|
|
|
|
* @since 1.27
|
|
|
|
|
*/
|
2015-10-08 03:51:00 +00:00
|
|
|
final public function getWithSetCallback( $key, $ttl, $callback, $flags = 0 ) {
|
|
|
|
|
$value = $this->get( $key, $flags );
|
2015-10-03 19:37:28 +00:00
|
|
|
|
|
|
|
|
if ( $value === false ) {
|
|
|
|
|
if ( !is_callable( $callback ) ) {
|
|
|
|
|
throw new InvalidArgumentException( "Invalid cache miss callback provided." );
|
|
|
|
|
}
|
|
|
|
|
$value = call_user_func( $callback );
|
|
|
|
|
if ( $value !== false ) {
|
|
|
|
|
$this->set( $key, $value, $ttl );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $value;
|
|
|
|
|
}
|
|
|
|
|
|
2011-03-03 04:38:17 +00:00
|
|
|
/**
|
2015-10-03 07:43:40 +00:00
|
|
|
* Get an item with the given key
|
|
|
|
|
*
|
2018-05-15 22:33:38 +00:00
|
|
|
* If the key includes a deterministic input hash (e.g. the key can only have
|
2015-10-03 07:43:40 +00:00
|
|
|
* the correct value) or complete staleness checks are handled by the caller
|
|
|
|
|
* (e.g. nothing relies on the TTL), then the READ_VERIFIED flag should be set.
|
|
|
|
|
* This lets tiered backends know they can safely upgrade a cached value to
|
|
|
|
|
* higher tiers using standard TTLs.
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $key
|
2017-08-20 11:20:59 +00:00
|
|
|
* @param int $flags Bitfield of BagOStuff::READ_* constants [optional]
|
|
|
|
|
* @param int $oldFlags [unused]
|
2015-10-03 07:43:40 +00:00
|
|
|
* @return mixed Returns false on failure and if the item does not exist
|
2011-03-03 04:38:17 +00:00
|
|
|
*/
|
2015-10-04 05:59:38 +00:00
|
|
|
public function get( $key, $flags = 0, $oldFlags = null ) {
|
|
|
|
|
// B/C for ( $key, &$casToken = null, $flags = 0 )
|
|
|
|
|
$flags = is_int( $oldFlags ) ? $oldFlags : $flags;
|
|
|
|
|
|
2016-03-02 02:25:55 +00:00
|
|
|
$this->trackDuplicateKeys( $key );
|
|
|
|
|
|
2015-10-04 05:59:38 +00:00
|
|
|
return $this->doGet( $key, $flags );
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-02 02:25:55 +00:00
|
|
|
/**
|
|
|
|
|
* Track the number of times that a given key has been used.
|
|
|
|
|
* @param string $key
|
|
|
|
|
*/
|
|
|
|
|
private function trackDuplicateKeys( $key ) {
|
|
|
|
|
if ( !$this->reportDupes ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( !isset( $this->duplicateKeyLookups[$key] ) ) {
|
|
|
|
|
// Track that we have seen this key. This N-1 counting style allows
|
|
|
|
|
// easy filtering with array_filter() later.
|
|
|
|
|
$this->duplicateKeyLookups[$key] = 0;
|
|
|
|
|
} else {
|
|
|
|
|
$this->duplicateKeyLookups[$key] += 1;
|
|
|
|
|
|
|
|
|
|
if ( $this->dupeTrackScheduled === false ) {
|
|
|
|
|
$this->dupeTrackScheduled = true;
|
|
|
|
|
// Schedule a callback that logs keys processed more than once by get().
|
|
|
|
|
call_user_func( $this->asyncHandler, function () {
|
|
|
|
|
$dups = array_filter( $this->duplicateKeyLookups );
|
|
|
|
|
foreach ( $dups as $key => $count ) {
|
|
|
|
|
$this->logger->warning(
|
|
|
|
|
'Duplicate get(): "{key}" fetched {count} times',
|
|
|
|
|
// Count is N-1 of the actual lookup count
|
|
|
|
|
[ 'key' => $key, 'count' => $count + 1, ]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-04 05:59:38 +00:00
|
|
|
/**
|
|
|
|
|
* @param string $key
|
2017-08-20 11:20:59 +00:00
|
|
|
* @param int $flags Bitfield of BagOStuff::READ_* constants [optional]
|
2015-10-04 05:59:38 +00:00
|
|
|
* @return mixed Returns false on failure and if the item does not exist
|
|
|
|
|
*/
|
|
|
|
|
abstract protected function doGet( $key, $flags = 0 );
|
|
|
|
|
|
|
|
|
|
/**
|
2015-10-22 01:35:35 +00:00
|
|
|
* @note: This method is only needed if merge() uses mergeViaCas()
|
2015-10-04 05:59:38 +00:00
|
|
|
*
|
|
|
|
|
* @param string $key
|
2017-08-11 00:23:16 +00:00
|
|
|
* @param mixed &$casToken
|
2017-08-20 11:20:59 +00:00
|
|
|
* @param int $flags Bitfield of BagOStuff::READ_* constants [optional]
|
2015-10-04 05:59:38 +00:00
|
|
|
* @return mixed Returns false on failure and if the item does not exist
|
2015-10-18 22:57:42 +00:00
|
|
|
* @throws Exception
|
2015-10-04 05:59:38 +00:00
|
|
|
*/
|
|
|
|
|
protected function getWithToken( $key, &$casToken, $flags = 0 ) {
|
|
|
|
|
throw new Exception( __METHOD__ . ' not implemented.' );
|
|
|
|
|
}
|
2011-03-03 04:38:17 +00:00
|
|
|
|
|
|
|
|
/**
|
2015-10-03 07:43:40 +00:00
|
|
|
* Set an item
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $key
|
|
|
|
|
* @param mixed $value
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param int $exptime Either an interval in seconds or a unix timestamp for expiry
|
2015-10-18 22:57:42 +00:00
|
|
|
* @param int $flags Bitfield of BagOStuff::WRITE_* constants
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return bool Success
|
2011-03-03 04:38:17 +00:00
|
|
|
*/
|
2015-10-18 22:57:42 +00:00
|
|
|
abstract public function set( $key, $value, $exptime = 0, $flags = 0 );
|
2011-03-03 04:38:17 +00:00
|
|
|
|
2011-05-21 19:35:16 +00:00
|
|
|
/**
|
2015-10-03 07:43:40 +00:00
|
|
|
* Delete an item
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $key
|
2012-05-15 03:34:24 +00:00
|
|
|
* @return bool True if the item was deleted or not found, false on failure
|
2011-03-03 04:38:17 +00:00
|
|
|
*/
|
2015-01-27 01:14:33 +00:00
|
|
|
abstract public function delete( $key );
|
2011-03-03 04:38:17 +00:00
|
|
|
|
2012-04-27 01:33:42 +00:00
|
|
|
/**
|
2016-05-11 20:50:33 +00:00
|
|
|
* Merge changes into the existing cache value (possibly creating a new one)
|
|
|
|
|
*
|
2015-04-20 17:23:07 +00:00
|
|
|
* The callback function returns the new value given the current value
|
|
|
|
|
* (which will be false if not present), and takes the arguments:
|
2016-05-11 20:50:33 +00:00
|
|
|
* (this BagOStuff, cache key, current value, TTL).
|
|
|
|
|
* The TTL parameter is reference set to $exptime. It can be overriden in the callback.
|
2012-10-01 14:05:22 +00:00
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $key
|
2015-02-10 19:11:53 +00:00
|
|
|
* @param callable $callback Callback method to be executed
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param int $exptime Either an interval in seconds or a unix timestamp for expiry
|
|
|
|
|
* @param int $attempts The amount of times to attempt a merge in case of failure
|
2015-10-18 22:57:42 +00:00
|
|
|
* @param int $flags Bitfield of BagOStuff::WRITE_* constants
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return bool Success
|
2015-04-27 19:55:19 +00:00
|
|
|
* @throws InvalidArgumentException
|
2012-04-27 01:33:42 +00:00
|
|
|
*/
|
2016-06-07 23:39:06 +00:00
|
|
|
public function merge( $key, callable $callback, $exptime = 0, $attempts = 10, $flags = 0 ) {
|
2015-10-18 22:57:42 +00:00
|
|
|
return $this->mergeViaLock( $key, $callback, $exptime, $attempts, $flags );
|
2012-10-01 14:05:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @see BagOStuff::merge()
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $key
|
2015-02-10 19:11:53 +00:00
|
|
|
* @param callable $callback Callback method to be executed
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param int $exptime Either an interval in seconds or a unix timestamp for expiry
|
|
|
|
|
* @param int $attempts The amount of times to attempt a merge in case of failure
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return bool Success
|
2012-10-01 14:05:22 +00:00
|
|
|
*/
|
2015-02-10 19:11:53 +00:00
|
|
|
protected function mergeViaCas( $key, $callback, $exptime = 0, $attempts = 10 ) {
|
2012-10-01 14:05:22 +00:00
|
|
|
do {
|
2015-08-06 23:08:41 +00:00
|
|
|
$this->clearLastError();
|
2016-04-29 19:03:50 +00:00
|
|
|
$reportDupes = $this->reportDupes;
|
|
|
|
|
$this->reportDupes = false;
|
2012-10-01 14:05:22 +00:00
|
|
|
$casToken = null; // passed by reference
|
2015-10-19 17:52:19 +00:00
|
|
|
$currentValue = $this->getWithToken( $key, $casToken, self::READ_LATEST );
|
2016-04-29 19:03:50 +00:00
|
|
|
$this->reportDupes = $reportDupes;
|
|
|
|
|
|
2015-08-06 23:08:41 +00:00
|
|
|
if ( $this->getLastError() ) {
|
|
|
|
|
return false; // don't spam retries (retry only on races)
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-10 19:11:53 +00:00
|
|
|
// Derive the new value from the old value
|
2016-05-11 20:50:33 +00:00
|
|
|
$value = call_user_func( $callback, $this, $key, $currentValue, $exptime );
|
2012-10-01 14:05:22 +00:00
|
|
|
|
2015-08-06 23:08:41 +00:00
|
|
|
$this->clearLastError();
|
2012-10-01 14:05:22 +00:00
|
|
|
if ( $value === false ) {
|
|
|
|
|
$success = true; // do nothing
|
|
|
|
|
} elseif ( $currentValue === false ) {
|
|
|
|
|
// Try to create the key, failing if it gets created in the meantime
|
|
|
|
|
$success = $this->add( $key, $value, $exptime );
|
|
|
|
|
} else {
|
|
|
|
|
// Try to update the key, failing if it gets changed in the meantime
|
|
|
|
|
$success = $this->cas( $casToken, $key, $value, $exptime );
|
|
|
|
|
}
|
2015-08-06 23:08:41 +00:00
|
|
|
if ( $this->getLastError() ) {
|
|
|
|
|
return false; // IO error; don't spam retries
|
|
|
|
|
}
|
2012-10-01 14:05:22 +00:00
|
|
|
} while ( !$success && --$attempts );
|
|
|
|
|
|
|
|
|
|
return $success;
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-27 10:15:03 +00:00
|
|
|
/**
|
2015-02-18 23:47:06 +00:00
|
|
|
* Check and set an item
|
|
|
|
|
*
|
2015-01-27 10:15:03 +00:00
|
|
|
* @param mixed $casToken
|
|
|
|
|
* @param string $key
|
|
|
|
|
* @param mixed $value
|
|
|
|
|
* @param int $exptime Either an interval in seconds or a unix timestamp for expiry
|
|
|
|
|
* @return bool Success
|
2015-04-27 19:55:19 +00:00
|
|
|
* @throws Exception
|
2015-01-27 10:15:03 +00:00
|
|
|
*/
|
2015-02-18 23:47:06 +00:00
|
|
|
protected function cas( $casToken, $key, $value, $exptime = 0 ) {
|
2018-05-31 20:41:48 +00:00
|
|
|
if ( !$this->lock( $key, 0 ) ) {
|
|
|
|
|
return false; // non-blocking
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$curCasToken = null; // passed by reference
|
|
|
|
|
$this->getWithToken( $key, $curCasToken, self::READ_LATEST );
|
|
|
|
|
if ( $casToken === $curCasToken ) {
|
|
|
|
|
$success = $this->set( $key, $value, $exptime );
|
|
|
|
|
} else {
|
|
|
|
|
$success = false; // mismatched or failed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->unlock( $key );
|
|
|
|
|
|
|
|
|
|
return $success;
|
2015-02-18 23:47:06 +00:00
|
|
|
}
|
2015-01-27 10:15:03 +00:00
|
|
|
|
2012-10-01 14:05:22 +00:00
|
|
|
/**
|
|
|
|
|
* @see BagOStuff::merge()
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $key
|
2015-02-10 19:11:53 +00:00
|
|
|
* @param callable $callback Callback method to be executed
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param int $exptime Either an interval in seconds or a unix timestamp for expiry
|
|
|
|
|
* @param int $attempts The amount of times to attempt a merge in case of failure
|
2015-10-18 22:57:42 +00:00
|
|
|
* @param int $flags Bitfield of BagOStuff::WRITE_* constants
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return bool Success
|
2012-10-01 14:05:22 +00:00
|
|
|
*/
|
2015-10-18 22:57:42 +00:00
|
|
|
protected function mergeViaLock( $key, $callback, $exptime = 0, $attempts = 10, $flags = 0 ) {
|
2014-03-18 17:16:58 +00:00
|
|
|
if ( !$this->lock( $key, 6 ) ) {
|
2012-10-01 14:05:22 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-06 23:08:41 +00:00
|
|
|
$this->clearLastError();
|
2016-04-29 19:03:50 +00:00
|
|
|
$reportDupes = $this->reportDupes;
|
|
|
|
|
$this->reportDupes = false;
|
2015-10-19 17:52:19 +00:00
|
|
|
$currentValue = $this->get( $key, self::READ_LATEST );
|
2016-04-29 19:03:50 +00:00
|
|
|
$this->reportDupes = $reportDupes;
|
|
|
|
|
|
2015-08-21 20:04:09 +00:00
|
|
|
if ( $this->getLastError() ) {
|
|
|
|
|
$success = false;
|
|
|
|
|
} else {
|
2015-08-06 23:08:41 +00:00
|
|
|
// Derive the new value from the old value
|
2016-05-11 20:50:33 +00:00
|
|
|
$value = call_user_func( $callback, $this, $key, $currentValue, $exptime );
|
2015-08-06 23:08:41 +00:00
|
|
|
if ( $value === false ) {
|
|
|
|
|
$success = true; // do nothing
|
|
|
|
|
} else {
|
2015-10-18 22:57:42 +00:00
|
|
|
$success = $this->set( $key, $value, $exptime, $flags ); // set the new value
|
2015-08-06 23:08:41 +00:00
|
|
|
}
|
2012-10-01 14:05:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( !$this->unlock( $key ) ) {
|
|
|
|
|
// this should never happen
|
|
|
|
|
trigger_error( "Could not release lock for key '$key'." );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $success;
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-12 02:26:21 +00:00
|
|
|
/**
|
|
|
|
|
* Reset the TTL on a key if it exists
|
|
|
|
|
*
|
|
|
|
|
* @param string $key
|
|
|
|
|
* @param int $expiry
|
|
|
|
|
* @return bool Success Returns false if there is no key
|
|
|
|
|
* @since 1.28
|
|
|
|
|
*/
|
|
|
|
|
public function changeTTL( $key, $expiry = 0 ) {
|
|
|
|
|
$value = $this->get( $key );
|
|
|
|
|
|
|
|
|
|
return ( $value === false ) ? false : $this->set( $key, $value, $expiry );
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-01 14:05:22 +00:00
|
|
|
/**
|
2015-08-21 21:03:48 +00:00
|
|
|
* Acquire an advisory lock on a key string
|
|
|
|
|
*
|
|
|
|
|
* Note that if reentry is enabled, duplicate calls ignore $expiry
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $key
|
2015-04-27 20:14:21 +00:00
|
|
|
* @param int $timeout Lock wait timeout; 0 for non-blocking [optional]
|
2015-07-27 19:28:59 +00:00
|
|
|
* @param int $expiry Lock expiry [optional]; 1 day maximum
|
2015-08-21 21:03:48 +00:00
|
|
|
* @param string $rclass Allow reentry if set and the current lock used this value
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return bool Success
|
2012-10-01 14:05:22 +00:00
|
|
|
*/
|
2015-08-21 21:03:48 +00:00
|
|
|
public function lock( $key, $timeout = 6, $expiry = 6, $rclass = '' ) {
|
|
|
|
|
// Avoid deadlocks and allow lock reentry if specified
|
|
|
|
|
if ( isset( $this->locks[$key] ) ) {
|
|
|
|
|
if ( $rclass != '' && $this->locks[$key]['class'] === $rclass ) {
|
|
|
|
|
++$this->locks[$key]['depth'];
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-19 17:52:19 +00:00
|
|
|
$expiry = min( $expiry ?: INF, self::TTL_DAY );
|
2016-09-08 07:12:56 +00:00
|
|
|
$loop = new WaitConditionLoop(
|
|
|
|
|
function () use ( $key, $timeout, $expiry ) {
|
2015-08-21 21:03:48 +00:00
|
|
|
$this->clearLastError();
|
2016-09-08 07:12:56 +00:00
|
|
|
if ( $this->add( "{$key}:lock", 1, $expiry ) ) {
|
|
|
|
|
return true; // locked!
|
|
|
|
|
} elseif ( $this->getLastError() ) {
|
|
|
|
|
return WaitConditionLoop::CONDITION_ABORTED; // network partition?
|
2015-08-21 21:03:48 +00:00
|
|
|
}
|
2012-10-01 14:05:22 +00:00
|
|
|
|
2016-09-08 07:12:56 +00:00
|
|
|
return WaitConditionLoop::CONDITION_CONTINUE;
|
|
|
|
|
},
|
|
|
|
|
$timeout
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$locked = ( $loop->invoke() === $loop::CONDITION_REACHED );
|
2015-08-21 21:03:48 +00:00
|
|
|
if ( $locked ) {
|
2016-02-17 09:09:32 +00:00
|
|
|
$this->locks[$key] = [ 'class' => $rclass, 'depth' => 1 ];
|
2015-08-21 21:03:48 +00:00
|
|
|
}
|
2012-10-01 14:05:22 +00:00
|
|
|
|
|
|
|
|
return $locked;
|
2011-03-03 04:38:17 +00:00
|
|
|
}
|
|
|
|
|
|
2012-04-27 01:33:42 +00:00
|
|
|
/**
|
2015-08-21 21:03:48 +00:00
|
|
|
* Release an advisory lock on a key string
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $key
|
|
|
|
|
* @return bool Success
|
2012-04-27 01:33:42 +00:00
|
|
|
*/
|
2011-03-03 04:38:17 +00:00
|
|
|
public function unlock( $key ) {
|
2015-08-21 21:03:48 +00:00
|
|
|
if ( isset( $this->locks[$key] ) && --$this->locks[$key]['depth'] <= 0 ) {
|
|
|
|
|
unset( $this->locks[$key] );
|
|
|
|
|
|
|
|
|
|
return $this->delete( "{$key}:lock" );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
2011-03-03 04:38:17 +00:00
|
|
|
}
|
|
|
|
|
|
2015-07-27 19:28:59 +00:00
|
|
|
/**
|
|
|
|
|
* Get a lightweight exclusive self-unlocking lock
|
|
|
|
|
*
|
|
|
|
|
* Note that the same lock cannot be acquired twice.
|
|
|
|
|
*
|
|
|
|
|
* This is useful for task de-duplication or to avoid obtrusive
|
|
|
|
|
* (though non-corrupting) DB errors like INSERT key conflicts
|
|
|
|
|
* or deadlocks when using LOCK IN SHARE MODE.
|
|
|
|
|
*
|
|
|
|
|
* @param string $key
|
|
|
|
|
* @param int $timeout Lock wait timeout; 0 for non-blocking [optional]
|
|
|
|
|
* @param int $expiry Lock expiry [optional]; 1 day maximum
|
2015-08-21 21:03:48 +00:00
|
|
|
* @param string $rclass Allow reentry if set and the current lock used this value
|
2015-08-19 18:31:09 +00:00
|
|
|
* @return ScopedCallback|null Returns null on failure
|
2015-07-27 19:28:59 +00:00
|
|
|
* @since 1.26
|
|
|
|
|
*/
|
2015-08-21 21:03:48 +00:00
|
|
|
final public function getScopedLock( $key, $timeout = 6, $expiry = 30, $rclass = '' ) {
|
2015-10-19 17:52:19 +00:00
|
|
|
$expiry = min( $expiry ?: INF, self::TTL_DAY );
|
2015-07-27 19:28:59 +00:00
|
|
|
|
2015-08-21 21:03:48 +00:00
|
|
|
if ( !$this->lock( $key, $timeout, $expiry, $rclass ) ) {
|
2015-07-27 19:28:59 +00:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-31 06:14:09 +00:00
|
|
|
$lSince = $this->getCurrentTime(); // lock timestamp
|
2015-07-27 19:28:59 +00:00
|
|
|
|
2017-06-26 16:35:31 +00:00
|
|
|
return new ScopedCallback( function () use ( $key, $lSince, $expiry ) {
|
2017-09-10 19:11:37 +00:00
|
|
|
$latency = 0.050; // latency skew (err towards keeping lock present)
|
2018-05-31 06:14:09 +00:00
|
|
|
$age = ( $this->getCurrentTime() - $lSince + $latency );
|
2015-07-27 19:28:59 +00:00
|
|
|
if ( ( $age + $latency ) >= $expiry ) {
|
2016-02-10 17:26:25 +00:00
|
|
|
$this->logger->warning( "Lock for $key held too long ($age sec)." );
|
2015-07-27 19:28:59 +00:00
|
|
|
return; // expired; it's not "safe" to delete the key
|
|
|
|
|
}
|
2016-02-10 17:26:25 +00:00
|
|
|
$this->unlock( $key );
|
2015-07-27 19:28:59 +00:00
|
|
|
} );
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-09 03:51:45 +00:00
|
|
|
/**
|
2011-11-01 18:31:38 +00:00
|
|
|
* Delete all objects expiring before a certain date.
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $date The reference date in MW format
|
2014-04-19 06:43:31 +00:00
|
|
|
* @param callable|bool $progressCallback Optional, a function which will be called
|
2011-12-02 02:59:11 +00:00
|
|
|
* regularly during long-running operations with the percentage progress
|
|
|
|
|
* as the first parameter.
|
2011-09-09 03:51:45 +00:00
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return bool Success, false if unimplemented
|
2011-09-09 03:51:45 +00:00
|
|
|
*/
|
2011-12-02 02:59:11 +00:00
|
|
|
public function deleteObjectsExpiringBefore( $date, $progressCallback = false ) {
|
2011-09-09 03:51:45 +00:00
|
|
|
// stub
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2012-06-05 22:58:54 +00:00
|
|
|
/**
|
|
|
|
|
* Get an associative array containing the item for each of the keys that have items.
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param array $keys List of strings
|
2017-08-20 11:20:59 +00:00
|
|
|
* @param int $flags Bitfield; supports READ_LATEST [optional]
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return array
|
2012-06-05 22:58:54 +00:00
|
|
|
*/
|
2015-05-27 18:52:44 +00:00
|
|
|
public function getMulti( array $keys, $flags = 0 ) {
|
2016-02-17 09:09:32 +00:00
|
|
|
$res = [];
|
2012-06-05 22:58:54 +00:00
|
|
|
foreach ( $keys as $key ) {
|
|
|
|
|
$val = $this->get( $key );
|
|
|
|
|
if ( $val !== false ) {
|
|
|
|
|
$res[$key] = $val;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $res;
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-15 07:08:29 +00:00
|
|
|
/**
|
|
|
|
|
* Batch insertion
|
|
|
|
|
* @param array $data $key => $value assoc array
|
|
|
|
|
* @param int $exptime Either an interval in seconds or a unix timestamp for expiry
|
2014-07-24 17:43:25 +00:00
|
|
|
* @return bool Success
|
2013-12-15 07:08:29 +00:00
|
|
|
* @since 1.24
|
|
|
|
|
*/
|
|
|
|
|
public function setMulti( array $data, $exptime = 0 ) {
|
|
|
|
|
$res = true;
|
|
|
|
|
foreach ( $data as $key => $value ) {
|
|
|
|
|
if ( !$this->set( $key, $value, $exptime ) ) {
|
|
|
|
|
$res = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $res;
|
|
|
|
|
}
|
|
|
|
|
|
2012-04-27 01:33:42 +00:00
|
|
|
/**
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $key
|
|
|
|
|
* @param mixed $value
|
|
|
|
|
* @param int $exptime
|
|
|
|
|
* @return bool Success
|
2012-04-27 01:33:42 +00:00
|
|
|
*/
|
2011-03-03 04:38:17 +00:00
|
|
|
public function add( $key, $value, $exptime = 0 ) {
|
2012-06-05 22:58:54 +00:00
|
|
|
if ( $this->get( $key ) === false ) {
|
2012-04-27 01:33:42 +00:00
|
|
|
return $this->set( $key, $value, $exptime );
|
2011-03-03 04:38:17 +00:00
|
|
|
}
|
2012-06-05 22:58:54 +00:00
|
|
|
return false; // key already set
|
2011-03-03 04:38:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-05-25 10:41:53 +00:00
|
|
|
* Increase stored value of $key by $value while preserving its TTL
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $key Key to increase
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param int $value Value to add to $key (Default 1)
|
|
|
|
|
* @return int|bool New value or false on failure
|
2011-03-03 04:38:17 +00:00
|
|
|
*/
|
|
|
|
|
public function incr( $key, $value = 1 ) {
|
|
|
|
|
if ( !$this->lock( $key ) ) {
|
2012-09-05 18:51:04 +00:00
|
|
|
return false;
|
2011-03-03 04:38:17 +00:00
|
|
|
}
|
2012-09-05 18:51:04 +00:00
|
|
|
$n = $this->get( $key );
|
|
|
|
|
if ( $this->isInteger( $n ) ) { // key exists?
|
|
|
|
|
$n += intval( $value );
|
|
|
|
|
$this->set( $key, max( 0, $n ) ); // exptime?
|
|
|
|
|
} else {
|
|
|
|
|
$n = false;
|
2011-03-03 04:38:17 +00:00
|
|
|
}
|
|
|
|
|
$this->unlock( $key );
|
|
|
|
|
|
|
|
|
|
return $n;
|
|
|
|
|
}
|
|
|
|
|
|
2012-04-27 01:33:42 +00:00
|
|
|
/**
|
2012-05-25 10:41:53 +00:00
|
|
|
* Decrease stored value of $key by $value while preserving its TTL
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $key
|
|
|
|
|
* @param int $value
|
2015-04-20 17:23:07 +00:00
|
|
|
* @return int|bool New value or false on failure
|
2012-04-27 01:33:42 +00:00
|
|
|
*/
|
2011-03-03 04:38:17 +00:00
|
|
|
public function decr( $key, $value = 1 ) {
|
|
|
|
|
return $this->incr( $key, - $value );
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-23 20:42:12 +00:00
|
|
|
/**
|
|
|
|
|
* Increase stored value of $key by $value while preserving its TTL
|
|
|
|
|
*
|
2015-12-21 20:12:06 +00:00
|
|
|
* This will create the key with value $init and TTL $ttl instead if not present
|
2014-04-23 20:42:12 +00:00
|
|
|
*
|
|
|
|
|
* @param string $key
|
2014-07-24 17:43:25 +00:00
|
|
|
* @param int $ttl
|
|
|
|
|
* @param int $value
|
|
|
|
|
* @param int $init
|
2015-12-21 20:12:06 +00:00
|
|
|
* @return int|bool New value or false on failure
|
2014-04-23 20:42:12 +00:00
|
|
|
* @since 1.24
|
|
|
|
|
*/
|
|
|
|
|
public function incrWithInit( $key, $ttl, $value = 1, $init = 1 ) {
|
2015-12-21 20:12:06 +00:00
|
|
|
$newValue = $this->incr( $key, $value );
|
|
|
|
|
if ( $newValue === false ) {
|
|
|
|
|
// No key set; initialize
|
|
|
|
|
$newValue = $this->add( $key, (int)$init, $ttl ) ? $init : false;
|
|
|
|
|
}
|
|
|
|
|
if ( $newValue === false ) {
|
|
|
|
|
// Raced out initializing; increment
|
|
|
|
|
$newValue = $this->incr( $key, $value );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $newValue;
|
2014-04-23 20:42:12 +00:00
|
|
|
}
|
|
|
|
|
|
2014-04-01 20:25:42 +00:00
|
|
|
/**
|
|
|
|
|
* Get the "last error" registered; clearLastError() should be called manually
|
2014-04-18 23:19:46 +00:00
|
|
|
* @return int ERR_* constant for the "last error" registry
|
2014-04-01 20:25:42 +00:00
|
|
|
* @since 1.23
|
|
|
|
|
*/
|
|
|
|
|
public function getLastError() {
|
|
|
|
|
return $this->lastError;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Clear the "last error" registry
|
|
|
|
|
* @since 1.23
|
|
|
|
|
*/
|
|
|
|
|
public function clearLastError() {
|
|
|
|
|
$this->lastError = self::ERR_NONE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set the "last error" registry
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param int $err ERR_* constant
|
2014-04-01 20:25:42 +00:00
|
|
|
* @since 1.23
|
|
|
|
|
*/
|
|
|
|
|
protected function setLastError( $err ) {
|
|
|
|
|
$this->lastError = $err;
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-29 17:00:05 +00:00
|
|
|
/**
|
|
|
|
|
* Let a callback be run to avoid wasting time on special blocking calls
|
|
|
|
|
*
|
|
|
|
|
* The callbacks may or may not be called ever, in any particular order.
|
|
|
|
|
* They are likely to be invoked when something WRITE_SYNC is used used.
|
|
|
|
|
* They should follow a caching pattern as shown below, so that any code
|
|
|
|
|
* using the word will get it's result no matter what happens.
|
|
|
|
|
* @code
|
|
|
|
|
* $result = null;
|
|
|
|
|
* $workCallback = function () use ( &$result ) {
|
|
|
|
|
* if ( !$result ) {
|
|
|
|
|
* $result = ....
|
|
|
|
|
* }
|
|
|
|
|
* return $result;
|
|
|
|
|
* }
|
|
|
|
|
* @endcode
|
|
|
|
|
*
|
|
|
|
|
* @param callable $workCallback
|
|
|
|
|
* @since 1.28
|
|
|
|
|
*/
|
|
|
|
|
public function addBusyCallback( callable $workCallback ) {
|
|
|
|
|
$this->busyCallbacks[] = $workCallback;
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-27 19:56:44 +00:00
|
|
|
/**
|
|
|
|
|
* Modify a cache update operation array for EventRelayer::notify()
|
|
|
|
|
*
|
|
|
|
|
* This is used for relayed writes, e.g. for broadcasting a change
|
|
|
|
|
* to multiple data-centers. If the array contains a 'val' field
|
|
|
|
|
* then the command involves setting a key to that value. Note that
|
|
|
|
|
* for simplicity, 'val' is always a simple scalar value. This method
|
|
|
|
|
* is used to possibly serialize the value and add any cache-specific
|
|
|
|
|
* key/values needed for the relayer daemon (e.g. memcached flags).
|
|
|
|
|
*
|
|
|
|
|
* @param array $event
|
|
|
|
|
* @return array
|
|
|
|
|
* @since 1.26
|
|
|
|
|
*/
|
|
|
|
|
public function modifySimpleRelayEvent( array $event ) {
|
|
|
|
|
return $event;
|
|
|
|
|
}
|
|
|
|
|
|
2012-04-27 01:33:42 +00:00
|
|
|
/**
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param string $text
|
2012-04-27 01:33:42 +00:00
|
|
|
*/
|
2015-01-25 07:45:41 +00:00
|
|
|
protected function debug( $text ) {
|
2011-03-03 04:38:17 +00:00
|
|
|
if ( $this->debugMode ) {
|
2016-02-17 09:09:32 +00:00
|
|
|
$this->logger->debug( "{class} debug: $text", [
|
2017-03-07 02:14:14 +00:00
|
|
|
'class' => static::class,
|
2016-02-17 09:09:32 +00:00
|
|
|
] );
|
2011-03-03 04:38:17 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convert an optionally relative time to an absolute time
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param int $exptime
|
2012-02-09 21:35:05 +00:00
|
|
|
* @return int
|
2011-03-03 04:38:17 +00:00
|
|
|
*/
|
|
|
|
|
protected function convertExpiry( $exptime ) {
|
2015-10-19 17:52:19 +00:00
|
|
|
if ( $exptime != 0 && $exptime < ( 10 * self::TTL_YEAR ) ) {
|
2018-05-31 06:14:09 +00:00
|
|
|
return (int)$this->getCurrentTime() + $exptime;
|
2011-03-03 04:38:17 +00:00
|
|
|
} else {
|
|
|
|
|
return $exptime;
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-08-08 07:31:41 +00:00
|
|
|
|
|
|
|
|
/**
|
2012-09-05 18:51:04 +00:00
|
|
|
* Convert an optionally absolute expiry time to a relative time. If an
|
2012-08-08 07:31:41 +00:00
|
|
|
* absolute time is specified which is in the past, use a short expiry time.
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param int $exptime
|
|
|
|
|
* @return int
|
2012-08-08 07:31:41 +00:00
|
|
|
*/
|
|
|
|
|
protected function convertToRelative( $exptime ) {
|
2015-10-19 17:52:19 +00:00
|
|
|
if ( $exptime >= ( 10 * self::TTL_YEAR ) ) {
|
2018-05-31 06:14:09 +00:00
|
|
|
$exptime -= (int)$this->getCurrentTime();
|
2012-08-08 07:31:41 +00:00
|
|
|
if ( $exptime <= 0 ) {
|
|
|
|
|
$exptime = 1;
|
|
|
|
|
}
|
|
|
|
|
return $exptime;
|
|
|
|
|
} else {
|
|
|
|
|
return $exptime;
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-05-25 10:41:53 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if a value is an integer
|
|
|
|
|
*
|
2014-04-18 23:19:46 +00:00
|
|
|
* @param mixed $value
|
2012-05-25 10:41:53 +00:00
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected function isInteger( $value ) {
|
|
|
|
|
return ( is_int( $value ) || ctype_digit( $value ) );
|
|
|
|
|
}
|
2015-10-09 23:35:08 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Construct a cache key.
|
|
|
|
|
*
|
|
|
|
|
* @since 1.27
|
|
|
|
|
* @param string $keyspace
|
|
|
|
|
* @param array $args
|
2017-10-18 21:25:23 +00:00
|
|
|
* @return string Colon-delimited list of $keyspace followed by escaped components of $args
|
2015-10-09 23:35:08 +00:00
|
|
|
*/
|
|
|
|
|
public function makeKeyInternal( $keyspace, $args ) {
|
2015-10-23 22:12:51 +00:00
|
|
|
$key = $keyspace;
|
|
|
|
|
foreach ( $args as $arg ) {
|
|
|
|
|
$arg = str_replace( ':', '%3A', $arg );
|
|
|
|
|
$key = $key . ':' . $arg;
|
|
|
|
|
}
|
2015-10-09 23:35:08 +00:00
|
|
|
return strtr( $key, ' ', '_' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Make a global cache key.
|
|
|
|
|
*
|
|
|
|
|
* @since 1.27
|
2017-11-18 20:39:21 +00:00
|
|
|
* @param string $class Key class
|
|
|
|
|
* @param string $component [optional] Key component (starting with a key collection name)
|
2017-10-18 21:25:23 +00:00
|
|
|
* @return string Colon-delimited list of $keyspace followed by escaped components of $args
|
2015-10-09 23:35:08 +00:00
|
|
|
*/
|
2017-11-18 20:39:21 +00:00
|
|
|
public function makeGlobalKey( $class, $component = null ) {
|
2015-10-09 23:35:08 +00:00
|
|
|
return $this->makeKeyInternal( 'global', func_get_args() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Make a cache key, scoped to this instance's keyspace.
|
|
|
|
|
*
|
|
|
|
|
* @since 1.27
|
2017-11-18 20:39:21 +00:00
|
|
|
* @param string $class Key class
|
|
|
|
|
* @param string $component [optional] Key component (starting with a key collection name)
|
2017-10-18 21:25:23 +00:00
|
|
|
* @return string Colon-delimited list of $keyspace followed by escaped components of $args
|
2015-10-09 23:35:08 +00:00
|
|
|
*/
|
2017-11-18 20:39:21 +00:00
|
|
|
public function makeKey( $class, $component = null ) {
|
2015-10-09 23:35:08 +00:00
|
|
|
return $this->makeKeyInternal( $this->keyspace, func_get_args() );
|
|
|
|
|
}
|
2016-08-02 10:57:52 +00:00
|
|
|
|
|
|
|
|
/**
|
2017-08-20 11:20:59 +00:00
|
|
|
* @param int $flag ATTR_* class constant
|
|
|
|
|
* @return int QOS_* class constant
|
2016-08-02 10:57:52 +00:00
|
|
|
* @since 1.28
|
|
|
|
|
*/
|
|
|
|
|
public function getQoS( $flag ) {
|
2017-10-06 22:17:58 +00:00
|
|
|
return $this->attrMap[$flag] ?? self::QOS_UNKNOWN;
|
2016-08-02 10:57:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Merge the flag maps of one or more BagOStuff objects into a "lowest common denominator" map
|
|
|
|
|
*
|
|
|
|
|
* @param BagOStuff[] $bags
|
2017-08-20 11:20:59 +00:00
|
|
|
* @return int[] Resulting flag map (class ATTR_* constant => class QOS_* constant)
|
2016-08-02 10:57:52 +00:00
|
|
|
*/
|
|
|
|
|
protected function mergeFlagMaps( array $bags ) {
|
|
|
|
|
$map = [];
|
|
|
|
|
foreach ( $bags as $bag ) {
|
|
|
|
|
foreach ( $bag->attrMap as $attr => $rank ) {
|
|
|
|
|
if ( isset( $map[$attr] ) ) {
|
|
|
|
|
$map[$attr] = min( $map[$attr], $rank );
|
|
|
|
|
} else {
|
|
|
|
|
$map[$attr] = $rank;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $map;
|
|
|
|
|
}
|
2018-05-31 06:14:09 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return float UNIX timestamp
|
|
|
|
|
* @codeCoverageIgnore
|
|
|
|
|
*/
|
|
|
|
|
protected function getCurrentTime() {
|
|
|
|
|
return $this->wallClockOverride ?: microtime( true );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param float|null &$time Mock UNIX timestamp for testing
|
|
|
|
|
* @codeCoverageIgnore
|
|
|
|
|
*/
|
|
|
|
|
public function setMockTime( &$time ) {
|
|
|
|
|
$this->wallClockOverride =& $time;
|
|
|
|
|
}
|
2011-03-03 04:38:17 +00:00
|
|
|
}
|