MediaWiki REL1_34
LBFactory.php
Go to the documentation of this file.
1<?php
24namespace Wikimedia\Rdbms;
25
26use Psr\Log\LoggerInterface;
27use Psr\Log\NullLogger;
28use Wikimedia\ScopedCallback;
29use BagOStuff;
32use Exception;
33use RuntimeException;
34use LogicException;
35
40abstract class LBFactory implements ILBFactory {
42 private $chronProt;
44 private $profiler;
46 private $trxProfiler;
48 private $replLogger;
50 private $connLogger;
52 private $queryLogger;
54 private $perfLogger;
56 private $errorLogger;
59
61 protected $srvCache;
63 protected $memStash;
65 protected $wanCache;
66
68 protected $localDomain;
69
71 private $hostname;
73 private $requestInfo;
75 private $cliMode;
77 private $agent;
79 private $secret;
80
82 private $tableAliases = [];
84 private $indexAliases = [];
87
89 private $id;
91 private $ticket;
93 private $trxRoundId = false;
96
98 protected $readOnlyReason = false;
99
101 private $defaultGroup = null;
102
104 protected $maxLag;
105
106 const ROUND_CURSORY = 'cursory';
107 const ROUND_BEGINNING = 'within-begin';
108 const ROUND_COMMITTING = 'within-commit';
109 const ROUND_ROLLING_BACK = 'within-rollback';
110 const ROUND_COMMIT_CALLBACKS = 'within-commit-callbacks';
111 const ROUND_ROLLBACK_CALLBACKS = 'within-rollback-callbacks';
112
113 private static $loggerFields =
114 [ 'replLogger', 'connLogger', 'queryLogger', 'perfLogger' ];
115
116 public function __construct( array $conf ) {
117 $this->localDomain = isset( $conf['localDomain'] )
118 ? DatabaseDomain::newFromId( $conf['localDomain'] )
120
121 $this->maxLag = $conf['maxLag'] ?? null;
122 if ( isset( $conf['readOnlyReason'] ) && is_string( $conf['readOnlyReason'] ) ) {
123 $this->readOnlyReason = $conf['readOnlyReason'];
124 }
125
126 $this->srvCache = $conf['srvCache'] ?? new EmptyBagOStuff();
127 $this->memStash = $conf['memStash'] ?? new EmptyBagOStuff();
128 $this->wanCache = $conf['wanCache'] ?? WANObjectCache::newEmpty();
129
130 foreach ( self::$loggerFields as $key ) {
131 $this->$key = $conf[$key] ?? new NullLogger();
132 }
133 $this->errorLogger = $conf['errorLogger'] ?? function ( Exception $e ) {
134 trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING );
135 };
136 $this->deprecationLogger = $conf['deprecationLogger'] ?? function ( $msg ) {
137 trigger_error( $msg, E_USER_DEPRECATED );
138 };
139
140 $this->profiler = $conf['profiler'] ?? null;
141 $this->trxProfiler = $conf['trxProfiler'] ?? new TransactionProfiler();
142
143 $this->requestInfo = [
144 'IPAddress' => $_SERVER[ 'REMOTE_ADDR' ] ?? '',
145 'UserAgent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
146 // Headers application can inject via LBFactory::setRequestInfo()
147 'ChronologyProtection' => null,
148 'ChronologyClientId' => null, // prior $cpClientId value from LBFactory::shutdown()
149 'ChronologyPositionIndex' => null // prior $cpIndex value from LBFactory::shutdown()
150 ];
151
152 $this->cliMode = $conf['cliMode'] ?? ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' );
153 $this->hostname = $conf['hostname'] ?? gethostname();
154 $this->agent = $conf['agent'] ?? '';
155 $this->defaultGroup = $conf['defaultGroup'] ?? null;
156 $this->secret = $conf['secret'] ?? '';
157
158 static $nextId, $nextTicket;
159 $this->id = $nextId = ( is_int( $nextId ) ? $nextId++ : mt_rand() );
160 $this->ticket = $nextTicket = ( is_int( $nextTicket ) ? $nextTicket++ : mt_rand() );
161 }
162
163 public function destroy() {
165 $scope = ScopedCallback::newScopedIgnoreUserAbort();
166
167 $this->forEachLBCallMethod( 'disable', [ __METHOD__, $this->id ] );
168 }
169
170 public function getLocalDomainID() {
171 return $this->localDomain->getId();
172 }
173
174 public function resolveDomainID( $domain ) {
175 return ( $domain !== false ) ? (string)$domain : $this->getLocalDomainID();
176 }
177
178 public function shutdown(
179 $mode = self::SHUTDOWN_CHRONPROT_SYNC,
180 callable $workCallback = null,
181 &$cpIndex = null,
182 &$cpClientId = null
183 ) {
185 $scope = ScopedCallback::newScopedIgnoreUserAbort();
186
188 if ( $mode === self::SHUTDOWN_CHRONPROT_SYNC ) {
189 $this->shutdownChronologyProtector( $chronProt, $workCallback, 'sync', $cpIndex );
190 } elseif ( $mode === self::SHUTDOWN_CHRONPROT_ASYNC ) {
191 $this->shutdownChronologyProtector( $chronProt, null, 'async', $cpIndex );
192 }
193
194 $cpClientId = $chronProt->getClientId();
195
196 $this->commitMasterChanges( __METHOD__ ); // sanity
197 }
198
205 protected function forEachLBCallMethod( $methodName, array $args = [] ) {
206 $this->forEachLB(
207 function ( ILoadBalancer $loadBalancer, $methodName, array $args ) {
208 $loadBalancer->$methodName( ...$args );
209 },
210 [ $methodName, $args ]
211 );
212 }
213
214 public function flushReplicaSnapshots( $fname = __METHOD__ ) {
215 if ( $this->trxRoundId !== false && $this->trxRoundId !== $fname ) {
216 $this->queryLogger->warning(
217 "$fname: transaction round '{$this->trxRoundId}' still running",
218 [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
219 );
220 }
221 $this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname, $this->id ] );
222 }
223
224 final public function commitAll( $fname = __METHOD__, array $options = [] ) {
225 $this->commitMasterChanges( $fname, $options );
226 $this->forEachLBCallMethod( 'flushMasterSnapshots', [ $fname, $this->id ] );
227 $this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname, $this->id ] );
228 }
229
230 final public function beginMasterChanges( $fname = __METHOD__ ) {
231 $this->assertTransactionRoundStage( self::ROUND_CURSORY );
233 $scope = ScopedCallback::newScopedIgnoreUserAbort();
234
235 $this->trxRoundStage = self::ROUND_BEGINNING;
236 if ( $this->trxRoundId !== false ) {
237 throw new DBTransactionError(
238 null,
239 "$fname: transaction round '{$this->trxRoundId}' already started"
240 );
241 }
242 $this->trxRoundId = $fname;
243 // Set DBO_TRX flags on all appropriate DBs
244 $this->forEachLBCallMethod( 'beginMasterChanges', [ $fname, $this->id ] );
245 $this->trxRoundStage = self::ROUND_CURSORY;
246 }
247
248 final public function commitMasterChanges( $fname = __METHOD__, array $options = [] ) {
249 $this->assertTransactionRoundStage( self::ROUND_CURSORY );
251 $scope = ScopedCallback::newScopedIgnoreUserAbort();
252
253 $this->trxRoundStage = self::ROUND_COMMITTING;
254 if ( $this->trxRoundId !== false && $this->trxRoundId !== $fname ) {
255 throw new DBTransactionError(
256 null,
257 "$fname: transaction round '{$this->trxRoundId}' still running"
258 );
259 }
260 // Run pre-commit callbacks and suppress post-commit callbacks, aborting on failure
261 do {
262 $count = 0; // number of callbacks executed this iteration
263 $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$count, $fname ) {
264 $count += $lb->finalizeMasterChanges( $fname, $this->id );
265 } );
266 } while ( $count > 0 );
267 $this->trxRoundId = false;
268 // Perform pre-commit checks, aborting on failure
269 $this->forEachLBCallMethod( 'approveMasterChanges', [ $options, $fname, $this->id ] );
270 // Log the DBs and methods involved in multi-DB transactions
272 // Actually perform the commit on all master DB connections and revert DBO_TRX
273 $this->forEachLBCallMethod( 'commitMasterChanges', [ $fname, $this->id ] );
274 // Run all post-commit callbacks in a separate step
275 $this->trxRoundStage = self::ROUND_COMMIT_CALLBACKS;
277 $this->trxRoundStage = self::ROUND_CURSORY;
278 // Throw any last post-commit callback error
279 if ( $e instanceof Exception ) {
280 throw $e;
281 }
282 }
283
284 final public function rollbackMasterChanges( $fname = __METHOD__ ) {
286 $scope = ScopedCallback::newScopedIgnoreUserAbort();
287
288 $this->trxRoundStage = self::ROUND_ROLLING_BACK;
289 $this->trxRoundId = false;
290 // Actually perform the rollback on all master DB connections and revert DBO_TRX
291 $this->forEachLBCallMethod( 'rollbackMasterChanges', [ $fname, $this->id ] );
292 // Run all post-commit callbacks in a separate step
293 $this->trxRoundStage = self::ROUND_ROLLBACK_CALLBACKS;
295 $this->trxRoundStage = self::ROUND_CURSORY;
296 }
297
302 $fname = __METHOD__;
303 // Run all post-commit callbacks until new ones stop getting added
304 $e = null; // first callback exception
305 do {
306 $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$e, $fname ) {
307 $ex = $lb->runMasterTransactionIdleCallbacks( $fname, $this->id );
308 $e = $e ?: $ex;
309 } );
310 } while ( $this->hasMasterChanges() );
311 // Run all listener callbacks once
312 $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$e, $fname ) {
313 $ex = $lb->runMasterTransactionListenerCallbacks( $fname, $this->id );
314 $e = $e ?: $ex;
315 } );
316
317 return $e;
318 }
319
320 public function hasTransactionRound() {
321 return ( $this->trxRoundId !== false );
322 }
323
324 public function isReadyForRoundOperations() {
325 return ( $this->trxRoundStage === self::ROUND_CURSORY );
326 }
327
331 private function logIfMultiDbTransaction() {
332 $callersByDB = [];
333 $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$callersByDB ) {
334 $masterName = $lb->getServerName( $lb->getWriterIndex() );
335 $callers = $lb->pendingMasterChangeCallers();
336 if ( $callers ) {
337 $callersByDB[$masterName] = $callers;
338 }
339 } );
340
341 if ( count( $callersByDB ) >= 2 ) {
342 $dbs = implode( ', ', array_keys( $callersByDB ) );
343 $msg = "Multi-DB transaction [{$dbs}]:\n";
344 foreach ( $callersByDB as $db => $callers ) {
345 $msg .= "$db: " . implode( '; ', $callers ) . "\n";
346 }
347 $this->queryLogger->info( $msg );
348 }
349 }
350
351 public function hasMasterChanges() {
352 $ret = false;
353 $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$ret ) {
354 $ret = $ret || $lb->hasMasterChanges();
355 } );
356
357 return $ret;
358 }
359
360 public function laggedReplicaUsed() {
361 $ret = false;
362 $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$ret ) {
363 $ret = $ret || $lb->laggedReplicaUsed();
364 } );
365
366 return $ret;
367 }
368
369 public function hasOrMadeRecentMasterChanges( $age = null ) {
370 $ret = false;
371 $this->forEachLB( function ( ILoadBalancer $lb ) use ( $age, &$ret ) {
372 $ret = $ret || $lb->hasOrMadeRecentMasterChanges( $age );
373 } );
374 return $ret;
375 }
376
377 public function waitForReplication( array $opts = [] ) {
378 $opts += [
379 'domain' => false,
380 'cluster' => false,
381 'timeout' => $this->cliMode ? 60 : 1,
382 'ifWritesSince' => null
383 ];
384
385 if ( $opts['domain'] === false && isset( $opts['wiki'] ) ) {
386 $opts['domain'] = $opts['wiki']; // b/c
387 }
388
389 // Figure out which clusters need to be checked
391 $lbs = [];
392 if ( $opts['cluster'] !== false ) {
393 $lbs[] = $this->getExternalLB( $opts['cluster'] );
394 } elseif ( $opts['domain'] !== false ) {
395 $lbs[] = $this->getMainLB( $opts['domain'] );
396 } else {
397 $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$lbs ) {
398 $lbs[] = $lb;
399 } );
400 if ( !$lbs ) {
401 return true; // nothing actually used
402 }
403 }
404
405 // Get all the master positions of applicable DBs right now.
406 // This can be faster since waiting on one cluster reduces the
407 // time needed to wait on the next clusters.
408 $masterPositions = array_fill( 0, count( $lbs ), false );
409 foreach ( $lbs as $i => $lb ) {
410 if (
411 // No writes to wait on getting replicated
412 !$lb->hasMasterConnection() ||
413 // No replication; avoid getMasterPos() permissions errors (T29975)
414 !$lb->hasStreamingReplicaServers() ||
415 // No writes since the last replication wait
416 (
417 $opts['ifWritesSince'] &&
418 $lb->lastMasterChangeTimestamp() < $opts['ifWritesSince']
419 )
420 ) {
421 continue; // no need to wait
422 }
423
424 $masterPositions[$i] = $lb->getMasterPos();
425 }
426
427 // Run any listener callbacks *after* getting the DB positions. The more
428 // time spent in the callbacks, the less time is spent in waitForAll().
429 foreach ( $this->replicationWaitCallbacks as $callback ) {
430 $callback();
431 }
432
433 $failed = [];
434 foreach ( $lbs as $i => $lb ) {
435 if ( $masterPositions[$i] ) {
436 // The RDBMS may not support getMasterPos()
437 if ( !$lb->waitForAll( $masterPositions[$i], $opts['timeout'] ) ) {
438 $failed[] = $lb->getServerName( $lb->getWriterIndex() );
439 }
440 }
441 }
442
443 return !$failed;
444 }
445
446 public function setWaitForReplicationListener( $name, callable $callback = null ) {
447 if ( $callback ) {
448 $this->replicationWaitCallbacks[$name] = $callback;
449 } else {
450 unset( $this->replicationWaitCallbacks[$name] );
451 }
452 }
453
454 public function getEmptyTransactionTicket( $fname ) {
455 if ( $this->hasMasterChanges() ) {
456 $this->queryLogger->error(
457 __METHOD__ . ": $fname does not have outer scope",
458 [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
459 );
460
461 return null;
462 }
463
464 return $this->ticket;
465 }
466
467 final public function commitAndWaitForReplication( $fname, $ticket, array $opts = [] ) {
468 if ( $ticket !== $this->ticket ) {
469 $this->perfLogger->error(
470 __METHOD__ . ": $fname does not have outer scope",
471 [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
472 );
473
474 return false;
475 }
476
477 // The transaction owner and any caller with the empty transaction ticket can commit
478 // so that getEmptyTransactionTicket() callers don't risk seeing DBTransactionError.
479 if ( $this->trxRoundId !== false && $fname !== $this->trxRoundId ) {
480 $this->queryLogger->info( "$fname: committing on behalf of {$this->trxRoundId}" );
481 $fnameEffective = $this->trxRoundId;
482 } else {
483 $fnameEffective = $fname;
484 }
485
486 $this->commitMasterChanges( $fnameEffective );
487 $waitSucceeded = $this->waitForReplication( $opts );
488 // If a nested caller committed on behalf of $fname, start another empty $fname
489 // transaction, leaving the caller with the same empty transaction state as before.
490 if ( $fnameEffective !== $fname ) {
491 $this->beginMasterChanges( $fnameEffective );
492 }
493
494 return $waitSucceeded;
495 }
496
497 public function getChronologyProtectorTouched( $dbName ) {
498 return $this->getChronologyProtector()->getTouched( $dbName );
499 }
500
501 public function disableChronologyProtection() {
502 $this->getChronologyProtector()->setEnabled( false );
503 }
504
508 protected function getChronologyProtector() {
509 if ( $this->chronProt ) {
510 return $this->chronProt;
511 }
512
513 $this->chronProt = new ChronologyProtector(
514 $this->memStash,
515 [
516 'ip' => $this->requestInfo['IPAddress'],
517 'agent' => $this->requestInfo['UserAgent'],
518 'clientId' => $this->requestInfo['ChronologyClientId'] ?: null
519 ],
520 $this->requestInfo['ChronologyPositionIndex'],
522 );
523 $this->chronProt->setLogger( $this->replLogger );
524
525 if ( $this->cliMode ) {
526 $this->chronProt->setEnabled( false );
527 } elseif ( $this->requestInfo['ChronologyProtection'] === 'false' ) {
528 // Request opted out of using position wait logic. This is useful for requests
529 // done by the job queue or background ETL that do not have a meaningful session.
530 $this->chronProt->setWaitEnabled( false );
531 } elseif ( $this->memStash instanceof EmptyBagOStuff ) {
532 // No where to store any DB positions and wait for them to appear
533 $this->chronProt->setEnabled( false );
534 $this->replLogger->info( 'Cannot use ChronologyProtector with EmptyBagOStuff' );
535 }
536
537 $this->replLogger->debug(
538 __METHOD__ . ': request info ' .
539 json_encode( $this->requestInfo, JSON_PRETTY_PRINT )
540 );
541
542 return $this->chronProt;
543 }
544
553 protected function shutdownChronologyProtector(
554 ChronologyProtector $cp, $workCallback, $mode, &$cpIndex = null
555 ) {
556 // Record all the master positions needed
557 $this->forEachLB( function ( ILoadBalancer $lb ) use ( $cp ) {
559 } );
560 // Write them to the persistent stash. Try to do something useful by running $work
561 // while ChronologyProtector waits for the stash write to replicate to all DCs.
562 $unsavedPositions = $cp->shutdown( $workCallback, $mode, $cpIndex );
563 if ( $unsavedPositions && $workCallback ) {
564 // Invoke callback in case it did not cache the result yet
565 $workCallback(); // work now to block for less time in waitForAll()
566 }
567 // If the positions failed to write to the stash, at least wait on local datacenter
568 // replica DBs to catch up before responding. Even if there are several DCs, this increases
569 // the chance that the user will see their own changes immediately afterwards. As long
570 // as the sticky DC cookie applies (same domain), this is not even an issue.
571 $this->forEachLB( function ( ILoadBalancer $lb ) use ( $unsavedPositions ) {
572 $masterName = $lb->getServerName( $lb->getWriterIndex() );
573 if ( isset( $unsavedPositions[$masterName] ) ) {
574 $lb->waitForAll( $unsavedPositions[$masterName] );
575 }
576 } );
577 }
578
585 final protected function baseLoadBalancerParams( $owner ) {
586 if ( $this->trxRoundStage === self::ROUND_COMMIT_CALLBACKS ) {
587 $initStage = ILoadBalancer::STAGE_POSTCOMMIT_CALLBACKS;
588 } elseif ( $this->trxRoundStage === self::ROUND_ROLLBACK_CALLBACKS ) {
589 $initStage = ILoadBalancer::STAGE_POSTROLLBACK_CALLBACKS;
590 } else {
591 $initStage = null;
592 }
593
594 return [
595 'localDomain' => $this->localDomain,
596 'readOnlyReason' => $this->readOnlyReason,
597 'srvCache' => $this->srvCache,
598 'wanCache' => $this->wanCache,
599 'profiler' => $this->profiler,
600 'trxProfiler' => $this->trxProfiler,
601 'queryLogger' => $this->queryLogger,
602 'connLogger' => $this->connLogger,
603 'replLogger' => $this->replLogger,
604 'errorLogger' => $this->errorLogger,
605 'deprecationLogger' => $this->deprecationLogger,
606 'hostname' => $this->hostname,
607 'cliMode' => $this->cliMode,
608 'agent' => $this->agent,
609 'maxLag' => $this->maxLag,
610 'defaultGroup' => $this->defaultGroup,
611 'chronologyCallback' => function ( ILoadBalancer $lb ) {
612 // Defer ChronologyProtector construction in case setRequestInfo() ends up
613 // being called later (but before the first connection attempt) (T192611)
614 $this->getChronologyProtector()->applySessionReplicationPosition( $lb );
615 },
616 'roundStage' => $initStage,
617 'ownerId' => $owner
618 ];
619 }
620
624 protected function initLoadBalancer( ILoadBalancer $lb ) {
625 if ( $this->trxRoundId !== false ) {
626 $lb->beginMasterChanges( $this->trxRoundId, $this->id ); // set DBO_TRX
627 }
628
629 $lb->setTableAliases( $this->tableAliases );
630 $lb->setIndexAliases( $this->indexAliases );
631 }
632
633 public function setTableAliases( array $aliases ) {
634 $this->tableAliases = $aliases;
635 }
636
637 public function setIndexAliases( array $aliases ) {
638 $this->indexAliases = $aliases;
639 }
640
641 public function setLocalDomainPrefix( $prefix ) {
642 $this->localDomain = new DatabaseDomain(
643 $this->localDomain->getDatabase(),
644 $this->localDomain->getSchema(),
645 $prefix
646 );
647
648 $this->forEachLB( function ( ILoadBalancer $lb ) use ( $prefix ) {
649 $lb->setLocalDomainPrefix( $prefix );
650 } );
651 }
652
653 public function redefineLocalDomain( $domain ) {
654 $this->closeAll();
655
656 $this->localDomain = DatabaseDomain::newFromId( $domain );
657
658 $this->forEachLB( function ( ILoadBalancer $lb ) {
659 $lb->redefineLocalDomain( $this->localDomain );
660 } );
661 }
662
663 public function closeAll() {
665 $scope = ScopedCallback::newScopedIgnoreUserAbort();
666
667 $this->forEachLBCallMethod( 'closeAll', [ __METHOD__, $this->id ] );
668 }
669
670 public function setAgentName( $agent ) {
671 $this->agent = $agent;
672 }
673
674 public function appendShutdownCPIndexAsQuery( $url, $index ) {
675 $usedCluster = 0;
676 $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$usedCluster ) {
677 $usedCluster |= $lb->hasStreamingReplicaServers();
678 } );
679
680 if ( !$usedCluster ) {
681 return $url; // no master/replica clusters touched
682 }
683
684 return strpos( $url, '?' ) === false ? "$url?cpPosIndex=$index" : "$url&cpPosIndex=$index";
685 }
686
688 return $this->getChronologyProtector()->getClientId();
689 }
690
698 public static function makeCookieValueFromCPIndex( $index, $time, $clientId ) {
699 return "$index@$time#$clientId";
700 }
701
708 public static function getCPInfoFromCookieValue( $value, $minTimestamp ) {
709 static $placeholder = [ 'index' => null, 'clientId' => null ];
710
711 if ( !preg_match( '/^(\d+)@(\d+)#([0-9a-f]{32})$/', $value, $m ) ) {
712 return $placeholder; // invalid
713 }
714
715 $index = (int)$m[1];
716 if ( $index <= 0 ) {
717 return $placeholder; // invalid
718 } elseif ( isset( $m[2] ) && $m[2] !== '' && (int)$m[2] < $minTimestamp ) {
719 return $placeholder; // expired
720 }
721
722 $clientId = ( isset( $m[3] ) && $m[3] !== '' ) ? $m[3] : null;
723
724 return [ 'index' => $index, 'clientId' => $clientId ];
725 }
726
727 public function setRequestInfo( array $info ) {
728 if ( $this->chronProt ) {
729 throw new LogicException( 'ChronologyProtector already initialized' );
730 }
731
732 $this->requestInfo = $info + $this->requestInfo;
733 }
734
739 final protected function getOwnershipId() {
740 return $this->id;
741 }
742
746 private function assertTransactionRoundStage( $stage ) {
747 if ( $this->trxRoundStage !== $stage ) {
748 throw new DBTransactionError(
749 null,
750 "Transaction round stage must be '$stage' (not '{$this->trxRoundStage}')"
751 );
752 }
753 }
754
755 function __destruct() {
756 $this->destroy();
757 }
758}
if( $line===false) $args
Definition cdb.php:64
Class representing a cache/ephemeral data store.
Definition BagOStuff.php:63
A BagOStuff object with no objects in it.
Multi-datacenter aware caching interface.
Helper class for mitigating DB replication lag in order to provide "session consistency".
storeSessionReplicationPosition(ILoadBalancer $lb)
Save the "session consistency" DB replication position for an end-of-life ILoadBalancer.
shutdown(callable $workCallback=null, $mode='sync', &$cpIndex=null)
Notify the ChronologyProtector that the LBFactory is done calling shutdownLB() for now.
Class to handle database/schema/prefix specifications for IDatabase.
An interface for generating database load balancers.
Definition LBFactory.php:40
LoggerInterface $replLogger
Definition LBFactory.php:48
int null $ticket
Ticket used to delegate transaction ownership.
Definition LBFactory.php:91
baseLoadBalancerParams( $owner)
Get parameters to ILoadBalancer::__construct()
callable[] $replicationWaitCallbacks
Definition LBFactory.php:86
isReadyForRoundOperations()
Check if transaction rounds can be started, committed, or rolled back right now.
setTableAliases(array $aliases)
Make certain table names use their own database, schema, and table prefix when passed into SQL querie...
hasMasterChanges()
Determine if any master connection has pending changes.
WANObjectCache $wanCache
Definition LBFactory.php:65
assertTransactionRoundStage( $stage)
shutdownChronologyProtector(ChronologyProtector $cp, $workCallback, $mode, &$cpIndex=null)
Get and record all of the staged DB positions into persistent memory storage.
string[] $indexAliases
Map of (index alias => index)
Definition LBFactory.php:84
getEmptyTransactionTicket( $fname)
Get a token asserting that no transaction writes are active.
hasTransactionRound()
Check if an explicit transaction round is active.
redefineLocalDomain( $domain)
Close all connection and redefine the local domain for testing or schema creation.
LoggerInterface $connLogger
Definition LBFactory.php:50
setRequestInfo(array $info)
LoggerInterface $queryLogger
Definition LBFactory.php:52
commitAndWaitForReplication( $fname, $ticket, array $opts=[])
Convenience method for safely running commitMasterChanges()/waitForReplication()
string $trxRoundStage
One of the ROUND_* class constants.
Definition LBFactory.php:95
callable $errorLogger
Error logger.
Definition LBFactory.php:56
shutdown( $mode=self::SHUTDOWN_CHRONPROT_SYNC, callable $workCallback=null, &$cpIndex=null, &$cpClientId=null)
Prepare all currently tracked (instantiated) load balancers for shutdown.
beginMasterChanges( $fname=__METHOD__)
Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set)
array $requestInfo
Web request information about the client.
Definition LBFactory.php:73
string $hostname
Local hostname of the app server.
Definition LBFactory.php:71
getChronologyProtectorTouched( $dbName)
getLocalDomainID()
Get the local (and default) database domain ID of connection handles.
waitForReplication(array $opts=[])
Waits for the replica DBs to catch up to the current master position.
LoggerInterface $perfLogger
Definition LBFactory.php:54
rollbackMasterChanges( $fname=__METHOD__)
Rollback changes on all master connections.
initLoadBalancer(ILoadBalancer $lb)
$id
var int An identifier for this class instance
Definition LBFactory.php:89
hasOrMadeRecentMasterChanges( $age=null)
Determine if any master connection has pending/written changes from this request.
string $secret
Secret string for HMAC hashing.
Definition LBFactory.php:79
DatabaseDomain $localDomain
Local domain.
Definition LBFactory.php:68
TransactionProfiler $trxProfiler
Definition LBFactory.php:46
ChronologyProtector $chronProt
Definition LBFactory.php:42
string bool $trxRoundId
String if a requested DBO_TRX transaction round is active.
Definition LBFactory.php:93
array[] $tableAliases
$aliases Map of (table => (dbname, schema, prefix) map)
Definition LBFactory.php:82
destroy()
Disables all load balancers.
__construct(array $conf)
Construct a manager of ILoadBalancer objects.
setLocalDomainPrefix( $prefix)
Set a new table prefix for the existing local domain ID for testing.
forEachLBCallMethod( $methodName, array $args=[])
Call a method of each tracked load balancer.
object string $profiler
Class name or object With profileIn/profileOut methods.
Definition LBFactory.php:44
setWaitForReplicationListener( $name, callable $callback=null)
Add a callback to be run in every call to waitForReplication() before waiting.
laggedReplicaUsed()
Detemine if any lagged replica DB connection was used.
appendShutdownCPIndexAsQuery( $url, $index)
Append ?cpPosIndex parameter to a URL for ChronologyProtector purposes if needed.
disableChronologyProtection()
Disable the ChronologyProtector for all load balancers.
setIndexAliases(array $aliases)
Convert certain index names to alternative names before querying the DB.
commitMasterChanges( $fname=__METHOD__, array $options=[])
Commit changes and clear view snapshots on all master connections.
commitAll( $fname=__METHOD__, array $options=[])
Commit open transactions on all connections.
bool $cliMode
Whether this PHP instance is for a CLI script.
Definition LBFactory.php:75
logIfMultiDbTransaction()
Log query info if multi DB transactions are going to be committed now.
static getCPInfoFromCookieValue( $value, $minTimestamp)
string bool $readOnlyReason
Reason all LBs are read-only or false if not.
Definition LBFactory.php:98
static makeCookieValueFromCPIndex( $index, $time, $clientId)
string $agent
Agent name for query profiling.
Definition LBFactory.php:77
callable $deprecationLogger
Deprecation logger.
Definition LBFactory.php:58
closeAll()
Close all open database connections on all open load balancers.
getChronologyProtectorClientId()
Get the client ID of the ChronologyProtector instance.
flushReplicaSnapshots( $fname=__METHOD__)
Commit all replica DB transactions so as to flush any REPEATABLE-READ or SSI snapshot.
Helper class that detects high-contention DB queries via profiling calls.
An interface for generating database load balancers.
getExternalLB( $cluster)
Get a cached (tracked) load balancer for external storage.
getMainLB( $domain=false)
Get a cached (tracked) load balancer object.
forEachLB( $callback, array $params=[])
Execute a function for each currently tracked (instantiated) load balancer.
Database cluster connection, tracking, load balancing, and transaction manager interface.
runMasterTransactionIdleCallbacks( $fname=__METHOD__, $owner=null)
Consume and run all pending post-COMMIT/ROLLBACK callbacks and commit dangling transactions.
beginMasterChanges( $fname=__METHOD__, $owner=null)
Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set)
hasOrMadeRecentMasterChanges( $age=null)
Check if this load balancer object had any recent or still pending writes issued against it by this P...
pendingMasterChangeCallers()
Get the list of callers that have pending master changes.
hasMasterChanges()
Whether there are pending changes or callbacks in a transaction by this thread.
setTableAliases(array $aliases)
Make certain table names use their own database, schema, and table prefix when passed into SQL querie...
redefineLocalDomain( $domain)
Close all connection and redefine the local domain for testing or schema creation.
getWriterIndex()
Get the server index of the master server.
runMasterTransactionListenerCallbacks( $fname=__METHOD__, $owner=null)
Run all recurring post-COMMIT/ROLLBACK listener callbacks.
laggedReplicaUsed()
Checks whether the database for generic connections this request was both:
setIndexAliases(array $aliases)
Convert certain index names to alternative names before querying the DB.
setLocalDomainPrefix( $prefix)
Set a new table prefix for the existing local domain ID for testing.
finalizeMasterChanges( $fname=__METHOD__, $owner=null)
Run pre-commit callbacks and defer execution of post-commit callbacks.
hasStreamingReplicaServers()
Whether any replica servers use streaming replication from the master server.
getServerName( $i)
Get the host name or IP address of the server with the specified index.
waitForAll( $pos, $timeout=null)
Set the master wait position and wait for ALL replica DBs to catch up to it.