Aaron Schulz has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/321909

Change subject: [DNM] objectcache: detect default getWithSetCallback() set 
options
......................................................................

[DNM] objectcache: detect default getWithSetCallback() set options

This works by setting a callback to return the cache set
options. The callback will watch DB reads and create a
merged result from said usage.

This handles callers that are missing getCacheSetOptions().

Change-Id: I6a1edc2a52da3ff98b94c1bcf373a9b0355a2e9f
---
M includes/ServiceWiring.php
M includes/db/MWLBFactory.php
M includes/libs/objectcache/WANObjectCache.php
M includes/libs/rdbms/database/DBConnRef.php
M includes/libs/rdbms/database/Database.php
M includes/libs/rdbms/database/IDatabase.php
M includes/libs/rdbms/lbfactory/ILBFactory.php
M includes/libs/rdbms/lbfactory/LBFactory.php
M includes/libs/rdbms/loadbalancer/ILoadBalancer.php
M includes/libs/rdbms/loadbalancer/LoadBalancer.php
M includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php
M tests/phpunit/includes/db/LBFactoryTest.php
M tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php
13 files changed, 419 insertions(+), 42 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core 
refs/changes/09/321909/1

diff --git a/includes/ServiceWiring.php b/includes/ServiceWiring.php
index c2197a6..beefb33 100644
--- a/includes/ServiceWiring.php
+++ b/includes/ServiceWiring.php
@@ -52,7 +52,10 @@
                );
                $class = MWLBFactory::getLBFactoryClass( $lbConf );
 
-               return new $class( $lbConf );
+               $instance = new $class( $lbConf );
+               MWLBFactory::setCacheUsageCallbacks( $instance, $services );
+
+               return $instance;
        },
 
        'DBLoadBalancer' => function( MediaWikiServices $services ) {
diff --git a/includes/db/MWLBFactory.php b/includes/db/MWLBFactory.php
index 42ef685..5a5c46c 100644
--- a/includes/db/MWLBFactory.php
+++ b/includes/db/MWLBFactory.php
@@ -134,6 +134,25 @@
        }
 
        /**
+        * @param LBFactory $lbf New LBFactory instance that will be bound to 
$services
+        * @param MediaWikiServices $services
+        */
+       public static function setCacheUsageCallbacks( LBFactory $lbf, 
MediaWikiServices $services ) {
+               // Account for lag and pending updates by default in cache 
generator callbacks
+               $wCache = $services->getMainWANObjectCache();
+               $wCache->setDefaultCacheSetOptionCallbacks(
+                       function () use ( $lbf ) {
+                               return $lbf->declareUsageSectionStart();
+                       },
+                       function ( $id ) use ( $lbf ) {
+                               $info = $lbf->declareUsageSectionEnd( $id );
+
+                               return $info['cacheSetOptions'] ?: [];
+                       }
+               );
+       }
+
+       /**
         * Returns the LBFactory class to use and the load balancer 
configuration.
         *
         * @todo instead of this, use a ServiceContainer for managing the 
different implementations.
diff --git a/includes/libs/objectcache/WANObjectCache.php 
b/includes/libs/objectcache/WANObjectCache.php
index 8d3c6d9..b9753d3 100644
--- a/includes/libs/objectcache/WANObjectCache.php
+++ b/includes/libs/objectcache/WANObjectCache.php
@@ -93,6 +93,11 @@
        /** @var mixed[] Temporary warm-up cache */
        private $warmupCache = [];
 
+       /** @var callable Callback used in generating default options in 
getWithSetCallback() */
+       private $sowSetOptsCallback;
+       /** @var callable Callback used in generating default options in 
getWithSetCallback() */
+       private $reapSetOptsCallback;
+
        /** Max time expected to pass between delete() and DB commit finishing 
*/
        const MAX_COMMIT_DELAY = 3;
        /** Max replication+snapshot lag before applying TTL_LAGGED or 
disallowing set() */
@@ -181,6 +186,12 @@
                        ? $params['relayers']['purge']
                        : new EventRelayerNull( [] );
                $this->setLogger( isset( $params['logger'] ) ? 
$params['logger'] : new NullLogger() );
+               $this->sowSetOptsCallback = function () {
+                       return null; // no-op
+               };
+               $this->reapSetOptsCallback = function () {
+                       return []; // no-op
+               };
        }
 
        public function setLogger( LoggerInterface $logger ) {
@@ -1001,7 +1012,9 @@
                $setOpts = [];
                ++$this->callbackDepth;
                try {
+                       $tag = call_user_func( $this->sowSetOptsCallback );
                        $value = call_user_func_array( $callback, [ $cValue, 
&$ttl, &$setOpts, $asOf ] );
+                       $setOptDefaults = call_user_func( 
$this->reapSetOptsCallback, $tag );
                } finally {
                        --$this->callbackDepth;
                }
@@ -1026,6 +1039,8 @@
                        $setOpts['lockTSE'] = $lockTSE;
                        // Use best known "since" timestamp if not provided
                        $setOpts += [ 'since' => $preCallbackTime ];
+                       // Use default "lag" and "pending" values if not set
+                       $setOpts += $setOptDefaults;
                        // Update the cache; this will fail if the key is 
tombstoned
                        $this->set( $key, $value, $ttl, $setOpts );
                }
@@ -1253,6 +1268,22 @@
        }
 
        /**
+        * Set the callbacks that provide the fallback values for cache set 
options
+        *
+        * The $reap callback returns default values to use for the "lag", 
"since", and "pending"
+        * options used by WANObjectCache::set(). It takes the ID from $sow as 
the sole parameter.
+        * An empty array should be returned if there is no usage to base the 
return value on.
+        *
+        * @param callable $sow Function that starts recording and returns an ID
+        * @param callable $reap Function that takes an ID, stops recording, 
and returns the options
+        * @since 1.28
+        */
+       public function setDefaultCacheSetOptionCallbacks( callable $sow, 
callable $reap ) {
+               $this->sowSetOptsCallback = $sow;
+               $this->reapSetOptsCallback = $reap;
+       }
+
+       /**
         * Do the actual async bus purge of a key
         *
         * This must set the key to "PURGED:<UNIX timestamp>:<holdoff>"
diff --git a/includes/libs/rdbms/database/DBConnRef.php 
b/includes/libs/rdbms/database/DBConnRef.php
index 20198bf..90da154 100644
--- a/includes/libs/rdbms/database/DBConnRef.php
+++ b/includes/libs/rdbms/database/DBConnRef.php
@@ -591,6 +591,14 @@
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
+       public function declareUsageSectionStart( $id ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function declareUsageSectionEnd( $id ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
        /**
         * Clean up the connection when out of scope
         */
diff --git a/includes/libs/rdbms/database/Database.php 
b/includes/libs/rdbms/database/Database.php
index 3d35d76..0bbbb82 100644
--- a/includes/libs/rdbms/database/Database.php
+++ b/includes/libs/rdbms/database/Database.php
@@ -69,6 +69,8 @@
        protected $cliMode;
        /** @var string Agent name for query profiling */
        protected $agent;
+       /** @var array[] Map of (section ID => info map) for usage section IDs 
*/
+       protected $usageSectionInfo = [];
 
        /** @var BagOStuff APC cache */
        protected $srvCache;
@@ -918,16 +920,29 @@
        }
 
        private function doProfiledQuery( $sql, $commentedSql, $isWrite, $fname 
) {
+               // Update usage information for all active usage tracking 
sections
+               foreach ( $this->usageSectionInfo as $id => &$info ) {
+                       if ( $isWrite ) {
+                               ++$info['writeQueries'];
+                       } else {
+                               ++$info['readQueries'];
+                       }
+                       if ( $info['cacheSetOptions'] === null ) {
+                               $info['cacheSetOptions'] = 
self::getCacheSetOptions( $this );
+                       }
+               }
+               unset( $info ); // destroy any reference
+
                $isMaster = !is_null( $this->getLBInfo( 'master' ) );
-               # generalizeSQL() will probably cut down the query to reasonable
-               # logging size most of the time. The substr is really just a 
sanity check.
+               // generalizeSQL() will probably cut down the query to 
reasonable
+               // logging size most of the time. The substr is really just a 
sanity check.
                if ( $isMaster ) {
                        $queryProf = 'query-m: ' . substr( self::generalizeSQL( 
$sql ), 0, 255 );
                } else {
                        $queryProf = 'query: ' . substr( self::generalizeSQL( 
$sql ), 0, 255 );
                }
 
-               # Include query transaction state
+               // Include query transaction state
                $queryProf .= $this->mTrxShortId ? " 
[TRX#{$this->mTrxShortId}]" : "";
 
                $startTime = microtime( true );
@@ -3023,20 +3038,33 @@
         * @since 1.27
         */
        public static function getCacheSetOptions( IDatabase $db1 ) {
-               $res = [ 'lag' => 0, 'since' => INF, 'pending' => false ];
+               $opts = [ 'lag' => 0, 'since' => INF, 'pending' => false ];
                foreach ( func_get_args() as $db ) {
                        /** @var IDatabase $db */
-                       $status = $db->getSessionLagStatus();
-                       if ( $status['lag'] === false ) {
-                               $res['lag'] = false;
-                       } elseif ( $res['lag'] !== false ) {
-                               $res['lag'] = max( $res['lag'], $status['lag'] 
);
-                       }
-                       $res['since'] = min( $res['since'], $status['since'] );
-                       $res['pending'] = $res['pending'] ?: 
$db->writesPending();
+                       $dbOpts = $db->getSessionLagStatus();
+                       $dbOpts['pending'] = $db->writesPending();
+                       $opts = self::mergeCacheSetOptions( $opts, $dbOpts );
                }
 
-               return $res;
+               return $opts;
+       }
+
+       /**
+        * @param array $base Map in the format of getCacheSetOptions() results
+        * @param array $other Map in the format of getCacheSetOptions() results
+        * @return array Pessimistically merged result of $base/$other in the 
format of $base
+        * @since 1.28
+        */
+       public static function mergeCacheSetOptions( array $base, array $other 
) {
+               if ( $other['lag'] === false ) {
+                       $base['lag'] = false;
+               } elseif ( $base['lag'] !== false ) {
+                       $base['lag'] = max( $base['lag'], $other['lag'] );
+               }
+               $base['since'] = min( $base['since'], $other['since'] );
+               $base['pending'] = $base['pending'] ?: $other['pending'];
+
+               return $base;
        }
 
        public function getLag() {
@@ -3383,6 +3411,25 @@
                $this->tableAliases = $aliases;
        }
 
+       public function declareUsageSectionStart( $id ) {
+               $this->usageSectionInfo[$id] = [
+                       'readQueries' => 0,
+                       'writeQueries' => 0,
+                       'cacheSetOptions' => null
+               ];
+       }
+
+       public function declareUsageSectionEnd( $id ) {
+               if ( !isset( $this->usageSectionInfo[$id] ) ) {
+                       throw new InvalidArgumentException( "No section with ID 
'$id'" );
+               }
+
+               $info = $this->usageSectionInfo[$id];
+               unset( $this->usageSectionInfo[$id] );
+
+               return $info;
+       }
+
        /**
         * @return bool Whether a DB user is required to access the DB
         * @since 1.28
diff --git a/includes/libs/rdbms/database/IDatabase.php 
b/includes/libs/rdbms/database/IDatabase.php
index 48d76c4..761b6ed 100644
--- a/includes/libs/rdbms/database/IDatabase.php
+++ b/includes/libs/rdbms/database/IDatabase.php
@@ -1792,4 +1792,24 @@
         * @since 1.28
         */
        public function setTableAliases( array $aliases );
+
+       /**
+        * Mark the beginning of a new section to track database usage 
information for
+        *
+        * @param string|integer Section ID
+        */
+       public function declareUsageSectionStart( $id );
+
+       /**
+        * End a section started by declareUsageSectionStart() and return the 
information map
+        *
+        * The map includes information about activity during the section:
+        *   - readQueries: number of read queries issued.
+        *   - writeQueries: number of write queries issued.
+        *   - cacheSetOptions: result of getCacheSetOptions() before the first 
query.
+        *      This is null if no actual queries took place in the section 
interval.
+        * @param integer|string $id Section ID passed to 
declareUsageSectionStart() earlier
+        * @return array
+        */
+       public function declareUsageSectionEnd( $id );
 }
diff --git a/includes/libs/rdbms/lbfactory/ILBFactory.php 
b/includes/libs/rdbms/lbfactory/ILBFactory.php
index 5288c24..f0d3995 100644
--- a/includes/libs/rdbms/lbfactory/ILBFactory.php
+++ b/includes/libs/rdbms/lbfactory/ILBFactory.php
@@ -310,4 +310,32 @@
         *   - ChronologyProtection : cookie/header value specifying 
ChronologyProtector usage
         */
        public function setRequestInfo( array $info );
+
+       /**
+        * Mark the beginning of a new section to track database usage 
information for
+        *
+        * This returns an ID which can be passed to declareUsageSectionEnd() 
to indicate
+        * the end of the section. If $id is provided, the returned ID equals 
$id.
+        * @param string|integer Section ID to use instead of auto-generated ID 
[optional]
+        * @return string|integer
+        */
+       public function declareUsageSectionStart( $id = null );
+
+       /**
+        * End a section started by declareUsageSectionStart() and return the 
information map
+        *
+        * The map includes information about activity during the section:
+        *   - readQueries: number of read queries issued.
+        *   - writeQueries: number of write queries issued.
+        *   - cacheSetOptions: result of pessimistically merging the result of 
getCacheSetOptions()
+        *      on each DB handle before the first query of the respective 
handle. This is null if
+        *      no actual queries took place in the section interval.
+        *
+        * This can be called before cache value generation functions commence 
queries
+        * and then passed the caching storage layer to detect and avoid lag 
race conditions.
+        *
+        * @param integer|string $id Section ID passed to 
declareUsageSectionStart() earlier
+        * @return array
+        */
+       public function declareUsageSectionEnd( $id );
 }
diff --git a/includes/libs/rdbms/lbfactory/LBFactory.php 
b/includes/libs/rdbms/lbfactory/LBFactory.php
index 15a5c0d..70302a0 100644
--- a/includes/libs/rdbms/lbfactory/LBFactory.php
+++ b/includes/libs/rdbms/lbfactory/LBFactory.php
@@ -59,6 +59,9 @@
        /** @var array Web request information about the client */
        protected $requestInfo;
 
+       /** @var bool[] Map of (section ID => true) for usage section IDs */
+       protected $usageSections = [];
+
        /** @var mixed */
        protected $ticket;
        /** @var string|bool String if a requested DBO_TRX transaction round is 
active */
@@ -503,11 +506,16 @@
        }
 
        /**
+        * Method called whenever a new LoadBalancer is created
+        *
         * @param ILoadBalancer $lb
         */
        protected function initLoadBalancer( ILoadBalancer $lb ) {
                if ( $this->trxRoundId !== false ) {
                        $lb->beginMasterChanges( $this->trxRoundId ); // set 
DBO_TRX
+               }
+               foreach ( $this->usageSections as $id => $unused ) {
+                       $lb->declareUsageSectionStart( $id );
                }
        }
 
@@ -548,6 +556,40 @@
                $this->requestInfo = $info + $this->requestInfo;
        }
 
+       public function declareUsageSectionStart( $id = null ) {
+               static $nextId = 1;
+               if ( $id === null ) {
+                       $id = $nextId;
+                       ++$nextId;
+               }
+               // Handle existing load balancers
+               $this->forEachLB( function ( ILoadBalancer $lb ) use ( $id ) {
+                       $lb->declareUsageSectionStart( $id );
+               } );
+               // Remember to set this for new load balancers
+               $this->usageSections[$id] = true;
+
+               return $id;
+       }
+
+       public function declareUsageSectionEnd( $id ) {
+               $info = [ 'readQueries' => 0, 'writeQueries' => 0, 
'cacheSetOptions' => null ];
+               $this->forEachLB( function ( ILoadBalancer $lb ) use ( $id, 
&$info ) {
+                       $lbInfo = $lb->declareUsageSectionEnd( $id );
+                       $info['readQueries'] += $lbInfo['readQueries'];
+                       $info['writeQueries'] += $lbInfo['writeQueries'];
+                       $dbCacheOpts = $lbInfo['cacheSetOptions'];
+                       if ( $dbCacheOpts ) {
+                               $info['cacheSetOptions'] = 
$info['cacheSetOptions']
+                                       ? Database::mergeCacheSetOptions( 
$info['cacheSetOptions'], $dbCacheOpts )
+                                       : $dbCacheOpts;
+                       }
+               } );
+               unset( $this->usageSections[$id] );
+
+               return $info;
+       }
+
        /**
         * Make PHP ignore user aborts/disconnects until the returned
         * value leaves scope. This returns null and does nothing in CLI mode.
diff --git a/includes/libs/rdbms/loadbalancer/ILoadBalancer.php 
b/includes/libs/rdbms/loadbalancer/ILoadBalancer.php
index 8854479..65b18e7 100644
--- a/includes/libs/rdbms/loadbalancer/ILoadBalancer.php
+++ b/includes/libs/rdbms/loadbalancer/ILoadBalancer.php
@@ -549,4 +549,32 @@
         * @param array[] $aliases Map of (table => (dbname, schema, prefix) 
map)
         */
        public function setTableAliases( array $aliases );
+
+       /**
+        * Mark the beginning of a new section to track database usage 
information for
+        *
+        * This returns an ID which can be passed to declareUsageSectionEnd() 
to indicate
+        * the end of the section. If $id is provided, the returned ID equals 
$id.
+        * @param string|integer Section ID to use instead of auto-generated ID 
[optional]
+        * @return string|integer
+        */
+       public function declareUsageSectionStart( $id = null );
+
+       /**
+        * End a section started by declareUsageSectionStart() and return the 
information map
+        *
+        * The map includes information about activity during the section:
+        *   - readQueries: number of read queries issued.
+        *   - writeQueries: number of write queries issued.
+        *   - cacheSetOptions: result of pessimistically merging the result of 
getCacheSetOptions()
+        *      on each DB handle before the first query of the respective 
handle. This is null if
+        *      no actual queries took place in the section interval.
+        *
+        * This can be called before cache value generation functions commence 
queries
+        * and then passed the caching storage layer to detect and avoid lag 
race conditions.
+        *
+        * @param integer|string $id Section ID passed to 
declareUsageSectionStart() earlier
+        * @return array
+        */
+       public function declareUsageSectionEnd( $id );
 }
diff --git a/includes/libs/rdbms/loadbalancer/LoadBalancer.php 
b/includes/libs/rdbms/loadbalancer/LoadBalancer.php
index d42fed9..61359bc 100644
--- a/includes/libs/rdbms/loadbalancer/LoadBalancer.php
+++ b/includes/libs/rdbms/loadbalancer/LoadBalancer.php
@@ -94,9 +94,11 @@
        /** @var string Current server name */
        private $host;
        /** @var bool Whether this PHP instance is for a CLI script */
-       protected $cliMode;
+       private $cliMode;
        /** @var string Agent name for query profiling */
-       protected $agent;
+       private $agent;
+       /** @var bool[] Map of (section ID => true) for usage section IDs */
+       private $usageSections = [];
 
        /** @var callable Exception logger */
        private $errorLogger;
@@ -864,6 +866,10 @@
                        }
                }
 
+               foreach ( $this->usageSections as $id => $unused ) {
+                       $db->declareUsageSectionStart( $id );
+               }
+
                return $db;
        }
 
@@ -1522,6 +1528,40 @@
                } );
        }
 
+       public function declareUsageSectionStart( $id = null ) {
+               static $nextId = 1;
+               if ( $id === null ) {
+                       $id = $nextId;
+                       ++$nextId;
+               }
+               // Handle existing connections
+               $this->forEachOpenConnection( function ( IDatabase $db ) use ( 
$id ) {
+                       $db->declareUsageSectionStart( $id );
+               } );
+               // Remember to set this for new connections
+               $this->usageSections[$id] = true;
+
+               return $id;
+       }
+
+       public function declareUsageSectionEnd( $id ) {
+               $info = [ 'readQueries' => 0, 'writeQueries' => 0, 
'cacheSetOptions' => null ];
+               $this->forEachOpenConnection( function ( IDatabase $db ) use ( 
$id, &$info ) {
+                       $dbInfo = $db->declareUsageSectionEnd( $id );
+                       $info['readQueries'] += $dbInfo['readQueries'];
+                       $info['writeQueries'] += $dbInfo['writeQueries'];
+                       $dbCacheOpts = $dbInfo['cacheSetOptions'];
+                       if ( $dbCacheOpts ) {
+                               $info['cacheSetOptions'] = 
$info['cacheSetOptions']
+                                       ? Database::mergeCacheSetOptions( 
$info['cacheSetOptions'], $dbCacheOpts )
+                                       : $dbCacheOpts;
+                       }
+               } );
+               unset( $this->usageSections[$id] );
+
+               return $info;
+       }
+
        /**
         * Make PHP ignore user aborts/disconnects until the returned
         * value leaves scope. This returns null and does nothing in CLI mode.
diff --git a/includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php 
b/includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php
index 0a05202..707db0a 100644
--- a/includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php
+++ b/includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php
@@ -71,4 +71,17 @@
        protected function reallyOpenConnection( array $server, $dbNameOverride 
= false ) {
                return $this->db;
        }
+
+       public function forEachOpenConnection( $callback, array $params = [] ) {
+               $mergedParams = array_merge( [ $this->db ], $params );
+               call_user_func_array( $callback, $mergedParams );
+       }
+
+       public function forEachOpenMasterConnection( $callback, array $params = 
[] ) {
+               return $this->forEachOpenConnection( $callback, $params );
+       }
+
+       public function forEachOpenReplicaConnection( $callback, array $params 
= [] ) {
+               return $this->forEachOpenConnection( $callback, $params );
+       }
 }
diff --git a/tests/phpunit/includes/db/LBFactoryTest.php 
b/tests/phpunit/includes/db/LBFactoryTest.php
index d8773f8..13c5e1e 100644
--- a/tests/phpunit/includes/db/LBFactoryTest.php
+++ b/tests/phpunit/includes/db/LBFactoryTest.php
@@ -240,32 +240,6 @@
                $cp->shutdown();
        }
 
-       private function newLBFactoryMulti( array $baseOverride = [], array 
$serverOverride = [] ) {
-               global $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, 
$wgDBtype, $wgSQLiteDataDir;
-
-               return new LBFactoryMulti( $baseOverride + [
-                       'sectionsByDB' => [],
-                       'sectionLoads' => [
-                               'DEFAULT' => [
-                                       'test-db1' => 1,
-                               ],
-                       ],
-                       'serverTemplate' => $serverOverride + [
-                               'dbname' => $wgDBname,
-                               'user' => $wgDBuser,
-                               'password' => $wgDBpassword,
-                               'type' => $wgDBtype,
-                               'dbDirectory' => $wgSQLiteDataDir,
-                               'flags' => DBO_DEFAULT
-                       ],
-                       'hostsByName' => [
-                               'test-db1' => $wgDBserver,
-                       ],
-                       'loadMonitorClass' => 'LoadMonitorNull',
-                       'localDomain' => wfWikiID()
-               ] );
-       }
-
        public function testNiceDomains() {
                global $wgDBname, $wgDBtype;
 
@@ -414,6 +388,99 @@
                $factory->destroy();
        }
 
+       /**
+        * @covers LBFactory::declareUsageSectionStart()
+        * @covers LBFactory::declareUsageSectionEnd()
+        * @covers LoadBalancer::declareUsageSectionStart()
+        * @covers LoadBalancer::declareUsageSectionEnd()
+        */
+       public function testUsageInfo() {
+               $wallTime = microtime( true );
+
+               $mockDB = $this->getMockBuilder( 'DatabaseMysql' )
+                       ->disableOriginalConstructor()
+                       ->setMethods( [
+                               'doQuery',
+                               'affectedRows',
+                               'getLag',
+                               'assertOpen',
+                               'getSessionLagStatus',
+                               'getApproximateLagStatus'
+                       ] )
+                       ->getMock();
+               $mockDB->method( 'doQuery' )->willReturn( new 
FakeResultWrapper( [] ) );
+               $mockDB->method( 'affectedRows' )->willReturn( 0 );
+               $mockDB->method( 'getLag' )->willReturn( 3 );
+               $mockDB->method( 'getSessionLagStatus' )->willReturn( [
+                       'lag' => 3, 'since' => $wallTime
+               ] );
+               $mockDB->method( 'getApproximateLagStatus' )->willReturn( [
+                       'lag' => 3, 'since' => $wallTime
+               ] );
+               $mockDBProbe = TestingAccessWrapper::newFromObject( $mockDB );
+               $mockDBProbe->profiler = new ProfilerStub( [] );
+               $mockDBProbe->trxProfiler = new TransactionProfiler();
+               $mockDBProbe->connLogger = new \Psr\Log\NullLogger();
+               $mockDBProbe->queryLogger = new \Psr\Log\NullLogger();
+               $lbFactory = new LBFactorySingle( [
+                       'connection' => $mockDB
+               ] );
+               $mockDB->setLBInfo( 'replica', true );
+
+               $id = $lbFactory->declareUsageSectionStart( 'test' );
+               $mockDB->query( "SELECT 1" );
+               $mockDB->query( "SELECT 1" );
+               $mockDB->query( "SELECT 1" );
+               $info = $lbFactory->declareUsageSectionEnd( $id );
+
+               $this->assertEquals( 3, $info['readQueries'] );
+               $this->assertEquals( 0, $info['writeQueries'] );
+               $this->assertEquals( false, $info['cacheSetOptions']['pending'] 
);
+               $this->assertEquals( 3, $info['cacheSetOptions']['lag'] );
+               $this->assertGreaterThanOrEqual( $wallTime - 10, 
$info['cacheSetOptions']['since'] );
+               $this->assertLessThan( $wallTime + 10, 
$info['cacheSetOptions']['since'] );
+
+               $mockDB->begin();
+               $mockDB->query( "UPDATE x SET y=1" );
+               $id = $lbFactory->declareUsageSectionStart( 'k' );
+               $mockDB->query( "UPDATE x SET y=2" );
+               $mockDB->commit();
+               $info = $lbFactory->declareUsageSectionEnd( $id );
+
+               $this->assertEquals( 2, $info['readQueries'] ); // +1 for ping()
+               $this->assertEquals( 1, $info['writeQueries'] );
+               $this->assertEquals( true, $info['cacheSetOptions']['pending'] 
);
+               $this->assertEquals( 3, $info['cacheSetOptions']['lag'] );
+               $this->assertGreaterThanOrEqual( $wallTime - 10, 
$info['cacheSetOptions']['since'] );
+               $this->assertLessThan( $wallTime + 10, 
$info['cacheSetOptions']['since'] );
+       }
+
+       private function newLBFactoryMulti( array $baseOverride = [], array 
$serverOverride = [] ) {
+               global $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, 
$wgDBtype, $wgSQLiteDataDir;
+
+               return new LBFactoryMulti( $baseOverride + [
+                               'sectionsByDB' => [],
+                               'sectionLoads' => [
+                                       'DEFAULT' => [
+                                               'test-db1' => 1,
+                                       ],
+                               ],
+                               'serverTemplate' => $serverOverride + [
+                                               'dbname' => $wgDBname,
+                                               'user' => $wgDBuser,
+                                               'password' => $wgDBpassword,
+                                               'type' => $wgDBtype,
+                                               'dbDirectory' => 
$wgSQLiteDataDir,
+                                               'flags' => DBO_DEFAULT
+                                       ],
+                               'hostsByName' => [
+                                       'test-db1' => $wgDBserver,
+                               ],
+                               'loadMonitorClass' => 'LoadMonitorNull',
+                               'localDomain' => wfWikiID()
+                       ] );
+       }
+
        private function quoteTable( Database $db, $table ) {
                if ( $db->getType() === 'sqlite' ) {
                        return $table;
diff --git a/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php 
b/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php
index aa46c96..e456328 100644
--- a/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php
+++ b/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php
@@ -960,4 +960,35 @@
                        [ null, 86400, 800, .2, 800 ]
                ];
        }
+
+       public function testDefaultCacheOptions() {
+               $wCache = clone $this->cache;
+               $key = wfRandomString();
+
+               $called = false;
+               $infos = [];
+               $wCache->setDefaultCacheSetOptionCallbacks(
+                       function () use ( &$infos ) {
+                               $infos['sometag'] = [ 'since' => 1999, 'lag' => 
4, 'pending' => false ];
+
+                               return 'sometag';
+                       },
+                       function ( $tag ) use ( &$infos, &$called ) {
+                               $res = $infos[$tag];
+                               unset( $infos[$tag] );
+                               $called = true;
+
+                               return $res;
+                       }
+               );
+
+               $callback = function () {
+                       return 42;
+               };
+
+               $value = $wCache->getWithSetCallback( $key, 5, $callback );
+
+               $this->assertEquals( 42, $value, 'Correct value' );
+               $this->assertTrue( $called, 'Options callback ran' );
+       }
 }

-- 
To view, visit https://gerrit.wikimedia.org/r/321909
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I6a1edc2a52da3ff98b94c1bcf373a9b0355a2e9f
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Aaron Schulz <asch...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to