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;
48  private $profiler;
50  private $trxProfiler;
52  private $replLogger;
54  private $connLogger;
56  private $queryLogger;
58  private $perfLogger;
60  private $errorLogger;
63 
65  protected $cpStash;
67  protected $srvCache;
69  protected $wanCache;
70 
72  protected $localDomain;
73 
75  private $hostname;
77  private $requestInfo;
79  private $cliMode;
81  private $agent;
83  private $secret;
84 
86  private $tableAliases = [];
88  private $indexAliases = [];
90  private $domainAliases = [];
93 
95  private $id;
97  private $ticket;
99  private $trxRoundId = false;
104 
106  protected $readOnlyReason = false;
107 
109  private $defaultGroup = null;
110 
112  protected $maxLag;
113 
115  private $nonLocalDomainCache = [];
116 
117  private const ROUND_CURSORY = 'cursory';
118  private const ROUND_BEGINNING = 'within-begin';
119  private const ROUND_COMMITTING = 'within-commit';
120  private const ROUND_ROLLING_BACK = 'within-rollback';
121  private const ROUND_COMMIT_CALLBACKS = 'within-commit-callbacks';
122  private const ROUND_ROLLBACK_CALLBACKS = 'within-rollback-callbacks';
123 
124  private static $loggerFields =
125  [ 'replLogger', 'connLogger', 'queryLogger', 'perfLogger' ];
126 
127  public function __construct( array $conf ) {
128  $this->localDomain = isset( $conf['localDomain'] )
129  ? DatabaseDomain::newFromId( $conf['localDomain'] )
131 
132  $this->maxLag = $conf['maxLag'] ?? null;
133  if ( isset( $conf['readOnlyReason'] ) && is_string( $conf['readOnlyReason'] ) ) {
134  $this->readOnlyReason = $conf['readOnlyReason'];
135  }
136 
137  $this->cpStash = $conf['cpStash'] ?? new EmptyBagOStuff();
138  $this->srvCache = $conf['srvCache'] ?? new EmptyBagOStuff();
139  $this->wanCache = $conf['wanCache'] ?? WANObjectCache::newEmpty();
140 
141  foreach ( self::$loggerFields as $key ) {
142  $this->$key = $conf[$key] ?? new NullLogger();
143  }
144  $this->errorLogger = $conf['errorLogger'] ?? static function ( Throwable $e ) {
145  trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING );
146  };
147  $this->deprecationLogger = $conf['deprecationLogger'] ?? static function ( $msg ) {
148  trigger_error( $msg, E_USER_DEPRECATED );
149  };
150 
151  $this->profiler = $conf['profiler'] ?? null;
152  $this->trxProfiler = $conf['trxProfiler'] ?? new TransactionProfiler();
153 
154  $this->requestInfo = [
155  'IPAddress' => $_SERVER[ 'REMOTE_ADDR' ] ?? '',
156  'UserAgent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
157  // Headers application can inject via LBFactory::setRequestInfo()
158  'ChronologyProtection' => null,
159  'ChronologyClientId' => null, // prior $cpClientId value from LBFactory::shutdown()
160  'ChronologyPositionIndex' => null // prior $cpIndex value from LBFactory::shutdown()
161  ];
162 
163  $this->cliMode = $conf['cliMode'] ?? ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' );
164  $this->hostname = $conf['hostname'] ?? gethostname();
165  $this->agent = $conf['agent'] ?? '';
166  $this->defaultGroup = $conf['defaultGroup'] ?? null;
167  $this->secret = $conf['secret'] ?? '';
168  $this->replicationWaitTimeout = $this->cliMode ? 60 : 1;
169 
170  static $nextId, $nextTicket;
171  $this->id = $nextId = ( is_int( $nextId ) ? $nextId++ : mt_rand() );
172  $this->ticket = $nextTicket = ( is_int( $nextTicket ) ? $nextTicket++ : mt_rand() );
173  }
174 
175  public function destroy() {
177  $scope = ScopedCallback::newScopedIgnoreUserAbort();
178 
179  $this->forEachLBCallMethod( 'disable', [ __METHOD__, $this->id ] );
180  }
181 
182  public function getLocalDomainID() {
183  return $this->localDomain->getId();
184  }
185 
186  public function resolveDomainID( $domain ) {
187  return $this->resolveDomainInstance( $domain )->getId();
188  }
189 
194  final protected function resolveDomainInstance( $domain ) {
195  if ( $domain instanceof DatabaseDomain ) {
196  return $domain; // already a domain instance
197  } elseif ( $domain === false || $domain === $this->localDomain->getId() ) {
198  return $this->localDomain;
199  } elseif ( isset( $this->domainAliases[$domain] ) ) {
200  // This array acts as both the original map and as instance cache.
201  // Instances pass-through DatabaseDomain::newFromId as-is.
202  $this->domainAliases[$domain] =
203  DatabaseDomain::newFromId( $this->domainAliases[$domain] );
204 
205  return $this->domainAliases[$domain];
206  }
207 
208  $cachedDomain = $this->nonLocalDomainCache[$domain] ?? null;
209  if ( $cachedDomain === null ) {
210  $cachedDomain = DatabaseDomain::newFromId( $domain );
211  $this->nonLocalDomainCache = [ $domain => $cachedDomain ];
212  }
213 
214  return $cachedDomain;
215  }
216 
217  public function shutdown(
218  $flags = self::SHUTDOWN_NORMAL,
219  callable $workCallback = null,
220  &$cpIndex = null,
221  &$cpClientId = null
222  ) {
224  $scope = ScopedCallback::newScopedIgnoreUserAbort();
225 
227  if ( ( $flags & self::SHUTDOWN_NO_CHRONPROT ) != self::SHUTDOWN_NO_CHRONPROT ) {
228  $this->shutdownChronologyProtector( $chronProt, $workCallback, $cpIndex );
229  $this->replLogger->debug( __METHOD__ . ': finished ChronologyProtector shutdown' );
230  }
231  $cpClientId = $chronProt->getClientId();
232 
233  $this->commitMasterChanges( __METHOD__ ); // sanity
234 
235  $this->replLogger->debug( 'LBFactory shutdown completed' );
236  }
237 
244  protected function forEachLBCallMethod( $methodName, array $args = [] ) {
245  $this->forEachLB(
246  static function ( ILoadBalancer $loadBalancer, $methodName, array $args ) {
247  $loadBalancer->$methodName( ...$args );
248  },
249  [ $methodName, $args ]
250  );
251  }
252 
253  public function flushReplicaSnapshots( $fname = __METHOD__ ) {
254  if ( $this->trxRoundId !== false && $this->trxRoundId !== $fname ) {
255  $this->queryLogger->warning(
256  "$fname: transaction round '{$this->trxRoundId}' still running",
257  [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
258  );
259  }
260  $this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname, $this->id ] );
261  }
262 
263  final public function commitAll( $fname = __METHOD__, array $options = [] ) {
264  $this->commitMasterChanges( $fname, $options );
265  $this->forEachLBCallMethod( 'flushMasterSnapshots', [ $fname, $this->id ] );
266  $this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname, $this->id ] );
267  }
268 
269  final public function beginMasterChanges( $fname = __METHOD__ ) {
270  $this->assertTransactionRoundStage( self::ROUND_CURSORY );
272  $scope = ScopedCallback::newScopedIgnoreUserAbort();
273 
274  $this->trxRoundStage = self::ROUND_BEGINNING;
275  if ( $this->trxRoundId !== false ) {
276  throw new DBTransactionError(
277  null,
278  "$fname: transaction round '{$this->trxRoundId}' already started"
279  );
280  }
281  $this->trxRoundId = $fname;
282  // Set DBO_TRX flags on all appropriate DBs
283  $this->forEachLBCallMethod( 'beginMasterChanges', [ $fname, $this->id ] );
284  $this->trxRoundStage = self::ROUND_CURSORY;
285  }
286 
287  final public function commitMasterChanges( $fname = __METHOD__, array $options = [] ) {
288  $this->assertTransactionRoundStage( self::ROUND_CURSORY );
290  $scope = ScopedCallback::newScopedIgnoreUserAbort();
291 
292  $this->trxRoundStage = self::ROUND_COMMITTING;
293  if ( $this->trxRoundId !== false && $this->trxRoundId !== $fname ) {
294  throw new DBTransactionError(
295  null,
296  "$fname: transaction round '{$this->trxRoundId}' still running"
297  );
298  }
299  // Run pre-commit callbacks and suppress post-commit callbacks, aborting on failure
300  do {
301  $count = 0; // number of callbacks executed this iteration
302  $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$count, $fname ) {
303  $count += $lb->finalizeMasterChanges( $fname, $this->id );
304  } );
305  } while ( $count > 0 );
306  $this->trxRoundId = false;
307  // Perform pre-commit checks, aborting on failure
308  $this->forEachLBCallMethod( 'approveMasterChanges', [ $options, $fname, $this->id ] );
309  // Log the DBs and methods involved in multi-DB transactions
310  $this->logIfMultiDbTransaction();
311  // Actually perform the commit on all master DB connections and revert DBO_TRX
312  $this->forEachLBCallMethod( 'commitMasterChanges', [ $fname, $this->id ] );
313  // Run all post-commit callbacks in a separate step
314  $this->trxRoundStage = self::ROUND_COMMIT_CALLBACKS;
315  $e = $this->executePostTransactionCallbacks();
316  $this->trxRoundStage = self::ROUND_CURSORY;
317  // Throw any last post-commit callback error
318  if ( $e instanceof Exception ) {
319  throw $e;
320  }
321  }
322 
323  final public function rollbackMasterChanges( $fname = __METHOD__ ) {
325  $scope = ScopedCallback::newScopedIgnoreUserAbort();
326 
327  $this->trxRoundStage = self::ROUND_ROLLING_BACK;
328  $this->trxRoundId = false;
329  // Actually perform the rollback on all master DB connections and revert DBO_TRX
330  $this->forEachLBCallMethod( 'rollbackMasterChanges', [ $fname, $this->id ] );
331  // Run all post-commit callbacks in a separate step
332  $this->trxRoundStage = self::ROUND_ROLLBACK_CALLBACKS;
334  $this->trxRoundStage = self::ROUND_CURSORY;
335  }
336 
340  private function executePostTransactionCallbacks() {
341  $fname = __METHOD__;
342  // Run all post-commit callbacks until new ones stop getting added
343  $e = null; // first callback exception
344  do {
345  $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$e, $fname ) {
346  $ex = $lb->runMasterTransactionIdleCallbacks( $fname, $this->id );
347  $e = $e ?: $ex;
348  } );
349  } while ( $this->hasMasterChanges() );
350  // Run all listener callbacks once
351  $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$e, $fname ) {
352  $ex = $lb->runMasterTransactionListenerCallbacks( $fname, $this->id );
353  $e = $e ?: $ex;
354  } );
355 
356  return $e;
357  }
358 
359  public function hasTransactionRound() {
360  return ( $this->trxRoundId !== false );
361  }
362 
363  public function isReadyForRoundOperations() {
364  return ( $this->trxRoundStage === self::ROUND_CURSORY );
365  }
366 
370  private function logIfMultiDbTransaction() {
371  $callersByDB = [];
372  $this->forEachLB( static function ( ILoadBalancer $lb ) use ( &$callersByDB ) {
373  $masterName = $lb->getServerName( $lb->getWriterIndex() );
374  $callers = $lb->pendingMasterChangeCallers();
375  if ( $callers ) {
376  $callersByDB[$masterName] = $callers;
377  }
378  } );
379 
380  if ( count( $callersByDB ) >= 2 ) {
381  $dbs = implode( ', ', array_keys( $callersByDB ) );
382  $msg = "Multi-DB transaction [{$dbs}]:\n";
383  foreach ( $callersByDB as $db => $callers ) {
384  $msg .= "$db: " . implode( '; ', $callers ) . "\n";
385  }
386  $this->queryLogger->info( $msg );
387  }
388  }
389 
390  public function hasMasterChanges() {
391  $ret = false;
392  $this->forEachLB( static function ( ILoadBalancer $lb ) use ( &$ret ) {
393  $ret = $ret || $lb->hasMasterChanges();
394  } );
395 
396  return $ret;
397  }
398 
399  public function laggedReplicaUsed() {
400  $ret = false;
401  $this->forEachLB( static function ( ILoadBalancer $lb ) use ( &$ret ) {
402  $ret = $ret || $lb->laggedReplicaUsed();
403  } );
404 
405  return $ret;
406  }
407 
408  public function hasOrMadeRecentMasterChanges( $age = null ) {
409  $ret = false;
410  $this->forEachLB( static function ( ILoadBalancer $lb ) use ( $age, &$ret ) {
411  $ret = $ret || $lb->hasOrMadeRecentMasterChanges( $age );
412  } );
413  return $ret;
414  }
415 
416  public function waitForReplication( array $opts = [] ) {
417  $opts += [
418  'domain' => false,
419  'cluster' => false,
420  'timeout' => $this->replicationWaitTimeout,
421  'ifWritesSince' => null
422  ];
423 
424  // @phan-suppress-next-line PhanSuspiciousValueComparison
425  if ( $opts['domain'] === false && isset( $opts['wiki'] ) ) {
426  $opts['domain'] = $opts['wiki']; // b/c
427  }
428 
429  // Figure out which clusters need to be checked
431  $lbs = [];
432  // @phan-suppress-next-line PhanSuspiciousValueComparison
433  if ( $opts['cluster'] !== false ) {
434  $lbs[] = $this->getExternalLB( $opts['cluster'] );
435  } elseif ( $opts['domain'] !== false ) {
436  $lbs[] = $this->getMainLB( $opts['domain'] );
437  } else {
438  $this->forEachLB( static function ( ILoadBalancer $lb ) use ( &$lbs ) {
439  $lbs[] = $lb;
440  } );
441  if ( !$lbs ) {
442  return true; // nothing actually used
443  }
444  }
445 
446  // Get all the master positions of applicable DBs right now.
447  // This can be faster since waiting on one cluster reduces the
448  // time needed to wait on the next clusters.
449  $masterPositions = array_fill( 0, count( $lbs ), false );
450  foreach ( $lbs as $i => $lb ) {
451  if (
452  // No writes to wait on getting replicated
453  !$lb->hasMasterConnection() ||
454  // No replication; avoid getMasterPos() permissions errors (T29975)
455  !$lb->hasStreamingReplicaServers() ||
456  // No writes since the last replication wait
457  (
458  $opts['ifWritesSince'] &&
459  $lb->lastMasterChangeTimestamp() < $opts['ifWritesSince']
460  )
461  ) {
462  continue; // no need to wait
463  }
464 
465  $masterPositions[$i] = $lb->getMasterPos();
466  }
467 
468  // Run any listener callbacks *after* getting the DB positions. The more
469  // time spent in the callbacks, the less time is spent in waitForAll().
470  foreach ( $this->replicationWaitCallbacks as $callback ) {
471  $callback();
472  }
473 
474  $failed = [];
475  foreach ( $lbs as $i => $lb ) {
476  if ( $masterPositions[$i] ) {
477  // The RDBMS may not support getMasterPos()
478  if ( !$lb->waitForAll( $masterPositions[$i], $opts['timeout'] ) ) {
479  $failed[] = $lb->getServerName( $lb->getWriterIndex() );
480  }
481  }
482  }
483 
484  return !$failed;
485  }
486 
487  public function setWaitForReplicationListener( $name, callable $callback = null ) {
488  if ( $callback ) {
489  $this->replicationWaitCallbacks[$name] = $callback;
490  } else {
491  unset( $this->replicationWaitCallbacks[$name] );
492  }
493  }
494 
495  public function getEmptyTransactionTicket( $fname ) {
496  if ( $this->hasMasterChanges() ) {
497  $this->queryLogger->error(
498  __METHOD__ . ": $fname does not have outer scope",
499  [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
500  );
501 
502  return null;
503  }
504 
505  return $this->ticket;
506  }
507 
508  final public function commitAndWaitForReplication( $fname, $ticket, array $opts = [] ) {
509  if ( $ticket !== $this->ticket ) {
510  $this->perfLogger->error(
511  __METHOD__ . ": $fname does not have outer scope",
512  [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
513  );
514 
515  return false;
516  }
517 
518  // The transaction owner and any caller with the empty transaction ticket can commit
519  // so that getEmptyTransactionTicket() callers don't risk seeing DBTransactionError.
520  if ( $this->trxRoundId !== false && $fname !== $this->trxRoundId ) {
521  $this->queryLogger->info( "$fname: committing on behalf of {$this->trxRoundId}" );
522  $fnameEffective = $this->trxRoundId;
523  } else {
524  $fnameEffective = $fname;
525  }
526 
527  $this->commitMasterChanges( $fnameEffective );
528  $waitSucceeded = $this->waitForReplication( $opts );
529  // If a nested caller committed on behalf of $fname, start another empty $fname
530  // transaction, leaving the caller with the same empty transaction state as before.
531  if ( $fnameEffective !== $fname ) {
532  $this->beginMasterChanges( $fnameEffective );
533  }
534 
535  return $waitSucceeded;
536  }
537 
538  public function getChronologyProtectorTouched( $domain = false ) {
539  return $this->getChronologyProtector()->getTouched( $this->getMainLB( $domain ) );
540  }
541 
542  public function disableChronologyProtection() {
543  $this->getChronologyProtector()->setEnabled( false );
544  }
545 
549  protected function getChronologyProtector() {
550  if ( $this->chronProt ) {
551  return $this->chronProt;
552  }
553 
554  $this->chronProt = new ChronologyProtector(
555  $this->cpStash,
556  [
557  'ip' => $this->requestInfo['IPAddress'],
558  'agent' => $this->requestInfo['UserAgent'],
559  'clientId' => $this->requestInfo['ChronologyClientId'] ?: null
560  ],
561  $this->requestInfo['ChronologyPositionIndex'],
562  $this->secret
563  );
564  $this->chronProt->setLogger( $this->replLogger );
565 
566  if ( $this->cliMode ) {
567  $this->chronProt->setEnabled( false );
568  } elseif ( $this->requestInfo['ChronologyProtection'] === 'false' ) {
569  // Request opted out of using position wait logic. This is useful for requests
570  // done by the job queue or background ETL that do not have a meaningful session.
571  $this->chronProt->setWaitEnabled( false );
572  } elseif ( $this->cpStash instanceof EmptyBagOStuff ) {
573  // No where to store any DB positions and wait for them to appear
574  $this->chronProt->setEnabled( false );
575  $this->replLogger->debug( 'Cannot use ChronologyProtector with EmptyBagOStuff' );
576  }
577 
578  $this->replLogger->debug(
579  __METHOD__ . ': request info ' .
580  json_encode( $this->requestInfo, JSON_PRETTY_PRINT )
581  );
582 
583  return $this->chronProt;
584  }
585 
593  protected function shutdownChronologyProtector(
594  ChronologyProtector $cp, $workCallback, &$cpIndex = null
595  ) {
596  // Remark all of the relevant DB master positions
597  $this->forEachLB( static function ( ILoadBalancer $lb ) use ( $cp ) {
599  } );
600  // Write the positions to the persistent stash
601  $unsavedPositions = $cp->shutdown( $cpIndex );
602  if ( $unsavedPositions && $workCallback ) {
603  // Invoke callback in case it did not cache the result yet
604  $workCallback();
605  }
606  // If the positions failed to write to the stash, then wait on the local datacenter
607  // replica DBs to catch up before sending an HTTP response. As long as the request that
608  // caused such DB writes occurred in the master datacenter, and clients are temporarily
609  // pinned to the master datacenter after causing DB writes, then this should suffice.
610  $this->forEachLB( static 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 ) {
627  } elseif ( $this->trxRoundStage === self::ROUND_ROLLBACK_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( static 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( static 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 ( $value === null ) {
760  return $placeholder; // not set
761  } elseif ( !preg_match( '/^(\d+)@(\d+)#([0-9a-f]{32})$/', $value, $m ) ) {
762  return $placeholder; // invalid
763  }
764 
765  $index = (int)$m[1];
766  if ( $index <= 0 ) {
767  return $placeholder; // invalid
768  } elseif ( isset( $m[2] ) && $m[2] !== '' && (int)$m[2] < $minTimestamp ) {
769  return $placeholder; // expired
770  }
771 
772  $clientId = ( isset( $m[3] ) && $m[3] !== '' ) ? $m[3] : null;
773 
774  return [ 'index' => $index, 'clientId' => $clientId ];
775  }
776 
777  public function setRequestInfo( array $info ) {
778  if ( $this->chronProt ) {
779  throw new LogicException( 'ChronologyProtector already initialized' );
780  }
781 
782  $this->requestInfo = $info + $this->requestInfo;
783  }
784 
785  public function setDefaultReplicationWaitTimeout( $seconds ) {
787  $this->replicationWaitTimeout = max( 1, (int)$seconds );
788 
789  return $old;
790  }
791 
796  final protected function getOwnershipId() {
797  return $this->id;
798  }
799 
803  private function assertTransactionRoundStage( $stage ) {
804  if ( $this->trxRoundStage !== $stage ) {
805  throw new DBTransactionError(
806  null,
807  "Transaction round stage must be '$stage' (not '{$this->trxRoundStage}')"
808  );
809  }
810  }
811 
812  public function __destruct() {
813  $this->destroy();
814  }
815 
820  public function setMockTime( &$time ) {
821  $this->getChronologyProtector()->setMockTime( $time );
822  }
823 }
Wikimedia\Rdbms\LBFactory\$cpStash
BagOStuff $cpStash
Definition: LBFactory.php:65
Wikimedia\Rdbms\ILoadBalancer\pendingMasterChangeCallers
pendingMasterChangeCallers()
Get the list of callers that have pending master changes.
Wikimedia\Rdbms\ILoadBalancer\STAGE_POSTCOMMIT_CALLBACKS
const STAGE_POSTCOMMIT_CALLBACKS
Manager of ILoadBalancer instances is running post-commit callbacks.
Definition: ILoadBalancer.php:102
Wikimedia\Rdbms\LBFactory\getLocalDomainID
getLocalDomainID()
Get the local (and default) database domain ID of connection handles.
Definition: LBFactory.php:182
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:52
Wikimedia\Rdbms\LBFactory\$replicationWaitTimeout
int $replicationWaitTimeout
Default replication wait timeout.
Definition: LBFactory.php:103
Wikimedia\Rdbms\DatabaseDomain\newFromId
static newFromId( $domain)
Definition: DatabaseDomain.php:77
Wikimedia\Rdbms\LBFactory\$errorLogger
callable $errorLogger
Error logger.
Definition: LBFactory.php:60
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:90
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:79
Wikimedia\Rdbms\LBFactory\isReadyForRoundOperations
isReadyForRoundOperations()
Check if transaction rounds can be started, committed, or rolled back right now.
Definition: LBFactory.php:363
if
if(ini_get( 'mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
Definition: Setup.php:87
Wikimedia\Rdbms\LBFactory\setMockTime
setMockTime(&$time)
Definition: LBFactory.php:820
Wikimedia\Rdbms\LBFactory\$wanCache
WANObjectCache $wanCache
Definition: LBFactory.php:69
Wikimedia\Rdbms\LBFactory\__destruct
__destruct()
Definition: LBFactory.php:812
Wikimedia\Rdbms\LBFactory\$hostname
string $hostname
Local hostname of the app server.
Definition: LBFactory.php:75
Wikimedia\Rdbms\LBFactory\$connLogger
LoggerInterface $connLogger
Definition: LBFactory.php:54
Wikimedia\Rdbms\LBFactory\commitAndWaitForReplication
commitAndWaitForReplication( $fname, $ticket, array $opts=[])
Convenience method for safely running commitMasterChanges()/waitForReplication()
Definition: LBFactory.php:508
Wikimedia\Rdbms\LBFactory\$secret
string $secret
Secret string for HMAC hashing.
Definition: LBFactory.php:83
Wikimedia\Rdbms\LBFactory\setDefaultReplicationWaitTimeout
setDefaultReplicationWaitTimeout( $seconds)
Definition: LBFactory.php:785
Wikimedia\Rdbms\LBFactory\$defaultGroup
string null $defaultGroup
Definition: LBFactory.php:109
Wikimedia\Rdbms\LBFactory\initLoadBalancer
initLoadBalancer(ILoadBalancer $lb)
Definition: LBFactory.php:663
Wikimedia\Rdbms\LBFactory\resolveDomainID
resolveDomainID( $domain)
Definition: LBFactory.php:186
Wikimedia\Rdbms\LBFactory\getOwnershipId
getOwnershipId()
Definition: LBFactory.php:796
Wikimedia\Rdbms\LBFactory\getChronologyProtector
getChronologyProtector()
Definition: LBFactory.php:549
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:244
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:86
Wikimedia\Rdbms\LBFactory\resolveDomainInstance
resolveDomainInstance( $domain)
Definition: LBFactory.php:194
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:253
Wikimedia\Rdbms\LBFactory\$loggerFields
static $loggerFields
Definition: LBFactory.php:124
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:127
Wikimedia\Rdbms\LBFactory\executePostTransactionCallbacks
executePostTransactionCallbacks()
Definition: LBFactory.php:340
Wikimedia\Rdbms\LBFactory\waitForReplication
waitForReplication(array $opts=[])
Waits for the replica DBs to catch up to the current master position.
Definition: LBFactory.php:416
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: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:287
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:118
WANObjectCache\newEmpty
static newEmpty()
Get an instance that wraps EmptyBagOStuff.
Definition: WANObjectCache.php:372
Wikimedia\Rdbms\LBFactory\$deprecationLogger
callable $deprecationLogger
Deprecation logger.
Definition: LBFactory.php:62
Wikimedia\Rdbms\LBFactory\$id
$id
var int An identifier for this class instance
Definition: LBFactory.php:95
Wikimedia\Rdbms\LBFactory\getCPInfoFromCookieValue
static getCPInfoFromCookieValue( $value, $minTimestamp)
Definition: LBFactory.php:756
Wikimedia\Rdbms\LBFactory\$srvCache
BagOStuff $srvCache
Definition: LBFactory.php:67
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:115
Wikimedia\Rdbms\LBFactory\$queryLogger
LoggerInterface $queryLogger
Definition: LBFactory.php:56
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:106
Wikimedia\Rdbms\LBFactory\ROUND_COMMIT_CALLBACKS
const ROUND_COMMIT_CALLBACKS
Definition: LBFactory.php:121
$args
if( $line===false) $args
Definition: mcc.php:124
Wikimedia\Rdbms\ILoadBalancer\getWriterIndex
getWriterIndex()
Get the specific 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\$requestInfo
array $requestInfo
Web request information about the client.
Definition: LBFactory.php:77
Wikimedia\Rdbms\LBFactory\hasMasterChanges
hasMasterChanges()
Determine if any master connection has pending changes.
Definition: LBFactory.php:390
Wikimedia\Rdbms\LBFactory\$indexAliases
string[] $indexAliases
Map of (index alias => index)
Definition: LBFactory.php:88
Wikimedia\Rdbms\ChronologyProtector\getClientId
getClientId()
Definition: ChronologyProtector.php:222
Wikimedia\Rdbms\LBFactory\$localDomain
DatabaseDomain $localDomain
Local domain.
Definition: LBFactory.php:72
Wikimedia\Rdbms\LBFactory\laggedReplicaUsed
laggedReplicaUsed()
Detemine if any lagged replica DB connection was used.
Definition: LBFactory.php:399
Wikimedia\Rdbms\LBFactory\ROUND_COMMITTING
const ROUND_COMMITTING
Definition: LBFactory.php:119
Wikimedia\Rdbms\LBFactory\hasOrMadeRecentMasterChanges
hasOrMadeRecentMasterChanges( $age=null)
Determine if any master connection has pending/written changes from this request.
Definition: LBFactory.php:408
Wikimedia\Rdbms\LBFactory\commitAll
commitAll( $fname=__METHOD__, array $options=[])
Commit open transactions on all connections.
Definition: LBFactory.php:263
Wikimedia\Rdbms\LBFactory\getEmptyTransactionTicket
getEmptyTransactionTicket( $fname)
Get a token asserting that no transaction writes are active.
Definition: LBFactory.php:495
Wikimedia\Rdbms\LBFactory\$replicationWaitCallbacks
callable[] $replicationWaitCallbacks
Definition: LBFactory.php:92
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:120
Wikimedia\Rdbms\LBFactory\$tableAliases
array[] $tableAliases
$aliases Map of (table => (dbname, schema, prefix) map)
Definition: LBFactory.php:86
Wikimedia\Rdbms\ChronologyProtector\stageSessionReplicationPosition
stageSessionReplicationPosition(ILoadBalancer $lb)
Update the "session consistency" DB replication position for an end-of-life ILoadBalancer.
Definition: ChronologyProtector.php:283
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:48
Wikimedia\Rdbms\LBFactory\$maxLag
int null $maxLag
Definition: LBFactory.php:112
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:359
Wikimedia\Rdbms\LBFactory\ROUND_BEGINNING
const ROUND_BEGINNING
Definition: LBFactory.php:118
Wikimedia\Rdbms\LBFactory\beginMasterChanges
beginMasterChanges( $fname=__METHOD__)
Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set)
Definition: LBFactory.php:269
Wikimedia\Rdbms\ChronologyProtector\shutdown
shutdown(&$cpIndex=null)
Save any remarked "session consistency" DB replication positions to persistent storage.
Definition: ChronologyProtector.php:315
Wikimedia\Rdbms\DBTransactionError
@newable
Definition: DBTransactionError.php:29
Wikimedia\Rdbms\LBFactory\destroy
destroy()
Disables all load balancers.
Definition: LBFactory.php:175
Wikimedia\Rdbms\LBFactory\rollbackMasterChanges
rollbackMasterChanges( $fname=__METHOD__)
Rollback changes on all master connections.
Definition: LBFactory.php:323
Wikimedia\Rdbms\ChronologyProtector
Provide a given client with protection against visible database lag.
Definition: ChronologyProtector.php:135
Wikimedia\Rdbms\LBFactory\$ticket
int null $ticket
Ticket used to delegate transaction ownership.
Definition: LBFactory.php:97
Wikimedia\Rdbms\LBFactory\disableChronologyProtection
disableChronologyProtection()
Disable the ChronologyProtector for all load balancers.
Definition: LBFactory.php:542
Wikimedia\Rdbms\LBFactory
An interface for generating database load balancers.
Definition: LBFactory.php:41
Wikimedia\Rdbms\LBFactory\$perfLogger
LoggerInterface $perfLogger
Definition: LBFactory.php:58
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:101
Wikimedia\Rdbms\LBFactory\$trxProfiler
TransactionProfiler $trxProfiler
Definition: LBFactory.php:50
Wikimedia\Rdbms\LBFactory\ROUND_CURSORY
const ROUND_CURSORY
Definition: LBFactory.php:117
Wikimedia\Rdbms\LBFactory\$trxRoundId
string bool $trxRoundId
String if a requested DBO_TRX transaction round is active.
Definition: LBFactory.php:99
Wikimedia\Rdbms\LBFactory\assertTransactionRoundStage
assertTransactionRoundStage( $stage)
Definition: LBFactory.php:803
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\shutdownChronologyProtector
shutdownChronologyProtector(ChronologyProtector $cp, $workCallback, &$cpIndex=null)
Get and record all of the staged DB positions into persistent memory storage.
Definition: LBFactory.php:593
Wikimedia\Rdbms\LBFactory\logIfMultiDbTransaction
logIfMultiDbTransaction()
Log query info if multi DB transactions are going to be committed now.
Definition: LBFactory.php:370
Wikimedia\Rdbms\LBFactory\shutdown
shutdown( $flags=self::SHUTDOWN_NORMAL, callable $workCallback=null, &$cpIndex=null, &$cpClientId=null)
Prepare all currently tracked (instantiated) load balancers for shutdown.
Definition: LBFactory.php:217
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)
Get the UNIX timestamp when the client last touched the DB, if they did so recently.
Definition: LBFactory.php:538
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:39
Wikimedia\Rdbms\LBFactory\makeCookieValueFromCPIndex
static makeCookieValueFromCPIndex( $index, $time, $clientId)
Definition: LBFactory.php:746
Wikimedia\Rdbms\ILoadBalancer\STAGE_POSTROLLBACK_CALLBACKS
const STAGE_POSTROLLBACK_CALLBACKS
Manager of ILoadBalancer instances is running post-rollback callbacks.
Definition: ILoadBalancer.php:104
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:81
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:122
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:487
Wikimedia\Rdbms\ILBFactory
An interface for generating database load balancers.
Definition: ILBFactory.php:33
Wikimedia\Rdbms\LBFactory\setRequestInfo
setRequestInfo(array $info)
Definition: LBFactory.php:777