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  if ( $opts['domain'] === false && isset( $opts['wiki'] ) ) {
422  $opts['domain'] = $opts['wiki']; // b/c
423  }
424 
425  // Figure out which clusters need to be checked
427  $lbs = [];
428  if ( $opts['cluster'] !== false ) {
429  $lbs[] = $this->getExternalLB( $opts['cluster'] );
430  } elseif ( $opts['domain'] !== false ) {
431  $lbs[] = $this->getMainLB( $opts['domain'] );
432  } else {
433  $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$lbs ) {
434  $lbs[] = $lb;
435  } );
436  if ( !$lbs ) {
437  return true; // nothing actually used
438  }
439  }
440 
441  // Get all the master positions of applicable DBs right now.
442  // This can be faster since waiting on one cluster reduces the
443  // time needed to wait on the next clusters.
444  $masterPositions = array_fill( 0, count( $lbs ), false );
445  foreach ( $lbs as $i => $lb ) {
446  if (
447  // No writes to wait on getting replicated
448  !$lb->hasMasterConnection() ||
449  // No replication; avoid getMasterPos() permissions errors (T29975)
450  !$lb->hasStreamingReplicaServers() ||
451  // No writes since the last replication wait
452  (
453  $opts['ifWritesSince'] &&
454  $lb->lastMasterChangeTimestamp() < $opts['ifWritesSince']
455  )
456  ) {
457  continue; // no need to wait
458  }
459 
460  $masterPositions[$i] = $lb->getMasterPos();
461  }
462 
463  // Run any listener callbacks *after* getting the DB positions. The more
464  // time spent in the callbacks, the less time is spent in waitForAll().
465  foreach ( $this->replicationWaitCallbacks as $callback ) {
466  $callback();
467  }
468 
469  $failed = [];
470  foreach ( $lbs as $i => $lb ) {
471  if ( $masterPositions[$i] ) {
472  // The RDBMS may not support getMasterPos()
473  if ( !$lb->waitForAll( $masterPositions[$i], $opts['timeout'] ) ) {
474  $failed[] = $lb->getServerName( $lb->getWriterIndex() );
475  }
476  }
477  }
478 
479  return !$failed;
480  }
481 
482  public function setWaitForReplicationListener( $name, callable $callback = null ) {
483  if ( $callback ) {
484  $this->replicationWaitCallbacks[$name] = $callback;
485  } else {
486  unset( $this->replicationWaitCallbacks[$name] );
487  }
488  }
489 
490  public function getEmptyTransactionTicket( $fname ) {
491  if ( $this->hasMasterChanges() ) {
492  $this->queryLogger->error(
493  __METHOD__ . ": $fname does not have outer scope",
494  [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
495  );
496 
497  return null;
498  }
499 
500  return $this->ticket;
501  }
502 
503  final public function commitAndWaitForReplication( $fname, $ticket, array $opts = [] ) {
504  if ( $ticket !== $this->ticket ) {
505  $this->perfLogger->error(
506  __METHOD__ . ": $fname does not have outer scope",
507  [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
508  );
509 
510  return false;
511  }
512 
513  // The transaction owner and any caller with the empty transaction ticket can commit
514  // so that getEmptyTransactionTicket() callers don't risk seeing DBTransactionError.
515  if ( $this->trxRoundId !== false && $fname !== $this->trxRoundId ) {
516  $this->queryLogger->info( "$fname: committing on behalf of {$this->trxRoundId}" );
517  $fnameEffective = $this->trxRoundId;
518  } else {
519  $fnameEffective = $fname;
520  }
521 
522  $this->commitMasterChanges( $fnameEffective );
523  $waitSucceeded = $this->waitForReplication( $opts );
524  // If a nested caller committed on behalf of $fname, start another empty $fname
525  // transaction, leaving the caller with the same empty transaction state as before.
526  if ( $fnameEffective !== $fname ) {
527  $this->beginMasterChanges( $fnameEffective );
528  }
529 
530  return $waitSucceeded;
531  }
532 
533  public function getChronologyProtectorTouched( $domain = false ) {
534  return $this->getChronologyProtector()->getTouched( $this->getMainLB( $domain ) );
535  }
536 
537  public function disableChronologyProtection() {
538  $this->getChronologyProtector()->setEnabled( false );
539  }
540 
544  protected function getChronologyProtector() {
545  if ( $this->chronProt ) {
546  return $this->chronProt;
547  }
548 
549  $this->chronProt = new ChronologyProtector(
550  $this->memStash,
551  [
552  'ip' => $this->requestInfo['IPAddress'],
553  'agent' => $this->requestInfo['UserAgent'],
554  'clientId' => $this->requestInfo['ChronologyClientId'] ?: null
555  ],
556  $this->requestInfo['ChronologyPositionIndex'],
557  $this->secret
558  );
559  $this->chronProt->setLogger( $this->replLogger );
560 
561  if ( $this->cliMode ) {
562  $this->chronProt->setEnabled( false );
563  } elseif ( $this->requestInfo['ChronologyProtection'] === 'false' ) {
564  // Request opted out of using position wait logic. This is useful for requests
565  // done by the job queue or background ETL that do not have a meaningful session.
566  $this->chronProt->setWaitEnabled( false );
567  } elseif ( $this->memStash instanceof EmptyBagOStuff ) {
568  // No where to store any DB positions and wait for them to appear
569  $this->chronProt->setEnabled( false );
570  $this->replLogger->info( 'Cannot use ChronologyProtector with EmptyBagOStuff' );
571  }
572 
573  $this->replLogger->debug(
574  __METHOD__ . ': request info ' .
575  json_encode( $this->requestInfo, JSON_PRETTY_PRINT )
576  );
577 
578  return $this->chronProt;
579  }
580 
589  protected function shutdownChronologyProtector(
590  ChronologyProtector $cp, $workCallback, $mode, &$cpIndex = null
591  ) {
592  // Record all the master positions needed
593  $this->forEachLB( function ( ILoadBalancer $lb ) use ( $cp ) {
595  } );
596  // Write them to the persistent stash. Try to do something useful by running $work
597  // while ChronologyProtector waits for the stash write to replicate to all DCs.
598  $unsavedPositions = $cp->shutdown( $workCallback, $mode, $cpIndex );
599  if ( $unsavedPositions && $workCallback ) {
600  // Invoke callback in case it did not cache the result yet
601  $workCallback(); // work now to block for less time in waitForAll()
602  }
603  // If the positions failed to write to the stash, at least wait on local datacenter
604  // replica DBs to catch up before responding. Even if there are several DCs, this increases
605  // the chance that the user will see their own changes immediately afterwards. As long
606  // as the sticky DC cookie applies (same domain), this is not even an issue.
607  $this->forEachLB( function ( ILoadBalancer $lb ) use ( $unsavedPositions ) {
608  $masterName = $lb->getServerName( $lb->getWriterIndex() );
609  if ( isset( $unsavedPositions[$masterName] ) ) {
610  $lb->waitForAll( $unsavedPositions[$masterName] );
611  }
612  } );
613  }
614 
621  final protected function baseLoadBalancerParams( $owner ) {
622  if ( $this->trxRoundStage === self::ROUND_COMMIT_CALLBACKS ) {
623  $initStage = ILoadBalancer::STAGE_POSTCOMMIT_CALLBACKS;
624  } elseif ( $this->trxRoundStage === self::ROUND_ROLLBACK_CALLBACKS ) {
625  $initStage = ILoadBalancer::STAGE_POSTROLLBACK_CALLBACKS;
626  } else {
627  $initStage = null;
628  }
629 
630  return [
631  'localDomain' => $this->localDomain,
632  'readOnlyReason' => $this->readOnlyReason,
633  'srvCache' => $this->srvCache,
634  'wanCache' => $this->wanCache,
635  'profiler' => $this->profiler,
636  'trxProfiler' => $this->trxProfiler,
637  'queryLogger' => $this->queryLogger,
638  'connLogger' => $this->connLogger,
639  'replLogger' => $this->replLogger,
640  'errorLogger' => $this->errorLogger,
641  'deprecationLogger' => $this->deprecationLogger,
642  'hostname' => $this->hostname,
643  'cliMode' => $this->cliMode,
644  'agent' => $this->agent,
645  'maxLag' => $this->maxLag,
646  'defaultGroup' => $this->defaultGroup,
647  'chronologyCallback' => function ( ILoadBalancer $lb ) {
648  // Defer ChronologyProtector construction in case setRequestInfo() ends up
649  // being called later (but before the first connection attempt) (T192611)
650  $this->getChronologyProtector()->applySessionReplicationPosition( $lb );
651  },
652  'roundStage' => $initStage,
653  'ownerId' => $owner
654  ];
655  }
656 
660  protected function initLoadBalancer( ILoadBalancer $lb ) {
661  if ( $this->trxRoundId !== false ) {
662  $lb->beginMasterChanges( $this->trxRoundId, $this->id ); // set DBO_TRX
663  }
664 
665  $lb->setTableAliases( $this->tableAliases );
666  $lb->setIndexAliases( $this->indexAliases );
667  $lb->setDomainAliases( $this->domainAliases );
668  }
669 
670  public function setTableAliases( array $aliases ) {
671  $this->tableAliases = $aliases;
672  }
673 
674  public function setIndexAliases( array $aliases ) {
675  $this->indexAliases = $aliases;
676  }
677 
678  public function setDomainAliases( array $aliases ) {
679  $this->domainAliases = $aliases;
680  }
681 
683  return $this->trxProfiler;
684  }
685 
686  public function setLocalDomainPrefix( $prefix ) {
687  $this->localDomain = new DatabaseDomain(
688  $this->localDomain->getDatabase(),
689  $this->localDomain->getSchema(),
690  $prefix
691  );
692 
693  $this->forEachLB( function ( ILoadBalancer $lb ) use ( $prefix ) {
694  $lb->setLocalDomainPrefix( $prefix );
695  } );
696  }
697 
698  public function redefineLocalDomain( $domain ) {
699  $this->closeAll();
700 
701  $this->localDomain = DatabaseDomain::newFromId( $domain );
702 
703  $this->forEachLB( function ( ILoadBalancer $lb ) {
704  $lb->redefineLocalDomain( $this->localDomain );
705  } );
706  }
707 
708  public function closeAll() {
710  $scope = ScopedCallback::newScopedIgnoreUserAbort();
711 
712  $this->forEachLBCallMethod( 'closeAll', [ __METHOD__, $this->id ] );
713  }
714 
715  public function setAgentName( $agent ) {
716  $this->agent = $agent;
717  }
718 
719  public function appendShutdownCPIndexAsQuery( $url, $index ) {
720  $usedCluster = 0;
721  $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$usedCluster ) {
722  $usedCluster |= $lb->hasStreamingReplicaServers();
723  } );
724 
725  if ( !$usedCluster ) {
726  return $url; // no master/replica clusters touched
727  }
728 
729  return strpos( $url, '?' ) === false ? "$url?cpPosIndex=$index" : "$url&cpPosIndex=$index";
730  }
731 
732  public function getChronologyProtectorClientId() {
733  return $this->getChronologyProtector()->getClientId();
734  }
735 
743  public static function makeCookieValueFromCPIndex( $index, $time, $clientId ) {
744  return "$index@$time#$clientId";
745  }
746 
753  public static function getCPInfoFromCookieValue( $value, $minTimestamp ) {
754  static $placeholder = [ 'index' => null, 'clientId' => null ];
755 
756  if ( !preg_match( '/^(\d+)@(\d+)#([0-9a-f]{32})$/', $value, $m ) ) {
757  return $placeholder; // invalid
758  }
759 
760  $index = (int)$m[1];
761  if ( $index <= 0 ) {
762  return $placeholder; // invalid
763  } elseif ( isset( $m[2] ) && $m[2] !== '' && (int)$m[2] < $minTimestamp ) {
764  return $placeholder; // expired
765  }
766 
767  $clientId = ( isset( $m[3] ) && $m[3] !== '' ) ? $m[3] : null;
768 
769  return [ 'index' => $index, 'clientId' => $clientId ];
770  }
771 
772  public function setRequestInfo( array $info ) {
773  if ( $this->chronProt ) {
774  throw new LogicException( 'ChronologyProtector already initialized' );
775  }
776 
777  $this->requestInfo = $info + $this->requestInfo;
778  }
779 
780  public function setDefaultReplicationWaitTimeout( $seconds ) {
782  $this->replicationWaitTimeout = max( 1, (int)$seconds );
783 
784  return $old;
785  }
786 
791  final protected function getOwnershipId() {
792  return $this->id;
793  }
794 
798  private function assertTransactionRoundStage( $stage ) {
799  if ( $this->trxRoundStage !== $stage ) {
800  throw new DBTransactionError(
801  null,
802  "Transaction round stage must be '$stage' (not '{$this->trxRoundStage}')"
803  );
804  }
805  }
806 
807  public function __destruct() {
808  $this->destroy();
809  }
810 }
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:719
Wikimedia\Rdbms\LBFactory\getTransactionProfiler
getTransactionProfiler()
Get a TransactionProfiler used by this instance.
Definition: LBFactory.php:682
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:686
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:807
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:503
Wikimedia\Rdbms\LBFactory\$secret
string $secret
Secret string for HMAC hashing.
Definition: LBFactory.php:80
Wikimedia\Rdbms\LBFactory\setDefaultReplicationWaitTimeout
setDefaultReplicationWaitTimeout( $seconds)
Definition: LBFactory.php:780
Wikimedia\Rdbms\LBFactory\$defaultGroup
string null $defaultGroup
Definition: LBFactory.php:106
Wikimedia\Rdbms\LBFactory\initLoadBalancer
initLoadBalancer(ILoadBalancer $lb)
Definition: LBFactory.php:660
Wikimedia\Rdbms\LBFactory\resolveDomainID
resolveDomainID( $domain)
Definition: LBFactory.php:183
Wikimedia\Rdbms\LBFactory\getOwnershipId
getOwnershipId()
Definition: LBFactory.php:791
Wikimedia\Rdbms\LBFactory\getChronologyProtector
getChronologyProtector()
Definition: LBFactory.php:544
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:65
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:670
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:708
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:753
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:621
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:698
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:589
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:490
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:715
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:120
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
Definition: DBTransactionError.php:27
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:537
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:798
Wikimedia\Rdbms\LBFactory\setIndexAliases
setIndexAliases(array $aliases)
Convert certain index names to alternative names before querying the DB.
Definition: LBFactory.php:674
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:732
Wikimedia\Rdbms\LBFactory\getChronologyProtectorTouched
getChronologyProtectorTouched( $domain=false)
Definition: LBFactory.php:533
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:743
Wikimedia\Rdbms\LBFactory\setDomainAliases
setDomainAliases(array $aliases)
Convert certain database domains to alternative ones.
Definition: LBFactory.php:678
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:482
Wikimedia\Rdbms\ILBFactory
An interface for generating database load balancers.
Definition: ILBFactory.php:33
Wikimedia\Rdbms\LBFactory\setRequestInfo
setRequestInfo(array $info)
Definition: LBFactory.php:772