Merge "Setup: Make wfMemoryLimit() internal and simplify"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Sat, 13 Jul 2019 02:27:43 +0000 (02:27 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Sat, 13 Jul 2019 02:27:43 +0000 (02:27 +0000)
29 files changed:
RELEASE-NOTES-1.34
docs/hooks.txt
includes/Storage/DerivedPageDataUpdater.php
includes/libs/objectcache/README.md [new file with mode: 0644]
includes/libs/objectcache/WANObjectCache.php
includes/libs/rdbms/ChronologyProtector.php
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/database/DatabaseMysqlBase.php
includes/libs/rdbms/database/DatabasePostgres.php
includes/libs/rdbms/lbfactory/LBFactory.php
includes/libs/rdbms/loadbalancer/ILoadBalancer.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/specials/SpecialChangeCredentials.php
maintenance/attachLatest.php
maintenance/cleanupInvalidDbKeys.php
maintenance/createAndPromote.php
maintenance/includes/BackupDumper.php
maintenance/includes/TextPassDumper.php
maintenance/initEditCount.php
maintenance/storage/recompressTracked.php
maintenance/syncFileBackend.php
maintenance/update.php
maintenance/updateSearchIndex.php
resources/src/mediawiki.legacy/shared.css
resources/src/mediawiki.less/mediawiki.ui/variables.less
resources/src/mediawiki.ui/components/forms.less
tests/phpunit/includes/db/LBFactoryTest.php
tests/phpunit/includes/db/LoadBalancerTest.php
tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php

index 4d643d1..be24b50 100644 (file)
@@ -270,6 +270,8 @@ because of Phabricator reports.
 * Previously, when iterating ResultWrapper with foreach() or a similar
   construct, the range of the index was 1..numRows. This has been fixed to be
   0..(numRows-1).
+* The ChangePasswordForm hook, deprecated in 1.27, has been removed. Use the
+  AuthChangeFormFields hook or security levels instead.
 * …
 
 === Deprecations in 1.34 ===
index 1e5072f..80453f4 100644 (file)
@@ -944,12 +944,6 @@ No return data is accepted; this hook is for auditing only.
 $req: AuthenticationRequest object describing the change (and target user)
 $status: StatusValue with the result of the action
 
-'ChangePasswordForm': DEPRECATED since 1.27! Use AuthChangeFormFields or
-security levels. For extensions that need to add a field to the ChangePassword
-form via the Preferences form.
-&$extraFields: An array of arrays that hold fields like would be passed to the
-  pretty function.
-
 'ChangesListInitRows': Batch process change list rows prior to rendering.
 $changesList: ChangesList instance
 $rows: The data that will be rendered. May be a \Wikimedia\Rdbms\IResultWrapper
index 2d53682..5d847b6 100644 (file)
@@ -343,14 +343,6 @@ class DerivedPageDataUpdater implements IDBAccessObject, LoggerAwareInterface {
                }
        }
 
-       /**
-        * @return bool|string
-        */
-       private function getWikiId() {
-               // TODO: get from RevisionStore
-               return false;
-       }
-
        /**
         * Checks whether this DerivedPageDataUpdater can be re-used for running updates targeting
         * the given revision.
@@ -1581,7 +1573,10 @@ class DerivedPageDataUpdater implements IDBAccessObject, LoggerAwareInterface {
 
                // TODO: In the wiring, register a listener for this on the new PageEventEmitter
                ResourceLoaderWikiModule::invalidateModuleCache(
-                       $title, $oldLegacyRevision, $legacyRevision, $this->getWikiId() ?: wfWikiID()
+                       $title,
+                       $oldLegacyRevision,
+                       $legacyRevision,
+                       $this->loadbalancerFactory->getLocalDomainID()
                );
 
                $this->doTransition( 'done' );
diff --git a/includes/libs/objectcache/README.md b/includes/libs/objectcache/README.md
new file mode 100644 (file)
index 0000000..833c045
--- /dev/null
@@ -0,0 +1,108 @@
+# wikimedia/objectcache
+
+## Statistics
+
+Sent to StatsD under MediaWiki's namespace.
+
+### WANObjectCache
+
+The default WANObjectCache provided by MediaWikiServices disables these
+statistics in processes where `$wgCommandLineMode` is true.
+
+#### `wanobjectcache.{kClass}.{cache_action_and_result}`
+
+Call counter from `WANObjectCache::getWithSetCallback()`.
+
+* Type: Counter.
+* Variable `kClass`: The first part of your cache key.
+* Variable `result`: One of:
+  * `"hit.good"`,
+  * `"hit.refresh"`,
+  * `"hit.volatile"`,
+  * `"hit.stale"`,
+  * `"miss.busy"` (or `"renew.busy"`, if the `minAsOf` is used),
+  * `"miss.compute"` (or `"renew.busy"`, if the `minAsOf` is used).
+
+#### `wanobjectcache.{kClass}.regen_set_delay`
+
+Upon cache miss, this measures the time spent in `WANObjectCache::getWithSetCallback()`,
+from the start of the method to right after the new value has been computed by the callback.
+
+This essentially measures the whole method (including retrieval of any old value,
+validation, any locks for `lockTSE`, and the callbacks), except for the time spent
+in sending the value to the backend server.
+
+* Type: Measure (in milliseconds).
+* Variable `kClass`: The first part of your cache key.
+
+#### `wanobjectcache.{kClass}.ck_touch.{result}`
+
+Call counter from `WANObjectCache::touchCheckKey()`.
+
+* Type: Counter.
+* Variable `kClass`: The first part of your cache key.
+* Variable `result`: One of `"ok"` or `"error"`.
+
+#### `wanobjectcache.{kClass}.ck_reset.{result}`
+
+Call counter from `WANObjectCache::resetCheckKey()`.
+
+* Type: Counter.
+* Variable `kClass`: The first part of your cache key.
+* Variable `result`: One of `"ok"` or `"error"`.
+
+#### `wanobjectcache.{kClass}.delete.{result}`
+
+Call counter from `WANObjectCache::delete()`.
+
+* Type: Counter.
+* Variable `kClass`: The first part of your cache key.
+* Variable `result`: One of `"ok"` or `"error"`.
+
+#### `wanobjectcache.{kClass}.cooloff_bounce`
+
+Upon a cache miss, the `WANObjectCache::getWithSetCallback()` method generally
+recomputes the value from the callback, and stores it for re-use.
+
+If regenerating the value costs more than a certain threshold of time (e.g. 50ms),
+then for popular keys it is likely that many web servers will generate and store
+the value simultaneously when the key is entirely absent from the cache. In this case,
+the cool-off feature can be used to protect backend cache servers against network
+congestion. This protection is implemented with a lock and subsequent cool-off period.
+The winner stores their value, while other web server return their value directly.
+
+This counter is incremented whenever a new value was regenerated but not stored.
+
+* Type: Counter.
+* Variable `kClass`: The first part of your cache key.
+
+When the regeneration callback is slow, these scenarios may use the cool-off feature:
+
+* Storing the first interim value for tombstoned keys.
+
+  If a key is currently tombstoned due to a recent `delete()` action, and thus in "hold-off", then
+  the key may not be written to. A mutex lock will let one web server generate the new value and
+  (until the hold-off is over) the generated value will be considered an interim (temporary) value
+  only. Requests that cannot get the lock will use the last stored interim value.
+  If there is no interim value yet, then requests that cannot get the lock may still generate their
+  own value. Here, the cool-off feature is used to decide which requests stores their interim value.
+
+* Storing the first interim value for stale keys.
+
+  If a key is currently in "hold-off" due to a recent `touchCheckKey()` action, then the key may
+  not be written to. A mutex lock will let one web request generate the new value and (until the
+  hold-off is over) such value will be considered an interim (temporary) value only. Requests that
+  lose the lock, will instead return the last stored interim value, or (if it remained in cache) the
+  stale value preserved from before `touchCheckKey()` was called.
+  If there is no stale value and no interim value yet, then multiple requests may need to
+  generate the value simultaneously. In this case, the cool-off feature is used to decide
+  which requests store their interim value.
+
+  The same logic applies when the callback passed to getWithSetCallback() in the "touchedCallback"
+  parameter starts returning an updated timestamp due to a dependency change.
+
+* Storing the first value when `lockTSE` is used.
+
+  When `lockTSE` is in use, and no stale value is found on the backend, and no `busyValue`
+  callback is provided, then multiple requests may generate the value simultaneously;
+  the cool-off is used to decide which requests store their interim value.
index 2c533b9..45caa78 100644 (file)
@@ -188,31 +188,40 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
 
        /** Idiom for getWithSetCallback() meaning "no minimum required as-of timestamp" */
        const MIN_TIMESTAMP_NONE = 0.0;
+       /** @var int One second into the UNIX timestamp epoch */
+       const EPOCH_UNIX_ONE_SECOND = 1.0;
 
        /** Tiny negative float to use when CTL comes up >= 0 due to clock skew */
        const TINY_NEGATIVE = -0.000001;
        /** Tiny positive float to use when using "minTime" to assert an inequality */
        const TINY_POSTIVE = 0.000001;
 
-       /** Milliseconds of delay after get() where set() storms are a consideration with 'lockTSE' */
+       /** Milliseconds of delay after get() where set() storms are a consideration with "lockTSE" */
        const SET_DELAY_HIGH_MS = 50;
        /** Min millisecond set() backoff for keys in hold-off (far less than INTERIM_KEY_TTL) */
        const RECENT_SET_LOW_MS = 50;
        /** Max millisecond set() backoff for keys in hold-off (far less than INTERIM_KEY_TTL) */
        const RECENT_SET_HIGH_MS = 100;
 
+       /** @var int Seconds needed for value generation considered slow */
+       const GENERATION_SLOW_SEC = 3;
+
        /** Parameter to get()/getMulti() to return extra information by reference */
        const PASS_BY_REF = -1;
 
        /** Cache format version number */
        const VERSION = 1;
 
-       const FLD_VERSION = 0; // key to cache version number
+       const FLD_FORMAT_VERSION = 0; // key to WAN cache version number
        const FLD_VALUE = 1; // key to the cached value
        const FLD_TTL = 2; // key to the original TTL
-       const FLD_TIME = 3; // key to the cache time
+       const FLD_TIME = 3; // key to the cache timestamp
        const FLD_FLAGS = 4; // key to the flags bitfield (reserved number)
-       const FLD_HOLDOFF = 5; // key to any hold-off TTL
+       const FLD_VALUE_VERSION = 5; // key to collection cache version number
+       const FLD_GENERATION_TIME = 6; // key to how long it took to generate the value
+
+       const PURGE_TIME = 0; // key to the tombstone entry timestamp
+       const PURGE_HOLDOFF = 1; // key to the tombstone entry hold-off TTL
 
        const VALUE_KEY_PREFIX = 'WANCache:v:';
        const INTERIM_KEY_PREFIX = 'WANCache:i:';
@@ -222,9 +231,6 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
 
        const PURGE_VAL_PREFIX = 'PURGED:';
 
-       const VFLD_DATA = 'WOC:d'; // key to the value of versioned data
-       const VFLD_VERSION = 'WOC:v'; // key to the version of the value present
-
        const PC_PRIMARY = 'primary:1000'; // process cache name and max key count
 
        /**
@@ -256,7 +262,7 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
                $this->region = $params['region'] ?? 'main';
                $this->cluster = $params['cluster'] ?? 'wan-main';
                $this->mcrouterAware = !empty( $params['mcrouterAware'] );
-               $this->epoch = $params['epoch'] ?? 1.0;
+               $this->epoch = $params['epoch'] ?? self::EPOCH_UNIX_ONE_SECOND;
 
                $this->setLogger( $params['logger'] ?? new NullLogger() );
                $this->stats = $params['stats'] ?? new NullStatsdDataFactory();
@@ -314,11 +320,12 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
         * Consider using getWithSetCallback() instead of get() and set() cycles.
         * That method has cache slam avoiding features for hot/expensive keys.
         *
-        * Pass $info as WANObjectCache::PASS_BY_REF to transform it into a cache key info map.
+        * Pass $info as WANObjectCache::PASS_BY_REF to transform it into a cache key metadata map.
         * This map includes the following metadata:
         *   - asOf: UNIX timestamp of the value or null if the key is nonexistant
         *   - tombAsOf: UNIX timestamp of the tombstone or null if the key is not tombstoned
         *   - lastCKPurge: UNIX timestamp of the highest check key or null if none provided
+        *   - version: cached value version number or null if the key is nonexistant
         *
         * Otherwise, $info will transform into the cached value timestamp.
         *
@@ -339,7 +346,8 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
                        $info = [
                                'asOf' => $infoByKey[$key]['asOf'] ?? null,
                                'tombAsOf' => $infoByKey[$key]['tombAsOf'] ?? null,
-                               'lastCKPurge' => $infoByKey[$key]['lastCKPurge'] ?? null
+                               'lastCKPurge' => $infoByKey[$key]['lastCKPurge'] ?? null,
+                               'version' => $infoByKey[$key]['version'] ?? null
                        ];
                } else {
                        $info = $infoByKey[$key]['asOf'] ?? null; // b/c
@@ -352,7 +360,7 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
         * Fetch the value of several keys from cache
         *
         * Pass $info as WANObjectCache::PASS_BY_REF to transform it into a map of cache keys
-        * to cache key info maps, each having the same style as those of WANObjectCache::get().
+        * to cache key metadata maps, each having the same style as those of WANObjectCache::get().
         * All the cache keys listed in $keys will have an entry.
         *
         * Othwerwise, $info will transform into a map of (cache key => cached value timestamp).
@@ -420,9 +428,7 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
                // Get the main cache value for each key and validate them
                foreach ( $valueKeys as $vKey ) {
                        $key = substr( $vKey, $vPrefixLen ); // unprefix
-                       list( $value, $curTTL, $asOf, $tombAsOf ) = isset( $wrappedValues[$vKey] )
-                               ? $this->unwrap( $wrappedValues[$vKey], $now )
-                               : [ false, null, null, null ]; // not found
+                       list( $value, $keyInfo ) = $this->unwrap( $wrappedValues[$vKey] ?? false, $now );
                        // Force dependent keys to be seen as stale for a while after purging
                        // to reduce race conditions involving stale data getting cached
                        $purgeValues = $purgeValuesForAll;
@@ -432,26 +438,27 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
 
                        $lastCKPurge = null; // timestamp of the highest check key
                        foreach ( $purgeValues as $purge ) {
-                               $lastCKPurge = max( $purge[self::FLD_TIME], $lastCKPurge );
-                               $safeTimestamp = $purge[self::FLD_TIME] + $purge[self::FLD_HOLDOFF];
-                               if ( $value !== false && $safeTimestamp >= $asOf ) {
+                               $lastCKPurge = max( $purge[self::PURGE_TIME], $lastCKPurge );
+                               $safeTimestamp = $purge[self::PURGE_TIME] + $purge[self::PURGE_HOLDOFF];
+                               if ( $value !== false && $safeTimestamp >= $keyInfo['asOf'] ) {
                                        // How long ago this value was invalidated by *this* check key
-                                       $ago = min( $purge[self::FLD_TIME] - $now, self::TINY_NEGATIVE );
+                                       $ago = min( $purge[self::PURGE_TIME] - $now, self::TINY_NEGATIVE );
                                        // How long ago this value was invalidated by *any* known check key
-                                       $curTTL = min( $curTTL, $ago );
+                                       $keyInfo['curTTL'] = min( $keyInfo['curTTL'], $ago );
                                }
                        }
+                       $keyInfo[ 'lastCKPurge'] = $lastCKPurge;
 
                        if ( $value !== false ) {
                                $result[$key] = $value;
                        }
-                       if ( $curTTL !== null ) {
-                               $curTTLs[$key] = $curTTL;
+                       if ( $keyInfo['curTTL'] !== null ) {
+                               $curTTLs[$key] = $keyInfo['curTTL'];
                        }
 
                        $infoByKey[$key] = ( $info === self::PASS_BY_REF )
-                               ? [ 'asOf' => $asOf, 'tombAsOf' => $tombAsOf, 'lastCKPurge' => $lastCKPurge ]
-                               : $asOf; // b/c
+                               ? $keyInfo
+                               : $keyInfo['asOf']; // b/c
                }
 
                $info = $infoByKey;
@@ -520,8 +527,9 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
         * @param mixed $value
         * @param int $ttl Seconds to live. Special values are:
         *   - WANObjectCache::TTL_INDEFINITE: Cache forever (default)
+        *   - WANObjectCache::TTL_UNCACHEABLE: Do not cache (if the key exists, it is not deleted)
         * @param array $opts Options map:
-        *   - lag: seconds of replica DB lag. Typically, this is either the replica DB lag
+        *   - lag: Seconds of replica DB lag. Typically, this is either the replica DB lag
         *      before the data was read or, if applicable, the replica DB lag before
         *      the snapshot-isolated transaction the data was read from started.
         *      Use false to indicate that replication is not running.
@@ -530,37 +538,48 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
         *      the current time the data was read or (if applicable) the time when
         *      the snapshot-isolated transaction the data was read from started.
         *      Default: 0 seconds
-        *   - pending: whether this data is possibly from an uncommitted write transaction.
+        *   - pending: Whether this data is possibly from an uncommitted write transaction.
         *      Generally, other threads should not see values from the future and
         *      they certainly should not see ones that ended up getting rolled back.
         *      Default: false
-        *   - lockTSE: if excessive replication/snapshot lag is detected, then store the value
+        *   - lockTSE: If excessive replication/snapshot lag is detected, then store the value
         *      with this TTL and flag it as stale. This is only useful if the reads for this key
         *      use getWithSetCallback() with "lockTSE" set. Note that if "staleTTL" is set
         *      then it will still add on to this TTL in the excessive lag scenario.
         *      Default: WANObjectCache::TSE_NONE
-        *   - staleTTL: seconds to keep the key around if it is stale. The get()/getMulti()
+        *   - staleTTL: Seconds to keep the key around if it is stale. The get()/getMulti()
         *      methods return such stale values with a $curTTL of 0, and getWithSetCallback()
         *      will call the regeneration callback in such cases, passing in the old value
         *      and its as-of time to the callback. This is useful if adaptiveTTL() is used
         *      on the old value's as-of time when it is verified as still being correct.
-        *      Default: WANObjectCache::STALE_TTL_NONE.
-        *   - creating: optimize for the case where the key does not already exist.
+        *      Default: WANObjectCache::STALE_TTL_NONE
+        *   - creating: Optimize for the case where the key does not already exist.
         *      Default: false
+        *   - version: Integer version number signifiying the format of the value.
+        *      Default: null
+        *   - walltime: How long the value took to generate in seconds. Default: 0.0
         * @note Options added in 1.28: staleTTL
         * @note Options added in 1.33: creating
+        * @note Options added in 1.34: version, walltime
         * @return bool Success
         */
        final public function set( $key, $value, $ttl = self::TTL_INDEFINITE, array $opts = [] ) {
                $now = $this->getCurrentTime();
+               $lag = $opts['lag'] ?? 0;
+               $age = isset( $opts['since'] ) ? max( 0, $now - $opts['since'] ) : 0;
+               $pending = $opts['pending'] ?? false;
                $lockTSE = $opts['lockTSE'] ?? self::TSE_NONE;
                $staleTTL = $opts['staleTTL'] ?? self::STALE_TTL_NONE;
-               $age = isset( $opts['since'] ) ? max( 0, $now - $opts['since'] ) : 0;
                $creating = $opts['creating'] ?? false;
-               $lag = $opts['lag'] ?? 0;
+               $version = $opts['version'] ?? null;
+               $walltime = $opts['walltime'] ?? 0.0;
+
+               if ( $ttl < 0 ) {
+                       return true;
+               }
 
                // Do not cache potentially uncommitted data as it might get rolled back
-               if ( !empty( $opts['pending'] ) ) {
+               if ( $pending ) {
                        $this->logger->info(
                                'Rejected set() for {cachekey} due to pending writes.',
                                [ 'cachekey' => $key ]
@@ -619,7 +638,7 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
                }
 
                // Wrap that value with time/TTL/version metadata
-               $wrapped = $this->wrap( $value, $logicalTTL ?: $ttl, $now );
+               $wrapped = $this->wrap( $value, $logicalTTL ?: $ttl, $version, $now, $walltime );
                $storeTTL = $ttl + $staleTTL;
 
                if ( $creating ) {
@@ -812,7 +831,7 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
                foreach ( $rawKeys as $key => $rawKey ) {
                        $purge = $this->parsePurgeValue( $rawValues[$rawKey] );
                        if ( $purge !== false ) {
-                               $time = $purge[self::FLD_TIME];
+                               $time = $purge[self::PURGE_TIME];
                        } else {
                                // Casting assures identical floats for the next getCheckKeyTime() calls
                                $now = (string)$this->getCurrentTime();
@@ -1216,62 +1235,36 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
                $version = $opts['version'] ?? null;
                $pcTTL = $opts['pcTTL'] ?? self::TTL_UNCACHEABLE;
 
-               // Try the process cache if enabled and the cache callback is not within a cache callback.
-               // Process cache use in nested callbacks is not lag-safe with regard to HOLDOFF_TTL since
-               // the in-memory value is further lagged than the shared one since it uses a blind TTL.
+               // Use the process cache if requested as long as no outer cache callback is running.
+               // Nested callback process cache use is not lag-safe with regard to HOLDOFF_TTL since
+               // process cached values are more lagged than persistent ones as they are not purged.
                if ( $pcTTL >= 0 && $this->callbackDepth == 0 ) {
-                       $procCache = $this->getProcessCache( $opts['pcGroup'] ?? self::PC_PRIMARY );
-                       if ( $procCache->has( $key, $pcTTL ) ) {
-                               return $procCache->get( $key );
+                       $pCache = $this->getProcessCache( $opts['pcGroup'] ?? self::PC_PRIMARY );
+                       $cached = $pCache->get( $this->getProcessCacheKey( $key, $version ), INF, false );
+                       if ( $cached !== false ) {
+                               return $cached;
                        }
                } else {
-                       $procCache = null;
+                       $pCache = null;
                }
 
-               if ( $version !== null ) {
-                       $curAsOf = self::PASS_BY_REF;
-                       $curValue = $this->doGetWithSetCallback(
-                               $key,
+               $res = $this->fetchOrRegenerate( $key, $ttl, $callback, $opts );
+               list( $value, $valueVersion, $curAsOf ) = $res;
+               if ( $valueVersion !== $version ) {
+                       // Current value has a different version; use the variant key for this version.
+                       // Regenerate the variant value if it is not newer than the main value at $key
+                       // so that purges to the main key propagate to the variant value.
+                       list( $value ) = $this->fetchOrRegenerate(
+                               $this->makeGlobalKey( 'WANCache-key-variant', md5( $key ), $version ),
                                $ttl,
-                               // Wrap the value in an array with version metadata but hide it from $callback
-                               function ( $oldValue, &$ttl, &$setOpts, $oldAsOf ) use ( $callback, $version ) {
-                                       if ( $this->isVersionedValue( $oldValue, $version ) ) {
-                                               $oldData = $oldValue[self::VFLD_DATA];
-                                       } else {
-                                               // VFLD_DATA is not set if an old, unversioned, key is present
-                                               $oldData = false;
-                                               $oldAsOf = null;
-                                       }
-
-                                       return [
-                                               self::VFLD_DATA => $callback( $oldData, $ttl, $setOpts, $oldAsOf ),
-                                               self::VFLD_VERSION => $version
-                                       ];
-                               },
-                               $opts,
-                               $curAsOf
+                               $callback,
+                               [ 'version' => null, 'minAsOf' => $curAsOf ] + $opts
                        );
-                       if ( $this->isVersionedValue( $curValue, $version ) ) {
-                               // Current value has the requested version; use it
-                               $value = $curValue[self::VFLD_DATA];
-                       } else {
-                               // Current value has a different version; use the variant key for this version.
-                               // Regenerate the variant value if it is not newer than the main value at $key
-                               // so that purges to they key propagate to the variant value.
-                               $value = $this->doGetWithSetCallback(
-                                       $this->makeGlobalKey( 'WANCache-key-variant', md5( $key ), $version ),
-                                       $ttl,
-                                       $callback,
-                                       [ 'version' => null, 'minAsOf' => $curAsOf ] + $opts
-                               );
-                       }
-               } else {
-                       $value = $this->doGetWithSetCallback( $key, $ttl, $callback, $opts );
                }
 
                // Update the process cache if enabled
-               if ( $procCache && $value !== false ) {
-                       $procCache->set( $key, $value );
+               if ( $pCache && $value !== false ) {
+                       $pCache->set( $this->getProcessCacheKey( $key, $version ), $value );
                }
 
                return $value;
@@ -1286,77 +1279,80 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
         * @param int $ttl
         * @param callable $callback
         * @param array $opts Options map for getWithSetCallback()
-        * @param float|null &$asOf Cache generation timestamp of returned value [returned]
-        * @return mixed
+        * @return array Ordered list of the following:
+        *   - Cached or regenerated value
+        *   - Cached or regenerated value version number or null if not versioned
+        *   - Timestamp of the cached value or null if there is no value
         * @note Callable type hints are not used to avoid class-autoloading
         */
-       protected function doGetWithSetCallback( $key, $ttl, $callback, array $opts, &$asOf = null ) {
-               $lowTTL = $opts['lowTTL'] ?? min( self::LOW_TTL, $ttl );
-               $lockTSE = $opts['lockTSE'] ?? self::TSE_NONE;
-               $staleTTL = $opts['staleTTL'] ?? self::STALE_TTL_NONE;
-               $graceTTL = $opts['graceTTL'] ?? self::GRACE_TTL_NONE;
+       private function fetchOrRegenerate( $key, $ttl, $callback, array $opts ) {
                $checkKeys = $opts['checkKeys'] ?? [];
-               $busyValue = $opts['busyValue'] ?? null;
-               $popWindow = $opts['hotTTR'] ?? self::HOT_TTR;
-               $ageNew = $opts['ageNew'] ?? self::AGE_NEW;
+               $graceTTL = $opts['graceTTL'] ?? self::GRACE_TTL_NONE;
                $minAsOf = $opts['minAsOf'] ?? self::MIN_TIMESTAMP_NONE;
+               $hotTTR = $opts['hotTTR'] ?? self::HOT_TTR;
+               $lowTTL = $opts['lowTTL'] ?? min( self::LOW_TTL, $ttl );
+               $ageNew = $opts['ageNew'] ?? self::AGE_NEW;
                $touchedCb = $opts['touchedCallback'] ?? null;
                $initialTime = $this->getCurrentTime();
 
                $kClass = $this->determineKeyClassForStats( $key );
 
-               // Get the current key value and metadata
+               // Get the current key value and its metadata
                $curTTL = self::PASS_BY_REF;
                $curInfo = self::PASS_BY_REF; /** @var array $curInfo */
                $curValue = $this->get( $key, $curTTL, $checkKeys, $curInfo );
                // Apply any $touchedCb invalidation timestamp to get the "last purge timestamp"
                list( $curTTL, $LPT ) = $this->resolveCTL( $curValue, $curTTL, $curInfo, $touchedCb );
-               // Best possible return value and its corresponding "as of" timestamp
-               $value = $curValue;
-               $asOf = $curInfo['asOf'];
-
-               // Determine if a cached value regeneration is needed or desired
+               // Use the cached value if it exists and is not due for synchronous regeneration
                if (
-                       $this->isValid( $value, $asOf, $minAsOf ) &&
+                       $this->isValid( $curValue, $curInfo['asOf'], $minAsOf ) &&
                        $this->isAliveOrInGracePeriod( $curTTL, $graceTTL )
                ) {
                        $preemptiveRefresh = (
                                $this->worthRefreshExpiring( $curTTL, $lowTTL ) ||
-                               $this->worthRefreshPopular( $asOf, $ageNew, $popWindow, $initialTime )
+                               $this->worthRefreshPopular( $curInfo['asOf'], $ageNew, $hotTTR, $initialTime )
                        );
-
                        if ( !$preemptiveRefresh ) {
                                $this->stats->increment( "wanobjectcache.$kClass.hit.good" );
 
-                               return $value;
+                               return [ $curValue, $curInfo['version'], $curInfo['asOf'] ];
                        } elseif ( $this->scheduleAsyncRefresh( $key, $ttl, $callback, $opts ) ) {
                                $this->stats->increment( "wanobjectcache.$kClass.hit.refresh" );
 
-                               return $value;
+                               return [ $curValue, $curInfo['version'], $curInfo['asOf'] ];
                        }
                }
 
+               // Determine if there is stale or volatile cached value that is still usable
                $isKeyTombstoned = ( $curInfo['tombAsOf'] !== null );
                if ( $isKeyTombstoned ) {
-                       // Get the interim key value since the key is tombstoned (write-holed)
-                       list( $value, $asOf ) = $this->getInterimValue( $key, $minAsOf );
+                       // Key is write-holed; use the (volatile) interim key as an alternative
+                       list( $possValue, $possInfo ) = $this->getInterimValue( $key, $minAsOf );
                        // Update the "last purge time" since the $touchedCb timestamp depends on $value
-                       $LPT = $this->resolveTouched( $value, $LPT, $touchedCb );
+                       $LPT = $this->resolveTouched( $possValue, $LPT, $touchedCb );
+               } else {
+                       $possValue = $curValue;
+                       $possInfo = $curInfo;
                }
 
-               // Reduce mutex and cache set spam while keys are in the tombstone/holdoff period by
-               // checking if $value was genereated by a recent thread much less than a second ago.
+               // Avoid overhead from callback runs, regeneration locks, and cache sets during
+               // hold-off periods for the key by reusing very recently generated cached values
                if (
-                       $this->isValid( $value, $asOf, $minAsOf, $LPT ) &&
-                       $this->isVolatileValueAgeNegligible( $initialTime - $asOf )
+                       $this->isValid( $possValue, $possInfo['asOf'], $minAsOf, $LPT ) &&
+                       $this->isVolatileValueAgeNegligible( $initialTime - $possInfo['asOf'] )
                ) {
                        $this->stats->increment( "wanobjectcache.$kClass.hit.volatile" );
 
-                       return $value;
+                       return [ $possValue, $possInfo['version'], $curInfo['asOf'] ];
                }
 
-               // Decide if only one thread should handle regeneration at a time
-               $useMutex =
+               $lockTSE = $opts['lockTSE'] ?? self::TSE_NONE;
+               $busyValue = $opts['busyValue'] ?? null;
+               $staleTTL = $opts['staleTTL'] ?? self::STALE_TTL_NONE;
+               $version = $opts['version'] ?? null;
+
+               // Determine whether one thread per datacenter should handle regeneration at a time
+               $useRegenerationLock =
                        // Note that since tombstones no-op set(), $lockTSE and $curTTL cannot be used to
                        // deduce the key hotness because |$curTTL| will always keep increasing until the
                        // tombstone expires or is overwritten by a new tombstone. Also, even if $lockTSE
@@ -1369,67 +1365,77 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
                        ( $curTTL !== null && $curTTL <= 0 && abs( $curTTL ) <= $lockTSE ) ||
                        // Assume a key is hot if there is no value and a busy fallback is given.
                        // This avoids stampedes on eviction or preemptive regeneration taking too long.
-                       ( $busyValue !== null && $value === false );
-
-               $hasLock = false;
-               if ( $useMutex ) {
-                       // Attempt to acquire a non-blocking lock specific to the local datacenter
-                       if ( $this->cache->add( self::MUTEX_KEY_PREFIX . $key, 1, self::LOCK_TTL ) ) {
-                               // Lock acquired; this thread will recompute the value and update cache
-                               $hasLock = true;
-                       } elseif ( $this->isValid( $value, $asOf, $minAsOf ) ) {
-                               // Not acquired and stale cache value exists; use the stale value
-                               $this->stats->increment( "wanobjectcache.$kClass.hit.stale" );
-
-                               return $value;
-                       } else {
-                               // Lock not acquired and no stale value exists
-                               if ( $busyValue !== null ) {
-                                       // Use the busy fallback value if nothing else
+                       ( $busyValue !== null && $possValue === false );
+
+               // If a regeneration lock is required, threads that do not get the lock will use any
+               // available stale or volatile value. If there is none, then the cheap/placeholder
+               // value from $busyValue will be used if provided; failing that, all threads will try
+               // to regenerate the value and ignore the lock.
+               if ( $useRegenerationLock ) {
+                       $hasLock = $this->cache->add( self::MUTEX_KEY_PREFIX . $key, 1, self::LOCK_TTL );
+                       if ( !$hasLock ) {
+                               if ( $this->isValid( $possValue, $possInfo['asOf'], $minAsOf ) ) {
+                                       $this->stats->increment( "wanobjectcache.$kClass.hit.stale" );
+
+                                       return [ $possValue, $possInfo['version'], $curInfo['asOf'] ];
+                               } elseif ( $busyValue !== null ) {
                                        $miss = is_infinite( $minAsOf ) ? 'renew' : 'miss';
                                        $this->stats->increment( "wanobjectcache.$kClass.$miss.busy" );
 
-                                       return is_callable( $busyValue ) ? $busyValue() : $busyValue;
+                                       return [
+                                               is_callable( $busyValue ) ? $busyValue() : $busyValue,
+                                               $version,
+                                               $curInfo['asOf']
+                                       ];
                                }
                        }
+               } else {
+                       $hasLock = false;
                }
 
-               if ( !is_callable( $callback ) ) {
-                       throw new InvalidArgumentException( "Invalid cache miss callback provided." );
-               }
-
-               $preCallbackTime = $this->getCurrentTime();
-               // Generate the new value from the callback...
+               // Generate the new value given any prior value with a matching version
                $setOpts = [];
+               $preCallbackTime = $this->getCurrentTime();
                ++$this->callbackDepth;
                try {
-                       $value = call_user_func_array( $callback, [ $curValue, &$ttl, &$setOpts, $asOf ] );
+                       $value = $callback(
+                               ( $curInfo['version'] === $version ) ? $curValue : false,
+                               $ttl,
+                               $setOpts,
+                               ( $curInfo['version'] === $version ) ? $curInfo['asOf'] : null
+                       );
                } finally {
                        --$this->callbackDepth;
                }
-               $valueIsCacheable = ( $value !== false && $ttl >= 0 );
+               $postCallbackTime = $this->getCurrentTime();
 
-               if ( $valueIsCacheable ) {
-                       $ago = max( $this->getCurrentTime() - $initialTime, 0.0 );
-                       $this->stats->timing( "wanobjectcache.$kClass.regen_set_delay", 1e3 * $ago );
+               // How long it took to fetch, validate, and generate the value
+               $elapsed = max( $postCallbackTime - $initialTime, 0.0 );
 
+               // Attempt to save the newly generated value if applicable
+               if (
+                       // Callback yielded a cacheable value
+                       ( $value !== false && $ttl >= 0 ) &&
+                       // Current thread was not raced out of a regeneration lock or key is tombstoned
+                       ( !$useRegenerationLock || $hasLock || $isKeyTombstoned ) &&
+                       // Key does not appear to be undergoing a set() stampede
+                       $this->checkAndSetCooloff( $key, $kClass, $elapsed, $lockTSE, $hasLock )
+               ) {
+                       // How long it took to generate the value
+                       $walltime = max( $postCallbackTime - $preCallbackTime, 0.0 );
+                       // If the key is write-holed then use the (volatile) interim key as an alternative
                        if ( $isKeyTombstoned ) {
-                               if ( $this->checkAndSetCooloff( $key, $kClass, $ago, $lockTSE, $hasLock ) ) {
-                                       // Use the interim key value since the key is tombstoned (write-holed)
-                                       $tempTTL = max( self::INTERIM_KEY_TTL, (int)$lockTSE );
-                                       $this->setInterimValue( $key, $value, $tempTTL, $this->getCurrentTime() );
-                               }
-                       } elseif ( !$useMutex || $hasLock ) {
-                               if ( $this->checkAndSetCooloff( $key, $kClass, $ago, $lockTSE, $hasLock ) ) {
-                                       $setOpts['creating'] = ( $curValue === false );
-                                       // Save the value unless a lock-winning thread is already expected to do that
-                                       $setOpts['lockTSE'] = $lockTSE;
-                                       $setOpts['staleTTL'] = $staleTTL;
-                                       // Use best known "since" timestamp if not provided
-                                       $setOpts += [ 'since' => $preCallbackTime ];
-                                       // Update the cache; this will fail if the key is tombstoned
-                                       $this->set( $key, $value, $ttl, $setOpts );
-                               }
+                               $this->setInterimValue( $key, $value, $lockTSE, $version, $walltime );
+                       } else {
+                               $finalSetOpts = [
+                                       'since' => $setOpts['since'] ?? $preCallbackTime,
+                                       'version' => $version,
+                                       'staleTTL' => $staleTTL,
+                                       'lockTSE' => $lockTSE, // informs lag vs performance trade-offs
+                                       'creating' => ( $curValue === false ), // optimization
+                                       'walltime' => $walltime
+                               ] + $setOpts;
+                               $this->set( $key, $value, $ttl, $finalSetOpts );
                        }
                }
 
@@ -1440,7 +1446,7 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
                $miss = is_infinite( $minAsOf ) ? 'renew' : 'miss';
                $this->stats->increment( "wanobjectcache.$kClass.$miss.compute" );
 
-               return $value;
+               return [ $value, $version, $curInfo['asOf'] ];
        }
 
        /**
@@ -1460,6 +1466,8 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
         * @return bool Whether it is OK to proceed with a key set operation
         */
        private function checkAndSetCooloff( $key, $kClass, $elapsed, $lockTSE, $hasLock ) {
+               $this->stats->timing( "wanobjectcache.$kClass.regen_set_delay", 1e3 * $elapsed );
+
                // If $lockTSE is set, the lock was bypassed because there was no stale/interim value,
                // and $elapsed indicates that regeration is slow, then there is a risk of set()
                // stampedes with large blobs. With a typical scale-out infrastructure, CPU and query
@@ -1494,15 +1502,11 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
         * @return array (current time left or null, UNIX timestamp of last purge or null)
         * @note Callable type hints are not used to avoid class-autoloading
         */
-       protected function resolveCTL( $value, $curTTL, $curInfo, $touchedCallback ) {
+       private function resolveCTL( $value, $curTTL, $curInfo, $touchedCallback ) {
                if ( $touchedCallback === null || $value === false ) {
                        return [ $curTTL, max( $curInfo['tombAsOf'], $curInfo['lastCKPurge'] ) ];
                }
 
-               if ( !is_callable( $touchedCallback ) ) {
-                       throw new InvalidArgumentException( "Invalid expiration callback provided." );
-               }
-
                $touched = $touchedCallback( $value );
                if ( $touched !== null && $touched >= $curInfo['asOf'] ) {
                        $curTTL = min( $curTTL, self::TINY_NEGATIVE, $curInfo['asOf'] - $touched );
@@ -1518,53 +1522,49 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
         * @return float|null UNIX timestamp of last purge or null
         * @note Callable type hints are not used to avoid class-autoloading
         */
-       protected function resolveTouched( $value, $lastPurge, $touchedCallback ) {
-               if ( $touchedCallback === null || $value === false ) {
-                       return $lastPurge;
-               }
-
-               if ( !is_callable( $touchedCallback ) ) {
-                       throw new InvalidArgumentException( "Invalid expiration callback provided." );
-               }
-
-               return max( $touchedCallback( $value ), $lastPurge );
+       private function resolveTouched( $value, $lastPurge, $touchedCallback ) {
+               return ( $touchedCallback === null || $value === false )
+                       ? $lastPurge // nothing to derive the "touched timestamp" from
+                       : max( $touchedCallback( $value ), $lastPurge );
        }
 
        /**
         * @param string $key
         * @param float $minAsOf Minimum acceptable "as of" timestamp
-        * @return array (cached value or false, cached value timestamp or null)
+        * @return array (cached value or false, cache key metadata map)
         */
-       protected function getInterimValue( $key, $minAsOf ) {
-               if ( !$this->useInterimHoldOffCaching ) {
-                       return [ false, null ]; // disabled
-               }
+       private function getInterimValue( $key, $minAsOf ) {
+               $now = $this->getCurrentTime();
+
+               if ( $this->useInterimHoldOffCaching ) {
+                       $wrapped = $this->cache->get( self::INTERIM_KEY_PREFIX . $key );
 
-               $wrapped = $this->cache->get( self::INTERIM_KEY_PREFIX . $key );
-               list( $value ) = $this->unwrap( $wrapped, $this->getCurrentTime() );
-               $valueAsOf = $wrapped[self::FLD_TIME] ?? null;
-               if ( $this->isValid( $value, $valueAsOf, $minAsOf ) ) {
-                       return [ $value, $valueAsOf ];
+                       list( $value, $keyInfo ) = $this->unwrap( $wrapped, $now );
+                       if ( $this->isValid( $value, $keyInfo['asOf'], $minAsOf ) ) {
+                               return [ $value, $keyInfo ];
+                       }
                }
 
-               return [ false, null ];
+               return $this->unwrap( false, $now );
        }
 
        /**
         * @param string $key
         * @param mixed $value
-        * @param int $tempTTL
-        * @param float $newAsOf
+        * @param int $ttl
+        * @param int|null $version Value version number
+        * @param float $walltime How long it took to generate the value in seconds
         */
-       protected function setInterimValue( $key, $value, $tempTTL, $newAsOf ) {
-               $wrapped = $this->wrap( $value, $tempTTL, $newAsOf );
+       private function setInterimValue( $key, $value, $ttl, $version, $walltime ) {
+               $ttl = max( self::INTERIM_KEY_TTL, (int)$ttl );
 
+               $wrapped = $this->wrap( $value, $ttl, $version, $this->getCurrentTime(), $walltime );
                $this->cache->merge(
                        self::INTERIM_KEY_PREFIX . $key,
                        function () use ( $wrapped ) {
                                return $wrapped;
                        },
-                       $tempTTL,
+                       $ttl,
                        1
                );
        }
@@ -1639,13 +1639,11 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
                ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = []
        ) {
                $valueKeys = array_keys( $keyedIds->getArrayCopy() );
-               $checkKeys = $opts['checkKeys'] ?? [];
-               $pcTTL = $opts['pcTTL'] ?? self::TTL_UNCACHEABLE;
 
                // Load required keys into process cache in one go
                $this->warmupCache = $this->getRawKeysForWarmup(
-                       $this->getNonProcessCachedKeys( $valueKeys, $opts, $pcTTL ),
-                       $checkKeys
+                       $this->getNonProcessCachedKeys( $valueKeys, $opts ),
+                       $opts['checkKeys'] ?? []
                );
                $this->warmupKeyMisses = 0;
 
@@ -1736,12 +1734,11 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
                $idsByValueKey = $keyedIds->getArrayCopy();
                $valueKeys = array_keys( $idsByValueKey );
                $checkKeys = $opts['checkKeys'] ?? [];
-               $pcTTL = $opts['pcTTL'] ?? self::TTL_UNCACHEABLE;
                unset( $opts['lockTSE'] ); // incompatible
                unset( $opts['busyValue'] ); // incompatible
 
                // Load required keys into process cache in one go
-               $keysGet = $this->getNonProcessCachedKeys( $valueKeys, $opts, $pcTTL );
+               $keysGet = $this->getNonProcessCachedKeys( $valueKeys, $opts );
                $this->warmupCache = $this->getRawKeysForWarmup( $keysGet, $checkKeys );
                $this->warmupKeyMisses = 0;
 
@@ -1838,7 +1835,7 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
         */
        final public function reapCheckKey( $key, $purgeTimestamp, &$isStale = false ) {
                $purge = $this->parsePurgeValue( $this->cache->get( self::TIME_KEY_PREFIX . $key ) );
-               if ( $purge && $purge[self::FLD_TIME] < $purgeTimestamp ) {
+               if ( $purge && $purge[self::PURGE_TIME] < $purgeTimestamp ) {
                        $isStale = true;
                        $this->logger->warning( "Reaping stale check key '$key'." );
                        $ok = $this->cache->changeTTL( self::TIME_KEY_PREFIX . $key, self::TTL_SECOND );
@@ -2049,7 +2046,7 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
         * This must set the key to "PURGED:<UNIX timestamp>:<holdoff>"
         *
         * @param string $key Cache key
-        * @param int $ttl How long to keep the tombstone [seconds]
+        * @param int $ttl Seconds to keep the tombstone around
         * @param int $holdoff HOLDOFF_* constant controlling how long to ignore sets for this key
         * @return bool Success
         */
@@ -2095,10 +2092,11 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
 
        /**
         * @param string $key
-        * @param int $ttl
+        * @param int $ttl Seconds to live
         * @param callable $callback
         * @param array $opts
         * @return bool Success
+        * @note Callable type hints are not used to avoid class-autoloading
         */
        private function scheduleAsyncRefresh( $key, $ttl, $callback, $opts ) {
                if ( !$this->asyncHandler ) {
@@ -2108,7 +2106,7 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
                $func = $this->asyncHandler;
                $func( function () use ( $key, $ttl, $callback, $opts ) {
                        $opts['minAsOf'] = INF; // force a refresh
-                       $this->doGetWithSetCallback( $key, $ttl, $callback, $opts );
+                       $this->fetchOrRegenerate( $key, $ttl, $callback, $opts );
                } );
 
                return true;
@@ -2127,7 +2125,7 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
         * @param int $graceTTL Consider using stale values if $curTTL is greater than this
         * @return bool
         */
-       protected function isAliveOrInGracePeriod( $curTTL, $graceTTL ) {
+       private function isAliveOrInGracePeriod( $curTTL, $graceTTL ) {
                if ( $curTTL > 0 ) {
                        return true;
                } elseif ( $graceTTL <= 0 ) {
@@ -2235,62 +2233,76 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
        }
 
        /**
-        * Do not use this method outside WANObjectCache
-        *
         * @param mixed $value
-        * @param int $ttl [0=forever]
+        * @param int $ttl Seconds to live or zero for "indefinite"
+        * @param int|null $version Value version number or null if not versioned
         * @param float $now Unix Current timestamp just before calling set()
+        * @param float $walltime How long it took to generate the value in seconds
         * @return array
         */
-       protected function wrap( $value, $ttl, $now ) {
-               return [
-                       self::FLD_VERSION => self::VERSION,
+       private function wrap( $value, $ttl, $version, $now, $walltime ) {
+               // Returns keys in ascending integer order for PHP7 array packing:
+               // https://nikic.github.io/2014/12/22/PHPs-new-hashtable-implementation.html
+               $wrapped = [
+                       self::FLD_FORMAT_VERSION => self::VERSION,
                        self::FLD_VALUE => $value,
                        self::FLD_TTL => $ttl,
                        self::FLD_TIME => $now
                ];
+               if ( $version !== null ) {
+                       $wrapped[self::FLD_VALUE_VERSION] = $version;
+               }
+               if ( $walltime >= self::GENERATION_SLOW_SEC ) {
+                       $wrapped[self::FLD_GENERATION_TIME] = $walltime;
+               }
+
+               return $wrapped;
        }
 
        /**
-        * Do not use this method outside WANObjectCache
-        *
-        * The cached value will be false if absent/tombstoned/malformed
-        *
-        * @param array|string|bool $wrapped
+        * @param array|string|bool $wrapped The entry at a cache key
         * @param float $now Unix Current timestamp (preferrably pre-query)
-        * @return array (cached value or false, current TTL, value timestamp, tombstone timestamp)
+        * @return array (value or false if absent/tombstoned/malformed, value metadata map).
+        * The cache key metadata includes the following metadata:
+        *   - asOf: UNIX timestamp of the value or null if there is no value
+        *   - curTTL: remaining time-to-live (negative if tombstoned) or null if there is no value
+        *   - version: value version number or null if the if there is no value
+        *   - tombAsOf: UNIX timestamp of the tombstone or null if there is no tombstone
         */
-       protected function unwrap( $wrapped, $now ) {
-               // Check if the value is a tombstone
-               $purge = $this->parsePurgeValue( $wrapped );
-               if ( $purge !== false ) {
-                       // Purged values should always have a negative current $ttl
-                       $curTTL = min( $purge[self::FLD_TIME] - $now, self::TINY_NEGATIVE );
-                       return [ false, $curTTL, null, $purge[self::FLD_TIME] ];
-               }
-
-               if ( !is_array( $wrapped ) // not found
-                       || !isset( $wrapped[self::FLD_VERSION] ) // wrong format
-                       || $wrapped[self::FLD_VERSION] !== self::VERSION // wrong version
-               ) {
-                       return [ false, null, null, null ];
-               }
-
-               if ( $wrapped[self::FLD_TTL] > 0 ) {
-                       // Get the approximate time left on the key
-                       $age = $now - $wrapped[self::FLD_TIME];
-                       $curTTL = max( $wrapped[self::FLD_TTL] - $age, 0.0 );
+       private function unwrap( $wrapped, $now ) {
+               $value = false;
+               $info = [ 'asOf' => null, 'curTTL' => null, 'version' => null, 'tombAsOf' => null ];
+
+               if ( is_array( $wrapped ) ) {
+                       // Entry expected to be a cached value; validate it
+                       if (
+                               ( $wrapped[self::FLD_FORMAT_VERSION] ?? null ) === self::VERSION &&
+                               $wrapped[self::FLD_TIME] >= $this->epoch
+                       ) {
+                               if ( $wrapped[self::FLD_TTL] > 0 ) {
+                                       // Get the approximate time left on the key
+                                       $age = $now - $wrapped[self::FLD_TIME];
+                                       $curTTL = max( $wrapped[self::FLD_TTL] - $age, 0.0 );
+                               } else {
+                                       // Key had no TTL, so the time left is unbounded
+                                       $curTTL = INF;
+                               }
+                               $value = $wrapped[self::FLD_VALUE];
+                               $info['version'] = $wrapped[self::FLD_VALUE_VERSION] ?? null;
+                               $info['asOf'] = $wrapped[self::FLD_TIME];
+                               $info['curTTL'] = $curTTL;
+                       }
                } else {
-                       // Key had no TTL, so the time left is unbounded
-                       $curTTL = INF;
-               }
-
-               if ( $wrapped[self::FLD_TIME] < $this->epoch ) {
-                       // Values this old are ignored
-                       return [ false, null, null, null ];
+                       // Entry expected to be a tombstone; parse it
+                       $purge = $this->parsePurgeValue( $wrapped );
+                       if ( $purge !== false ) {
+                               // Tombstoned keys should always have a negative current $ttl
+                               $info['curTTL'] = min( $purge[self::PURGE_TIME] - $now, self::TINY_NEGATIVE );
+                               $info['tombAsOf'] = $purge[self::PURGE_TIME];
+                       }
                }
 
-               return [ $wrapped[self::FLD_VALUE], $curTTL, $wrapped[self::FLD_TIME], null ];
+               return [ $value, $info ];
        }
 
        /**
@@ -2311,7 +2323,7 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
         * @param string $key String of the format <scope>:<class>[:<class or variable>]...
         * @return string A collection name to describe this class of key
         */
-       protected function determineKeyClassForStats( $key ) {
+       private function determineKeyClassForStats( $key ) {
                $parts = explode( ':', $key, 3 );
 
                return $parts[1] ?? $parts[0]; // sanity
@@ -2322,7 +2334,7 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
         * @return array|bool Array containing a UNIX timestamp (float) and holdoff period (integer),
         *  or false if value isn't a valid purge value
         */
-       protected function parsePurgeValue( $value ) {
+       private function parsePurgeValue( $value ) {
                if ( !is_string( $value ) ) {
                        return false;
                }
@@ -2345,8 +2357,8 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
                }
 
                return [
-                       self::FLD_TIME => (float)$segments[1],
-                       self::FLD_HOLDOFF => (int)$segments[2],
+                       self::PURGE_TIME => (float)$segments[1],
+                       self::PURGE_HOLDOFF => (int)$segments[2],
                ];
        }
 
@@ -2355,50 +2367,46 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
         * @param int $holdoff In seconds
         * @return string Wrapped purge value
         */
-       protected function makePurgeValue( $timestamp, $holdoff ) {
+       private function makePurgeValue( $timestamp, $holdoff ) {
                return self::PURGE_VAL_PREFIX . (float)$timestamp . ':' . (int)$holdoff;
        }
 
-       /**
-        * @param mixed $value
-        * @param int $version
-        * @return bool
-        */
-       protected function isVersionedValue( $value, $version ) {
-               return (
-                       is_array( $value ) &&
-                       array_key_exists( self::VFLD_DATA, $value ) &&
-                       array_key_exists( self::VFLD_VERSION, $value ) &&
-                       $value[self::VFLD_VERSION] === $version
-               );
-       }
-
        /**
         * @param string $group
         * @return MapCacheLRU
         */
-       protected function getProcessCache( $group ) {
+       private function getProcessCache( $group ) {
                if ( !isset( $this->processCaches[$group] ) ) {
-                       list( , $n ) = explode( ':', $group );
-                       $this->processCaches[$group] = new MapCacheLRU( (int)$n );
+                       list( , $size ) = explode( ':', $group );
+                       $this->processCaches[$group] = new MapCacheLRU( (int)$size );
                }
 
                return $this->processCaches[$group];
        }
 
+       /**
+        * @param string $key
+        * @param int $version
+        * @return string
+        */
+       private function getProcessCacheKey( $key, $version ) {
+               return $key . ' ' . (int)$version;
+       }
+
        /**
         * @param array $keys
         * @param array $opts
-        * @param int $pcTTL
-        * @return array List of keys
+        * @return string[] List of keys
         */
-       private function getNonProcessCachedKeys( array $keys, array $opts, $pcTTL ) {
+       private function getNonProcessCachedKeys( array $keys, array $opts ) {
+               $pcTTL = $opts['pcTTL'] ?? self::TTL_UNCACHEABLE;
+
                $keysFound = [];
-               if ( isset( $opts['pcTTL'] ) && $opts['pcTTL'] > 0 && $this->callbackDepth == 0 ) {
-                       $pcGroup = $opts['pcGroup'] ?? self::PC_PRIMARY;
-                       $procCache = $this->getProcessCache( $pcGroup );
+               if ( $pcTTL > 0 && $this->callbackDepth == 0 ) {
+                       $version = $opts['version'] ?? null;
+                       $pCache = $this->getProcessCache( $opts['pcGroup'] ?? self::PC_PRIMARY );
                        foreach ( $keys as $key ) {
-                               if ( $procCache->has( $key, $pcTTL ) ) {
+                               if ( $pCache->has( $this->getProcessCacheKey( $key, $version ), $pcTTL ) ) {
                                        $keysFound[] = $key;
                                }
                        }
index 24b5402..8615cfc 100644 (file)
@@ -177,7 +177,7 @@ class ChronologyProtector implements LoggerAwareInterface {
 
                $masterName = $lb->getServerName( $lb->getWriterIndex() );
                if ( $lb->hasStreamingReplicaServers() ) {
-                       $pos = $lb->getMasterPos();
+                       $pos = $lb->getReplicaResumePos();
                        if ( $pos ) {
                                $this->logger->debug( __METHOD__ . ": LB for '$masterName' has pos $pos\n" );
                                $this->shutdownPositions[$masterName] = $pos;
index 294cd0c..3024b00 100644 (file)
@@ -30,7 +30,7 @@ use Psr\Log\LoggerInterface;
 use Psr\Log\NullLogger;
 use Wikimedia\ScopedCallback;
 use Wikimedia\Timestamp\ConvertibleTimestamp;
-use Wikimedia;
+use Wikimedia\AtEase\AtEase;
 use BagOStuff;
 use HashBagOStuff;
 use LogicException;
@@ -4439,9 +4439,9 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                $fname = false,
                callable $inputCallback = null
        ) {
-               Wikimedia\suppressWarnings();
+               AtEase::suppressWarnings();
                $fp = fopen( $filename, 'r' );
-               Wikimedia\restoreWarnings();
+               AtEase::restoreWarnings();
 
                if ( $fp === false ) {
                        throw new RuntimeException( "Could not open \"{$filename}\"" );
@@ -4913,10 +4913,10 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                if ( $this->conn ) {
                        // Avoid connection leaks for sanity. Normally, resources close at script completion.
                        // The connection might already be closed in zend/hhvm by now, so suppress warnings.
-                       Wikimedia\suppressWarnings();
+                       AtEase::suppressWarnings();
                        $this->closeConnection();
-                       Wikimedia\restoreWarnings();
-                       $this->conn = false;
+                       AtEase::restoreWarnings();
+                       $this->conn = null;
                }
        }
 }
index 4774390..1e3fa84 100644 (file)
@@ -24,7 +24,7 @@ namespace Wikimedia\Rdbms;
 
 use DateTime;
 use DateTimeZone;
-use Wikimedia;
+use Wikimedia\AtEase\AtEase;
 use InvalidArgumentException;
 use Exception;
 use RuntimeException;
@@ -244,9 +244,9 @@ abstract class DatabaseMysqlBase extends Database {
         * @throws DBUnexpectedError
         */
        public function freeResult( $res ) {
-               Wikimedia\suppressWarnings();
+               AtEase::suppressWarnings();
                $ok = $this->mysqlFreeResult( ResultWrapper::unwrap( $res ) );
-               Wikimedia\restoreWarnings();
+               AtEase::restoreWarnings();
                if ( !$ok ) {
                        throw new DBUnexpectedError( $this, "Unable to free MySQL result" );
                }
@@ -266,9 +266,9 @@ abstract class DatabaseMysqlBase extends Database {
         * @throws DBUnexpectedError
         */
        public function fetchObject( $res ) {
-               Wikimedia\suppressWarnings();
+               AtEase::suppressWarnings();
                $row = $this->mysqlFetchObject( ResultWrapper::unwrap( $res ) );
-               Wikimedia\restoreWarnings();
+               AtEase::restoreWarnings();
 
                $errno = $this->lastErrno();
                // Unfortunately, mysql_fetch_object does not reset the last errno.
@@ -299,9 +299,9 @@ abstract class DatabaseMysqlBase extends Database {
         * @throws DBUnexpectedError
         */
        public function fetchRow( $res ) {
-               Wikimedia\suppressWarnings();
+               AtEase::suppressWarnings();
                $row = $this->mysqlFetchArray( ResultWrapper::unwrap( $res ) );
-               Wikimedia\restoreWarnings();
+               AtEase::restoreWarnings();
 
                $errno = $this->lastErrno();
                // Unfortunately, mysql_fetch_array does not reset the last errno.
@@ -335,9 +335,9 @@ abstract class DatabaseMysqlBase extends Database {
                if ( is_bool( $res ) ) {
                        $n = 0;
                } else {
-                       Wikimedia\suppressWarnings();
+                       AtEase::suppressWarnings();
                        $n = $this->mysqlNumRows( ResultWrapper::unwrap( $res ) );
-                       Wikimedia\restoreWarnings();
+                       AtEase::restoreWarnings();
                }
 
                // Unfortunately, mysql_num_rows does not reset the last errno.
@@ -433,12 +433,12 @@ abstract class DatabaseMysqlBase extends Database {
        public function lastError() {
                if ( $this->conn ) {
                        # Even if it's non-zero, it can still be invalid
-                       Wikimedia\suppressWarnings();
+                       AtEase::suppressWarnings();
                        $error = $this->mysqlError( $this->conn );
                        if ( !$error ) {
                                $error = $this->mysqlError();
                        }
-                       Wikimedia\restoreWarnings();
+                       AtEase::restoreWarnings();
                } else {
                        $error = $this->mysqlError();
                }
index 954d400..cef3922 100644 (file)
@@ -24,7 +24,7 @@ namespace Wikimedia\Rdbms;
 
 use Wikimedia\Timestamp\ConvertibleTimestamp;
 use Wikimedia\WaitConditionLoop;
-use Wikimedia;
+use Wikimedia\AtEase\AtEase;
 use Exception;
 
 /**
@@ -274,18 +274,18 @@ class DatabasePostgres extends Database {
        }
 
        public function freeResult( $res ) {
-               Wikimedia\suppressWarnings();
+               AtEase::suppressWarnings();
                $ok = pg_free_result( ResultWrapper::unwrap( $res ) );
-               Wikimedia\restoreWarnings();
+               AtEase::restoreWarnings();
                if ( !$ok ) {
                        throw new DBUnexpectedError( $this, "Unable to free Postgres result\n" );
                }
        }
 
        public function fetchObject( $res ) {
-               Wikimedia\suppressWarnings();
+               AtEase::suppressWarnings();
                $row = pg_fetch_object( ResultWrapper::unwrap( $res ) );
-               Wikimedia\restoreWarnings();
+               AtEase::restoreWarnings();
                # @todo FIXME: HACK HACK HACK HACK debug
 
                # @todo hashar: not sure if the following test really trigger if the object
@@ -302,9 +302,9 @@ class DatabasePostgres extends Database {
        }
 
        public function fetchRow( $res ) {
-               Wikimedia\suppressWarnings();
+               AtEase::suppressWarnings();
                $row = pg_fetch_array( ResultWrapper::unwrap( $res ) );
-               Wikimedia\restoreWarnings();
+               AtEase::restoreWarnings();
 
                $conn = $this->getBindingHandle();
                if ( pg_last_error( $conn ) ) {
@@ -322,9 +322,9 @@ class DatabasePostgres extends Database {
                        return 0;
                }
 
-               Wikimedia\suppressWarnings();
+               AtEase::suppressWarnings();
                $n = pg_num_rows( ResultWrapper::unwrap( $res ) );
-               Wikimedia\restoreWarnings();
+               AtEase::restoreWarnings();
 
                $conn = $this->getBindingHandle();
                if ( pg_last_error( $conn ) ) {
index d94d24d..a85e1e5 100644 (file)
@@ -422,14 +422,20 @@ abstract class LBFactory implements ILBFactory {
                // time needed to wait on the next clusters.
                $masterPositions = array_fill( 0, count( $lbs ), false );
                foreach ( $lbs as $i => $lb ) {
-                       if ( !$lb->hasStreamingReplicaServers() ) {
-                               continue; // T29975: no replication; avoid getMasterPos() permissions errors
-                       } elseif (
-                               $opts['ifWritesSince'] &&
-                               $lb->lastMasterChangeTimestamp() < $opts['ifWritesSince']
+                       if (
+                               // No writes to wait on getting replicated
+                               !$lb->hasMasterConnection() ||
+                               // No replication; avoid getMasterPos() permissions errors (T29975)
+                               !$lb->hasStreamingReplicaServers() ||
+                               // No writes since the last replication wait
+                               (
+                                       $opts['ifWritesSince'] &&
+                                       $lb->lastMasterChangeTimestamp() < $opts['ifWritesSince']
+                               )
                        ) {
-                               continue; // no writes since the last wait
+                               continue; // no need to wait
                        }
+
                        $masterPositions[$i] = $lb->getMasterPos();
                }
 
index ba0b4a0..4a4e1bc 100644 (file)
@@ -455,11 +455,30 @@ interface ILoadBalancer {
        public function getServerAttributes( $i );
 
        /**
-        * Get the current master position for chronology control purposes
+        * Get the current master replication position
+        *
         * @return DBMasterPos|bool Returns false if not applicable
+        * @throws DBError
         */
        public function getMasterPos();
 
+       /**
+        * Get the highest DB replication position for chronology control purposes
+        *
+        * If there is only a master server then this returns false. If replication is present
+        * and correctly configured, then this returns the highest replication position of any
+        * server with an open connection. That position can later be passed to waitFor() on a
+        * new load balancer instance to make sure that queries on the new connections see data
+        * at least as up-to-date as queries (prior to this method call) on the old connections.
+        *
+        * This can be useful for implementing session consistency, where the session
+        * will be resumed accross multiple HTTP requests or CLI script instances.
+        *
+        * @return DBMasterPos|bool Replication position or false if not applicable
+        * @since 1.34
+        */
+       public function getReplicaResumePos();
+
        /**
         * Disable this load balancer. All connections are closed, and any attempt to
         * open a new connection will result in a DBAccessError.
index 184cb0e..cab0201 100644 (file)
@@ -1452,22 +1452,56 @@ class LoadBalancer implements ILoadBalancer {
        }
 
        public function getMasterPos() {
-               # If this entire request was served from a replica DB without opening a connection to the
-               # master (however unlikely that may be), then we can fetch the position from the replica DB.
+               $index = $this->getWriterIndex();
+
+               $conn = $this->getAnyOpenConnection( $index );
+               if ( $conn ) {
+                       return $conn->getMasterPos();
+               }
+
+               $conn = $this->getConnection( $index, self::CONN_SILENCE_ERRORS );
+               if ( !$conn ) {
+                       $this->reportConnectionError();
+                       return null; // unreachable due to exception
+               }
+
+               try {
+                       $pos = $conn->getMasterPos();
+               } finally {
+                       $this->closeConnection( $conn );
+               }
+
+               return $pos;
+       }
+
+       public function getReplicaResumePos() {
+               // Get the position of any existing master server connection
                $masterConn = $this->getAnyOpenConnection( $this->getWriterIndex() );
-               if ( !$masterConn ) {
-                       $serverCount = $this->getServerCount();
-                       for ( $i = 1; $i < $serverCount; $i++ ) {
-                               $conn = $this->getAnyOpenConnection( $i );
-                               if ( $conn ) {
-                                       return $conn->getReplicaPos();
-                               }
-                       }
-               } else {
+               if ( $masterConn ) {
                        return $masterConn->getMasterPos();
                }
 
-               return false;
+               // Get the highest position of any existing replica server connection
+               $highestPos = false;
+               $serverCount = $this->getServerCount();
+               for ( $i = 1; $i < $serverCount; $i++ ) {
+                       if ( !empty( $this->servers[$i]['is static'] ) ) {
+                               continue; // server does not use replication
+                       }
+
+                       $conn = $this->getAnyOpenConnection( $i );
+                       $pos = $conn ? $conn->getReplicaPos() : false;
+                       if ( !$pos ) {
+                               continue; // no open connection or could not get position
+                       }
+
+                       $highestPos = $highestPos ?: $pos;
+                       if ( $pos->hasReached( $highestPos ) ) {
+                               $highestPos = $pos;
+                       }
+               }
+
+               return $highestPos;
        }
 
        public function disable() {
index f899d76..6841cc5 100644 (file)
@@ -49,27 +49,6 @@ class SpecialChangeCredentials extends AuthManagerSpecialPage {
                return $params;
        }
 
-       public function onAuthChangeFormFields(
-               array $requests, array $fieldInfo, array &$formDescriptor, $action
-       ) {
-               // This method is never called for remove actions.
-
-               $extraFields = [];
-               Hooks::run( 'ChangePasswordForm', [ &$extraFields ], '1.27' );
-               foreach ( $extraFields as $extra ) {
-                       list( $name, $label, $type, $default ) = $extra;
-                       $formDescriptor[$name] = [
-                               'type' => $type,
-                               'name' => $name,
-                               'label-message' => $label,
-                               'default' => $default,
-                       ];
-
-               }
-
-               return parent::onAuthChangeFormFields( $requests, $fieldInfo, $formDescriptor, $action );
-       }
-
        public function execute( $subPage ) {
                $this->setHeaders();
                $this->outputHeader();
index 4da0901..e1d7fca 100644 (file)
@@ -56,6 +56,8 @@ class AttachLatest extends Maintenance {
                        __METHOD__ );
 
                $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+               $dbDomain = $lbFactory->getLocalDomainID();
+
                $n = 0;
                foreach ( $result as $row ) {
                        $pageId = intval( $row->page_id );
@@ -66,18 +68,19 @@ class AttachLatest extends Maintenance {
                                [ 'rev_page' => $pageId ],
                                __METHOD__ );
                        if ( !$latestTime ) {
-                               $this->output( wfWikiID() . " $pageId [[$name]] can't find latest rev time?!\n" );
+                               $this->output( "$dbDomain $pageId [[$name]] can't find latest rev time?!\n" );
                                continue;
                        }
 
                        $revision = Revision::loadFromTimestamp( $dbw, $title, $latestTime );
                        if ( is_null( $revision ) ) {
-                               $this->output( wfWikiID()
-                                       . " $pageId [[$name]] latest time $latestTime, can't find revision id\n" );
+                               $this->output(
+                                       "$dbDomain $pageId [[$name]] latest time $latestTime, can't find revision id\n"
+                               );
                                continue;
                        }
                        $id = $revision->getId();
-                       $this->output( wfWikiID() . " $pageId [[$name]] latest time $latestTime, rev id $id\n" );
+                       $this->output( "$dbDomain $pageId [[$name]] latest time $latestTime, rev id $id\n" );
                        if ( $this->hasOption( 'fix' ) ) {
                                $page = WikiPage::factory( $title );
                                $page->updateRevisionOn( $dbw, $revision );
index eb45cfc..536e6db 100644 (file)
@@ -87,7 +87,8 @@ TEXT
 
                $this->outputStatus( 'Done!' );
                if ( $this->hasOption( 'fix' ) ) {
-                       $this->outputStatus( ' Cleaned up invalid DB keys on ' . wfWikiID() . "!\n" );
+                       $dbDomain = WikiMap::getCurrentWikiDbDomain()->getId();
+                       $this->outputStatus( " Cleaned up invalid DB keys on $dbDomain!\n" );
                }
        }
 
index 5ff3af1..da9b4d6 100644 (file)
@@ -103,11 +103,12 @@ class CreateAndPromote extends Maintenance {
 
                        return;
                } elseif ( count( $promotions ) !== 0 ) {
+                       $dbDomain = WikiMap::getCurrentWikiDbDomain()->getId();
                        $promoText = "User:{$username} into " . implode( ', ', $promotions ) . "...\n";
                        if ( $exists ) {
-                               $this->output( wfWikiID() . ": Promoting $promoText" );
+                               $this->output( "$dbDomain: Promoting $promoText" );
                        } else {
-                               $this->output( wfWikiID() . ": Creating and promoting $promoText" );
+                               $this->output( "$dbDomain: Creating and promoting $promoText" );
                        }
                }
 
index 0118c94..df3b4a1 100644 (file)
@@ -412,10 +412,12 @@ abstract class BackupDumper extends Maintenance {
                                $pageRatePart = '-';
                                $revRatePart = '-';
                        }
+
+                       $dbDomain = WikiMap::getCurrentWikiDbDomain()->getId();
                        $this->progress( sprintf(
                                "%s: %s (ID %d) %d pages (%0.1f|%0.1f/sec all|curr), "
                                        . "%d revs (%0.1f|%0.1f/sec all|curr), ETA %s [max %d]",
-                               $now, wfWikiID(), $this->ID, $this->pageCount, $pageRate,
+                               $now, $dbDomain, $this->ID, $this->pageCount, $pageRate,
                                $pageRatePart, $this->revCount, $revRate, $revRatePart, $etats,
                                $this->maxCount
                        ) );
index b37fec1..c6738bc 100644 (file)
@@ -373,11 +373,13 @@ TEXT
                                $pageRatePart = '-';
                                $revRatePart = '-';
                        }
+
+                       $dbDomain = WikiMap::getCurrentWikiDbDomain()->getId();
                        $this->progress( sprintf(
                                "%s: %s (ID %d) %d pages (%0.1f|%0.1f/sec all|curr), "
                                        . "%d revs (%0.1f|%0.1f/sec all|curr), %0.1f%%|%0.1f%% "
                                        . "prefetched (all|curr), ETA %s [max %d]",
-                               $now, wfWikiID(), $this->ID, $this->pageCount, $pageRate,
+                               $now, $dbDomain, $this->ID, $this->pageCount, $pageRate,
                                $pageRatePart, $this->revCount, $revRate, $revRatePart,
                                $fetchRate, $fetchRatePart, $etats, $this->maxCount
                        ) );
index 938503c..f4cd2d3 100644 (file)
@@ -87,7 +87,7 @@ in the load balancer, usually indicating a replication environment.' );
                                $delta = microtime( true ) - $start;
                                $rate = ( $delta == 0.0 ) ? 0.0 : $migrated / $delta;
                                $this->output( sprintf( "%s %d (%0.1f%%) done in %0.1f secs (%0.3f accounts/sec).\n",
-                                       wfWikiID(),
+                                       WikiMap::getCurrentWikiDbDomain()->getId(),
                                        $migrated,
                                        min( $max, $lastUser ) / $lastUser * 100.0,
                                        $delta,
index e6733a1..8a8f4d8 100644 (file)
@@ -148,7 +148,7 @@ class RecompressTracked {
                if ( $this->replicaId !== false ) {
                        $header .= "({$this->replicaId})";
                }
-               $header .= ' ' . wfWikiID();
+               $header .= ' ' . WikiMap::getCurrentWikiDbDomain()->getId();
                LegacyLogger::emit( sprintf( "%-50s %s\n", $header, $msg ), $file );
        }
 
index 76a5721..7d343b2 100644 (file)
@@ -49,7 +49,11 @@ class SyncFileBackend extends Maintenance {
                $src = FileBackendGroup::singleton()->get( $this->getOption( 'src' ) );
 
                $posDir = $this->getOption( 'posdir' );
-               $posFile = $posDir ? $posDir . '/' . wfWikiID() : false;
+               if ( $posDir != '' ) {
+                       $posFile = "$posDir/" . rawurlencode( $src->getDomainId() );
+               } else {
+                       $posFile = false;
+               }
 
                if ( $this->hasOption( 'posdump' ) ) {
                        // Just dump the current position into the specified position dir
index 6edca6e..d84ec5c 100755 (executable)
@@ -28,6 +28,7 @@
 require_once __DIR__ . '/Maintenance.php';
 
 use Wikimedia\Rdbms\IMaintainableDatabase;
+use Wikimedia\Rdbms\DatabaseSqlite;
 
 /**
  * Maintenance script to run database schema updates.
@@ -160,7 +161,8 @@ class UpdateMediaWiki extends Maintenance {
                        $this->fatalError( $text );
                }
 
-               $this->output( "Going to run database updates for " . wfWikiID() . "\n" );
+               $dbDomain = WikiMap::getCurrentWikiDbDomain()->getId();
+               $this->output( "Going to run database updates for $dbDomain\n" );
                if ( $db->getType() === 'sqlite' ) {
                        /** @var IMaintainableDatabase|DatabaseSqlite $db */
                        $this->output( "Using SQLite file: '{$db->getDbFilePath()}'\n" );
index af2d828..0216b28 100644 (file)
@@ -61,7 +61,8 @@ class UpdateSearchIndex extends Maintenance {
        }
 
        public function execute() {
-               $posFile = $this->getOption( 'p', 'searchUpdate.' . wfWikiID() . '.pos' );
+               $dbDomain = WikiMap::getCurrentWikiDbDomain()->getId();
+               $posFile = $this->getOption( 'p', 'searchUpdate.' . rawurlencode( $dbDomain ) . '.pos' );
                $end = $this->getOption( 'e', wfTimestampNow() );
                if ( $this->hasOption( 's' ) ) {
                        $start = $this->getOption( 's' );
index bebc172..3b99696 100644 (file)
@@ -354,7 +354,7 @@ a.new {
        font-weight: bold;
 }
 
-/* success and error messages */
+/* Error, warning and success messages */
 .error,
 .warning,
 .success {
@@ -366,11 +366,11 @@ a.new {
 }
 
 .warning {
-       color: #705000;
+       color: #ac6600;
 }
 
 .success {
-       color: #009000;
+       color: #14866d;
 }
 
 .errorbox,
@@ -380,15 +380,13 @@ a.new {
        padding: 0.5em 1em;
        margin-bottom: 1em;
        display: inline-block;
-       zoom: 1;
-       *display: inline; /* stylelint-disable-line declaration-block-no-duplicate-properties */
 }
 
 .errorbox h2,
 .warningbox h2,
 .successbox h2 {
-       font-size: 1em;
        color: inherit;
+       font-size: 1em;
        font-weight: bold;
        display: inline;
        margin: 0 0.5em 0 0;
@@ -396,26 +394,26 @@ a.new {
 }
 
 .errorbox {
-       color: #d33;
-       border-color: #fac5c5;
-       background-color: #fae3e3;
+       background-color: #fee7e6;
+       color: #000;
+       border-color: #d33;
 }
 
 .warningbox {
-       color: #705000;
-       border-color: #fde29b;
-       background-color: #fdf1d1;
+       background-color: #fef6e7;
+       color: #000;
+       border-color: #fc3;
 }
 
 .successbox {
-       color: #008000;
-       border-color: #b7fdb5;
-       background-color: #e1fddf;
+       background-color: #d5fdf4;
+       color: #000;
+       border-color: #14866d;
 }
 
 /* general info/warning box for SP */
 .mw-infobox {
-       border: 2px solid #ff7f00;
+       border: 2px solid #fc3;
        margin: 0.5em;
        clear: left;
        overflow: hidden;
index 6a75db0..e493e59 100644 (file)
@@ -59,6 +59,7 @@
 
 // Text colors
 @colorText: @colorGray2;
+@colorTextEmphasized: @colorGray1;
 @colorTextLight: @colorGray5;
 @colorButtonText: @colorGray2;
 @colorButtonTextHighlight: @colorGray4;
 
 // Form input sizes, equal to OOUI at 14px base font-size
 @sizeInputBinary: 1.5625em;
+
+// Messages
+@backgroundColorError: #fee7e6;
+@borderColorError: #d33;
+@backgroundColorWarning: #fef6e7;
+@borderColorWarning: #fc3;
index d08fff5..01318c9 100644 (file)
                .box-sizing( border-box );
                font-size: 0.9em;
                margin: 0 0 1em 0;
-               padding: 0.5em;
+               padding: 0.5em 1em;
                word-wrap: break-word;
        }
 
        // Colours taken from those for .errorbox in shared.css
        .error {
-               color: @colorErrorText;
-               border: 1px solid #fac5c5;
-               background-color: #fae3e3;
+               background-color: @backgroundColorError;
+               color: @colorTextEmphasized;
+               border: 1px solid @borderColorError;
        }
 
        // Colours taken from those for .warningbox in shared.css
        .warning {
-               color: @colorWarningText;
-               border: 1px solid #fde29b;
-               background-color: #fdf1d1;
+               background-color: @backgroundColorWarning;
+               color: @colorTextEmphasized;
+               border: 1px solid @borderColorWarning;
        }
 
        // This specifies styling for individual field validation error messages.
index 123b080..424c64b 100644 (file)
@@ -315,6 +315,7 @@ class LBFactoryTest extends MediaWikiTestCase {
                                }
                        ) );
                $lb1->method( 'getMasterPos' )->willReturn( $m1Pos );
+               $lb1->method( 'getReplicaResumePos' )->willReturn( $m1Pos );
                $lb1->method( 'getServerName' )->with( 0 )->willReturn( 'master1' );
                // Master DB 2
                $mockDB2 = $this->getMockBuilder( IDatabase::class )
@@ -342,6 +343,7 @@ class LBFactoryTest extends MediaWikiTestCase {
                        }
                ) );
                $lb2->method( 'getMasterPos' )->willReturn( $m2Pos );
+               $lb2->method( 'getReplicaResumePos' )->willReturn( $m2Pos );
                $lb2->method( 'getServerName' )->with( 0 )->willReturn( 'master2' );
 
                $bag = new HashBagOStuff();
index f1bcd98..8510109 100644 (file)
@@ -51,8 +51,11 @@ class LoadBalancerTest extends MediaWikiTestCase {
        }
 
        /**
-        * @covers LoadBalancer::getLocalDomainID()
-        * @covers LoadBalancer::resolveDomainID()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::getConnection()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::getLocalDomainID()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::resolveDomainID()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::haveIndex()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::isNonZeroLoad()
         */
        public function testWithoutReplica() {
                global $wgDBname;
@@ -68,6 +71,15 @@ class LoadBalancerTest extends MediaWikiTestCase {
                        }
                ] );
 
+               $this->assertEquals( 1, $lb->getServerCount() );
+               $this->assertFalse( $lb->hasReplicaServers() );
+               $this->assertFalse( $lb->hasStreamingReplicaServers() );
+
+               $this->assertTrue( $lb->haveIndex( 0 ) );
+               $this->assertFalse( $lb->haveIndex( 1 ) );
+               $this->assertFalse( $lb->isNonZeroLoad( 0 ) );
+               $this->assertFalse( $lb->isNonZeroLoad( 1 ) );
+
                $ld = DatabaseDomain::newFromId( $lb->getLocalDomainID() );
                $this->assertEquals( $wgDBname, $ld->getDatabase(), 'local domain DB set' );
                $this->assertEquals( $this->dbPrefix(), $ld->getTablePrefix(), 'local domain prefix set' );
@@ -108,6 +120,17 @@ class LoadBalancerTest extends MediaWikiTestCase {
                $lb->closeAll();
        }
 
+       /**
+        * @covers \Wikimedia\Rdbms\LoadBalancer::getConnection()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::getReaderIndex()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::getWriterIndex()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::haveIndex()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::isNonZeroLoad()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::getServerName()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::getServerInfo()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::getServerType()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::getServerAttributes()
+        */
        public function testWithReplica() {
                global $wgDBserver;
 
@@ -118,6 +141,18 @@ class LoadBalancerTest extends MediaWikiTestCase {
                $this->assertTrue( $lb->hasReplicaServers() );
                $this->assertTrue( $lb->hasStreamingReplicaServers() );
 
+               $this->assertTrue( $lb->haveIndex( 0 ) );
+               $this->assertTrue( $lb->haveIndex( 1 ) );
+               $this->assertFalse( $lb->isNonZeroLoad( 0 ) );
+               $this->assertTrue( $lb->isNonZeroLoad( 1 ) );
+
+               for ( $i = 0; $i < $lb->getServerCount(); ++$i ) {
+                       $this->assertType( 'string', $lb->getServerName( $i ) );
+                       $this->assertType( 'array', $lb->getServerInfo( $i ) );
+                       $this->assertType( 'string', $lb->getServerType( $i ) );
+                       $this->assertType( 'array', $lb->getServerAttributes( $i ) );
+               }
+
                $dbw = $lb->getConnection( DB_MASTER );
                $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
                $this->assertEquals(
@@ -136,6 +171,7 @@ class LoadBalancerTest extends MediaWikiTestCase {
                        'cluster master set' );
                $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on replica" );
                $this->assertWriteForbidden( $dbr );
+               $this->assertEquals( $dbr->getLBInfo( 'serverIndex' ), $lb->getReaderIndex() );
 
                if ( !$lb->getServerAttributes( $lb->getWriterIndex() )[$dbw::ATTR_DB_LEVEL_LOCKING] ) {
                        $dbwAuto = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTOCOMMIT );
@@ -389,8 +425,10 @@ class LoadBalancerTest extends MediaWikiTestCase {
        }
 
        /**
-        * @covers LoadBalancer::openConnection()
-        * @covers LoadBalancer::getAnyOpenConnection()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::getConnection()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::openConnection()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::getAnyOpenConnection()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::getWriterIndex()
         */
        function testOpenConnection() {
                $lb = $this->newSingleServerLocalLoadBalancer();
@@ -436,6 +474,18 @@ class LoadBalancerTest extends MediaWikiTestCase {
                $lb->closeAll();
        }
 
+       /**
+        * @covers \Wikimedia\Rdbms\LoadBalancer::openConnection()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::getWriterIndex()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::forEachOpenMasterConnection()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::setTransactionListener()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::beginMasterChanges()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::finalizeMasterChanges()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::approveMasterChanges()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::commitMasterChanges()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::runMasterTransactionIdleCallbacks()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::runMasterTransactionListenerCallbacks()
+        */
        public function testTransactionCallbackChains() {
                global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
 
@@ -523,6 +573,10 @@ class LoadBalancerTest extends MediaWikiTestCase {
                $conn2->close();
        }
 
+       /**
+        * @covers \Wikimedia\Rdbms\LoadBalancer::getConnectionRef
+        * @covers \Wikimedia\Rdbms\LoadBalancer::getConnection()
+        */
        public function testDBConnRefReadsMasterAndReplicaRoles() {
                $lb = $this->newSingleServerLocalLoadBalancer();
 
@@ -547,6 +601,7 @@ class LoadBalancerTest extends MediaWikiTestCase {
        }
 
        /**
+        * @covers \Wikimedia\Rdbms\LoadBalancer::getConnectionRef
         * @expectedException \Wikimedia\Rdbms\DBReadOnlyRoleError
         */
        public function testDBConnRefWritesReplicaRole() {
@@ -558,6 +613,7 @@ class LoadBalancerTest extends MediaWikiTestCase {
        }
 
        /**
+        * @covers \Wikimedia\Rdbms\LoadBalancer::getConnectionRef
         * @expectedException \Wikimedia\Rdbms\DBReadOnlyRoleError
         */
        public function testDBConnRefWritesReplicaRoleIndex() {
@@ -569,6 +625,7 @@ class LoadBalancerTest extends MediaWikiTestCase {
        }
 
        /**
+        * @covers \Wikimedia\Rdbms\LoadBalancer::getConnectionRef
         * @expectedException \Wikimedia\Rdbms\DBReadOnlyRoleError
         */
        public function testDBConnRefWritesReplicaRoleInsert() {
@@ -579,6 +636,10 @@ class LoadBalancerTest extends MediaWikiTestCase {
                $rConn->insert( 'test', [ 't' => 1 ], __METHOD__ );
        }
 
+       /**
+        * @covers \Wikimedia\Rdbms\LoadBalancer::getConnection()
+        * @covers \Wikimedia\Rdbms\LoadBalancer::getMaintenanceConnectionRef()
+        */
        public function testQueryGroupIndex() {
                $lb = $this->newMultiServerLocalLoadBalancer( [ 'defaultGroup' => false ] );
                /** @var LoadBalancer $lbWrapper */
@@ -599,9 +660,13 @@ class LoadBalancerTest extends MediaWikiTestCase {
 
                $rRC = $lb->getConnectionRef( DB_REPLICA, [ 'recentchanges' ] );
                $rWL = $lb->getConnectionRef( DB_REPLICA, [ 'watchlist' ] );
+               $rRCMaint = $lb->getMaintenanceConnectionRef( DB_REPLICA, [ 'recentchanges' ] );
+               $rWLMaint = $lb->getMaintenanceConnectionRef( DB_REPLICA, [ 'watchlist' ] );
 
                $this->assertEquals( 3, $rRC->getLBInfo( 'serverIndex' ) );
                $this->assertEquals( 3, $rWL->getLBInfo( 'serverIndex' ) );
+               $this->assertEquals( 3, $rRCMaint->getLBInfo( 'serverIndex' ) );
+               $this->assertEquals( 3, $rWLMaint->getLBInfo( 'serverIndex' ) );
 
                $rLog = $lb->getConnectionRef( DB_REPLICA, [ 'logging', 'watchlist' ] );
                $logIndexPicked = $rLog->getLBInfo( 'serverIndex' );
@@ -628,4 +693,30 @@ class LoadBalancerTest extends MediaWikiTestCase {
                $rGeneric = $lb->getConnectionRef( DB_REPLICA );
                $this->assertEquals( $lb->getWriterIndex(), $rGeneric->getLBInfo( 'serverIndex' ) );
        }
+
+       /**
+        * @covers \Wikimedia\Rdbms\LoadBalancer::getLazyConnectionRef
+        */
+       public function testGetLazyConnectionRef() {
+               $lb = $this->newMultiServerLocalLoadBalancer();
+
+               $rMaster = $lb->getLazyConnectionRef( DB_MASTER );
+               $rReplica = $lb->getLazyConnectionRef( 1 );
+               $this->assertFalse( $lb->getAnyOpenConnection( 0 ) );
+               $this->assertFalse( $lb->getAnyOpenConnection( 1 ) );
+
+               $rMaster->getType();
+               $rReplica->getType();
+               $rMaster->getDomainID();
+               $rReplica->getDomainID();
+               $this->assertFalse( $lb->getAnyOpenConnection( 0 ) );
+               $this->assertFalse( $lb->getAnyOpenConnection( 1 ) );
+
+               $rMaster->query( "SELECT 1", __METHOD__ );
+               $this->assertNotFalse( $lb->getAnyOpenConnection( 0 ) );
+
+               $rReplica->query( "SELECT 1", __METHOD__ );
+               $this->assertNotFalse( $lb->getAnyOpenConnection( 0 ) );
+               $this->assertNotFalse( $lb->getAnyOpenConnection( 1 ) );
+       }
 }
index 017d745..593dd45 100644 (file)
@@ -47,18 +47,26 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase {
         * @param int $ttl
         */
        public function testSetAndGet( $value, $ttl ) {
+               $cache = $this->cache;
+
                $curTTL = null;
                $asOf = null;
-               $key = $this->cache->makeKey( 'x', wfRandomString() );
+               $key = $cache->makeKey( 'x', wfRandomString() );
 
-               $this->cache->get( $key, $curTTL, [], $asOf );
+               $cache->get( $key, $curTTL, [], $asOf );
                $this->assertNull( $curTTL, "Current TTL is null" );
                $this->assertNull( $asOf, "Current as-of-time is infinite" );
 
                $t = microtime( true );
-               $this->cache->set( $key, $value, $ttl );
 
-               $this->assertEquals( $value, $this->cache->get( $key, $curTTL, [], $asOf ) );
+               $cache->set( $key, $value, $cache::TTL_UNCACHEABLE );
+               $cache->get( $key, $curTTL, [], $asOf );
+               $this->assertNull( $curTTL, "Current TTL is null (TTL_UNCACHEABLE)" );
+               $this->assertNull( $asOf, "Current as-of-time is infinite (TTL_UNCACHEABLE)" );
+
+               $cache->set( $key, $value, $ttl );
+
+               $this->assertEquals( $value, $cache->get( $key, $curTTL, [], $asOf ) );
                if ( is_infinite( $ttl ) || $ttl == 0 ) {
                        $this->assertTrue( is_infinite( $curTTL ), "Current TTL is infinite" );
                } else {
@@ -157,7 +165,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase {
                $this->assertEquals( 6, $hit, "New values cached" );
 
                foreach ( $keys as $i => $key ) {
-                       // Should evict from process cache
+                       // Should not evict from process cache
                        $this->cache->delete( $key );
                        $mockWallClock += 0.001; // cached values will be newer than tombstone
                        // Get into cache (specific process cache group)
@@ -192,7 +200,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase {
        /**
         * @dataProvider getWithSetCallback_provider
         * @covers WANObjectCache::getWithSetCallback()
-        * @covers WANObjectCache::doGetWithSetCallback()
+        * @covers WANObjectCache::fetchOrRegenerate()
         * @param array $extOpts
         * @param bool $versioned
         */
@@ -268,11 +276,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase {
 
                $curTTL = null;
                $v = $cache->get( $key, $curTTL, [ $cKey1, $cKey2 ] );
-               if ( $versioned ) {
-                       $this->assertEquals( $value, $v[$cache::VFLD_DATA], "Value returned" );
-               } else {
-                       $this->assertEquals( $value, $v, "Value returned" );
-               }
+               $this->assertEquals( $value, $v, "Value returned" );
                $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
 
                $wasSet = 0;
@@ -378,7 +382,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase {
        /**
         * @dataProvider getWithSetCallback_provider
         * @covers WANObjectCache::getWithSetCallback()
-        * @covers WANObjectCache::doGetWithSetCallback()
+        * @covers WANObjectCache::fetchOrRegenerate()
         * @param array $extOpts
         * @param bool $versioned
         */
@@ -544,15 +548,6 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase {
                $this->assertEquals( 2, $wasSet, "Value re-calculated" );
        }
 
-       /**
-        * @covers WANObjectCache::getWithSetCallback()
-        * @covers WANObjectCache::doGetWithSetCallback()
-        */
-       public function testGetWithSetCallback_invalidCallback() {
-               $this->setExpectedException( InvalidArgumentException::class );
-               $this->cache->getWithSetCallback( 'key', 30, 'invalid callback' );
-       }
-
        /**
         * @dataProvider getMultiWithSetCallback_provider
         * @covers WANObjectCache::getMultiWithSetCallback
@@ -606,15 +601,16 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase {
                $value = "@efef$";
                $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
                $v = $cache->getMultiWithSetCallback(
-                       $keyedIds, 30, $genFunc, [ 'lowTTL' => 0, 'lockTSE' => 5, ] + $extOpts );
+                       $keyedIds, 30, $genFunc, [ 'lowTTL' => 0, 'lockTSE' => 5 ] + $extOpts );
                $this->assertEquals( $value, $v[$keyB], "Value returned" );
                $this->assertEquals( 1, $wasSet, "Value regenerated" );
-               $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed yet in process cache" );
+               $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in warmup cache" );
+
                $v = $cache->getMultiWithSetCallback(
-                       $keyedIds, 30, $genFunc, [ 'lowTTL' => 0, 'lockTSE' => 5, ] + $extOpts );
+                       $keyedIds, 30, $genFunc, [ 'lowTTL' => 0, 'lockTSE' => 5 ] + $extOpts );
                $this->assertEquals( $value, $v[$keyB], "Value returned" );
                $this->assertEquals( 1, $wasSet, "Value not regenerated" );
-               $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in process cache" );
+               $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in warmup cache" );
 
                $mockWallClock += 1;
 
@@ -649,11 +645,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase {
 
                $curTTL = null;
                $v = $cache->get( $keyC, $curTTL, [ $cKey1, $cKey2 ] );
-               if ( $versioned ) {
-                       $this->assertEquals( $value, $v[$cache::VFLD_DATA], "Value returned" );
-               } else {
-                       $this->assertEquals( $value, $v, "Value returned" );
-               }
+               $this->assertEquals( $value, $v, "Value returned" );
                $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
 
                $wasSet = 0;
@@ -780,12 +772,13 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase {
                        $keyedIds, 30, $genFunc, [ 'lowTTL' => 0 ] + $extOpts );
                $this->assertEquals( $value, $v[$keyB], "Value returned" );
                $this->assertEquals( 1, $wasSet, "Value regenerated" );
-               $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed yet in process cache" );
+               $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in warmup cache" );
+
                $v = $cache->getMultiWithUnionSetCallback(
                        $keyedIds, 30, $genFunc, [ 'lowTTL' => 0 ] + $extOpts );
                $this->assertEquals( $value, $v[$keyB], "Value returned" );
                $this->assertEquals( 1, $wasSet, "Value not regenerated" );
-               $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in process cache" );
+               $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in warmup cache" );
 
                $mockWallClock += 1;
 
@@ -818,11 +811,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase {
 
                $curTTL = null;
                $v = $cache->get( $keyC, $curTTL, [ $cKey1, $cKey2 ] );
-               if ( $versioned ) {
-                       $this->assertEquals( $value, $v[$cache::VFLD_DATA], "Value returned" );
-               } else {
-                       $this->assertEquals( $value, $v, "Value returned" );
-               }
+               $this->assertEquals( $value, $v, "Value returned" );
                $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
 
                $wasSet = 0;
@@ -880,7 +869,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase {
 
        /**
         * @covers WANObjectCache::getWithSetCallback()
-        * @covers WANObjectCache::doGetWithSetCallback()
+        * @covers WANObjectCache::fetchOrRegenerate()
         */
        public function testLockTSE() {
                $cache = $this->cache;
@@ -924,7 +913,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase {
 
        /**
         * @covers WANObjectCache::getWithSetCallback()
-        * @covers WANObjectCache::doGetWithSetCallback()
+        * @covers WANObjectCache::fetchOrRegenerate()
         * @covers WANObjectCache::set()
         */
        public function testLockTSESlow() {
@@ -1005,7 +994,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase {
 
        /**
         * @covers WANObjectCache::getWithSetCallback()
-        * @covers WANObjectCache::doGetWithSetCallback()
+        * @covers WANObjectCache::fetchOrRegenerate()
         */
        public function testBusyValue() {
                $cache = $this->cache;
@@ -1283,7 +1272,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase {
        /**
         * @dataProvider getWithSetCallback_versions_provider
         * @covers WANObjectCache::getWithSetCallback()
-        * @covers WANObjectCache::doGetWithSetCallback()
+        * @covers WANObjectCache::fetchOrRegenerate()
         * @param array $extOpts
         * @param bool $versioned
         */
@@ -1523,7 +1512,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase {
                $this->internalCache->set(
                        WANObjectCache::VALUE_KEY_PREFIX . $vKey1,
                        [
-                               WANObjectCache::FLD_VERSION => WANObjectCache::VERSION,
+                               WANObjectCache::FLD_FORMAT_VERSION => WANObjectCache::VERSION,
                                WANObjectCache::FLD_VALUE => $value,
                                WANObjectCache::FLD_TTL => 3600,
                                WANObjectCache::FLD_TIME => $goodTime
@@ -1532,7 +1521,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase {
                $this->internalCache->set(
                        WANObjectCache::VALUE_KEY_PREFIX . $vKey2,
                        [
-                               WANObjectCache::FLD_VERSION => WANObjectCache::VERSION,
+                               WANObjectCache::FLD_FORMAT_VERSION => WANObjectCache::VERSION,
                                WANObjectCache::FLD_VALUE => $value,
                                WANObjectCache::FLD_TTL => 3600,
                                WANObjectCache::FLD_TIME => $badTime
@@ -1569,7 +1558,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase {
                        ->setMethods( [ 'get', 'changeTTL' ] )->getMock();
                $backend->expects( $this->once() )->method( 'get' )
                        ->willReturn( [
-                               WANObjectCache::FLD_VERSION => WANObjectCache::VERSION,
+                               WANObjectCache::FLD_FORMAT_VERSION => WANObjectCache::VERSION,
                                WANObjectCache::FLD_VALUE => 'value',
                                WANObjectCache::FLD_TTL => 3600,
                                WANObjectCache::FLD_TIME => 300,