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