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