MediaWiki  master
LBFactory.php
Go to the documentation of this file.
1 <?php
24 namespace Wikimedia\Rdbms;
25 
29 use BagOStuff;
30 use EmptyBagOStuff;
31 use WANObjectCache;
32 use Exception;
34 use LogicException;
35 
40 abstract 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;
95  private $trxRoundStage = self::ROUND_CURSORY;
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
271  $this->logIfMultiDbTransaction();
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;
276  $e = $this->executePostTransactionCallbacks();
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 
301  private function executePostTransactionCallbacks() {
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'],
521  $this->secret
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 
687  public function getChronologyProtectorClientId() {
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 }
Helper class that detects high-contention DB queries via profiling calls.
disableChronologyProtection()
Disable the ChronologyProtector for all load balancers.
Definition: LBFactory.php:501
ChronologyProtector $chronProt
Definition: LBFactory.php:42
array $requestInfo
Web request information about the client.
Definition: LBFactory.php:73
flushReplicaSnapshots( $fname=__METHOD__)
Commit all replica DB transactions so as to flush any REPEATABLE-READ or SSI snapshot.
Definition: LBFactory.php:214
commitAll( $fname=__METHOD__, array $options=[])
Commit open transactions on all connections.
Definition: LBFactory.php:224
baseLoadBalancerParams( $owner)
Get parameters to ILoadBalancer::__construct()
Definition: LBFactory.php:585
string bool $readOnlyReason
Reason all LBs are read-only or false if not.
Definition: LBFactory.php:98
shutdown(callable $workCallback=null, $mode='sync', &$cpIndex=null)
Notify the ChronologyProtector that the LBFactory is done calling shutdownLB() for now...
An interface for generating database load balancers.
Definition: LBFactory.php:40
LoggerInterface $replLogger
Definition: LBFactory.php:48
setRequestInfo(array $info)
Definition: LBFactory.php:727
hasTransactionRound()
Check if an explicit transaction round is active.
Definition: LBFactory.php:320
getChronologyProtectorClientId()
Get the client ID of the ChronologyProtector instance.
Definition: LBFactory.php:687
TransactionProfiler $trxProfiler
Definition: LBFactory.php:46
string $hostname
Local hostname of the app server.
Definition: LBFactory.php:71
getLocalDomainID()
Get the local (and default) database domain ID of connection handles.
Definition: LBFactory.php:170
commitAndWaitForReplication( $fname, $ticket, array $opts=[])
Convenience method for safely running commitMasterChanges()/waitForReplication()
Definition: LBFactory.php:467
laggedReplicaUsed()
Detemine if any lagged replica DB connection was used.
Definition: LBFactory.php:360
DatabaseDomain $localDomain
Local domain.
Definition: LBFactory.php:68
redefineLocalDomain( $domain)
Close all connection and redefine the local domain for testing or schema creation.
__construct(array $conf)
Construct a manager of ILoadBalancer objects.
Definition: LBFactory.php:116
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.
Definition: LBFactory.php:205
LoggerInterface $connLogger
Definition: LBFactory.php:50
isReadyForRoundOperations()
Check if transaction rounds can be started, committed, or rolled back right now.
Definition: LBFactory.php:324
commitMasterChanges( $fname=__METHOD__, array $options=[])
Commit changes and clear view snapshots on all master connections.
Definition: LBFactory.php:248
runMasterTransactionIdleCallbacks( $fname=__METHOD__, $owner=null)
Consume and run all pending post-COMMIT/ROLLBACK callbacks and commit dangling transactions.
LoggerInterface $perfLogger
Definition: LBFactory.php:54
waitForAll( $pos, $timeout=null)
Set the master wait position and wait for ALL replica DBs to catch up to it.
forEachLB( $callback, array $params=[])
Execute a function for each currently tracked (instantiated) load balancer.
if( $line===false) $args
Definition: cdb.php:64
hasMasterChanges()
Whether there are pending changes or callbacks in a transaction by this thread.
getServerName( $i)
Get the host name or IP address of the server with the specified index.
static newEmpty()
Get an instance that wraps EmptyBagOStuff.
setIndexAliases(array $aliases)
Convert certain index names to alternative names before querying the DB.
string [] $indexAliases
Map of (index alias => index)
Definition: LBFactory.php:84
Helper class for mitigating DB replication lag in order to provide "session consistency".
hasMasterChanges()
Determine if any master connection has pending changes.
Definition: LBFactory.php:351
initLoadBalancer(ILoadBalancer $lb)
Definition: LBFactory.php:624
setTableAliases(array $aliases)
Make certain table names use their own database, schema, and table prefix when passed into SQL querie...
Definition: LBFactory.php:633
LoggerInterface $queryLogger
Definition: LBFactory.php:52
callable [] $replicationWaitCallbacks
Definition: LBFactory.php:86
string bool $trxRoundId
String if a requested DBO_TRX transaction round is active.
Definition: LBFactory.php:93
static makeCookieValueFromCPIndex( $index, $time, $clientId)
Definition: LBFactory.php:698
getEmptyTransactionTicket( $fname)
Get a token asserting that no transaction writes are active.
Definition: LBFactory.php:454
hasOrMadeRecentMasterChanges( $age=null)
Determine if any master connection has pending/written changes from this request. ...
Definition: LBFactory.php:369
laggedReplicaUsed()
Checks whether the database for generic connections this request was both:
storeSessionReplicationPosition(ILoadBalancer $lb)
Save the "session consistency" DB replication position for an end-of-life ILoadBalancer.
hasStreamingReplicaServers()
Whether any replica servers use streaming replication from the master server.
getChronologyProtectorTouched( $dbName)
Definition: LBFactory.php:497
shutdownChronologyProtector(ChronologyProtector $cp, $workCallback, $mode, &$cpIndex=null)
Get and record all of the staged DB positions into persistent memory storage.
Definition: LBFactory.php:553
bool $cliMode
Whether this PHP instance is for a CLI script.
Definition: LBFactory.php:75
WANObjectCache $wanCache
Definition: LBFactory.php:65
beginMasterChanges( $fname=__METHOD__, $owner=null)
Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set)
appendShutdownCPIndexAsQuery( $url, $index)
Append ?cpPosIndex parameter to a URL for ChronologyProtector purposes if needed. ...
Definition: LBFactory.php:674
setWaitForReplicationListener( $name, callable $callback=null)
Add a callback to be run in every call to waitForReplication() before waiting.
Definition: LBFactory.php:446
int null $ticket
Ticket used to delegate transaction ownership.
Definition: LBFactory.php:91
destroy()
Disables all load balancers.
Definition: LBFactory.php:163
getMainLB( $domain=false)
Get a cached (tracked) load balancer object.
string $agent
Agent name for query profiling.
Definition: LBFactory.php:77
setIndexAliases(array $aliases)
Convert certain index names to alternative names before querying the DB.
Definition: LBFactory.php:637
waitForReplication(array $opts=[])
Waits for the replica DBs to catch up to the current master position.
Definition: LBFactory.php:377
hasOrMadeRecentMasterChanges( $age=null)
Check if this load balancer object had any recent or still pending writes issued against it by this P...
shutdown( $mode=self::SHUTDOWN_CHRONPROT_SYNC, callable $workCallback=null, &$cpIndex=null, &$cpClientId=null)
Prepare all currently tracked (instantiated) load balancers for shutdown.
Definition: LBFactory.php:178
$id
var int An identifier for this class instance
Definition: LBFactory.php:89
runMasterTransactionListenerCallbacks( $fname=__METHOD__, $owner=null)
Run all recurring post-COMMIT/ROLLBACK listener callbacks.
getWriterIndex()
Get the server index of the master server.
Database cluster connection, tracking, load balancing, and transaction manager interface.
callable $deprecationLogger
Deprecation logger.
Definition: LBFactory.php:58
array [] $tableAliases
$aliases Map of (table => (dbname, schema, prefix) map)
Definition: LBFactory.php:82
An interface for generating database load balancers.
Definition: ILBFactory.php:33
object string $profiler
Class name or object With profileIn/profileOut methods.
Definition: LBFactory.php:44
Class to handle database/schema/prefix specifications for IDatabase.
static getCPInfoFromCookieValue( $value, $minTimestamp)
Definition: LBFactory.php:708
rollbackMasterChanges( $fname=__METHOD__)
Rollback changes on all master connections.
Definition: LBFactory.php:284
getExternalLB( $cluster)
Get a cached (tracked) load balancer for external storage.
string $secret
Secret string for HMAC hashing.
Definition: LBFactory.php:79
setTableAliases(array $aliases)
Make certain table names use their own database, schema, and table prefix when passed into SQL querie...
beginMasterChanges( $fname=__METHOD__)
Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set)
Definition: LBFactory.php:230
logIfMultiDbTransaction()
Log query info if multi DB transactions are going to be committed now.
Definition: LBFactory.php:331
callable $errorLogger
Error logger.
Definition: LBFactory.php:56
string $trxRoundStage
One of the ROUND_* class constants.
Definition: LBFactory.php:95
closeAll()
Close all open database connections on all open load balancers.
Definition: LBFactory.php:663
finalizeMasterChanges( $fname=__METHOD__, $owner=null)
Run pre-commit callbacks and defer execution of post-commit callbacks.
pendingMasterChangeCallers()
Get the list of callers that have pending master changes.
assertTransactionRoundStage( $stage)
Definition: LBFactory.php:746
redefineLocalDomain( $domain)
Close all connection and redefine the local domain for testing or schema creation.
Definition: LBFactory.php:653
setLocalDomainPrefix( $prefix)
Set a new table prefix for the existing local domain ID for testing.
Definition: LBFactory.php:641