4 * Holds tests for LoadBalancer MediaWiki class.
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * http://www.gnu.org/copyleft/gpl.html
24 use Wikimedia\Rdbms\DBError
;
25 use Wikimedia\Rdbms\DatabaseDomain
;
26 use Wikimedia\Rdbms\Database
;
27 use Wikimedia\Rdbms\LoadBalancer
;
28 use Wikimedia\Rdbms\LoadMonitorNull
;
29 use Wikimedia\TestingAccessWrapper
;
34 * @covers \Wikimedia\Rdbms\LoadBalancer
36 class LoadBalancerTest
extends MediaWikiTestCase
{
37 private function makeServerConfig( $flags = DBO_DEFAULT
) {
38 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
41 'host' => $wgDBserver,
42 'dbname' => $wgDBname,
43 'tablePrefix' => $this->dbPrefix(),
45 'password' => $wgDBpassword,
47 'dbDirectory' => $wgSQLiteDataDir,
54 * @covers \Wikimedia\Rdbms\LoadBalancer::getConnection()
55 * @covers \Wikimedia\Rdbms\LoadBalancer::getLocalDomainID()
56 * @covers \Wikimedia\Rdbms\LoadBalancer::resolveDomainID()
57 * @covers \Wikimedia\Rdbms\LoadBalancer::haveIndex()
58 * @covers \Wikimedia\Rdbms\LoadBalancer::isNonZeroLoad()
60 public function testWithoutReplica() {
64 $lb = new LoadBalancer( [
65 // Simulate web request with DBO_TRX
66 'servers' => [ $this->makeServerConfig( DBO_TRX
) ],
67 'queryLogger' => MediaWiki\Logger\LoggerFactory
::getInstance( 'DBQuery' ),
68 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() ),
69 'chronologyCallback' => function () use ( &$called ) {
74 $this->assertEquals( 1, $lb->getServerCount() );
75 $this->assertFalse( $lb->hasReplicaServers() );
76 $this->assertFalse( $lb->hasStreamingReplicaServers() );
78 $this->assertTrue( $lb->haveIndex( 0 ) );
79 $this->assertFalse( $lb->haveIndex( 1 ) );
80 $this->assertFalse( $lb->isNonZeroLoad( 0 ) );
81 $this->assertFalse( $lb->isNonZeroLoad( 1 ) );
83 $ld = DatabaseDomain
::newFromId( $lb->getLocalDomainID() );
84 $this->assertEquals( $wgDBname, $ld->getDatabase(), 'local domain DB set' );
85 $this->assertEquals( $this->dbPrefix(), $ld->getTablePrefix(), 'local domain prefix set' );
86 $this->assertSame( 'my_test_wiki', $lb->resolveDomainID( 'my_test_wiki' ) );
87 $this->assertSame( $ld->getId(), $lb->resolveDomainID( false ) );
88 $this->assertSame( $ld->getId(), $lb->resolveDomainID( $ld ) );
89 $this->assertFalse( $called );
91 $dbw = $lb->getConnection( DB_MASTER
);
92 $this->assertTrue( $called );
93 $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
94 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX
), "DBO_TRX set on master" );
95 $this->assertWriteAllowed( $dbw );
97 $dbr = $lb->getConnection( DB_REPLICA
);
98 $this->assertTrue( $dbr->getLBInfo( 'master' ), 'DB_REPLICA also gets the master' );
99 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX
), "DBO_TRX set on replica" );
101 if ( !$lb->getServerAttributes( $lb->getWriterIndex() )[$dbw::ATTR_DB_LEVEL_LOCKING
] ) {
102 $dbwAuto = $lb->getConnection( DB_MASTER
, [], false, $lb::CONN_TRX_AUTOCOMMIT
);
104 $dbwAuto->getFlag( $dbw::DBO_TRX
), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
105 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX
), "DBO_TRX still set on master" );
106 $this->assertNotEquals(
107 $dbw, $dbwAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
109 $dbrAuto = $lb->getConnection( DB_REPLICA
, [], false, $lb::CONN_TRX_AUTOCOMMIT
);
111 $dbrAuto->getFlag( $dbw::DBO_TRX
), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
112 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX
), "DBO_TRX still set on replica" );
113 $this->assertNotEquals(
114 $dbr, $dbrAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
116 $dbwAuto2 = $lb->getConnection( DB_MASTER
, [], false, $lb::CONN_TRX_AUTOCOMMIT
);
117 $this->assertEquals( $dbwAuto2, $dbwAuto, "CONN_TRX_AUTOCOMMIT reuses connections" );
124 * @covers \Wikimedia\Rdbms\LoadBalancer::getConnection()
125 * @covers \Wikimedia\Rdbms\LoadBalancer::getReaderIndex()
126 * @covers \Wikimedia\Rdbms\LoadBalancer::getWriterIndex()
127 * @covers \Wikimedia\Rdbms\LoadBalancer::haveIndex()
128 * @covers \Wikimedia\Rdbms\LoadBalancer::isNonZeroLoad()
129 * @covers \Wikimedia\Rdbms\LoadBalancer::getServerName()
130 * @covers \Wikimedia\Rdbms\LoadBalancer::getServerInfo()
131 * @covers \Wikimedia\Rdbms\LoadBalancer::getServerType()
132 * @covers \Wikimedia\Rdbms\LoadBalancer::getServerAttributes()
134 public function testWithReplica() {
137 // Simulate web request with DBO_TRX
138 $lb = $this->newMultiServerLocalLoadBalancer( [], [ 'flags' => DBO_TRX
] );
140 $this->assertEquals( 8, $lb->getServerCount() );
141 $this->assertTrue( $lb->hasReplicaServers() );
142 $this->assertTrue( $lb->hasStreamingReplicaServers() );
144 $this->assertTrue( $lb->haveIndex( 0 ) );
145 $this->assertTrue( $lb->haveIndex( 1 ) );
146 $this->assertFalse( $lb->isNonZeroLoad( 0 ) );
147 $this->assertTrue( $lb->isNonZeroLoad( 1 ) );
149 for ( $i = 0; $i < $lb->getServerCount(); ++
$i ) {
150 $this->assertType( 'string', $lb->getServerName( $i ) );
151 $this->assertType( 'array', $lb->getServerInfo( $i ) );
152 $this->assertType( 'string', $lb->getServerType( $i ) );
153 $this->assertType( 'array', $lb->getServerAttributes( $i ) );
156 $dbw = $lb->getConnection( DB_MASTER
);
157 $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
159 ( $wgDBserver != '' ) ?
$wgDBserver : 'localhost',
160 $dbw->getLBInfo( 'clusterMasterHost' ),
161 'cluster master set' );
162 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX
), "DBO_TRX set on master" );
163 $this->assertWriteAllowed( $dbw );
165 $dbr = $lb->getConnection( DB_REPLICA
);
166 $this->assertTrue( $dbr->getLBInfo( 'replica' ), 'replica shows as replica' );
167 $this->assertTrue( $dbr->isReadOnly(), 'replica shows as replica' );
169 ( $wgDBserver != '' ) ?
$wgDBserver : 'localhost',
170 $dbr->getLBInfo( 'clusterMasterHost' ),
171 'cluster master set' );
172 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX
), "DBO_TRX set on replica" );
173 $this->assertWriteForbidden( $dbr );
174 $this->assertEquals( $dbr->getLBInfo( 'serverIndex' ), $lb->getReaderIndex() );
176 if ( !$lb->getServerAttributes( $lb->getWriterIndex() )[$dbw::ATTR_DB_LEVEL_LOCKING
] ) {
177 $dbwAuto = $lb->getConnection( DB_MASTER
, [], false, $lb::CONN_TRX_AUTOCOMMIT
);
179 $dbwAuto->getFlag( $dbw::DBO_TRX
), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
180 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX
), "DBO_TRX still set on master" );
181 $this->assertNotEquals(
182 $dbw, $dbwAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
184 $dbrAuto = $lb->getConnection( DB_REPLICA
, [], false, $lb::CONN_TRX_AUTOCOMMIT
);
186 $dbrAuto->getFlag( $dbw::DBO_TRX
), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
187 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX
), "DBO_TRX still set on replica" );
188 $this->assertNotEquals(
189 $dbr, $dbrAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
191 $dbwAuto2 = $lb->getConnection( DB_MASTER
, [], false, $lb::CONN_TRX_AUTOCOMMIT
);
192 $this->assertEquals( $dbwAuto2, $dbwAuto, "CONN_TRX_AUTOCOMMIT reuses connections" );
198 private function newSingleServerLocalLoadBalancer() {
201 return new LoadBalancer( [
202 'servers' => [ $this->makeServerConfig() ],
203 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() )
207 private function newMultiServerLocalLoadBalancer(
208 $lbExtra = [], $srvExtra = [], $masterOnly = false
210 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
215 'host' => $wgDBserver,
216 'dbname' => $wgDBname,
217 'tablePrefix' => $this->dbPrefix(),
219 'password' => $wgDBpassword,
221 'dbDirectory' => $wgSQLiteDataDir,
222 'load' => $masterOnly ?
100 : 0,
226 'host' => $wgDBserver,
227 'dbname' => $wgDBname,
228 'tablePrefix' => $this->dbPrefix(),
230 'password' => $wgDBpassword,
232 'dbDirectory' => $wgSQLiteDataDir,
233 'load' => $masterOnly ?
0 : 100,
236 'host' => $wgDBserver,
237 'dbname' => $wgDBname,
238 'tablePrefix' => $this->dbPrefix(),
240 'password' => $wgDBpassword,
242 'dbDirectory' => $wgSQLiteDataDir,
243 'load' => $masterOnly ?
0 : 100,
247 'host' => $wgDBserver,
248 'dbname' => $wgDBname,
249 'tablePrefix' => $this->dbPrefix(),
251 'password' => $wgDBpassword,
253 'dbDirectory' => $wgSQLiteDataDir,
256 'recentchanges' => 100,
260 // Logging replica DBs
262 'host' => $wgDBserver,
263 'dbname' => $wgDBname,
264 'tablePrefix' => $this->dbPrefix(),
266 'password' => $wgDBpassword,
268 'dbDirectory' => $wgSQLiteDataDir,
275 'host' => $wgDBserver,
276 'dbname' => $wgDBname,
277 'tablePrefix' => $this->dbPrefix(),
279 'password' => $wgDBpassword,
281 'dbDirectory' => $wgSQLiteDataDir,
287 // Maintenance query replica DBs
289 'host' => $wgDBserver,
290 'dbname' => $wgDBname,
291 'tablePrefix' => $this->dbPrefix(),
293 'password' => $wgDBpassword,
295 'dbDirectory' => $wgSQLiteDataDir,
301 // Replica DB that only has a copy of some static tables
303 'host' => $wgDBserver,
304 'dbname' => $wgDBname,
305 'tablePrefix' => $this->dbPrefix(),
307 'password' => $wgDBpassword,
309 'dbDirectory' => $wgSQLiteDataDir,
318 return new LoadBalancer( $lbExtra +
[
319 'servers' => $servers,
320 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() ),
321 'queryLogger' => MediaWiki\Logger\LoggerFactory
::getInstance( 'DBQuery' ),
322 'loadMonitorClass' => LoadMonitorNull
::class
326 private function assertWriteForbidden( Database
$db ) {
328 $db->delete( 'some_table', [ 'id' => 57634126 ], __METHOD__
);
329 $this->fail( 'Write operation should have failed!' );
330 } catch ( DBError
$ex ) {
331 // check that the exception message contains "Write operation"
332 $constraint = new PHPUnit_Framework_Constraint_StringContains( 'Write operation' );
334 if ( !$constraint->evaluate( $ex->getMessage(), '', true ) ) {
335 // re-throw original error, to preserve stack trace
341 private function assertWriteAllowed( Database
$db ) {
342 $table = $db->tableName( 'some_table' );
343 // Trigger a transaction so that rollback() will remove all the tables.
344 // Don't do this for MySQL/Oracle as they auto-commit transactions for DDL
345 // statements such as CREATE TABLE.
346 $useAtomicSection = in_array( $db->getType(), [ 'sqlite', 'postgres', 'mssql' ], true );
348 $db->dropTable( 'some_table' ); // clear for sanity
349 $this->assertNotEquals( $db::STATUS_TRX_ERROR
, $db->trxStatus() );
351 if ( $useAtomicSection ) {
352 $db->startAtomic( __METHOD__
);
354 // Use only basic SQL and trivial types for these queries for compatibility
355 $this->assertNotSame(
357 $db->query( "CREATE TABLE $table (id INT, time INT)", __METHOD__
),
360 $this->assertNotEquals( $db::STATUS_TRX_ERROR
, $db->trxStatus() );
361 $this->assertNotSame(
363 $db->query( "DELETE FROM $table WHERE id=57634126", __METHOD__
),
366 $this->assertNotEquals( $db::STATUS_TRX_ERROR
, $db->trxStatus() );
368 if ( !$useAtomicSection ) {
369 // Drop the table to clean up, ignoring any error.
370 $db->dropTable( 'some_table' );
372 // Rollback the atomic section for sqlite's benefit.
373 $db->rollback( __METHOD__
, 'flush' );
374 $this->assertNotEquals( $db::STATUS_TRX_ERROR
, $db->trxStatus() );
378 public function testServerAttributes() {
381 'dbname' => 'my_unittest_wiki',
382 'tablePrefix' => 'unittest_',
384 'dbDirectory' => "some_directory",
389 $lb = new LoadBalancer( [
390 'servers' => $servers,
391 'localDomain' => new DatabaseDomain( 'my_unittest_wiki', null, 'unittest_' ),
392 'loadMonitorClass' => LoadMonitorNull
::class
395 $this->assertTrue( $lb->getServerAttributes( 0 )[Database
::ATTR_DB_LEVEL_LOCKING
] );
400 'user' => 'wikiuser',
401 'password' => 'none',
402 'dbname' => 'my_unittest_wiki',
403 'tablePrefix' => 'unittest_',
407 [ // emulated replica
409 'user' => 'wikiuser',
410 'password' => 'none',
411 'dbname' => 'my_unittest_wiki',
412 'tablePrefix' => 'unittest_',
418 $lb = new LoadBalancer( [
419 'servers' => $servers,
420 'localDomain' => new DatabaseDomain( 'my_unittest_wiki', null, 'unittest_' ),
421 'loadMonitorClass' => LoadMonitorNull
::class
424 $this->assertFalse( $lb->getServerAttributes( 1 )[Database
::ATTR_DB_LEVEL_LOCKING
] );
428 * @covers \Wikimedia\Rdbms\LoadBalancer::getConnection()
429 * @covers \Wikimedia\Rdbms\LoadBalancer::openConnection()
430 * @covers \Wikimedia\Rdbms\LoadBalancer::getAnyOpenConnection()
431 * @covers \Wikimedia\Rdbms\LoadBalancer::getWriterIndex()
433 function testOpenConnection() {
434 $lb = $this->newSingleServerLocalLoadBalancer();
436 $i = $lb->getWriterIndex();
437 $this->assertEquals( null, $lb->getAnyOpenConnection( $i ) );
439 $conn1 = $lb->getConnection( $i );
440 $this->assertNotEquals( null, $conn1 );
441 $this->assertEquals( $conn1, $lb->getAnyOpenConnection( $i ) );
442 $this->assertFalse( $conn1->getFlag( DBO_TRX
) );
444 $conn2 = $lb->getConnection( $i, [], false, $lb::CONN_TRX_AUTOCOMMIT
);
445 $this->assertNotEquals( null, $conn2 );
446 $this->assertFalse( $conn2->getFlag( DBO_TRX
) );
448 if ( $lb->getServerAttributes( $i )[Database
::ATTR_DB_LEVEL_LOCKING
] ) {
449 $this->assertEquals( null,
450 $lb->getAnyOpenConnection( $i, $lb::CONN_TRX_AUTOCOMMIT
) );
451 $this->assertEquals( $conn1,
453 $i, [], false, $lb::CONN_TRX_AUTOCOMMIT
), $lb::CONN_TRX_AUTOCOMMIT
);
455 $this->assertEquals( $conn2,
456 $lb->getAnyOpenConnection( $i, $lb::CONN_TRX_AUTOCOMMIT
) );
457 $this->assertEquals( $conn2,
458 $lb->getConnection( $i, [], false, $lb::CONN_TRX_AUTOCOMMIT
) );
460 $conn2->startAtomic( __METHOD__
);
462 $lb->getConnection( $i, [], false, $lb::CONN_TRX_AUTOCOMMIT
);
463 $conn2->endAtomic( __METHOD__
);
464 $this->fail( "No exception thrown." );
465 } catch ( DBUnexpectedError
$e ) {
467 'Handle requested with CONN_TRX_AUTOCOMMIT yet it has a transaction',
471 $conn2->endAtomic( __METHOD__
);
478 * @covers \Wikimedia\Rdbms\LoadBalancer::openConnection()
479 * @covers \Wikimedia\Rdbms\LoadBalancer::getWriterIndex()
480 * @covers \Wikimedia\Rdbms\LoadBalancer::forEachOpenMasterConnection()
481 * @covers \Wikimedia\Rdbms\LoadBalancer::setTransactionListener()
482 * @covers \Wikimedia\Rdbms\LoadBalancer::beginMasterChanges()
483 * @covers \Wikimedia\Rdbms\LoadBalancer::finalizeMasterChanges()
484 * @covers \Wikimedia\Rdbms\LoadBalancer::approveMasterChanges()
485 * @covers \Wikimedia\Rdbms\LoadBalancer::commitMasterChanges()
486 * @covers \Wikimedia\Rdbms\LoadBalancer::runMasterTransactionIdleCallbacks()
487 * @covers \Wikimedia\Rdbms\LoadBalancer::runMasterTransactionListenerCallbacks()
489 public function testTransactionCallbackChains() {
490 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
494 'host' => $wgDBserver,
495 'dbname' => $wgDBname,
496 'tablePrefix' => $this->dbPrefix(),
498 'password' => $wgDBpassword,
500 'dbDirectory' => $wgSQLiteDataDir,
502 'flags' => DBO_TRX
// simulate a web request with DBO_TRX
506 $lb = new LoadBalancer( [
507 'servers' => $servers,
508 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() )
511 $conn1 = $lb->openConnection( $lb->getWriterIndex(), false );
512 $conn2 = $lb->openConnection( $lb->getWriterIndex(), '' );
515 $lb->forEachOpenMasterConnection( function () use ( &$count ) {
518 $this->assertEquals( 2, $count, 'Connection handle count' );
521 $lb->setTransactionListener( 'test-listener', function () use ( &$tlCalls ) {
525 $lb->beginMasterChanges( __METHOD__
);
526 $bc = array_fill_keys( [ 'a', 'b', 'c', 'd' ], 0 );
527 $conn1->onTransactionPreCommitOrIdle( function () use ( &$bc, $conn1, $conn2 ) {
529 $conn2->onTransactionPreCommitOrIdle( function () use ( &$bc, $conn1, $conn2 ) {
531 $conn1->onTransactionPreCommitOrIdle( function () use ( &$bc, $conn1, $conn2 ) {
533 $conn1->onTransactionPreCommitOrIdle( function () use ( &$bc, $conn1, $conn2 ) {
539 $lb->finalizeMasterChanges();
540 $lb->approveMasterChanges( [] );
541 $lb->commitMasterChanges( __METHOD__
);
542 $lb->runMasterTransactionIdleCallbacks();
543 $lb->runMasterTransactionListenerCallbacks();
545 $this->assertEquals( array_fill_keys( [ 'a', 'b', 'c', 'd' ], 1 ), $bc );
546 $this->assertEquals( 2, $tlCalls );
549 $lb->beginMasterChanges( __METHOD__
);
550 $ac = array_fill_keys( [ 'a', 'b', 'c', 'd' ], 0 );
551 $conn1->onTransactionCommitOrIdle( function () use ( &$ac, $conn1, $conn2 ) {
553 $conn2->onTransactionCommitOrIdle( function () use ( &$ac, $conn1, $conn2 ) {
555 $conn1->onTransactionCommitOrIdle( function () use ( &$ac, $conn1, $conn2 ) {
557 $conn1->onTransactionCommitOrIdle( function () use ( &$ac, $conn1, $conn2 ) {
563 $lb->finalizeMasterChanges();
564 $lb->approveMasterChanges( [] );
565 $lb->commitMasterChanges( __METHOD__
);
566 $lb->runMasterTransactionIdleCallbacks();
567 $lb->runMasterTransactionListenerCallbacks();
569 $this->assertEquals( array_fill_keys( [ 'a', 'b', 'c', 'd' ], 1 ), $ac );
570 $this->assertEquals( 2, $tlCalls );
577 * @covers \Wikimedia\Rdbms\LoadBalancer::getConnectionRef
578 * @covers \Wikimedia\Rdbms\LoadBalancer::getConnection()
580 public function testDBConnRefReadsMasterAndReplicaRoles() {
581 $lb = $this->newSingleServerLocalLoadBalancer();
583 $rConn = $lb->getConnectionRef( DB_REPLICA
);
584 $wConn = $lb->getConnectionRef( DB_MASTER
);
585 $wConn2 = $lb->getConnectionRef( 0 );
587 $v = [ 'value' => '1', '1' ];
588 $sql = 'SELECT MAX(1) AS value';
589 foreach ( [ $rConn, $wConn, $wConn2 ] as $conn ) {
590 $conn->clearFlag( $conn::DBO_TRX
);
592 $res = $conn->query( $sql, __METHOD__
);
593 $this->assertEquals( $v, $conn->fetchRow( $res ) );
595 $res = $conn->query( $sql, __METHOD__
, $conn::QUERY_REPLICA_ROLE
);
596 $this->assertEquals( $v, $conn->fetchRow( $res ) );
599 $wConn->getScopedLockAndFlush( 'key', __METHOD__
, 1 );
600 $wConn2->getScopedLockAndFlush( 'key2', __METHOD__
, 1 );
604 * @covers \Wikimedia\Rdbms\LoadBalancer::getConnectionRef
605 * @expectedException \Wikimedia\Rdbms\DBReadOnlyRoleError
607 public function testDBConnRefWritesReplicaRole() {
608 $lb = $this->newSingleServerLocalLoadBalancer();
610 $rConn = $lb->getConnectionRef( DB_REPLICA
);
612 $rConn->query( 'DELETE FROM sometesttable WHERE 1=0' );
616 * @covers \Wikimedia\Rdbms\LoadBalancer::getConnectionRef
617 * @expectedException \Wikimedia\Rdbms\DBReadOnlyRoleError
619 public function testDBConnRefWritesReplicaRoleIndex() {
620 $lb = $this->newMultiServerLocalLoadBalancer();
622 $rConn = $lb->getConnectionRef( 1 );
624 $rConn->query( 'DELETE FROM sometesttable WHERE 1=0' );
628 * @covers \Wikimedia\Rdbms\LoadBalancer::getConnectionRef
629 * @expectedException \Wikimedia\Rdbms\DBReadOnlyRoleError
631 public function testDBConnRefWritesReplicaRoleInsert() {
632 $lb = $this->newMultiServerLocalLoadBalancer();
634 $rConn = $lb->getConnectionRef( DB_REPLICA
);
636 $rConn->insert( 'test', [ 't' => 1 ], __METHOD__
);
640 * @covers \Wikimedia\Rdbms\LoadBalancer::getConnection()
641 * @covers \Wikimedia\Rdbms\LoadBalancer::getMaintenanceConnectionRef()
643 public function testQueryGroupIndex() {
644 $lb = $this->newMultiServerLocalLoadBalancer( [ 'defaultGroup' => false ] );
645 /** @var LoadBalancer $lbWrapper */
646 $lbWrapper = TestingAccessWrapper
::newFromObject( $lb );
648 $rGeneric = $lb->getConnectionRef( DB_REPLICA
);
649 $mainIndexPicked = $rGeneric->getLBInfo( 'serverIndex' );
651 $this->assertEquals( $mainIndexPicked, $lbWrapper->getExistingReaderIndex( false ) );
652 $this->assertTrue( in_array( $mainIndexPicked, [ 1, 2 ] ) );
653 for ( $i = 0; $i < 300; ++
$i ) {
654 $rLog = $lb->getConnectionRef( DB_REPLICA
, [] );
657 $rLog->getLBInfo( 'serverIndex' ),
658 "Main index unchanged" );
661 $rRC = $lb->getConnectionRef( DB_REPLICA
, [ 'recentchanges' ] );
662 $rWL = $lb->getConnectionRef( DB_REPLICA
, [ 'watchlist' ] );
663 $rRCMaint = $lb->getMaintenanceConnectionRef( DB_REPLICA
, [ 'recentchanges' ] );
664 $rWLMaint = $lb->getMaintenanceConnectionRef( DB_REPLICA
, [ 'watchlist' ] );
666 $this->assertEquals( 3, $rRC->getLBInfo( 'serverIndex' ) );
667 $this->assertEquals( 3, $rWL->getLBInfo( 'serverIndex' ) );
668 $this->assertEquals( 3, $rRCMaint->getLBInfo( 'serverIndex' ) );
669 $this->assertEquals( 3, $rWLMaint->getLBInfo( 'serverIndex' ) );
671 $rLog = $lb->getConnectionRef( DB_REPLICA
, [ 'logging', 'watchlist' ] );
672 $logIndexPicked = $rLog->getLBInfo( 'serverIndex' );
674 $this->assertEquals( $logIndexPicked, $lbWrapper->getExistingReaderIndex( 'logging' ) );
675 $this->assertTrue( in_array( $logIndexPicked, [ 4, 5 ] ) );
677 for ( $i = 0; $i < 300; ++
$i ) {
678 $rLog = $lb->getConnectionRef( DB_REPLICA
, [ 'logging', 'watchlist' ] );
680 $logIndexPicked, $rLog->getLBInfo( 'serverIndex' ), "Index unchanged" );
683 $rVslow = $lb->getConnectionRef( DB_REPLICA
, [ 'vslow', 'logging' ] );
684 $vslowIndexPicked = $rVslow->getLBInfo( 'serverIndex' );
686 $this->assertEquals( $vslowIndexPicked, $lbWrapper->getExistingReaderIndex( 'vslow' ) );
687 $this->assertEquals( 6, $vslowIndexPicked );
690 public function testNonZeroMasterLoad() {
691 $lb = $this->newMultiServerLocalLoadBalancer( [], [ 'flags' => DBO_DEFAULT
], true );
692 // Make sure that no infinite loop occurs (T226678)
693 $rGeneric = $lb->getConnectionRef( DB_REPLICA
);
694 $this->assertEquals( $lb->getWriterIndex(), $rGeneric->getLBInfo( 'serverIndex' ) );
698 * @covers \Wikimedia\Rdbms\LoadBalancer::getLazyConnectionRef
700 public function testGetLazyConnectionRef() {
701 $lb = $this->newMultiServerLocalLoadBalancer();
703 $rMaster = $lb->getLazyConnectionRef( DB_MASTER
);
704 $rReplica = $lb->getLazyConnectionRef( 1 );
705 $this->assertFalse( $lb->getAnyOpenConnection( 0 ) );
706 $this->assertFalse( $lb->getAnyOpenConnection( 1 ) );
709 $rReplica->getType();
710 $rMaster->getDomainID();
711 $rReplica->getDomainID();
712 $this->assertFalse( $lb->getAnyOpenConnection( 0 ) );
713 $this->assertFalse( $lb->getAnyOpenConnection( 1 ) );
715 $rMaster->query( "SELECT 1", __METHOD__
);
716 $this->assertNotFalse( $lb->getAnyOpenConnection( 0 ) );
718 $rReplica->query( "SELECT 1", __METHOD__
);
719 $this->assertNotFalse( $lb->getAnyOpenConnection( 0 ) );
720 $this->assertNotFalse( $lb->getAnyOpenConnection( 1 ) );