X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Flibs%2Fobjectcache%2FBagOStuff.php;h=782f4c618e772cb39bf8b024fb98adcc8550b9af;hb=05a4432e2a165844b1295401a62ee313a55a5f2d;hp=8a8858102673ce8d7eb48226d337a476161c802c;hpb=b4054da0f9c2a1c23f4af93871329e39c0ab6a8f;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/libs/objectcache/BagOStuff.php b/includes/libs/objectcache/BagOStuff.php index 8a88581026..782f4c618e 100644 --- a/includes/libs/objectcache/BagOStuff.php +++ b/includes/libs/objectcache/BagOStuff.php @@ -81,6 +81,9 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { /** @var callable[] */ protected $busyCallbacks = []; + /** @var float|null */ + private $wallClockOverride; + /** @var int[] Map of (ATTR_* class constant => QOS_* class constant) */ protected $attrMap = []; @@ -119,15 +122,13 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { $this->keyspace = $params['keyspace']; } - $this->asyncHandler = isset( $params['asyncHandler'] ) - ? $params['asyncHandler'] - : null; + $this->asyncHandler = $params['asyncHandler'] ?? null; if ( !empty( $params['reportDupes'] ) && is_callable( $this->asyncHandler ) ) { $this->reportDupes = true; } - $this->syncTimeout = isset( $params['syncTimeout'] ) ? $params['syncTimeout'] : 3; + $this->syncTimeout = $params['syncTimeout'] ?? 3; } /** @@ -184,7 +185,7 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { * * @param string $key * @param int $flags Bitfield of BagOStuff::READ_* constants [optional] - * @param int $oldFlags [unused] + * @param int|null $oldFlags [unused] * @return mixed Returns false on failure and if the item does not exist */ public function get( $key, $flags = 0, $oldFlags = null ) { @@ -342,7 +343,21 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { * @throws Exception */ protected function cas( $casToken, $key, $value, $exptime = 0 ) { - throw new Exception( "CAS is not implemented in " . __CLASS__ ); + 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; } /** @@ -356,7 +371,13 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { * @return bool Success */ protected function mergeViaLock( $key, $callback, $exptime = 0, $attempts = 10, $flags = 0 ) { - if ( !$this->lock( $key, 6 ) ) { + if ( $attempts <= 1 ) { + $timeout = 0; // clearly intended to be "non-blocking" + } else { + $timeout = 3; + } + + if ( !$this->lock( $key, $timeout ) ) { return false; } @@ -484,11 +505,11 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { return null; } - $lSince = microtime( true ); // lock timestamp + $lSince = $this->getCurrentTime(); // lock timestamp return new ScopedCallback( function () use ( $key, $lSince, $expiry ) { $latency = 0.050; // latency skew (err towards keeping lock present) - $age = ( microtime( true ) - $lSince + $latency ); + $age = ( $this->getCurrentTime() - $lSince + $latency ); if ( ( $age + $latency ) >= $expiry ) { $this->logger->warning( "Lock for $key held too long ($age sec)." ); return; // expired; it's not "safe" to delete the key @@ -702,7 +723,7 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { */ protected function convertExpiry( $exptime ) { if ( $exptime != 0 && $exptime < ( 10 * self::TTL_YEAR ) ) { - return time() + $exptime; + return (int)$this->getCurrentTime() + $exptime; } else { return $exptime; } @@ -717,7 +738,7 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { */ protected function convertToRelative( $exptime ) { if ( $exptime >= ( 10 * self::TTL_YEAR ) ) { - $exptime -= time(); + $exptime -= (int)$this->getCurrentTime(); if ( $exptime <= 0 ) { $exptime = 1; } @@ -759,7 +780,7 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { * * @since 1.27 * @param string $class Key class - * @param string $component [optional] Key component (starting with a key collection name) + * @param string|null $component [optional] Key component (starting with a key collection name) * @return string Colon-delimited list of $keyspace followed by escaped components of $args */ public function makeGlobalKey( $class, $component = null ) { @@ -771,7 +792,7 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { * * @since 1.27 * @param string $class Key class - * @param string $component [optional] Key component (starting with a key collection name) + * @param string|null $component [optional] Key component (starting with a key collection name) * @return string Colon-delimited list of $keyspace followed by escaped components of $args */ public function makeKey( $class, $component = null ) { @@ -784,7 +805,7 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { * @since 1.28 */ public function getQoS( $flag ) { - return isset( $this->attrMap[$flag] ) ? $this->attrMap[$flag] : self::QOS_UNKNOWN; + return $this->attrMap[$flag] ?? self::QOS_UNKNOWN; } /** @@ -807,4 +828,20 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { return $map; } + + /** + * @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; + } }