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\RequestTimeout\CriticalSectionProvider;
36 use Wikimedia\ScopedCallback;
37 
42 abstract class LBFactory implements ILBFactory {
44  private $chronProt;
46  private $csProvider;
51  private $profiler;
53  private $trxProfiler;
55  private $replLogger;
57  private $connLogger;
59  private $queryLogger;
61  private $perfLogger;
63  private $errorLogger;
66 
68  protected $cpStash;
70  protected $srvCache;
72  protected $wanCache;
73 
75  protected $localDomain;
76 
78  private $requestInfo;
80  private $cliMode;
82  private $agent;
84  private $secret;
85 
87  private $tableAliases = [];
89  private $indexAliases = [];
91  private $domainAliases = [];
94 
96  private $id;
98  private $ticket;
100  private $trxRoundId = false;
105 
107  protected $readOnlyReason = false;
108 
110  private $defaultGroup = null;
111 
113  protected $maxLag;
114 
116  private $nonLocalDomainCache = [];
117 
118  private const ROUND_CURSORY = 'cursory';
119  private const ROUND_BEGINNING = 'within-begin';
120  private const ROUND_COMMITTING = 'within-commit';
121  private const ROUND_ROLLING_BACK = 'within-rollback';
122  private const ROUND_COMMIT_CALLBACKS = 'within-commit-callbacks';
123  private const ROUND_ROLLBACK_CALLBACKS = 'within-rollback-callbacks';
124 
125  private static $loggerFields =
126  [ 'replLogger', 'connLogger', 'queryLogger', 'perfLogger' ];
127 
128  public function __construct( array $conf ) {
129  $this->localDomain = isset( $conf['localDomain'] )
130  ? DatabaseDomain::newFromId( $conf['localDomain'] )
132 
133  $this->maxLag = $conf['maxLag'] ?? null;
134  if ( isset( $conf['readOnlyReason'] ) && is_string( $conf['readOnlyReason'] ) ) {
135  $this->readOnlyReason = $conf['readOnlyReason'];
136  }
137 
138  $this->cpStash = $conf['cpStash'] ?? new EmptyBagOStuff();
139  $this->srvCache = $conf['srvCache'] ?? new EmptyBagOStuff();
140  $this->wanCache = $conf['wanCache'] ?? WANObjectCache::newEmpty();
141 
142  foreach ( self::$loggerFields as $key ) {
143  $this->$key = $conf[$key] ?? new NullLogger();
144  }
145  $this->errorLogger = $conf['errorLogger'] ?? static function ( Throwable $e ) {
146  trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING );
147  };
148  $this->deprecationLogger = $conf['deprecationLogger'] ?? static function ( $msg ) {
149  trigger_error( $msg, E_USER_DEPRECATED );
150  };
151 
152  $this->profiler = $conf['profiler'] ?? null;
153  $this->trxProfiler = $conf['trxProfiler'] ?? new TransactionProfiler();
154 
155  $this->csProvider = $conf['criticalSectionProvider'] ?? null;
156 
157  $this->requestInfo = [
158  'IPAddress' => $_SERVER[ 'REMOTE_ADDR' ] ?? '',
159  'UserAgent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
160  // Headers application can inject via LBFactory::setRequestInfo()
161  'ChronologyProtection' => null,
162  'ChronologyClientId' => null, // prior $cpClientId value from LBFactory::shutdown()
163  'ChronologyPositionIndex' => null // prior $cpIndex value from LBFactory::shutdown()
164  ];
165 
166  $this->cliMode = $conf['cliMode'] ?? ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' );
167  $this->agent = $conf['agent'] ?? '';
168  $this->defaultGroup = $conf['defaultGroup'] ?? null;
169  $this->secret = $conf['secret'] ?? '';
170  $this->replicationWaitTimeout = $this->cliMode ? 60 : 1;
171 
172  static $nextId, $nextTicket;
173  $this->id = $nextId = ( is_int( $nextId ) ? $nextId++ : mt_rand() );
174  $this->ticket = $nextTicket = ( is_int( $nextTicket ) ? $nextTicket++ : mt_rand() );
175  }
176 
177  public function destroy() {
179  $scope = ScopedCallback::newScopedIgnoreUserAbort();
180 
181  $this->forEachLBCallMethod( 'disable', [ __METHOD__, $this->id ] );
182  }
183 
184  public function getLocalDomainID() {
185  return $this->localDomain->getId();
186  }
187 
188  public function resolveDomainID( $domain ) {
189  return $this->resolveDomainInstance( $domain )->getId();
190  }
191 
196  final protected function resolveDomainInstance( $domain ) {
197  if ( $domain instanceof DatabaseDomain ) {
198  return $domain; // already a domain instance
199  } elseif ( $domain === false || $domain === $this->localDomain->getId() ) {
200  return $this->localDomain;
201  } elseif ( isset( $this->domainAliases[$domain] ) ) {
202  // This array acts as both the original map and as instance cache.
203  // Instances pass-through DatabaseDomain::newFromId as-is.
204  $this->domainAliases[$domain] =
205  DatabaseDomain::newFromId( $this->domainAliases[$domain] );
206 
207  return $this->domainAliases[$domain];
208  }
209 
210  $cachedDomain = $this->nonLocalDomainCache[$domain] ?? null;
211  if ( $cachedDomain === null ) {
212  $cachedDomain = DatabaseDomain::newFromId( $domain );
213  $this->nonLocalDomainCache = [ $domain => $cachedDomain ];
214  }
215 
216  return $cachedDomain;
217  }
218 
219  public function shutdown(
220  $flags = self::SHUTDOWN_NORMAL,
221  callable $workCallback = null,
222  &$cpIndex = null,
223  &$cpClientId = null
224  ) {
226  $scope = ScopedCallback::newScopedIgnoreUserAbort();
227 
229  if ( ( $flags & self::SHUTDOWN_NO_CHRONPROT ) != self::SHUTDOWN_NO_CHRONPROT ) {
230  $this->shutdownChronologyProtector( $chronProt, $workCallback, $cpIndex );
231  $this->replLogger->debug( __METHOD__ . ': finished ChronologyProtector shutdown' );
232  }
233  $cpClientId = $chronProt->getClientId();
234 
235  $this->commitMasterChanges( __METHOD__ ); // sanity
236 
237  $this->replLogger->debug( 'LBFactory shutdown completed' );
238  }
239 
246  protected function forEachLBCallMethod( $methodName, array $args = [] ) {
247  $this->forEachLB(
248  static function ( ILoadBalancer $loadBalancer, $methodName, array $args ) {
249  $loadBalancer->$methodName( ...$args );
250  },
251  [ $methodName, $args ]
252  );
253  }
254 
255  public function flushReplicaSnapshots( $fname = __METHOD__ ) {
256  if ( $this->trxRoundId !== false && $this->trxRoundId !== $fname ) {
257  $this->queryLogger->warning(
258  "$fname: transaction round '{$this->trxRoundId}' still running",
259  [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
260  );
261  }
262  $this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname, $this->id ] );
263  }
264 
265  final public function commitAll( $fname = __METHOD__, array $options = [] ) {
266  $this->commitMasterChanges( $fname, $options );
267  $this->forEachLBCallMethod( 'flushMasterSnapshots', [ $fname, $this->id ] );
268  $this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname, $this->id ] );
269  }
270 
271  final public function beginMasterChanges( $fname = __METHOD__ ) {
272  $this->assertTransactionRoundStage( self::ROUND_CURSORY );
274  $scope = ScopedCallback::newScopedIgnoreUserAbort();
275 
276  $this->trxRoundStage = self::ROUND_BEGINNING;
277  if ( $this->trxRoundId !== false ) {
278  throw new DBTransactionError(
279  null,
280  "$fname: transaction round '{$this->trxRoundId}' already started"
281  );
282  }
283  $this->trxRoundId = $fname;
284  // Set DBO_TRX flags on all appropriate DBs
285  $this->forEachLBCallMethod( 'beginMasterChanges', [ $fname, $this->id ] );
286  $this->trxRoundStage = self::ROUND_CURSORY;
287  }
288 
289  final public function commitMasterChanges( $fname = __METHOD__, array $options = [] ) {
290  $this->assertTransactionRoundStage( self::ROUND_CURSORY );
292  $scope = ScopedCallback::newScopedIgnoreUserAbort();
293 
294  $this->trxRoundStage = self::ROUND_COMMITTING;
295  if ( $this->trxRoundId !== false && $this->trxRoundId !== $fname ) {
296  throw new DBTransactionError(
297  null,
298  "$fname: transaction round '{$this->trxRoundId}' still running"
299  );
300  }
301  // Run pre-commit callbacks and suppress post-commit callbacks, aborting on failure
302  do {
303  $count = 0; // number of callbacks executed this iteration
304  $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$count, $fname ) {
305  $count += $lb->finalizeMasterChanges( $fname, $this->id );
306  } );
307  } while ( $count > 0 );
308  $this->trxRoundId = false;
309  // Perform pre-commit checks, aborting on failure
310  $this->forEachLBCallMethod( 'approveMasterChanges', [ $options, $fname, $this->id ] );
311  // Log the DBs and methods involved in multi-DB transactions
312  $this->logIfMultiDbTransaction();
313  // Actually perform the commit on all master DB connections and revert DBO_TRX
314  $this->forEachLBCallMethod( 'commitMasterChanges', [ $fname, $this->id ] );
315  // Run all post-commit callbacks in a separate step
316  $this->trxRoundStage = self::ROUND_COMMIT_CALLBACKS;
317  $e = $this->executePostTransactionCallbacks();
318  $this->trxRoundStage = self::ROUND_CURSORY;
319  // Throw any last post-commit callback error
320  if ( $e instanceof Exception ) {
321  throw $e;
322  }
323  }
324 
325  final public function rollbackMasterChanges( $fname = __METHOD__ ) {
327  $scope = ScopedCallback::newScopedIgnoreUserAbort();
328 
329  $this->trxRoundStage = self::ROUND_ROLLING_BACK;
330  $this->trxRoundId = false;
331  // Actually perform the rollback on all master DB connections and revert DBO_TRX
332  $this->forEachLBCallMethod( 'rollbackMasterChanges', [ $fname, $this->id ] );
333  // Run all post-commit callbacks in a separate step
334  $this->trxRoundStage = self::ROUND_ROLLBACK_CALLBACKS;
336  $this->trxRoundStage = self::ROUND_CURSORY;
337  }
338 
342  private function executePostTransactionCallbacks() {
343  $fname = __METHOD__;
344  // Run all post-commit callbacks until new ones stop getting added
345  $e = null; // first callback exception
346  do {
347  $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$e, $fname ) {
348  $ex = $lb->runMasterTransactionIdleCallbacks( $fname, $this->id );
349  $e = $e ?: $ex;
350  } );
351  } while ( $this->hasMasterChanges() );
352  // Run all listener callbacks once
353  $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$e, $fname ) {
354  $ex = $lb->runMasterTransactionListenerCallbacks( $fname, $this->id );
355  $e = $e ?: $ex;
356  } );
357 
358  return $e;
359  }
360 
361  public function hasTransactionRound() {
362  return ( $this->trxRoundId !== false );
363  }
364 
365  public function isReadyForRoundOperations() {
366  return ( $this->trxRoundStage === self::ROUND_CURSORY );
367  }
368 
372  private function logIfMultiDbTransaction() {
373  $callersByDB = [];
374  $this->forEachLB( static function ( ILoadBalancer $lb ) use ( &$callersByDB ) {
375  $masterName = $lb->getServerName( $lb->getWriterIndex() );
376  $callers = $lb->pendingMasterChangeCallers();
377  if ( $callers ) {
378  $callersByDB[$masterName] = $callers;
379  }
380  } );
381 
382  if ( count( $callersByDB ) >= 2 ) {
383  $dbs = implode( ', ', array_keys( $callersByDB ) );
384  $msg = "Multi-DB transaction [{$dbs}]:\n";
385  foreach ( $callersByDB as $db => $callers ) {
386  $msg .= "$db: " . implode( '; ', $callers ) . "\n";
387  }
388  $this->queryLogger->info( $msg );
389  }
390  }
391 
392  public function hasMasterChanges() {
393  $ret = false;
394  $this->forEachLB( static function ( ILoadBalancer $lb ) use ( &$ret ) {
395  $ret = $ret || $lb->hasMasterChanges();
396  } );
397 
398  return $ret;
399  }
400 
401  public function laggedReplicaUsed() {
402  $ret = false;
403  $this->forEachLB( static function ( ILoadBalancer $lb ) use ( &$ret ) {
404  $ret = $ret || $lb->laggedReplicaUsed();
405  } );
406 
407  return $ret;
408  }
409 
410  public function hasOrMadeRecentMasterChanges( $age = null ) {
411  $ret = false;
412  $this->forEachLB( static function ( ILoadBalancer $lb ) use ( $age, &$ret ) {
413  $ret = $ret || $lb->hasOrMadeRecentMasterChanges( $age );
414  } );
415  return $ret;
416  }
417 
418  public function waitForReplication( array $opts = [] ) {
419  $opts += [
420  'domain' => false,
421  'cluster' => false,
422  'timeout' => $this->replicationWaitTimeout,
423  'ifWritesSince' => null
424  ];
425 
426  // @phan-suppress-next-line PhanSuspiciousValueComparison
427  if ( $opts['domain'] === false && isset( $opts['wiki'] ) ) {
428  $opts['domain'] = $opts['wiki']; // b/c
429  }
430 
431  // Figure out which clusters need to be checked
433  $lbs = [];
434  // @phan-suppress-next-line PhanSuspiciousValueComparison
435  if ( $opts['cluster'] !== false ) {
436  $lbs[] = $this->getExternalLB( $opts['cluster'] );
437  } elseif ( $opts['domain'] !== false ) {
438  $lbs[] = $this->getMainLB( $opts['domain'] );
439  } else {
440  $this->forEachLB( static function ( ILoadBalancer $lb ) use ( &$lbs ) {
441  $lbs[] = $lb;
442  } );
443  if ( !$lbs ) {
444  return true; // nothing actually used
445  }
446  }
447 
448  // Get all the master positions of applicable DBs right now.
449  // This can be faster since waiting on one cluster reduces the
450  // time needed to wait on the next clusters.
451  $masterPositions = array_fill( 0, count( $lbs ), false );
452  foreach ( $lbs as $i => $lb ) {
453  if (
454  // No writes to wait on getting replicated
455  !$lb->hasMasterConnection() ||
456  // No replication; avoid getMasterPos() permissions errors (T29975)
457  !$lb->hasStreamingReplicaServers() ||
458  // No writes since the last replication wait
459  (
460  $opts['ifWritesSince'] &&
461  $lb->lastMasterChangeTimestamp() < $opts['ifWritesSince']
462  )
463  ) {
464  continue; // no need to wait
465  }
466 
467  $masterPositions[$i] = $lb->getMasterPos();
468  }
469 
470  // Run any listener callbacks *after* getting the DB positions. The more
471  // time spent in the callbacks, the less time is spent in waitForAll().
472  foreach ( $this->replicationWaitCallbacks as $callback ) {
473  $callback();
474  }
475 
476  $failed = [];
477  foreach ( $lbs as $i => $lb ) {
478  if ( $masterPositions[$i] ) {
479  // The RDBMS may not support getMasterPos()
480  if ( !$lb->waitForAll( $masterPositions[$i], $opts['timeout'] ) ) {
481  $failed[] = $lb->getServerName( $lb->getWriterIndex() );
482  }
483  }
484  }
485 
486  return !$failed;
487  }
488 
489  public function setWaitForReplicationListener( $name, callable $callback = null ) {
490  if ( $callback ) {
491  $this->replicationWaitCallbacks[$name] = $callback;
492  } else {
493  unset( $this->replicationWaitCallbacks[$name] );
494  }
495  }
496 
497  public function getEmptyTransactionTicket( $fname ) {
498  if ( $this->hasMasterChanges() ) {
499  $this->queryLogger->error(
500  __METHOD__ . ": $fname does not have outer scope",
501  [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
502  );
503 
504  return null;
505  }
506 
507  return $this->ticket;
508  }
509 
510  final public function commitAndWaitForReplication( $fname, $ticket, array $opts = [] ) {
511  if ( $ticket !== $this->ticket ) {
512  $this->perfLogger->error(
513  __METHOD__ . ": $fname does not have outer scope",
514  [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
515  );
516 
517  return false;
518  }
519 
520  // The transaction owner and any caller with the empty transaction ticket can commit
521  // so that getEmptyTransactionTicket() callers don't risk seeing DBTransactionError.
522  if ( $this->trxRoundId !== false && $fname !== $this->trxRoundId ) {
523  $this->queryLogger->info( "$fname: committing on behalf of {$this->trxRoundId}" );
524  $fnameEffective = $this->trxRoundId;
525  } else {
526  $fnameEffective = $fname;
527  }
528 
529  $this->commitMasterChanges( $fnameEffective );
530  $waitSucceeded = $this->waitForReplication( $opts );
531  // If a nested caller committed on behalf of $fname, start another empty $fname
532  // transaction, leaving the caller with the same empty transaction state as before.
533  if ( $fnameEffective !== $fname ) {
534  $this->beginMasterChanges( $fnameEffective );
535  }
536 
537  return $waitSucceeded;
538  }
539 
540  public function getChronologyProtectorTouched( $domain = false ) {
541  return $this->getChronologyProtector()->getTouched( $this->getMainLB( $domain ) );
542  }
543 
544  public function disableChronologyProtection() {
545  $this->getChronologyProtector()->setEnabled( false );
546  }
547 
551  protected function getChronologyProtector() {
552  if ( $this->chronProt ) {
553  return $this->chronProt;
554  }
555 
556  $this->chronProt = new ChronologyProtector(
557  $this->cpStash,
558  [
559  'ip' => $this->requestInfo['IPAddress'],
560  'agent' => $this->requestInfo['UserAgent'],
561  'clientId' => $this->requestInfo['ChronologyClientId'] ?: null
562  ],
563  $this->requestInfo['ChronologyPositionIndex'],
564  $this->secret
565  );
566  $this->chronProt->setLogger( $this->replLogger );
567 
568  if ( $this->cliMode ) {
569  $this->chronProt->setEnabled( false );
570  } elseif ( $this->requestInfo['ChronologyProtection'] === 'false' ) {
571  // Request opted out of using position wait logic. This is useful for requests
572  // done by the job queue or background ETL that do not have a meaningful session.
573  $this->chronProt->setWaitEnabled( false );
574  } elseif ( $this->cpStash instanceof EmptyBagOStuff ) {
575  // No where to store any DB positions and wait for them to appear
576  $this->chronProt->setEnabled( false );
577  $this->replLogger->debug( 'Cannot use ChronologyProtector with EmptyBagOStuff' );
578  }
579 
580  $this->replLogger->debug(
581  __METHOD__ . ': request info ' .
582  json_encode( $this->requestInfo, JSON_PRETTY_PRINT )
583  );
584 
585  return $this->chronProt;
586  }
587 
595  protected function shutdownChronologyProtector(
596  ChronologyProtector $cp, $workCallback, &$cpIndex = null
597  ) {
598  // Remark all of the relevant DB master positions
599  $this->forEachLB( static function ( ILoadBalancer $lb ) use ( $cp ) {
601  } );
602  // Write the positions to the persistent stash
603  $unsavedPositions = $cp->persistSessionReplicationPositions( $cpIndex );
604  if ( $unsavedPositions && $workCallback ) {
605  // Invoke callback in case it did not cache the result yet
606  $workCallback();
607  }
608  // If the positions failed to write to the stash, then wait on the local datacenter
609  // replica DBs to catch up before sending an HTTP response. As long as the request that
610  // caused such DB writes occurred in the master datacenter, and clients are temporarily
611  // pinned to the master datacenter after causing DB writes, then this should suffice.
612  $this->forEachLB( static function ( ILoadBalancer $lb ) use ( $unsavedPositions ) {
613  $masterName = $lb->getServerName( $lb->getWriterIndex() );
614  if ( isset( $unsavedPositions[$masterName] ) ) {
615  $lb->waitForAll( $unsavedPositions[$masterName] );
616  }
617  } );
618  }
619 
626  final protected function baseLoadBalancerParams( $owner ) {
627  if ( $this->trxRoundStage === self::ROUND_COMMIT_CALLBACKS ) {
629  } elseif ( $this->trxRoundStage === self::ROUND_ROLLBACK_CALLBACKS ) {
631  } else {
632  $initStage = null;
633  }
634 
635  return [
636  'localDomain' => $this->localDomain,
637  'readOnlyReason' => $this->readOnlyReason,
638  'srvCache' => $this->srvCache,
639  'wanCache' => $this->wanCache,
640  'profiler' => $this->profiler,
641  'trxProfiler' => $this->trxProfiler,
642  'queryLogger' => $this->queryLogger,
643  'connLogger' => $this->connLogger,
644  'replLogger' => $this->replLogger,
645  'errorLogger' => $this->errorLogger,
646  'deprecationLogger' => $this->deprecationLogger,
647  'cliMode' => $this->cliMode,
648  'agent' => $this->agent,
649  'maxLag' => $this->maxLag,
650  'defaultGroup' => $this->defaultGroup,
651  'chronologyCallback' => function ( ILoadBalancer $lb ) {
652  // Defer ChronologyProtector construction in case setRequestInfo() ends up
653  // being called later (but before the first connection attempt) (T192611)
654  $this->getChronologyProtector()->applySessionReplicationPosition( $lb );
655  },
656  'roundStage' => $initStage,
657  'ownerId' => $owner,
658  'criticalSectionProvider' => $this->csProvider
659  ];
660  }
661 
665  protected function initLoadBalancer( ILoadBalancer $lb ) {
666  if ( $this->trxRoundId !== false ) {
667  $lb->beginMasterChanges( $this->trxRoundId, $this->id ); // set DBO_TRX
668  }
669 
670  $lb->setTableAliases( $this->tableAliases );
671  $lb->setIndexAliases( $this->indexAliases );
672  $lb->setDomainAliases( $this->domainAliases );
673  }
674 
675  public function setTableAliases( array $aliases ) {
676  $this->tableAliases = $aliases;
677  }
678 
679  public function setIndexAliases( array $aliases ) {
680  $this->indexAliases = $aliases;
681  }
682 
683  public function setDomainAliases( array $aliases ) {
684  $this->domainAliases = $aliases;
685  }
686 
688  return $this->trxProfiler;
689  }
690 
691  public function setLocalDomainPrefix( $prefix ) {
692  $this->localDomain = new DatabaseDomain(
693  $this->localDomain->getDatabase(),
694  $this->localDomain->getSchema(),
695  $prefix
696  );
697 
698  $this->forEachLB( static function ( ILoadBalancer $lb ) use ( $prefix ) {
699  $lb->setLocalDomainPrefix( $prefix );
700  } );
701  }
702 
703  public function redefineLocalDomain( $domain ) {
704  $this->closeAll();
705 
706  $this->localDomain = DatabaseDomain::newFromId( $domain );
707 
708  $this->forEachLB( function ( ILoadBalancer $lb ) {
709  $lb->redefineLocalDomain( $this->localDomain );
710  } );
711  }
712 
713  public function closeAll() {
715  $scope = ScopedCallback::newScopedIgnoreUserAbort();
716 
717  $this->forEachLBCallMethod( 'closeAll', [ __METHOD__, $this->id ] );
718  }
719 
720  public function setAgentName( $agent ) {
721  $this->agent = $agent;
722  }
723 
724  public function appendShutdownCPIndexAsQuery( $url, $index ) {
725  $usedCluster = 0;
726  $this->forEachLB( static function ( ILoadBalancer $lb ) use ( &$usedCluster ) {
727  $usedCluster |= $lb->hasStreamingReplicaServers();
728  } );
729 
730  if ( !$usedCluster ) {
731  return $url; // no master/replica clusters touched
732  }
733 
734  return strpos( $url, '?' ) === false ? "$url?cpPosIndex=$index" : "$url&cpPosIndex=$index";
735  }
736 
737  public function getChronologyProtectorClientId() {
738  return $this->getChronologyProtector()->getClientId();
739  }
740 
750  public static function makeCookieValueFromCPIndex(
751  int $writeIndex,
752  int $time,
753  string $clientId
754  ) {
755  // Format is "<write index>@<write timestamp>#<client ID hash>"
756  return "{$writeIndex}@{$time}#{$clientId}";
757  }
758 
767  public static function getCPInfoFromCookieValue( ?string $value, int $minTimestamp ) {
768  static $placeholder = [ 'index' => null, 'clientId' => null ];
769 
770  if ( $value === null ) {
771  return $placeholder; // not set
772  }
773 
774  // Format is "<write index>@<write timestamp>#<client ID hash>"
775  if ( !preg_match( '/^(\d+)@(\d+)#([0-9a-f]{32})$/', $value, $m ) ) {
776  return $placeholder; // invalid
777  }
778 
779  $index = (int)$m[1];
780  if ( $index <= 0 ) {
781  return $placeholder; // invalid
782  } elseif ( isset( $m[2] ) && $m[2] !== '' && (int)$m[2] < $minTimestamp ) {
783  return $placeholder; // expired
784  }
785 
786  $clientId = ( isset( $m[3] ) && $m[3] !== '' ) ? $m[3] : null;
787 
788  return [ 'index' => $index, 'clientId' => $clientId ];
789  }
790 
791  public function setRequestInfo( array $info ) {
792  if ( $this->chronProt ) {
793  throw new LogicException( 'ChronologyProtector already initialized' );
794  }
795 
796  $this->requestInfo = $info + $this->requestInfo;
797  }
798 
799  public function setDefaultReplicationWaitTimeout( $seconds ) {
801  $this->replicationWaitTimeout = max( 1, (int)$seconds );
802 
803  return $old;
804  }
805 
810  final protected function getOwnershipId() {
811  return $this->id;
812  }
813 
817  private function assertTransactionRoundStage( $stage ) {
818  if ( $this->trxRoundStage !== $stage ) {
819  throw new DBTransactionError(
820  null,
821  "Transaction round stage must be '$stage' (not '{$this->trxRoundStage}')"
822  );
823  }
824  }
825 
826  public function __destruct() {
827  $this->destroy();
828  }
829 
834  public function setMockTime( &$time ) {
835  $this->getChronologyProtector()->setMockTime( $time );
836  }
837 }
Wikimedia\Rdbms\LBFactory\$cpStash
BagOStuff $cpStash
Definition: LBFactory.php:68
Wikimedia\Rdbms\ILoadBalancer\pendingMasterChangeCallers
pendingMasterChangeCallers()
Get the list of callers that have pending primary changes.
Wikimedia\Rdbms\ILoadBalancer\STAGE_POSTCOMMIT_CALLBACKS
const STAGE_POSTCOMMIT_CALLBACKS
Manager of ILoadBalancer instances is running post-commit callbacks.
Definition: ILoadBalancer.php:113
Wikimedia\Rdbms\LBFactory\getLocalDomainID
getLocalDomainID()
Get the local (and default) database domain ID of connection handles.
Definition: LBFactory.php:184
Wikimedia\Rdbms\LBFactory\appendShutdownCPIndexAsQuery
appendShutdownCPIndexAsQuery( $url, $index)
Append ?cpPosIndex parameter to a URL for ChronologyProtector purposes if needed.
Definition: LBFactory.php:724
Wikimedia\Rdbms\LBFactory\getTransactionProfiler
getTransactionProfiler()
Get the TransactionProfiler used by this instance.
Definition: LBFactory.php:687
Wikimedia\Rdbms\LBFactory\$replLogger
LoggerInterface $replLogger
Definition: LBFactory.php:55
Wikimedia\Rdbms\LBFactory\$replicationWaitTimeout
int $replicationWaitTimeout
Default replication wait timeout.
Definition: LBFactory.php:104
Wikimedia\Rdbms\DatabaseDomain\newFromId
static newFromId( $domain)
Definition: DatabaseDomain.php:77
Wikimedia\Rdbms\LBFactory\$errorLogger
callable $errorLogger
Error logger.
Definition: LBFactory.php:63
Wikimedia\Rdbms\ILoadBalancer\waitForAll
waitForAll( $pos, $timeout=null)
Set the primary 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:691
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:91
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:80
Wikimedia\Rdbms\LBFactory\isReadyForRoundOperations
isReadyForRoundOperations()
Check if transaction rounds can be started, committed, or rolled back right now.
Definition: LBFactory.php:365
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\setMockTime
setMockTime(&$time)
Definition: LBFactory.php:834
Wikimedia\Rdbms\LBFactory\$wanCache
WANObjectCache $wanCache
Definition: LBFactory.php:72
Wikimedia\Rdbms\LBFactory\__destruct
__destruct()
Definition: LBFactory.php:826
Wikimedia\Rdbms\LBFactory\$connLogger
LoggerInterface $connLogger
Definition: LBFactory.php:57
Wikimedia\Rdbms\LBFactory\commitAndWaitForReplication
commitAndWaitForReplication( $fname, $ticket, array $opts=[])
Call commitMasterChanges() and waitForReplication() if $ticket indicates it is safe.
Definition: LBFactory.php:510
Wikimedia\Rdbms\LBFactory\$secret
string $secret
Secret string for HMAC hashing.
Definition: LBFactory.php:84
Wikimedia\Rdbms\LBFactory\setDefaultReplicationWaitTimeout
setDefaultReplicationWaitTimeout( $seconds)
Set the default timeout for replication wait checks.
Definition: LBFactory.php:799
Wikimedia\Rdbms\LBFactory\$defaultGroup
string null $defaultGroup
Definition: LBFactory.php:110
Wikimedia\Rdbms\LBFactory\initLoadBalancer
initLoadBalancer(ILoadBalancer $lb)
Definition: LBFactory.php:665
Wikimedia\Rdbms\LBFactory\resolveDomainID
resolveDomainID( $domain)
Definition: LBFactory.php:188
Wikimedia\Rdbms\LBFactory\getOwnershipId
getOwnershipId()
Definition: LBFactory.php:810
Wikimedia\Rdbms\LBFactory\getChronologyProtector
getChronologyProtector()
Definition: LBFactory.php:551
Wikimedia\Rdbms
Definition: ChronologyProtector.php:24
Wikimedia\Rdbms\LBFactory\forEachLBCallMethod
forEachLBCallMethod( $methodName, array $args=[])
Call a method on each tracked (instantiated) load balancer instance.
Definition: LBFactory.php:246
Wikimedia\Rdbms\ILBFactory\forEachLB
forEachLB( $callback, array $params=[])
Execute a function for each instantiated tracked load balancer instance.
BagOStuff
Class representing a cache/ephemeral data store.
Definition: BagOStuff.php:86
Wikimedia\Rdbms\LBFactory\resolveDomainInstance
resolveDomainInstance( $domain)
Definition: LBFactory.php:196
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:255
Wikimedia\Rdbms\LBFactory\$loggerFields
static $loggerFields
Definition: LBFactory.php:125
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 the tracked load balancer instance for a main cluster.
Wikimedia\Rdbms\LBFactory\__construct
__construct(array $conf)
Construct a manager of ILoadBalancer instances.
Definition: LBFactory.php:128
Wikimedia\Rdbms\LBFactory\executePostTransactionCallbacks
executePostTransactionCallbacks()
Definition: LBFactory.php:342
Wikimedia\Rdbms\LBFactory\waitForReplication
waitForReplication(array $opts=[])
Waits for the replica DBs to catch up to the current master position.
Definition: LBFactory.php:418
Wikimedia\Rdbms\ILoadBalancer\runMasterTransactionIdleCallbacks
runMasterTransactionIdleCallbacks( $fname=__METHOD__, $owner=null)
Consume and run all pending post-COMMIT/ROLLBACK callbacks and commit dangling transactions.
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:675
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:289
Wikimedia\Rdbms\ChronologyProtector\persistSessionReplicationPositions
persistSessionReplicationPositions(&$clientPosIndex=null)
Persist any staged client "session consistency" replication positions.
Definition: ChronologyProtector.php:321
Wikimedia\Rdbms\LBFactory\closeAll
closeAll()
Close all connections on instantiated tracked load balancer instances.
Definition: LBFactory.php:713
Wikimedia\Rdbms\LBFactory\makeCookieValueFromCPIndex
static makeCookieValueFromCPIndex(int $writeIndex, int $time, string $clientId)
Build a string conveying the client and write index of the chronology protector data.
Definition: LBFactory.php:750
Wikimedia\Rdbms\DatabaseDomain\newUnspecified
static newUnspecified()
Definition: DatabaseDomain.php:118
WANObjectCache\newEmpty
static newEmpty()
Get an instance that wraps EmptyBagOStuff.
Definition: WANObjectCache.php:383
Wikimedia\Rdbms\LBFactory\$deprecationLogger
callable $deprecationLogger
Deprecation logger.
Definition: LBFactory.php:65
Wikimedia\Rdbms\LBFactory\$id
$id
var int An identifier for this class instance
Definition: LBFactory.php:96
Wikimedia\Rdbms\LBFactory\$srvCache
BagOStuff $srvCache
Definition: LBFactory.php:70
Wikimedia\Rdbms\ILoadBalancer\getServerName
getServerName( $i)
Get the readable name of the server with the specified index.
Wikimedia\Rdbms\LBFactory\$nonLocalDomainCache
DatabaseDomain[] $nonLocalDomainCache
Map of (domain ID => domain instance)
Definition: LBFactory.php:116
Wikimedia\Rdbms\LBFactory\$queryLogger
LoggerInterface $queryLogger
Definition: LBFactory.php:59
Wikimedia\Rdbms\ILBFactory\getExternalLB
getExternalLB( $cluster)
Get the tracked load balancer instance for an external cluster.
Wikimedia\Rdbms\ILoadBalancer\beginMasterChanges
beginMasterChanges( $fname=__METHOD__, $owner=null)
Flush any primary 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:107
Wikimedia\Rdbms\LBFactory\ROUND_COMMIT_CALLBACKS
const ROUND_COMMIT_CALLBACKS
Definition: LBFactory.php:122
$args
if( $line===false) $args
Definition: mcc.php:124
Wikimedia\Rdbms\ILoadBalancer\getWriterIndex
getWriterIndex()
Get the specific server index of the primary server.
Wikimedia\Rdbms\LBFactory\baseLoadBalancerParams
baseLoadBalancerParams( $owner)
Get parameters to ILoadBalancer::__construct()
Definition: LBFactory.php:626
Wikimedia\Rdbms\ILoadBalancer\setDomainAliases
setDomainAliases(array $aliases)
Convert certain database domains to alternative ones.
Wikimedia\Rdbms\LBFactory\redefineLocalDomain
redefineLocalDomain( $domain)
Close all connections and redefine the local domain for testing or schema creation.
Definition: LBFactory.php:703
Wikimedia\Rdbms\LBFactory\$requestInfo
array $requestInfo
Web request information about the client.
Definition: LBFactory.php:78
Wikimedia\Rdbms\LBFactory\hasMasterChanges
hasMasterChanges()
Determine if any master connection has pending changes.
Definition: LBFactory.php:392
Wikimedia\Rdbms\LBFactory\$indexAliases
string[] $indexAliases
Map of (index alias => index)
Definition: LBFactory.php:89
Wikimedia\Rdbms\ChronologyProtector\getClientId
getClientId()
Definition: ChronologyProtector.php:228
Wikimedia\Rdbms\LBFactory\$localDomain
DatabaseDomain $localDomain
Local domain.
Definition: LBFactory.php:75
Wikimedia\Rdbms\LBFactory\laggedReplicaUsed
laggedReplicaUsed()
Detemine if any lagged replica DB connection was used.
Definition: LBFactory.php:401
Wikimedia\Rdbms\LBFactory\ROUND_COMMITTING
const ROUND_COMMITTING
Definition: LBFactory.php:120
Wikimedia\Rdbms\LBFactory\hasOrMadeRecentMasterChanges
hasOrMadeRecentMasterChanges( $age=null)
Determine if any master connection has pending/written changes from this request.
Definition: LBFactory.php:410
Wikimedia\Rdbms\LBFactory\commitAll
commitAll( $fname=__METHOD__, array $options=[])
Commit open transactions on all connections.
Definition: LBFactory.php:265
Wikimedia\Rdbms\LBFactory\getEmptyTransactionTicket
getEmptyTransactionTicket( $fname)
Get a token asserting that no transaction writes are active on tracked load balancers.
Definition: LBFactory.php:497
Wikimedia\Rdbms\LBFactory\$replicationWaitCallbacks
callable[] $replicationWaitCallbacks
Definition: LBFactory.php:93
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:121
Wikimedia\Rdbms\LBFactory\$tableAliases
array[] $tableAliases
$aliases Map of (table => (dbname, schema, prefix) map)
Definition: LBFactory.php:87
Wikimedia\Rdbms\ChronologyProtector\stageSessionReplicationPosition
stageSessionReplicationPosition(ILoadBalancer $lb)
Update client "session consistency" replication position for an end-of-life ILoadBalancer.
Definition: ChronologyProtector.php:289
Wikimedia\Rdbms\LBFactory\$profiler
callable null $profiler
An optional callback that returns a ScopedCallback instance, meant to profile the actual query execut...
Definition: LBFactory.php:51
Wikimedia\Rdbms\LBFactory\$maxLag
int null $maxLag
Definition: LBFactory.php:113
Wikimedia\Rdbms\LBFactory\setAgentName
setAgentName( $agent)
Definition: LBFactory.php:720
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:129
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:361
Wikimedia\Rdbms\LBFactory\ROUND_BEGINNING
const ROUND_BEGINNING
Definition: LBFactory.php:119
Wikimedia\Rdbms\LBFactory\$csProvider
CriticalSectionProvider null $csProvider
Definition: LBFactory.php:46
Wikimedia\Rdbms\LBFactory\beginMasterChanges
beginMasterChanges( $fname=__METHOD__)
Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set)
Definition: LBFactory.php:271
Wikimedia\Rdbms\DBTransactionError
@newable
Definition: DBTransactionError.php:29
Wikimedia\Rdbms\LBFactory\destroy
destroy()
Close all connections and make further attempts to open connections result in DBAccessError.
Definition: LBFactory.php:177
Wikimedia\Rdbms\LBFactory\rollbackMasterChanges
rollbackMasterChanges( $fname=__METHOD__)
Rollback changes on all master connections.
Definition: LBFactory.php:325
Wikimedia\Rdbms\ChronologyProtector
Provide a given client with protection against visible database lag.
Definition: ChronologyProtector.php:136
Wikimedia\Rdbms\LBFactory\$ticket
int null $ticket
Ticket used to delegate transaction ownership.
Definition: LBFactory.php:98
Wikimedia\Rdbms\LBFactory\disableChronologyProtection
disableChronologyProtection()
Disable the ChronologyProtector on all instantiated tracked load balancer instances.
Definition: LBFactory.php:544
Wikimedia\Rdbms\LBFactory
An interface for generating database load balancers.
Definition: LBFactory.php:42
Wikimedia\Rdbms\LBFactory\getCPInfoFromCookieValue
static getCPInfoFromCookieValue(?string $value, int $minTimestamp)
Parse a string conveying the client and write index of the chronology protector data.
Definition: LBFactory.php:767
Wikimedia\Rdbms\LBFactory\$perfLogger
LoggerInterface $perfLogger
Definition: LBFactory.php:61
Wikimedia\Rdbms\ILoadBalancer\hasStreamingReplicaServers
hasStreamingReplicaServers()
Whether any replica servers use streaming replication from the primary server.
Wikimedia\Rdbms\LBFactory\$trxRoundStage
string $trxRoundStage
One of the ROUND_* class constants.
Definition: LBFactory.php:102
Wikimedia\Rdbms\LBFactory\$trxProfiler
TransactionProfiler $trxProfiler
Definition: LBFactory.php:53
Wikimedia\Rdbms\LBFactory\ROUND_CURSORY
const ROUND_CURSORY
Definition: LBFactory.php:118
Wikimedia\Rdbms\LBFactory\$trxRoundId
string bool $trxRoundId
String if a requested DBO_TRX transaction round is active.
Definition: LBFactory.php:100
Wikimedia\Rdbms\LBFactory\assertTransactionRoundStage
assertTransactionRoundStage( $stage)
Definition: LBFactory.php:817
Wikimedia\Rdbms\LBFactory\setIndexAliases
setIndexAliases(array $aliases)
Convert certain index names to alternative names before querying the DB.
Definition: LBFactory.php:679
Wikimedia\Rdbms\ILoadBalancer\hasMasterChanges
hasMasterChanges()
Whether there are pending changes or callbacks in a transaction by this thread.
Wikimedia\Rdbms\LBFactory\shutdownChronologyProtector
shutdownChronologyProtector(ChronologyProtector $cp, $workCallback, &$cpIndex=null)
Get and record all of the staged DB positions into persistent memory storage.
Definition: LBFactory.php:595
Wikimedia\Rdbms\LBFactory\logIfMultiDbTransaction
logIfMultiDbTransaction()
Log query info if multi DB transactions are going to be committed now.
Definition: LBFactory.php:372
Wikimedia\Rdbms\LBFactory\shutdown
shutdown( $flags=self::SHUTDOWN_NORMAL, callable $workCallback=null, &$cpIndex=null, &$cpClientId=null)
Prepare all instantiated tracked load balancer instances for shutdown.
Definition: LBFactory.php:219
Wikimedia\Rdbms\LBFactory\$chronProt
ChronologyProtector $chronProt
Definition: LBFactory.php:44
Wikimedia\Rdbms\LBFactory\getChronologyProtectorClientId
getChronologyProtectorClientId()
Get the client ID of the ChronologyProtector instance.
Definition: LBFactory.php:737
Wikimedia\Rdbms\LBFactory\getChronologyProtectorTouched
getChronologyProtectorTouched( $domain=false)
Get the UNIX timestamp when the client last touched the DB, if they did so recently.
Definition: LBFactory.php:540
Wikimedia\Rdbms\DatabaseDomain
Class to handle database/schema/prefix specifications for IDatabase.
Definition: DatabaseDomain.php:40
Wikimedia\Rdbms\TransactionProfiler
Detect high-contention DB queries via profiling calls.
Definition: TransactionProfiler.php:40
Wikimedia\Rdbms\ILoadBalancer\STAGE_POSTROLLBACK_CALLBACKS
const STAGE_POSTROLLBACK_CALLBACKS
Manager of ILoadBalancer instances is running post-rollback callbacks.
Definition: ILoadBalancer.php:115
Wikimedia\Rdbms\LBFactory\setDomainAliases
setDomainAliases(array $aliases)
Convert certain database domains to alternative ones.
Definition: LBFactory.php:683
Wikimedia\Rdbms\LBFactory\$agent
string $agent
Agent name for query profiling.
Definition: LBFactory.php:82
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:123
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:489
Wikimedia\Rdbms\ILBFactory
An interface for generating database load balancers.
Definition: ILBFactory.php:33
Wikimedia\Rdbms\LBFactory\setRequestInfo
setRequestInfo(array $info)
Inject HTTP request header/cookie information during setup of this instance.
Definition: LBFactory.php:791