Merge "Set default wgMultiContentRevisionSchemaMigrationStage to write-both"
[lhc/web/wiklou.git] / includes / libs / objectcache / BagOStuff.php
index 424d986..782f4c6 100644 (file)
@@ -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 = [];
 
@@ -182,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 ) {
@@ -340,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;
        }
 
        /**
@@ -354,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;
                }
 
@@ -482,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
@@ -700,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;
                }
@@ -715,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;
                        }
@@ -757,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 ) {
@@ -769,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 ) {
@@ -805,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;
+       }
 }