/** @var callable[] */
protected $busyCallbacks = [];
+ /** @var float|null */
+ private $wallClockOverride;
+
/** @var int[] Map of (ATTR_* class constant => QOS_* class constant) */
protected $attrMap = [];
*
* @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 ) {
* @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;
}
/**
* @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;
}
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
*/
protected function convertExpiry( $exptime ) {
if ( $exptime != 0 && $exptime < ( 10 * self::TTL_YEAR ) ) {
- return time() + $exptime;
+ return (int)$this->getCurrentTime() + $exptime;
} else {
return $exptime;
}
*/
protected function convertToRelative( $exptime ) {
if ( $exptime >= ( 10 * self::TTL_YEAR ) ) {
- $exptime -= time();
+ $exptime -= (int)$this->getCurrentTime();
if ( $exptime <= 0 ) {
$exptime = 1;
}
*
* @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 ) {
*
* @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 ) {
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;
+ }
}