Merge "Speed up code coverage generation for local development"
[lhc/web/wiklou.git] / tests / phpunit / includes / db / LoadBalancerTest.php
1 <?php
2
3 /**
4 * Holds tests for LoadBalancer MediaWiki class.
5 *
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.
10 *
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.
15 *
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
20 *
21 * @file
22 */
23
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;
30
31 /**
32 * @group Database
33 * @group medium
34 * @covers \Wikimedia\Rdbms\LoadBalancer
35 */
36 class LoadBalancerTest extends MediaWikiTestCase {
37 private function makeServerConfig( $flags = DBO_DEFAULT ) {
38 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
39
40 return [
41 'host' => $wgDBserver,
42 'dbname' => $wgDBname,
43 'tablePrefix' => $this->dbPrefix(),
44 'user' => $wgDBuser,
45 'password' => $wgDBpassword,
46 'type' => $wgDBtype,
47 'dbDirectory' => $wgSQLiteDataDir,
48 'load' => 0,
49 'flags' => $flags
50 ];
51 }
52
53 /**
54 * @covers LoadBalancer::getLocalDomainID()
55 * @covers LoadBalancer::resolveDomainID()
56 */
57 public function testWithoutReplica() {
58 global $wgDBname;
59
60 $called = false;
61 $lb = new LoadBalancer( [
62 // Simulate web request with DBO_TRX
63 'servers' => [ $this->makeServerConfig( DBO_TRX ) ],
64 'queryLogger' => MediaWiki\Logger\LoggerFactory::getInstance( 'DBQuery' ),
65 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() ),
66 'chronologyCallback' => function () use ( &$called ) {
67 $called = true;
68 }
69 ] );
70
71 $ld = DatabaseDomain::newFromId( $lb->getLocalDomainID() );
72 $this->assertEquals( $wgDBname, $ld->getDatabase(), 'local domain DB set' );
73 $this->assertEquals( $this->dbPrefix(), $ld->getTablePrefix(), 'local domain prefix set' );
74 $this->assertSame( 'my_test_wiki', $lb->resolveDomainID( 'my_test_wiki' ) );
75 $this->assertSame( $ld->getId(), $lb->resolveDomainID( false ) );
76 $this->assertSame( $ld->getId(), $lb->resolveDomainID( $ld ) );
77 $this->assertFalse( $called );
78
79 $dbw = $lb->getConnection( DB_MASTER );
80 $this->assertTrue( $called );
81 $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
82 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on master" );
83 $this->assertWriteAllowed( $dbw );
84
85 $dbr = $lb->getConnection( DB_REPLICA );
86 $this->assertTrue( $dbr->getLBInfo( 'master' ), 'DB_REPLICA also gets the master' );
87 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on replica" );
88
89 if ( !$lb->getServerAttributes( $lb->getWriterIndex() )[$dbw::ATTR_DB_LEVEL_LOCKING] ) {
90 $dbwAuto = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTOCOMMIT );
91 $this->assertFalse(
92 $dbwAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
93 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on master" );
94 $this->assertNotEquals(
95 $dbw, $dbwAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
96
97 $dbrAuto = $lb->getConnection( DB_REPLICA, [], false, $lb::CONN_TRX_AUTOCOMMIT );
98 $this->assertFalse(
99 $dbrAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
100 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on replica" );
101 $this->assertNotEquals(
102 $dbr, $dbrAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
103
104 $dbwAuto2 = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTOCOMMIT );
105 $this->assertEquals( $dbwAuto2, $dbwAuto, "CONN_TRX_AUTOCOMMIT reuses connections" );
106 }
107
108 $lb->closeAll();
109 }
110
111 public function testWithReplica() {
112 global $wgDBserver;
113
114 // Simulate web request with DBO_TRX
115 $lb = $this->newMultiServerLocalLoadBalancer( [], [ 'flags' => DBO_TRX ] );
116
117 $this->assertEquals( 8, $lb->getServerCount() );
118 $this->assertTrue( $lb->hasReplicaServers() );
119 $this->assertTrue( $lb->hasStreamingReplicaServers() );
120
121 $dbw = $lb->getConnection( DB_MASTER );
122 $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
123 $this->assertEquals(
124 ( $wgDBserver != '' ) ? $wgDBserver : 'localhost',
125 $dbw->getLBInfo( 'clusterMasterHost' ),
126 'cluster master set' );
127 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on master" );
128 $this->assertWriteAllowed( $dbw );
129
130 $dbr = $lb->getConnection( DB_REPLICA );
131 $this->assertTrue( $dbr->getLBInfo( 'replica' ), 'replica shows as replica' );
132 $this->assertTrue( $dbr->isReadOnly(), 'replica shows as replica' );
133 $this->assertEquals(
134 ( $wgDBserver != '' ) ? $wgDBserver : 'localhost',
135 $dbr->getLBInfo( 'clusterMasterHost' ),
136 'cluster master set' );
137 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on replica" );
138 $this->assertWriteForbidden( $dbr );
139
140 if ( !$lb->getServerAttributes( $lb->getWriterIndex() )[$dbw::ATTR_DB_LEVEL_LOCKING] ) {
141 $dbwAuto = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTOCOMMIT );
142 $this->assertFalse(
143 $dbwAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
144 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on master" );
145 $this->assertNotEquals(
146 $dbw, $dbwAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
147
148 $dbrAuto = $lb->getConnection( DB_REPLICA, [], false, $lb::CONN_TRX_AUTOCOMMIT );
149 $this->assertFalse(
150 $dbrAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
151 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on replica" );
152 $this->assertNotEquals(
153 $dbr, $dbrAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
154
155 $dbwAuto2 = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTOCOMMIT );
156 $this->assertEquals( $dbwAuto2, $dbwAuto, "CONN_TRX_AUTOCOMMIT reuses connections" );
157 }
158
159 $lb->closeAll();
160 }
161
162 private function newSingleServerLocalLoadBalancer() {
163 global $wgDBname;
164
165 return new LoadBalancer( [
166 'servers' => [ $this->makeServerConfig() ],
167 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() )
168 ] );
169 }
170
171 private function newMultiServerLocalLoadBalancer(
172 $lbExtra = [], $srvExtra = [], $masterOnly = false
173 ) {
174 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
175
176 $servers = [
177 // Master DB
178 0 => $srvExtra + [
179 'host' => $wgDBserver,
180 'dbname' => $wgDBname,
181 'tablePrefix' => $this->dbPrefix(),
182 'user' => $wgDBuser,
183 'password' => $wgDBpassword,
184 'type' => $wgDBtype,
185 'dbDirectory' => $wgSQLiteDataDir,
186 'load' => $masterOnly ? 100 : 0,
187 ],
188 // Main replica DBs
189 1 => $srvExtra + [
190 'host' => $wgDBserver,
191 'dbname' => $wgDBname,
192 'tablePrefix' => $this->dbPrefix(),
193 'user' => $wgDBuser,
194 'password' => $wgDBpassword,
195 'type' => $wgDBtype,
196 'dbDirectory' => $wgSQLiteDataDir,
197 'load' => $masterOnly ? 0 : 100,
198 ],
199 2 => $srvExtra + [
200 'host' => $wgDBserver,
201 'dbname' => $wgDBname,
202 'tablePrefix' => $this->dbPrefix(),
203 'user' => $wgDBuser,
204 'password' => $wgDBpassword,
205 'type' => $wgDBtype,
206 'dbDirectory' => $wgSQLiteDataDir,
207 'load' => $masterOnly ? 0 : 100,
208 ],
209 // RC replica DBs
210 3 => $srvExtra + [
211 'host' => $wgDBserver,
212 'dbname' => $wgDBname,
213 'tablePrefix' => $this->dbPrefix(),
214 'user' => $wgDBuser,
215 'password' => $wgDBpassword,
216 'type' => $wgDBtype,
217 'dbDirectory' => $wgSQLiteDataDir,
218 'load' => 0,
219 'groupLoads' => [
220 'recentchanges' => 100,
221 'watchlist' => 100
222 ],
223 ],
224 // Logging replica DBs
225 4 => $srvExtra + [
226 'host' => $wgDBserver,
227 'dbname' => $wgDBname,
228 'tablePrefix' => $this->dbPrefix(),
229 'user' => $wgDBuser,
230 'password' => $wgDBpassword,
231 'type' => $wgDBtype,
232 'dbDirectory' => $wgSQLiteDataDir,
233 'load' => 0,
234 'groupLoads' => [
235 'logging' => 100
236 ],
237 ],
238 5 => $srvExtra + [
239 'host' => $wgDBserver,
240 'dbname' => $wgDBname,
241 'tablePrefix' => $this->dbPrefix(),
242 'user' => $wgDBuser,
243 'password' => $wgDBpassword,
244 'type' => $wgDBtype,
245 'dbDirectory' => $wgSQLiteDataDir,
246 'load' => 0,
247 'groupLoads' => [
248 'logging' => 100
249 ],
250 ],
251 // Maintenance query replica DBs
252 6 => $srvExtra + [
253 'host' => $wgDBserver,
254 'dbname' => $wgDBname,
255 'tablePrefix' => $this->dbPrefix(),
256 'user' => $wgDBuser,
257 'password' => $wgDBpassword,
258 'type' => $wgDBtype,
259 'dbDirectory' => $wgSQLiteDataDir,
260 'load' => 0,
261 'groupLoads' => [
262 'vslow' => 100
263 ],
264 ],
265 // Replica DB that only has a copy of some static tables
266 7 => $srvExtra + [
267 'host' => $wgDBserver,
268 'dbname' => $wgDBname,
269 'tablePrefix' => $this->dbPrefix(),
270 'user' => $wgDBuser,
271 'password' => $wgDBpassword,
272 'type' => $wgDBtype,
273 'dbDirectory' => $wgSQLiteDataDir,
274 'load' => 0,
275 'groupLoads' => [
276 'archive' => 100
277 ],
278 'is static' => true
279 ]
280 ];
281
282 return new LoadBalancer( $lbExtra + [
283 'servers' => $servers,
284 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() ),
285 'queryLogger' => MediaWiki\Logger\LoggerFactory::getInstance( 'DBQuery' ),
286 'loadMonitorClass' => LoadMonitorNull::class
287 ] );
288 }
289
290 private function assertWriteForbidden( Database $db ) {
291 try {
292 $db->delete( 'some_table', [ 'id' => 57634126 ], __METHOD__ );
293 $this->fail( 'Write operation should have failed!' );
294 } catch ( DBError $ex ) {
295 // check that the exception message contains "Write operation"
296 $constraint = new PHPUnit_Framework_Constraint_StringContains( 'Write operation' );
297
298 if ( !$constraint->evaluate( $ex->getMessage(), '', true ) ) {
299 // re-throw original error, to preserve stack trace
300 throw $ex;
301 }
302 }
303 }
304
305 private function assertWriteAllowed( Database $db ) {
306 $table = $db->tableName( 'some_table' );
307 // Trigger a transaction so that rollback() will remove all the tables.
308 // Don't do this for MySQL/Oracle as they auto-commit transactions for DDL
309 // statements such as CREATE TABLE.
310 $useAtomicSection = in_array( $db->getType(), [ 'sqlite', 'postgres', 'mssql' ], true );
311 try {
312 $db->dropTable( 'some_table' ); // clear for sanity
313 $this->assertNotEquals( $db::STATUS_TRX_ERROR, $db->trxStatus() );
314
315 if ( $useAtomicSection ) {
316 $db->startAtomic( __METHOD__ );
317 }
318 // Use only basic SQL and trivial types for these queries for compatibility
319 $this->assertNotSame(
320 false,
321 $db->query( "CREATE TABLE $table (id INT, time INT)", __METHOD__ ),
322 "table created"
323 );
324 $this->assertNotEquals( $db::STATUS_TRX_ERROR, $db->trxStatus() );
325 $this->assertNotSame(
326 false,
327 $db->query( "DELETE FROM $table WHERE id=57634126", __METHOD__ ),
328 "delete query"
329 );
330 $this->assertNotEquals( $db::STATUS_TRX_ERROR, $db->trxStatus() );
331 } finally {
332 if ( !$useAtomicSection ) {
333 // Drop the table to clean up, ignoring any error.
334 $db->dropTable( 'some_table' );
335 }
336 // Rollback the atomic section for sqlite's benefit.
337 $db->rollback( __METHOD__, 'flush' );
338 $this->assertNotEquals( $db::STATUS_TRX_ERROR, $db->trxStatus() );
339 }
340 }
341
342 public function testServerAttributes() {
343 $servers = [
344 [ // master
345 'dbname' => 'my_unittest_wiki',
346 'tablePrefix' => 'unittest_',
347 'type' => 'sqlite',
348 'dbDirectory' => "some_directory",
349 'load' => 0
350 ]
351 ];
352
353 $lb = new LoadBalancer( [
354 'servers' => $servers,
355 'localDomain' => new DatabaseDomain( 'my_unittest_wiki', null, 'unittest_' ),
356 'loadMonitorClass' => LoadMonitorNull::class
357 ] );
358
359 $this->assertTrue( $lb->getServerAttributes( 0 )[Database::ATTR_DB_LEVEL_LOCKING] );
360
361 $servers = [
362 [ // master
363 'host' => 'db1001',
364 'user' => 'wikiuser',
365 'password' => 'none',
366 'dbname' => 'my_unittest_wiki',
367 'tablePrefix' => 'unittest_',
368 'type' => 'mysql',
369 'load' => 100
370 ],
371 [ // emulated replica
372 'host' => 'db1002',
373 'user' => 'wikiuser',
374 'password' => 'none',
375 'dbname' => 'my_unittest_wiki',
376 'tablePrefix' => 'unittest_',
377 'type' => 'mysql',
378 'load' => 100
379 ]
380 ];
381
382 $lb = new LoadBalancer( [
383 'servers' => $servers,
384 'localDomain' => new DatabaseDomain( 'my_unittest_wiki', null, 'unittest_' ),
385 'loadMonitorClass' => LoadMonitorNull::class
386 ] );
387
388 $this->assertFalse( $lb->getServerAttributes( 1 )[Database::ATTR_DB_LEVEL_LOCKING] );
389 }
390
391 /**
392 * @covers LoadBalancer::openConnection()
393 * @covers LoadBalancer::getAnyOpenConnection()
394 */
395 function testOpenConnection() {
396 $lb = $this->newSingleServerLocalLoadBalancer();
397
398 $i = $lb->getWriterIndex();
399 $this->assertEquals( null, $lb->getAnyOpenConnection( $i ) );
400
401 $conn1 = $lb->getConnection( $i );
402 $this->assertNotEquals( null, $conn1 );
403 $this->assertEquals( $conn1, $lb->getAnyOpenConnection( $i ) );
404 $this->assertFalse( $conn1->getFlag( DBO_TRX ) );
405
406 $conn2 = $lb->getConnection( $i, [], false, $lb::CONN_TRX_AUTOCOMMIT );
407 $this->assertNotEquals( null, $conn2 );
408 $this->assertFalse( $conn2->getFlag( DBO_TRX ) );
409
410 if ( $lb->getServerAttributes( $i )[Database::ATTR_DB_LEVEL_LOCKING] ) {
411 $this->assertEquals( null,
412 $lb->getAnyOpenConnection( $i, $lb::CONN_TRX_AUTOCOMMIT ) );
413 $this->assertEquals( $conn1,
414 $lb->getConnection(
415 $i, [], false, $lb::CONN_TRX_AUTOCOMMIT ), $lb::CONN_TRX_AUTOCOMMIT );
416 } else {
417 $this->assertEquals( $conn2,
418 $lb->getAnyOpenConnection( $i, $lb::CONN_TRX_AUTOCOMMIT ) );
419 $this->assertEquals( $conn2,
420 $lb->getConnection( $i, [], false, $lb::CONN_TRX_AUTOCOMMIT ) );
421
422 $conn2->startAtomic( __METHOD__ );
423 try {
424 $lb->getConnection( $i, [], false, $lb::CONN_TRX_AUTOCOMMIT );
425 $conn2->endAtomic( __METHOD__ );
426 $this->fail( "No exception thrown." );
427 } catch ( DBUnexpectedError $e ) {
428 $this->assertEquals(
429 'Handle requested with CONN_TRX_AUTOCOMMIT yet it has a transaction',
430 $e->getMessage()
431 );
432 }
433 $conn2->endAtomic( __METHOD__ );
434 }
435
436 $lb->closeAll();
437 }
438
439 public function testTransactionCallbackChains() {
440 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
441
442 $servers = [
443 [
444 'host' => $wgDBserver,
445 'dbname' => $wgDBname,
446 'tablePrefix' => $this->dbPrefix(),
447 'user' => $wgDBuser,
448 'password' => $wgDBpassword,
449 'type' => $wgDBtype,
450 'dbDirectory' => $wgSQLiteDataDir,
451 'load' => 0,
452 'flags' => DBO_TRX // simulate a web request with DBO_TRX
453 ],
454 ];
455
456 $lb = new LoadBalancer( [
457 'servers' => $servers,
458 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() )
459 ] );
460
461 $conn1 = $lb->openConnection( $lb->getWriterIndex(), false );
462 $conn2 = $lb->openConnection( $lb->getWriterIndex(), '' );
463
464 $count = 0;
465 $lb->forEachOpenMasterConnection( function () use ( &$count ) {
466 ++$count;
467 } );
468 $this->assertEquals( 2, $count, 'Connection handle count' );
469
470 $tlCalls = 0;
471 $lb->setTransactionListener( 'test-listener', function () use ( &$tlCalls ) {
472 ++$tlCalls;
473 } );
474
475 $lb->beginMasterChanges( __METHOD__ );
476 $bc = array_fill_keys( [ 'a', 'b', 'c', 'd' ], 0 );
477 $conn1->onTransactionPreCommitOrIdle( function () use ( &$bc, $conn1, $conn2 ) {
478 $bc['a'] = 1;
479 $conn2->onTransactionPreCommitOrIdle( function () use ( &$bc, $conn1, $conn2 ) {
480 $bc['b'] = 1;
481 $conn1->onTransactionPreCommitOrIdle( function () use ( &$bc, $conn1, $conn2 ) {
482 $bc['c'] = 1;
483 $conn1->onTransactionPreCommitOrIdle( function () use ( &$bc, $conn1, $conn2 ) {
484 $bc['d'] = 1;
485 } );
486 } );
487 } );
488 } );
489 $lb->finalizeMasterChanges();
490 $lb->approveMasterChanges( [] );
491 $lb->commitMasterChanges( __METHOD__ );
492 $lb->runMasterTransactionIdleCallbacks();
493 $lb->runMasterTransactionListenerCallbacks();
494
495 $this->assertEquals( array_fill_keys( [ 'a', 'b', 'c', 'd' ], 1 ), $bc );
496 $this->assertEquals( 2, $tlCalls );
497
498 $tlCalls = 0;
499 $lb->beginMasterChanges( __METHOD__ );
500 $ac = array_fill_keys( [ 'a', 'b', 'c', 'd' ], 0 );
501 $conn1->onTransactionCommitOrIdle( function () use ( &$ac, $conn1, $conn2 ) {
502 $ac['a'] = 1;
503 $conn2->onTransactionCommitOrIdle( function () use ( &$ac, $conn1, $conn2 ) {
504 $ac['b'] = 1;
505 $conn1->onTransactionCommitOrIdle( function () use ( &$ac, $conn1, $conn2 ) {
506 $ac['c'] = 1;
507 $conn1->onTransactionCommitOrIdle( function () use ( &$ac, $conn1, $conn2 ) {
508 $ac['d'] = 1;
509 } );
510 } );
511 } );
512 } );
513 $lb->finalizeMasterChanges();
514 $lb->approveMasterChanges( [] );
515 $lb->commitMasterChanges( __METHOD__ );
516 $lb->runMasterTransactionIdleCallbacks();
517 $lb->runMasterTransactionListenerCallbacks();
518
519 $this->assertEquals( array_fill_keys( [ 'a', 'b', 'c', 'd' ], 1 ), $ac );
520 $this->assertEquals( 2, $tlCalls );
521
522 $conn1->close();
523 $conn2->close();
524 }
525
526 public function testDBConnRefReadsMasterAndReplicaRoles() {
527 $lb = $this->newSingleServerLocalLoadBalancer();
528
529 $rConn = $lb->getConnectionRef( DB_REPLICA );
530 $wConn = $lb->getConnectionRef( DB_MASTER );
531 $wConn2 = $lb->getConnectionRef( 0 );
532
533 $v = [ 'value' => '1', '1' ];
534 $sql = 'SELECT MAX(1) AS value';
535 foreach ( [ $rConn, $wConn, $wConn2 ] as $conn ) {
536 $conn->clearFlag( $conn::DBO_TRX );
537
538 $res = $conn->query( $sql, __METHOD__ );
539 $this->assertEquals( $v, $conn->fetchRow( $res ) );
540
541 $res = $conn->query( $sql, __METHOD__, $conn::QUERY_REPLICA_ROLE );
542 $this->assertEquals( $v, $conn->fetchRow( $res ) );
543 }
544
545 $wConn->getScopedLockAndFlush( 'key', __METHOD__, 1 );
546 $wConn2->getScopedLockAndFlush( 'key2', __METHOD__, 1 );
547 }
548
549 /**
550 * @expectedException \Wikimedia\Rdbms\DBReadOnlyRoleError
551 */
552 public function testDBConnRefWritesReplicaRole() {
553 $lb = $this->newSingleServerLocalLoadBalancer();
554
555 $rConn = $lb->getConnectionRef( DB_REPLICA );
556
557 $rConn->query( 'DELETE FROM sometesttable WHERE 1=0' );
558 }
559
560 /**
561 * @expectedException \Wikimedia\Rdbms\DBReadOnlyRoleError
562 */
563 public function testDBConnRefWritesReplicaRoleIndex() {
564 $lb = $this->newMultiServerLocalLoadBalancer();
565
566 $rConn = $lb->getConnectionRef( 1 );
567
568 $rConn->query( 'DELETE FROM sometesttable WHERE 1=0' );
569 }
570
571 /**
572 * @expectedException \Wikimedia\Rdbms\DBReadOnlyRoleError
573 */
574 public function testDBConnRefWritesReplicaRoleInsert() {
575 $lb = $this->newMultiServerLocalLoadBalancer();
576
577 $rConn = $lb->getConnectionRef( DB_REPLICA );
578
579 $rConn->insert( 'test', [ 't' => 1 ], __METHOD__ );
580 }
581
582 public function testQueryGroupIndex() {
583 $lb = $this->newMultiServerLocalLoadBalancer( [ 'defaultGroup' => false ] );
584 /** @var LoadBalancer $lbWrapper */
585 $lbWrapper = TestingAccessWrapper::newFromObject( $lb );
586
587 $rGeneric = $lb->getConnectionRef( DB_REPLICA );
588 $mainIndexPicked = $rGeneric->getLBInfo( 'serverIndex' );
589
590 $this->assertEquals( $mainIndexPicked, $lbWrapper->getExistingReaderIndex( false ) );
591 $this->assertTrue( in_array( $mainIndexPicked, [ 1, 2 ] ) );
592 for ( $i = 0; $i < 300; ++$i ) {
593 $rLog = $lb->getConnectionRef( DB_REPLICA, [] );
594 $this->assertEquals(
595 $mainIndexPicked,
596 $rLog->getLBInfo( 'serverIndex' ),
597 "Main index unchanged" );
598 }
599
600 $rRC = $lb->getConnectionRef( DB_REPLICA, [ 'recentchanges' ] );
601 $rWL = $lb->getConnectionRef( DB_REPLICA, [ 'watchlist' ] );
602
603 $this->assertEquals( 3, $rRC->getLBInfo( 'serverIndex' ) );
604 $this->assertEquals( 3, $rWL->getLBInfo( 'serverIndex' ) );
605
606 $rLog = $lb->getConnectionRef( DB_REPLICA, [ 'logging', 'watchlist' ] );
607 $logIndexPicked = $rLog->getLBInfo( 'serverIndex' );
608
609 $this->assertEquals( $logIndexPicked, $lbWrapper->getExistingReaderIndex( 'logging' ) );
610 $this->assertTrue( in_array( $logIndexPicked, [ 4, 5 ] ) );
611
612 for ( $i = 0; $i < 300; ++$i ) {
613 $rLog = $lb->getConnectionRef( DB_REPLICA, [ 'logging', 'watchlist' ] );
614 $this->assertEquals(
615 $logIndexPicked, $rLog->getLBInfo( 'serverIndex' ), "Index unchanged" );
616 }
617
618 $rVslow = $lb->getConnectionRef( DB_REPLICA, [ 'vslow', 'logging' ] );
619 $vslowIndexPicked = $rVslow->getLBInfo( 'serverIndex' );
620
621 $this->assertEquals( $vslowIndexPicked, $lbWrapper->getExistingReaderIndex( 'vslow' ) );
622 $this->assertEquals( 6, $vslowIndexPicked );
623 }
624
625 public function testNonZeroMasterLoad() {
626 $lb = $this->newMultiServerLocalLoadBalancer( [], [ 'flags' => DBO_DEFAULT ], true );
627 // Make sure that no infinite loop occurs (T226678)
628 $rGeneric = $lb->getConnectionRef( DB_REPLICA );
629 $this->assertEquals( $lb->getWriterIndex(), $rGeneric->getLBInfo( 'serverIndex' ) );
630 }
631 }