MediaWiki  1.29.1
LoadBalancer.php
Go to the documentation of this file.
1 <?php
23 namespace Wikimedia\Rdbms;
24 
25 use Psr\Log\LoggerInterface;
26 use Psr\Log\NullLogger;
27 use Wikimedia\ScopedCallback;
32 use InvalidArgumentException;
33 use RuntimeException;
34 use Exception;
35 
41 class LoadBalancer implements ILoadBalancer {
43  private $mServers;
45  private $mConns;
47  private $mLoads;
49  private $mGroupLoads;
51  private $mAllowLagged;
53  private $mWaitTimeout;
57  private $tableAliases = [];
58 
60  private $loadMonitor;
62  private $chronProt;
64  private $srvCache;
66  private $memCache;
68  private $wanCache;
70  protected $profiler;
72  protected $trxProfiler;
74  protected $replLogger;
76  protected $connLogger;
78  protected $queryLogger;
80  protected $perfLogger;
81 
85  private $mReadIndex;
87  private $mWaitForPos;
89  private $laggedReplicaMode = false;
91  private $allReplicasDownMode = false;
93  private $mLastError = 'Unknown error';
95  private $readOnlyReason = false;
97  private $connsOpened = 0;
99  private $trxRoundId = false;
103  private $localDomain;
107  private $host;
109  protected $cliMode;
111  protected $agent;
112 
114  private $errorLogger;
115 
117  private $disabled = false;
119  private $chronProtInitialized = false;
120 
122  const CONN_HELD_WARN_THRESHOLD = 10;
123 
125  const MAX_LAG_DEFAULT = 10;
127  const TTL_CACHE_READONLY = 5;
128 
129  public function __construct( array $params ) {
130  if ( !isset( $params['servers'] ) ) {
131  throw new InvalidArgumentException( __CLASS__ . ': missing servers parameter' );
132  }
133  $this->mServers = $params['servers'];
134 
135  $this->localDomain = isset( $params['localDomain'] )
136  ? DatabaseDomain::newFromId( $params['localDomain'] )
138  // In case a caller assumes that the domain ID is simply <db>-<prefix>, which is almost
139  // always true, gracefully handle the case when they fail to account for escaping.
140  if ( $this->localDomain->getTablePrefix() != '' ) {
141  $this->localDomainIdAlias =
142  $this->localDomain->getDatabase() . '-' . $this->localDomain->getTablePrefix();
143  } else {
144  $this->localDomainIdAlias = $this->localDomain->getDatabase();
145  }
146 
147  $this->mWaitTimeout = isset( $params['waitTimeout'] ) ? $params['waitTimeout'] : 10;
148 
149  $this->mReadIndex = -1;
150  $this->mConns = [
151  'local' => [],
152  'foreignUsed' => [],
153  'foreignFree' => []
154  ];
155  $this->mLoads = [];
156  $this->mWaitForPos = false;
157  $this->mAllowLagged = false;
158 
159  if ( isset( $params['readOnlyReason'] ) && is_string( $params['readOnlyReason'] ) ) {
160  $this->readOnlyReason = $params['readOnlyReason'];
161  }
162 
163  if ( isset( $params['loadMonitor'] ) ) {
164  $this->loadMonitorConfig = $params['loadMonitor'];
165  } else {
166  $this->loadMonitorConfig = [ 'class' => 'LoadMonitorNull' ];
167  }
168 
169  foreach ( $params['servers'] as $i => $server ) {
170  $this->mLoads[$i] = $server['load'];
171  if ( isset( $server['groupLoads'] ) ) {
172  foreach ( $server['groupLoads'] as $group => $ratio ) {
173  if ( !isset( $this->mGroupLoads[$group] ) ) {
174  $this->mGroupLoads[$group] = [];
175  }
176  $this->mGroupLoads[$group][$i] = $ratio;
177  }
178  }
179  }
180 
181  if ( isset( $params['srvCache'] ) ) {
182  $this->srvCache = $params['srvCache'];
183  } else {
184  $this->srvCache = new EmptyBagOStuff();
185  }
186  if ( isset( $params['memCache'] ) ) {
187  $this->memCache = $params['memCache'];
188  } else {
189  $this->memCache = new EmptyBagOStuff();
190  }
191  if ( isset( $params['wanCache'] ) ) {
192  $this->wanCache = $params['wanCache'];
193  } else {
194  $this->wanCache = WANObjectCache::newEmpty();
195  }
196  $this->profiler = isset( $params['profiler'] ) ? $params['profiler'] : null;
197  if ( isset( $params['trxProfiler'] ) ) {
198  $this->trxProfiler = $params['trxProfiler'];
199  } else {
200  $this->trxProfiler = new TransactionProfiler();
201  }
202 
203  $this->errorLogger = isset( $params['errorLogger'] )
204  ? $params['errorLogger']
205  : function ( Exception $e ) {
206  trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING );
207  };
208 
209  foreach ( [ 'replLogger', 'connLogger', 'queryLogger', 'perfLogger' ] as $key ) {
210  $this->$key = isset( $params[$key] ) ? $params[$key] : new NullLogger();
211  }
212 
213  $this->host = isset( $params['hostname'] )
214  ? $params['hostname']
215  : ( gethostname() ?: 'unknown' );
216  $this->cliMode = isset( $params['cliMode'] ) ? $params['cliMode'] : PHP_SAPI === 'cli';
217  $this->agent = isset( $params['agent'] ) ? $params['agent'] : '';
218 
219  if ( isset( $params['chronologyProtector'] ) ) {
220  $this->chronProt = $params['chronologyProtector'];
221  }
222  }
223 
229  private function getLoadMonitor() {
230  if ( !isset( $this->loadMonitor ) ) {
231  $compat = [
232  'LoadMonitor' => LoadMonitor::class,
233  'LoadMonitorNull' => LoadMonitorNull::class,
234  'LoadMonitorMySQL' => LoadMonitorMySQL::class,
235  ];
236 
237  $class = $this->loadMonitorConfig['class'];
238  if ( isset( $compat[$class] ) ) {
239  $class = $compat[$class];
240  }
241 
242  $this->loadMonitor = new $class(
243  $this, $this->srvCache, $this->memCache, $this->loadMonitorConfig );
244  $this->loadMonitor->setLogger( $this->replLogger );
245  }
246 
247  return $this->loadMonitor;
248  }
249 
256  private function getRandomNonLagged( array $loads, $domain = false, $maxLag = INF ) {
257  $lags = $this->getLagTimes( $domain );
258 
259  # Unset excessively lagged servers
260  foreach ( $lags as $i => $lag ) {
261  if ( $i != 0 ) {
262  # How much lag this server nominally is allowed to have
263  $maxServerLag = isset( $this->mServers[$i]['max lag'] )
264  ? $this->mServers[$i]['max lag']
265  : self::MAX_LAG_DEFAULT; // default
266  # Constrain that futher by $maxLag argument
267  $maxServerLag = min( $maxServerLag, $maxLag );
268 
269  $host = $this->getServerName( $i );
270  if ( $lag === false && !is_infinite( $maxServerLag ) ) {
271  $this->replLogger->error(
272  "Server {host} is not replicating?", [ 'host' => $host ] );
273  unset( $loads[$i] );
274  } elseif ( $lag > $maxServerLag ) {
275  $this->replLogger->warning(
276  "Server {host} has {lag} seconds of lag (>= {maxlag})",
277  [ 'host' => $host, 'lag' => $lag, 'maxlag' => $maxServerLag ]
278  );
279  unset( $loads[$i] );
280  }
281  }
282  }
283 
284  # Find out if all the replica DBs with non-zero load are lagged
285  $sum = 0;
286  foreach ( $loads as $load ) {
287  $sum += $load;
288  }
289  if ( $sum == 0 ) {
290  # No appropriate DB servers except maybe the master and some replica DBs with zero load
291  # Do NOT use the master
292  # Instead, this function will return false, triggering read-only mode,
293  # and a lagged replica DB will be used instead.
294  return false;
295  }
296 
297  if ( count( $loads ) == 0 ) {
298  return false;
299  }
300 
301  # Return a random representative of the remainder
302  return ArrayUtils::pickRandom( $loads );
303  }
304 
305  public function getReaderIndex( $group = false, $domain = false ) {
306  if ( count( $this->mServers ) == 1 ) {
307  // Skip the load balancing if there's only one server
308  return $this->getWriterIndex();
309  } elseif ( $group === false && $this->mReadIndex >= 0 ) {
310  // Shortcut if the generic reader index was already cached
311  return $this->mReadIndex;
312  }
313 
314  if ( $group !== false ) {
315  // Use the server weight array for this load group
316  if ( isset( $this->mGroupLoads[$group] ) ) {
317  $loads = $this->mGroupLoads[$group];
318  } else {
319  // No loads for this group, return false and the caller can use some other group
320  $this->connLogger->info( __METHOD__ . ": no loads for group $group" );
321 
322  return false;
323  }
324  } else {
325  // Use the generic load group
326  $loads = $this->mLoads;
327  }
328 
329  // Scale the configured load ratios according to each server's load and state
330  $this->getLoadMonitor()->scaleLoads( $loads, $domain );
331 
332  // Pick a server to use, accounting for weights, load, lag, and mWaitForPos
333  list( $i, $laggedReplicaMode ) = $this->pickReaderIndex( $loads, $domain );
334  if ( $i === false ) {
335  // Replica DB connection unsuccessful
336  return false;
337  }
338 
339  if ( $this->mWaitForPos && $i != $this->getWriterIndex() ) {
340  // Before any data queries are run, wait for the server to catch up to the
341  // specified position. This is used to improve session consistency. Note that
342  // when LoadBalancer::waitFor() sets mWaitForPos, the waiting triggers here,
343  // so update laggedReplicaMode as needed for consistency.
344  if ( !$this->doWait( $i ) ) {
345  $laggedReplicaMode = true;
346  }
347  }
348 
349  if ( $this->mReadIndex <= 0 && $this->mLoads[$i] > 0 && $group === false ) {
350  // Cache the generic reader index for future ungrouped DB_REPLICA handles
351  $this->mReadIndex = $i;
352  // Record if the generic reader index is in "lagged replica DB" mode
353  if ( $laggedReplicaMode ) {
354  $this->laggedReplicaMode = true;
355  }
356  }
357 
358  $serverName = $this->getServerName( $i );
359  $this->connLogger->debug( __METHOD__ . ": using server $serverName for group '$group'" );
360 
361  return $i;
362  }
363 
369  private function pickReaderIndex( array $loads, $domain = false ) {
370  if ( !count( $loads ) ) {
371  throw new InvalidArgumentException( "Empty server array given to LoadBalancer" );
372  }
373 
375  $i = false;
377  $laggedReplicaMode = false;
378 
379  // Quickly look through the available servers for a server that meets criteria...
380  $currentLoads = $loads;
381  while ( count( $currentLoads ) ) {
382  if ( $this->mAllowLagged || $laggedReplicaMode ) {
383  $i = ArrayUtils::pickRandom( $currentLoads );
384  } else {
385  $i = false;
386  if ( $this->mWaitForPos && $this->mWaitForPos->asOfTime() ) {
387  // ChronologyProtecter sets mWaitForPos for session consistency.
388  // This triggers doWait() after connect, so it's especially good to
389  // avoid lagged servers so as to avoid excessive delay in that method.
390  $ago = microtime( true ) - $this->mWaitForPos->asOfTime();
391  // Aim for <= 1 second of waiting (being too picky can backfire)
392  $i = $this->getRandomNonLagged( $currentLoads, $domain, $ago + 1 );
393  }
394  if ( $i === false ) {
395  // Any server with less lag than it's 'max lag' param is preferable
396  $i = $this->getRandomNonLagged( $currentLoads, $domain );
397  }
398  if ( $i === false && count( $currentLoads ) != 0 ) {
399  // All replica DBs lagged. Switch to read-only mode
400  $this->replLogger->error( "All replica DBs lagged. Switch to read-only mode" );
401  $i = ArrayUtils::pickRandom( $currentLoads );
402  $laggedReplicaMode = true;
403  }
404  }
405 
406  if ( $i === false ) {
407  // pickRandom() returned false.
408  // This is permanent and means the configuration or the load monitor
409  // wants us to return false.
410  $this->connLogger->debug( __METHOD__ . ": pickRandom() returned false" );
411 
412  return [ false, false ];
413  }
414 
415  $serverName = $this->getServerName( $i );
416  $this->connLogger->debug( __METHOD__ . ": Using reader #$i: $serverName..." );
417 
418  $conn = $this->openConnection( $i, $domain );
419  if ( !$conn ) {
420  $this->connLogger->warning( __METHOD__ . ": Failed connecting to $i/$domain" );
421  unset( $currentLoads[$i] ); // avoid this server next iteration
422  $i = false;
423  continue;
424  }
425 
426  // Decrement reference counter, we are finished with this connection.
427  // It will be incremented for the caller later.
428  if ( $domain !== false ) {
429  $this->reuseConnection( $conn );
430  }
431 
432  // Return this server
433  break;
434  }
435 
436  // If all servers were down, quit now
437  if ( !count( $currentLoads ) ) {
438  $this->connLogger->error( "All servers down" );
439  }
440 
441  return [ $i, $laggedReplicaMode ];
442  }
443 
444  public function waitFor( $pos ) {
445  $oldPos = $this->mWaitForPos;
446  try {
447  $this->mWaitForPos = $pos;
448  // If a generic reader connection was already established, then wait now
449  $i = $this->mReadIndex;
450  if ( $i > 0 ) {
451  if ( !$this->doWait( $i ) ) {
452  $this->laggedReplicaMode = true;
453  }
454  }
455  } finally {
456  // Restore the older position if it was higher since this is used for lag-protection
457  $this->setWaitForPositionIfHigher( $oldPos );
458  }
459  }
460 
461  public function waitForOne( $pos, $timeout = null ) {
462  $oldPos = $this->mWaitForPos;
463  try {
464  $this->mWaitForPos = $pos;
465 
466  $i = $this->mReadIndex;
467  if ( $i <= 0 ) {
468  // Pick a generic replica DB if there isn't one yet
469  $readLoads = $this->mLoads;
470  unset( $readLoads[$this->getWriterIndex()] ); // replica DBs only
471  $readLoads = array_filter( $readLoads ); // with non-zero load
472  $i = ArrayUtils::pickRandom( $readLoads );
473  }
474 
475  if ( $i > 0 ) {
476  $ok = $this->doWait( $i, true, $timeout );
477  } else {
478  $ok = true; // no applicable loads
479  }
480  } finally {
481  # Restore the old position, as this is not used for lag-protection but for throttling
482  $this->mWaitForPos = $oldPos;
483  }
484 
485  return $ok;
486  }
487 
488  public function waitForAll( $pos, $timeout = null ) {
489  $oldPos = $this->mWaitForPos;
490  try {
491  $this->mWaitForPos = $pos;
492  $serverCount = count( $this->mServers );
493 
494  $ok = true;
495  for ( $i = 1; $i < $serverCount; $i++ ) {
496  if ( $this->mLoads[$i] > 0 ) {
497  $ok = $this->doWait( $i, true, $timeout ) && $ok;
498  }
499  }
500  } finally {
501  # Restore the old position, as this is not used for lag-protection but for throttling
502  $this->mWaitForPos = $oldPos;
503  }
504 
505  return $ok;
506  }
507 
511  private function setWaitForPositionIfHigher( $pos ) {
512  if ( !$pos ) {
513  return;
514  }
515 
516  if ( !$this->mWaitForPos || $pos->hasReached( $this->mWaitForPos ) ) {
517  $this->mWaitForPos = $pos;
518  }
519  }
520 
525  public function getAnyOpenConnection( $i ) {
526  foreach ( $this->mConns as $connsByServer ) {
527  if ( !empty( $connsByServer[$i] ) ) {
529  $serverConns = $connsByServer[$i];
530 
531  return reset( $serverConns );
532  }
533  }
534 
535  return false;
536  }
537 
545  protected function doWait( $index, $open = false, $timeout = null ) {
546  $close = false; // close the connection afterwards
547 
548  // Check if we already know that the DB has reached this point
549  $server = $this->getServerName( $index );
550  $key = $this->srvCache->makeGlobalKey( __CLASS__, 'last-known-pos', $server, 'v1' );
552  $knownReachedPos = $this->srvCache->get( $key );
553  if (
554  $knownReachedPos instanceof DBMasterPos &&
555  $knownReachedPos->hasReached( $this->mWaitForPos )
556  ) {
557  $this->replLogger->debug( __METHOD__ .
558  ": replica DB $server known to be caught up (pos >= $knownReachedPos)." );
559  return true;
560  }
561 
562  // Find a connection to wait on, creating one if needed and allowed
563  $conn = $this->getAnyOpenConnection( $index );
564  if ( !$conn ) {
565  if ( !$open ) {
566  $this->replLogger->debug( __METHOD__ . ": no connection open for $server" );
567 
568  return false;
569  } else {
570  $conn = $this->openConnection( $index, self::DOMAIN_ANY );
571  if ( !$conn ) {
572  $this->replLogger->warning( __METHOD__ . ": failed to connect to $server" );
573 
574  return false;
575  }
576  // Avoid connection spam in waitForAll() when connections
577  // are made just for the sake of doing this lag check.
578  $close = true;
579  }
580  }
581 
582  $this->replLogger->info( __METHOD__ . ": Waiting for replica DB $server to catch up..." );
583  $timeout = $timeout ?: $this->mWaitTimeout;
584  $result = $conn->masterPosWait( $this->mWaitForPos, $timeout );
585 
586  if ( $result == -1 || is_null( $result ) ) {
587  // Timed out waiting for replica DB, use master instead
588  $this->replLogger->warning(
589  __METHOD__ . ": Timed out waiting on {host} pos {$this->mWaitForPos}",
590  [ 'host' => $server ]
591  );
592  $ok = false;
593  } else {
594  $this->replLogger->info( __METHOD__ . ": Done" );
595  $ok = true;
596  // Remember that the DB reached this point
597  $this->srvCache->set( $key, $this->mWaitForPos, BagOStuff::TTL_DAY );
598  }
599 
600  if ( $close ) {
601  $this->closeConnection( $conn );
602  }
603 
604  return $ok;
605  }
606 
616  public function getConnection( $i, $groups = [], $domain = false ) {
617  if ( $i === null || $i === false ) {
618  throw new InvalidArgumentException( 'Attempt to call ' . __METHOD__ .
619  ' with invalid server index' );
620  }
621 
622  if ( $this->localDomain->equals( $domain ) || $domain === $this->localDomainIdAlias ) {
623  $domain = false; // local connection requested
624  }
625 
626  $groups = ( $groups === false || $groups === [] )
627  ? [ false ] // check one "group": the generic pool
628  : (array)$groups;
629 
630  $masterOnly = ( $i == self::DB_MASTER || $i == $this->getWriterIndex() );
631  $oldConnsOpened = $this->connsOpened; // connections open now
632 
633  if ( $i == self::DB_MASTER ) {
634  $i = $this->getWriterIndex();
635  } else {
636  # Try to find an available server in any the query groups (in order)
637  foreach ( $groups as $group ) {
638  $groupIndex = $this->getReaderIndex( $group, $domain );
639  if ( $groupIndex !== false ) {
640  $i = $groupIndex;
641  break;
642  }
643  }
644  }
645 
646  # Operation-based index
647  if ( $i == self::DB_REPLICA ) {
648  $this->mLastError = 'Unknown error'; // reset error string
649  # Try the general server pool if $groups are unavailable.
650  $i = ( $groups === [ false ] )
651  ? false // don't bother with this if that is what was tried above
652  : $this->getReaderIndex( false, $domain );
653  # Couldn't find a working server in getReaderIndex()?
654  if ( $i === false ) {
655  $this->mLastError = 'No working replica DB server: ' . $this->mLastError;
656  // Throw an exception
657  $this->reportConnectionError();
658  return null; // not reached
659  }
660  }
661 
662  # Now we have an explicit index into the servers array
663  $conn = $this->openConnection( $i, $domain );
664  if ( !$conn ) {
665  // Throw an exception
666  $this->reportConnectionError();
667  return null; // not reached
668  }
669 
670  # Profile any new connections that happen
671  if ( $this->connsOpened > $oldConnsOpened ) {
672  $host = $conn->getServer();
673  $dbname = $conn->getDBname();
674  $this->trxProfiler->recordConnection( $host, $dbname, $masterOnly );
675  }
676 
677  if ( $masterOnly ) {
678  # Make master-requested DB handles inherit any read-only mode setting
679  $conn->setLBInfo( 'readOnlyReason', $this->getReadOnlyReason( $domain, $conn ) );
680  }
681 
682  return $conn;
683  }
684 
685  public function reuseConnection( $conn ) {
686  $serverIndex = $conn->getLBInfo( 'serverIndex' );
687  $refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
688  if ( $serverIndex === null || $refCount === null ) {
699  return;
700  } elseif ( $conn instanceof DBConnRef ) {
701  // DBConnRef already handles calling reuseConnection() and only passes the live
702  // Database instance to this method. Any caller passing in a DBConnRef is broken.
703  $this->connLogger->error( __METHOD__ . ": got DBConnRef instance.\n" .
704  ( new RuntimeException() )->getTraceAsString() );
705 
706  return;
707  }
708 
709  if ( $this->disabled ) {
710  return; // DBConnRef handle probably survived longer than the LoadBalancer
711  }
712 
713  $domain = $conn->getDomainID();
714  if ( !isset( $this->mConns['foreignUsed'][$serverIndex][$domain] ) ) {
715  throw new InvalidArgumentException( __METHOD__ .
716  ": connection $serverIndex/$domain not found; it may have already been freed." );
717  } elseif ( $this->mConns['foreignUsed'][$serverIndex][$domain] !== $conn ) {
718  throw new InvalidArgumentException( __METHOD__ .
719  ": connection $serverIndex/$domain mismatched; it may have already been freed." );
720  }
721  $conn->setLBInfo( 'foreignPoolRefCount', --$refCount );
722  if ( $refCount <= 0 ) {
723  $this->mConns['foreignFree'][$serverIndex][$domain] = $conn;
724  unset( $this->mConns['foreignUsed'][$serverIndex][$domain] );
725  if ( !$this->mConns['foreignUsed'][$serverIndex] ) {
726  unset( $this->mConns[ 'foreignUsed' ][$serverIndex] ); // clean up
727  }
728  $this->connLogger->debug( __METHOD__ . ": freed connection $serverIndex/$domain" );
729  } else {
730  $this->connLogger->debug( __METHOD__ .
731  ": reference count for $serverIndex/$domain reduced to $refCount" );
732  }
733  }
734 
735  public function getConnectionRef( $db, $groups = [], $domain = false ) {
736  $domain = ( $domain !== false ) ? $domain : $this->localDomain;
737 
738  return new DBConnRef( $this, $this->getConnection( $db, $groups, $domain ) );
739  }
740 
741  public function getLazyConnectionRef( $db, $groups = [], $domain = false ) {
742  $domain = ( $domain !== false ) ? $domain : $this->localDomain;
743 
744  return new DBConnRef( $this, [ $db, $groups, $domain ] );
745  }
746 
747  public function getMaintenanceConnectionRef( $db, $groups = [], $domain = false ) {
748  $domain = ( $domain !== false ) ? $domain : $this->localDomain;
749 
750  return new MaintainableDBConnRef( $this, $this->getConnection( $db, $groups, $domain ) );
751  }
752 
761  public function openConnection( $i, $domain = false ) {
762  if ( $this->localDomain->equals( $domain ) || $domain === $this->localDomainIdAlias ) {
763  $domain = false; // local connection requested
764  }
765 
766  if ( !$this->chronProtInitialized && $this->chronProt ) {
767  $this->connLogger->debug( __METHOD__ . ': calling initLB() before first connection.' );
768  // Load CP positions before connecting so that doWait() triggers later if needed
769  $this->chronProtInitialized = true;
770  $this->chronProt->initLB( $this );
771  }
772 
773  if ( $domain !== false ) {
774  $conn = $this->openForeignConnection( $i, $domain );
775  } elseif ( isset( $this->mConns['local'][$i][0] ) ) {
776  $conn = $this->mConns['local'][$i][0];
777  } else {
778  if ( !isset( $this->mServers[$i] ) || !is_array( $this->mServers[$i] ) ) {
779  throw new InvalidArgumentException( "No server with index '$i'." );
780  }
781  // Open a new connection
782  $server = $this->mServers[$i];
783  $server['serverIndex'] = $i;
784  $conn = $this->reallyOpenConnection( $server, false );
785  $serverName = $this->getServerName( $i );
786  if ( $conn->isOpen() ) {
787  $this->connLogger->debug( "Connected to database $i at '$serverName'." );
788  $this->mConns['local'][$i][0] = $conn;
789  } else {
790  $this->connLogger->warning( "Failed to connect to database $i at '$serverName'." );
791  $this->errorConnection = $conn;
792  $conn = false;
793  }
794  }
795 
796  if ( $conn instanceof IDatabase && !$conn->isOpen() ) {
797  // Connection was made but later unrecoverably lost for some reason.
798  // Do not return a handle that will just throw exceptions on use,
799  // but let the calling code (e.g. getReaderIndex) try another server.
800  // See DatabaseMyslBase::ping() for how this can happen.
801  $this->errorConnection = $conn;
802  $conn = false;
803  }
804 
805  return $conn;
806  }
807 
828  private function openForeignConnection( $i, $domain ) {
829  $domainInstance = DatabaseDomain::newFromId( $domain );
830  $dbName = $domainInstance->getDatabase();
831  $prefix = $domainInstance->getTablePrefix();
832 
833  if ( isset( $this->mConns['foreignUsed'][$i][$domain] ) ) {
834  // Reuse an already-used connection
835  $conn = $this->mConns['foreignUsed'][$i][$domain];
836  $this->connLogger->debug( __METHOD__ . ": reusing connection $i/$domain" );
837  } elseif ( isset( $this->mConns['foreignFree'][$i][$domain] ) ) {
838  // Reuse a free connection for the same domain
839  $conn = $this->mConns['foreignFree'][$i][$domain];
840  unset( $this->mConns['foreignFree'][$i][$domain] );
841  $this->mConns['foreignUsed'][$i][$domain] = $conn;
842  $this->connLogger->debug( __METHOD__ . ": reusing free connection $i/$domain" );
843  } elseif ( !empty( $this->mConns['foreignFree'][$i] ) ) {
844  // Reuse a connection from another domain
845  $conn = reset( $this->mConns['foreignFree'][$i] );
846  $oldDomain = key( $this->mConns['foreignFree'][$i] );
847  // The empty string as a DB name means "don't care".
848  // DatabaseMysqlBase::open() already handle this on connection.
849  if ( strlen( $dbName ) && !$conn->selectDB( $dbName ) ) {
850  $this->mLastError = "Error selecting database '$dbName' on server " .
851  $conn->getServer() . " from client host {$this->host}";
852  $this->errorConnection = $conn;
853  $conn = false;
854  } else {
855  $conn->tablePrefix( $prefix );
856  unset( $this->mConns['foreignFree'][$i][$oldDomain] );
857  $this->mConns['foreignUsed'][$i][$domain] = $conn;
858  $this->connLogger->debug( __METHOD__ .
859  ": reusing free connection from $oldDomain for $domain" );
860  }
861  } else {
862  if ( !isset( $this->mServers[$i] ) || !is_array( $this->mServers[$i] ) ) {
863  throw new InvalidArgumentException( "No server with index '$i'." );
864  }
865  // Open a new connection
866  $server = $this->mServers[$i];
867  $server['serverIndex'] = $i;
868  $server['foreignPoolRefCount'] = 0;
869  $server['foreign'] = true;
870  $conn = $this->reallyOpenConnection( $server, $dbName );
871  if ( !$conn->isOpen() ) {
872  $this->connLogger->warning( __METHOD__ . ": connection error for $i/$domain" );
873  $this->errorConnection = $conn;
874  $conn = false;
875  } else {
876  $conn->tablePrefix( $prefix );
877  $this->mConns['foreignUsed'][$i][$domain] = $conn;
878  $this->connLogger->debug( __METHOD__ . ": opened new connection for $i/$domain" );
879  }
880  }
881 
882  // Increment reference count
883  if ( $conn instanceof IDatabase ) {
884  $refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
885  $conn->setLBInfo( 'foreignPoolRefCount', $refCount + 1 );
886  }
887 
888  return $conn;
889  }
890 
898  private function isOpen( $index ) {
899  if ( !is_integer( $index ) ) {
900  return false;
901  }
902 
903  return (bool)$this->getAnyOpenConnection( $index );
904  }
905 
917  protected function reallyOpenConnection( array $server, $dbNameOverride = false ) {
918  if ( $this->disabled ) {
919  throw new DBAccessError();
920  }
921 
922  if ( $dbNameOverride !== false ) {
923  $server['dbname'] = $dbNameOverride;
924  }
925 
926  // Let the handle know what the cluster master is (e.g. "db1052")
927  $masterName = $this->getServerName( $this->getWriterIndex() );
928  $server['clusterMasterHost'] = $masterName;
929 
930  // Log when many connection are made on requests
931  if ( ++$this->connsOpened >= self::CONN_HELD_WARN_THRESHOLD ) {
932  $this->perfLogger->warning( __METHOD__ . ": " .
933  "{$this->connsOpened}+ connections made (master=$masterName)" );
934  }
935 
936  $server['srvCache'] = $this->srvCache;
937  // Set loggers and profilers
938  $server['connLogger'] = $this->connLogger;
939  $server['queryLogger'] = $this->queryLogger;
940  $server['errorLogger'] = $this->errorLogger;
941  $server['profiler'] = $this->profiler;
942  $server['trxProfiler'] = $this->trxProfiler;
943  // Use the same agent and PHP mode for all DB handles
944  $server['cliMode'] = $this->cliMode;
945  $server['agent'] = $this->agent;
946  // Use DBO_DEFAULT flags by default for LoadBalancer managed databases. Assume that the
947  // application calls LoadBalancer::commitMasterChanges() before the PHP script completes.
948  $server['flags'] = isset( $server['flags'] ) ? $server['flags'] : IDatabase::DBO_DEFAULT;
949 
950  // Create a live connection object
951  try {
952  $db = Database::factory( $server['type'], $server );
953  } catch ( DBConnectionError $e ) {
954  // FIXME: This is probably the ugliest thing I have ever done to
955  // PHP. I'm half-expecting it to segfault, just out of disgust. -- TS
956  $db = $e->db;
957  }
958 
959  $db->setLBInfo( $server );
960  $db->setLazyMasterHandle(
961  $this->getLazyConnectionRef( self::DB_MASTER, [], $db->getDomainID() )
962  );
963  $db->setTableAliases( $this->tableAliases );
964 
965  if ( $server['serverIndex'] === $this->getWriterIndex() ) {
966  if ( $this->trxRoundId !== false ) {
967  $this->applyTransactionRoundFlags( $db );
968  }
969  foreach ( $this->trxRecurringCallbacks as $name => $callback ) {
970  $db->setTransactionListener( $name, $callback );
971  }
972  }
973 
974  return $db;
975  }
976 
980  private function reportConnectionError() {
981  $conn = $this->errorConnection; // the connection which caused the error
982  $context = [
983  'method' => __METHOD__,
984  'last_error' => $this->mLastError,
985  ];
986 
987  if ( $conn instanceof IDatabase ) {
988  $context['db_server'] = $conn->getServer();
989  $this->connLogger->warning(
990  "Connection error: {last_error} ({db_server})",
991  $context
992  );
993 
994  // throws DBConnectionError
995  $conn->reportConnectionError( "{$this->mLastError} ({$context['db_server']})" );
996  } else {
997  // No last connection, probably due to all servers being too busy
998  $this->connLogger->error(
999  "LB failure with no last connection. Connection error: {last_error}",
1000  $context
1001  );
1002 
1003  // If all servers were busy, mLastError will contain something sensible
1004  throw new DBConnectionError( null, $this->mLastError );
1005  }
1006  }
1007 
1008  public function getWriterIndex() {
1009  return 0;
1010  }
1011 
1012  public function haveIndex( $i ) {
1013  return array_key_exists( $i, $this->mServers );
1014  }
1015 
1016  public function isNonZeroLoad( $i ) {
1017  return array_key_exists( $i, $this->mServers ) && $this->mLoads[$i] != 0;
1018  }
1019 
1020  public function getServerCount() {
1021  return count( $this->mServers );
1022  }
1023 
1024  public function getServerName( $i ) {
1025  if ( isset( $this->mServers[$i]['hostName'] ) ) {
1026  $name = $this->mServers[$i]['hostName'];
1027  } elseif ( isset( $this->mServers[$i]['host'] ) ) {
1028  $name = $this->mServers[$i]['host'];
1029  } else {
1030  $name = '';
1031  }
1032 
1033  return ( $name != '' ) ? $name : 'localhost';
1034  }
1035 
1036  public function getServerInfo( $i ) {
1037  if ( isset( $this->mServers[$i] ) ) {
1038  return $this->mServers[$i];
1039  } else {
1040  return false;
1041  }
1042  }
1043 
1044  public function setServerInfo( $i, array $serverInfo ) {
1045  $this->mServers[$i] = $serverInfo;
1046  }
1047 
1048  public function getMasterPos() {
1049  # If this entire request was served from a replica DB without opening a connection to the
1050  # master (however unlikely that may be), then we can fetch the position from the replica DB.
1051  $masterConn = $this->getAnyOpenConnection( $this->getWriterIndex() );
1052  if ( !$masterConn ) {
1053  $serverCount = count( $this->mServers );
1054  for ( $i = 1; $i < $serverCount; $i++ ) {
1055  $conn = $this->getAnyOpenConnection( $i );
1056  if ( $conn ) {
1057  return $conn->getReplicaPos();
1058  }
1059  }
1060  } else {
1061  return $masterConn->getMasterPos();
1062  }
1063 
1064  return false;
1065  }
1066 
1067  public function disable() {
1068  $this->closeAll();
1069  $this->disabled = true;
1070  }
1071 
1072  public function closeAll() {
1073  $this->forEachOpenConnection( function ( IDatabase $conn ) {
1074  $host = $conn->getServer();
1075  $this->connLogger->debug( "Closing connection to database '$host'." );
1076  $conn->close();
1077  } );
1078 
1079  $this->mConns = [
1080  'local' => [],
1081  'foreignFree' => [],
1082  'foreignUsed' => [],
1083  ];
1084  $this->connsOpened = 0;
1085  }
1086 
1087  public function closeConnection( IDatabase $conn ) {
1088  $serverIndex = $conn->getLBInfo( 'serverIndex' ); // second index level of mConns
1089  foreach ( $this->mConns as $type => $connsByServer ) {
1090  if ( !isset( $connsByServer[$serverIndex] ) ) {
1091  continue;
1092  }
1093 
1094  foreach ( $connsByServer[$serverIndex] as $i => $trackedConn ) {
1095  if ( $conn === $trackedConn ) {
1096  $host = $this->getServerName( $i );
1097  $this->connLogger->debug( "Closing connection to database $i at '$host'." );
1098  unset( $this->mConns[$type][$serverIndex][$i] );
1100  break 2;
1101  }
1102  }
1103  }
1104 
1105  $conn->close();
1106  }
1107 
1108  public function commitAll( $fname = __METHOD__ ) {
1109  $failures = [];
1110 
1111  $restore = ( $this->trxRoundId !== false );
1112  $this->trxRoundId = false;
1113  $this->forEachOpenConnection(
1114  function ( IDatabase $conn ) use ( $fname, $restore, &$failures ) {
1115  try {
1116  $conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
1117  } catch ( DBError $e ) {
1118  call_user_func( $this->errorLogger, $e );
1119  $failures[] = "{$conn->getServer()}: {$e->getMessage()}";
1120  }
1121  if ( $restore && $conn->getLBInfo( 'master' ) ) {
1122  $this->undoTransactionRoundFlags( $conn );
1123  }
1124  }
1125  );
1126 
1127  if ( $failures ) {
1128  throw new DBExpectedError(
1129  null,
1130  "Commit failed on server(s) " . implode( "\n", array_unique( $failures ) )
1131  );
1132  }
1133  }
1134 
1135  public function finalizeMasterChanges() {
1136  $this->forEachOpenMasterConnection( function ( Database $conn ) {
1137  // Any error should cause all DB transactions to be rolled back together
1138  $conn->setTrxEndCallbackSuppression( false );
1140  // Defer post-commit callbacks until COMMIT finishes for all DBs
1141  $conn->setTrxEndCallbackSuppression( true );
1142  } );
1143  }
1144 
1145  public function approveMasterChanges( array $options ) {
1146  $limit = isset( $options['maxWriteDuration'] ) ? $options['maxWriteDuration'] : 0;
1147  $this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( $limit ) {
1148  // If atomic sections or explicit transactions are still open, some caller must have
1149  // caught an exception but failed to properly rollback any changes. Detect that and
1150  // throw and error (causing rollback).
1151  if ( $conn->explicitTrxActive() ) {
1152  throw new DBTransactionError(
1153  $conn,
1154  "Explicit transaction still active. A caller may have caught an error."
1155  );
1156  }
1157  // Assert that the time to replicate the transaction will be sane.
1158  // If this fails, then all DB transactions will be rollback back together.
1159  $time = $conn->pendingWriteQueryDuration( $conn::ESTIMATE_DB_APPLY );
1160  if ( $limit > 0 && $time > $limit ) {
1161  throw new DBTransactionSizeError(
1162  $conn,
1163  "Transaction spent $time second(s) in writes, exceeding the $limit limit.",
1164  [ $time, $limit ]
1165  );
1166  }
1167  // If a connection sits idle while slow queries execute on another, that connection
1168  // may end up dropped before the commit round is reached. Ping servers to detect this.
1169  if ( $conn->writesOrCallbacksPending() && !$conn->ping() ) {
1170  throw new DBTransactionError(
1171  $conn,
1172  "A connection to the {$conn->getDBname()} database was lost before commit."
1173  );
1174  }
1175  } );
1176  }
1177 
1178  public function beginMasterChanges( $fname = __METHOD__ ) {
1179  if ( $this->trxRoundId !== false ) {
1180  throw new DBTransactionError(
1181  null,
1182  "$fname: Transaction round '{$this->trxRoundId}' already started."
1183  );
1184  }
1185  $this->trxRoundId = $fname;
1186 
1187  $failures = [];
1189  function ( Database $conn ) use ( $fname, &$failures ) {
1190  $conn->setTrxEndCallbackSuppression( true );
1191  try {
1192  $conn->flushSnapshot( $fname );
1193  } catch ( DBError $e ) {
1194  call_user_func( $this->errorLogger, $e );
1195  $failures[] = "{$conn->getServer()}: {$e->getMessage()}";
1196  }
1197  $conn->setTrxEndCallbackSuppression( false );
1198  $this->applyTransactionRoundFlags( $conn );
1199  }
1200  );
1201 
1202  if ( $failures ) {
1203  throw new DBExpectedError(
1204  null,
1205  "$fname: Flush failed on server(s) " . implode( "\n", array_unique( $failures ) )
1206  );
1207  }
1208  }
1209 
1210  public function commitMasterChanges( $fname = __METHOD__ ) {
1211  $failures = [];
1212 
1214  $scope = $this->getScopedPHPBehaviorForCommit(); // try to ignore client aborts
1215 
1216  $restore = ( $this->trxRoundId !== false );
1217  $this->trxRoundId = false;
1219  function ( IDatabase $conn ) use ( $fname, $restore, &$failures ) {
1220  try {
1221  if ( $conn->writesOrCallbacksPending() ) {
1222  $conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
1223  } elseif ( $restore ) {
1224  $conn->flushSnapshot( $fname );
1225  }
1226  } catch ( DBError $e ) {
1227  call_user_func( $this->errorLogger, $e );
1228  $failures[] = "{$conn->getServer()}: {$e->getMessage()}";
1229  }
1230  if ( $restore ) {
1231  $this->undoTransactionRoundFlags( $conn );
1232  }
1233  }
1234  );
1235 
1236  if ( $failures ) {
1237  throw new DBExpectedError(
1238  null,
1239  "$fname: Commit failed on server(s) " . implode( "\n", array_unique( $failures ) )
1240  );
1241  }
1242  }
1243 
1244  public function runMasterPostTrxCallbacks( $type ) {
1245  $e = null; // first exception
1246  $this->forEachOpenMasterConnection( function ( Database $conn ) use ( $type, &$e ) {
1247  $conn->setTrxEndCallbackSuppression( false );
1248  if ( $conn->writesOrCallbacksPending() ) {
1249  // This happens if onTransactionIdle() callbacks leave callbacks on *another* DB
1250  // (which finished its callbacks already). Warn and recover in this case. Let the
1251  // callbacks run in the final commitMasterChanges() in LBFactory::shutdown().
1252  $this->queryLogger->error( __METHOD__ . ": found writes/callbacks pending." );
1253  return;
1254  } elseif ( $conn->trxLevel() ) {
1255  // This happens for single-DB setups where DB_REPLICA uses the master DB,
1256  // thus leaving an implicit read-only transaction open at this point. It
1257  // also happens if onTransactionIdle() callbacks leave implicit transactions
1258  // open on *other* DBs (which is slightly improper). Let these COMMIT on the
1259  // next call to commitMasterChanges(), possibly in LBFactory::shutdown().
1260  return;
1261  }
1262  try {
1264  } catch ( Exception $ex ) {
1265  $e = $e ?: $ex;
1266  }
1267  try {
1269  } catch ( Exception $ex ) {
1270  $e = $e ?: $ex;
1271  }
1272  } );
1273 
1274  return $e;
1275  }
1276 
1277  public function rollbackMasterChanges( $fname = __METHOD__ ) {
1278  $restore = ( $this->trxRoundId !== false );
1279  $this->trxRoundId = false;
1281  function ( IDatabase $conn ) use ( $fname, $restore ) {
1282  if ( $conn->writesOrCallbacksPending() ) {
1283  $conn->rollback( $fname, $conn::FLUSHING_ALL_PEERS );
1284  }
1285  if ( $restore ) {
1286  $this->undoTransactionRoundFlags( $conn );
1287  }
1288  }
1289  );
1290  }
1291 
1293  $this->forEachOpenMasterConnection( function ( Database $conn ) {
1294  $conn->setTrxEndCallbackSuppression( true );
1295  } );
1296  }
1297 
1301  private function applyTransactionRoundFlags( IDatabase $conn ) {
1302  if ( $conn->getFlag( $conn::DBO_DEFAULT ) ) {
1303  // DBO_TRX is controlled entirely by CLI mode presence with DBO_DEFAULT.
1304  // Force DBO_TRX even in CLI mode since a commit round is expected soon.
1305  $conn->setFlag( $conn::DBO_TRX, $conn::REMEMBER_PRIOR );
1306  // If config has explicitly requested DBO_TRX be either on or off by not
1307  // setting DBO_DEFAULT, then respect that. Forcing no transactions is useful
1308  // for things like blob stores (ExternalStore) which want auto-commit mode.
1309  }
1310  }
1311 
1315  private function undoTransactionRoundFlags( IDatabase $conn ) {
1316  if ( $conn->getFlag( $conn::DBO_DEFAULT ) ) {
1317  $conn->restoreFlags( $conn::RESTORE_PRIOR );
1318  }
1319  }
1320 
1321  public function flushReplicaSnapshots( $fname = __METHOD__ ) {
1322  $this->forEachOpenReplicaConnection( function ( IDatabase $conn ) {
1323  $conn->flushSnapshot( __METHOD__ );
1324  } );
1325  }
1326 
1327  public function hasMasterConnection() {
1328  return $this->isOpen( $this->getWriterIndex() );
1329  }
1330 
1331  public function hasMasterChanges() {
1332  $pending = 0;
1333  $this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( &$pending ) {
1334  $pending |= $conn->writesOrCallbacksPending();
1335  } );
1336 
1337  return (bool)$pending;
1338  }
1339 
1340  public function lastMasterChangeTimestamp() {
1341  $lastTime = false;
1342  $this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( &$lastTime ) {
1343  $lastTime = max( $lastTime, $conn->lastDoneWrites() );
1344  } );
1345 
1346  return $lastTime;
1347  }
1348 
1349  public function hasOrMadeRecentMasterChanges( $age = null ) {
1350  $age = ( $age === null ) ? $this->mWaitTimeout : $age;
1351 
1352  return ( $this->hasMasterChanges()
1353  || $this->lastMasterChangeTimestamp() > microtime( true ) - $age );
1354  }
1355 
1356  public function pendingMasterChangeCallers() {
1357  $fnames = [];
1358  $this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( &$fnames ) {
1359  $fnames = array_merge( $fnames, $conn->pendingWriteCallers() );
1360  } );
1361 
1362  return $fnames;
1363  }
1364 
1365  public function getLaggedReplicaMode( $domain = false ) {
1366  // No-op if there is only one DB (also avoids recursion)
1367  if ( !$this->laggedReplicaMode && $this->getServerCount() > 1 ) {
1368  try {
1369  // See if laggedReplicaMode gets set
1370  $conn = $this->getConnection( self::DB_REPLICA, false, $domain );
1371  $this->reuseConnection( $conn );
1372  } catch ( DBConnectionError $e ) {
1373  // Avoid expensive re-connect attempts and failures
1374  $this->allReplicasDownMode = true;
1375  $this->laggedReplicaMode = true;
1376  }
1377  }
1378 
1379  return $this->laggedReplicaMode;
1380  }
1381 
1387  public function getLaggedSlaveMode( $domain = false ) {
1388  return $this->getLaggedReplicaMode( $domain );
1389  }
1390 
1391  public function laggedReplicaUsed() {
1392  return $this->laggedReplicaMode;
1393  }
1394 
1400  public function laggedSlaveUsed() {
1401  return $this->laggedReplicaUsed();
1402  }
1403 
1404  public function getReadOnlyReason( $domain = false, IDatabase $conn = null ) {
1405  if ( $this->readOnlyReason !== false ) {
1406  return $this->readOnlyReason;
1407  } elseif ( $this->getLaggedReplicaMode( $domain ) ) {
1408  if ( $this->allReplicasDownMode ) {
1409  return 'The database has been automatically locked ' .
1410  'until the replica database servers become available';
1411  } else {
1412  return 'The database has been automatically locked ' .
1413  'while the replica database servers catch up to the master.';
1414  }
1415  } elseif ( $this->masterRunningReadOnly( $domain, $conn ) ) {
1416  return 'The database master is running in read-only mode.';
1417  }
1418 
1419  return false;
1420  }
1421 
1427  private function masterRunningReadOnly( $domain, IDatabase $conn = null ) {
1429  $masterServer = $this->getServerName( $this->getWriterIndex() );
1430 
1431  return (bool)$cache->getWithSetCallback(
1432  $cache->makeGlobalKey( __CLASS__, 'server-read-only', $masterServer ),
1433  self::TTL_CACHE_READONLY,
1434  function () use ( $domain, $conn ) {
1435  $old = $this->trxProfiler->setSilenced( true );
1436  try {
1437  $dbw = $conn ?: $this->getConnection( self::DB_MASTER, [], $domain );
1438  $readOnly = (int)$dbw->serverIsReadOnly();
1439  if ( !$conn ) {
1440  $this->reuseConnection( $dbw );
1441  }
1442  } catch ( DBError $e ) {
1443  $readOnly = 0;
1444  }
1445  $this->trxProfiler->setSilenced( $old );
1446  return $readOnly;
1447  },
1448  [ 'pcTTL' => $cache::TTL_PROC_LONG, 'busyValue' => 0 ]
1449  );
1450  }
1451 
1452  public function allowLagged( $mode = null ) {
1453  if ( $mode === null ) {
1454  return $this->mAllowLagged;
1455  }
1456  $this->mAllowLagged = $mode;
1457 
1458  return $this->mAllowLagged;
1459  }
1460 
1461  public function pingAll() {
1462  $success = true;
1463  $this->forEachOpenConnection( function ( IDatabase $conn ) use ( &$success ) {
1464  if ( !$conn->ping() ) {
1465  $success = false;
1466  }
1467  } );
1468 
1469  return $success;
1470  }
1471 
1472  public function forEachOpenConnection( $callback, array $params = [] ) {
1473  foreach ( $this->mConns as $connsByServer ) {
1474  foreach ( $connsByServer as $serverConns ) {
1475  foreach ( $serverConns as $conn ) {
1476  $mergedParams = array_merge( [ $conn ], $params );
1477  call_user_func_array( $callback, $mergedParams );
1478  }
1479  }
1480  }
1481  }
1482 
1483  public function forEachOpenMasterConnection( $callback, array $params = [] ) {
1484  $masterIndex = $this->getWriterIndex();
1485  foreach ( $this->mConns as $connsByServer ) {
1486  if ( isset( $connsByServer[$masterIndex] ) ) {
1488  foreach ( $connsByServer[$masterIndex] as $conn ) {
1489  $mergedParams = array_merge( [ $conn ], $params );
1490  call_user_func_array( $callback, $mergedParams );
1491  }
1492  }
1493  }
1494  }
1495 
1496  public function forEachOpenReplicaConnection( $callback, array $params = [] ) {
1497  foreach ( $this->mConns as $connsByServer ) {
1498  foreach ( $connsByServer as $i => $serverConns ) {
1499  if ( $i === $this->getWriterIndex() ) {
1500  continue; // skip master
1501  }
1502  foreach ( $serverConns as $conn ) {
1503  $mergedParams = array_merge( [ $conn ], $params );
1504  call_user_func_array( $callback, $mergedParams );
1505  }
1506  }
1507  }
1508  }
1509 
1510  public function getMaxLag( $domain = false ) {
1511  $maxLag = -1;
1512  $host = '';
1513  $maxIndex = 0;
1514 
1515  if ( $this->getServerCount() <= 1 ) {
1516  return [ $host, $maxLag, $maxIndex ]; // no replication = no lag
1517  }
1518 
1519  $lagTimes = $this->getLagTimes( $domain );
1520  foreach ( $lagTimes as $i => $lag ) {
1521  if ( $this->mLoads[$i] > 0 && $lag > $maxLag ) {
1522  $maxLag = $lag;
1523  $host = $this->mServers[$i]['host'];
1524  $maxIndex = $i;
1525  }
1526  }
1527 
1528  return [ $host, $maxLag, $maxIndex ];
1529  }
1530 
1531  public function getLagTimes( $domain = false ) {
1532  if ( $this->getServerCount() <= 1 ) {
1533  return [ $this->getWriterIndex() => 0 ]; // no replication = no lag
1534  }
1535 
1536  $knownLagTimes = []; // map of (server index => 0 seconds)
1537  $indexesWithLag = [];
1538  foreach ( $this->mServers as $i => $server ) {
1539  if ( empty( $server['is static'] ) ) {
1540  $indexesWithLag[] = $i; // DB server might have replication lag
1541  } else {
1542  $knownLagTimes[$i] = 0; // DB server is a non-replicating and read-only archive
1543  }
1544  }
1545 
1546  return $this->getLoadMonitor()->getLagTimes( $indexesWithLag, $domain ) + $knownLagTimes;
1547  }
1548 
1549  public function safeGetLag( IDatabase $conn ) {
1550  if ( $this->getServerCount() <= 1 ) {
1551  return 0;
1552  } else {
1553  return $conn->getLag();
1554  }
1555  }
1556 
1563  public function safeWaitForMasterPos( IDatabase $conn, $pos = false, $timeout = 10 ) {
1564  if ( $this->getServerCount() <= 1 || !$conn->getLBInfo( 'replica' ) ) {
1565  return true; // server is not a replica DB
1566  }
1567 
1568  if ( !$pos ) {
1569  // Get the current master position, opening a connection if needed
1570  $masterConn = $this->getAnyOpenConnection( $this->getWriterIndex() );
1571  if ( $masterConn ) {
1572  $pos = $masterConn->getMasterPos();
1573  } else {
1574  $masterConn = $this->openConnection( $this->getWriterIndex(), self::DOMAIN_ANY );
1575  $pos = $masterConn->getMasterPos();
1576  $this->closeConnection( $masterConn );
1577  }
1578  }
1579 
1580  if ( $pos instanceof DBMasterPos ) {
1581  $result = $conn->masterPosWait( $pos, $timeout );
1582  if ( $result == -1 || is_null( $result ) ) {
1583  $msg = __METHOD__ . ": Timed out waiting on {$conn->getServer()} pos {$pos}";
1584  $this->replLogger->warning( "$msg" );
1585  $ok = false;
1586  } else {
1587  $this->replLogger->info( __METHOD__ . ": Done" );
1588  $ok = true;
1589  }
1590  } else {
1591  $ok = false; // something is misconfigured
1592  $this->replLogger->error( "Could not get master pos for {$conn->getServer()}." );
1593  }
1594 
1595  return $ok;
1596  }
1597 
1598  public function setTransactionListener( $name, callable $callback = null ) {
1599  if ( $callback ) {
1600  $this->trxRecurringCallbacks[$name] = $callback;
1601  } else {
1602  unset( $this->trxRecurringCallbacks[$name] );
1603  }
1605  function ( IDatabase $conn ) use ( $name, $callback ) {
1606  $conn->setTransactionListener( $name, $callback );
1607  }
1608  );
1609  }
1610 
1611  public function setTableAliases( array $aliases ) {
1612  $this->tableAliases = $aliases;
1613  }
1614 
1615  public function setDomainPrefix( $prefix ) {
1616  if ( $this->mConns['foreignUsed'] ) {
1617  // Do not switch connections to explicit foreign domains unless marked as free
1618  $domains = [];
1619  foreach ( $this->mConns['foreignUsed'] as $i => $connsByDomain ) {
1620  $domains = array_merge( $domains, array_keys( $connsByDomain ) );
1621  }
1622  $domains = implode( ', ', $domains );
1623  throw new DBUnexpectedError( null,
1624  "Foreign domain connections are still in use ($domains)." );
1625  }
1626 
1627  $this->localDomain = new DatabaseDomain(
1628  $this->localDomain->getDatabase(),
1629  null,
1630  $prefix
1631  );
1632 
1633  $this->forEachOpenConnection( function ( IDatabase $db ) use ( $prefix ) {
1634  $db->tablePrefix( $prefix );
1635  } );
1636  }
1637 
1644  final protected function getScopedPHPBehaviorForCommit() {
1645  if ( PHP_SAPI != 'cli' ) { // https://bugs.php.net/bug.php?id=47540
1646  $old = ignore_user_abort( true ); // avoid half-finished operations
1647  return new ScopedCallback( function () use ( $old ) {
1648  ignore_user_abort( $old );
1649  } );
1650  }
1651 
1652  return null;
1653  }
1654 
1655  function __destruct() {
1656  // Avoid connection leaks for sanity
1657  $this->disable();
1658  }
1659 }
1660 
1661 class_alias( LoadBalancer::class, 'LoadBalancer' );
Wikimedia\Rdbms\LoadBalancer\setWaitForPositionIfHigher
setWaitForPositionIfHigher( $pos)
Definition: LoadBalancer.php:511
Wikimedia\Rdbms\DBTransactionSizeError
Definition: DBTransactionSizeError.php:27
Wikimedia\Rdbms\IDatabase\flushSnapshot
flushSnapshot( $fname=__METHOD__)
Commit any transaction but error out if writes or callbacks are pending.
Wikimedia\Rdbms\LoadBalancer\openForeignConnection
openForeignConnection( $i, $domain)
Open a connection to a foreign DB, or return one if it is already open.
Definition: LoadBalancer.php:828
Wikimedia\Rdbms\LoadBalancer\setDomainPrefix
setDomainPrefix( $prefix)
Set a new table prefix for the existing local domain ID for testing.
Definition: LoadBalancer.php:1615
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:45
Wikimedia\Rdbms\IDatabase\isOpen
isOpen()
Is a connection to the database open?
$context
error also a ContextSource you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition: hooks.txt:2612
Wikimedia\Rdbms\LoadBalancer\getServerInfo
getServerInfo( $i)
Return the server info structure for a given index, or false if the index is invalid.
Definition: LoadBalancer.php:1036
Wikimedia\Rdbms\LoadBalancer\$trxProfiler
TransactionProfiler $trxProfiler
Definition: LoadBalancer.php:72
Wikimedia\Rdbms\LoadBalancer\isOpen
isOpen( $index)
Test if the specified index represents an open connection.
Definition: LoadBalancer.php:898
Wikimedia\Rdbms\LoadBalancer\$trxRoundId
string bool $trxRoundId
String if a requested DBO_TRX transaction round is active.
Definition: LoadBalancer.php:99
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:189
Wikimedia\Rdbms\IDatabase\getServer
getServer()
Get the server hostname or IP address.
Wikimedia\Rdbms\LoadBalancer\hasOrMadeRecentMasterChanges
hasOrMadeRecentMasterChanges( $age=null)
Check if this load balancer object had any recent or still pending writes issued against it by this P...
Definition: LoadBalancer.php:1349
Wikimedia\Rdbms\IDatabase\pendingWriteQueryDuration
pendingWriteQueryDuration( $type=self::ESTIMATE_TOTAL)
Get the time spend running write queries for this transaction.
Wikimedia\Rdbms\DatabaseDomain\newFromId
static newFromId( $domain)
Definition: DatabaseDomain.php:63
Wikimedia\Rdbms\IDatabase\tablePrefix
tablePrefix( $prefix=null)
Get/set the table prefix.
Wikimedia\Rdbms\LoadBalancer\getWriterIndex
getWriterIndex()
Definition: LoadBalancer.php:1008
EmptyBagOStuff
A BagOStuff object with no objects in it.
Definition: EmptyBagOStuff.php:29
Wikimedia\Rdbms\DBAccessError
Exception class for attempted DB access.
Definition: DBAccessError.php:28
Wikimedia\Rdbms\LoadBalancer\undoTransactionRoundFlags
undoTransactionRoundFlags(IDatabase $conn)
Definition: LoadBalancer.php:1315
Wikimedia\Rdbms\ILoadMonitor
An interface for database load monitoring.
Definition: ILoadMonitor.php:34
captcha-old.count
count
Definition: captcha-old.py:225
Wikimedia\Rdbms\LoadBalancer\disable
disable()
Disable this load balancer.
Definition: LoadBalancer.php:1067
Wikimedia\Rdbms\LoadBalancer\getMasterPos
getMasterPos()
Get the current master position for chronology control purposes.
Definition: LoadBalancer.php:1048
Wikimedia\Rdbms\IDatabase\ping
ping(&$rtt=null)
Ping the server and try to reconnect if it there is no connection.
Wikimedia\Rdbms\LoadBalancer\$host
string $host
Current server name.
Definition: LoadBalancer.php:107
$result
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1954
Wikimedia\Rdbms\LoadBalancer\safeGetLag
safeGetLag(IDatabase $conn)
Get the lag in seconds for a given connection, or zero if this load balancer does not have replicatio...
Definition: LoadBalancer.php:1549
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
Wikimedia\Rdbms\IDatabase\close
close()
Closes a database connection.
Wikimedia\Rdbms\LoadBalancer\getAnyOpenConnection
getAnyOpenConnection( $i)
Definition: LoadBalancer.php:525
Wikimedia\Rdbms
Definition: ChronologyProtector.php:24
$fname
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined.
Definition: Setup.php:36
Wikimedia\Rdbms\LoadBalancer\$loadMonitorConfig
array $loadMonitorConfig
The LoadMonitor configuration.
Definition: LoadBalancer.php:55
Wikimedia\Rdbms\LoadBalancer\getServerName
getServerName( $i)
Get the host name or IP address of the server with the specified index Prefer a readable name if avai...
Definition: LoadBalancer.php:1024
Wikimedia\Rdbms\DBMasterPos
An object representing a master or replica DB position in a replicated setup.
Definition: DBMasterPos.php:10
$params
$params
Definition: styleTest.css.php:40
Wikimedia\Rdbms\LoadBalancer\forEachOpenMasterConnection
forEachOpenMasterConnection( $callback, array $params=[])
Call a function with each open connection object to a master.
Definition: LoadBalancer.php:1483
BagOStuff
interface is intended to be more or less compatible with the PHP memcached client.
Definition: BagOStuff.php:47
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:304
Wikimedia\Rdbms\LoadBalancer\runMasterPostTrxCallbacks
runMasterPostTrxCallbacks( $type)
Issue all pending post-COMMIT/ROLLBACK callbacks.
Definition: LoadBalancer.php:1244
Wikimedia\Rdbms\LoadBalancer\getLagTimes
getLagTimes( $domain=false)
Get an estimate of replication lag (in seconds) for each server.
Definition: LoadBalancer.php:1531
$success
$success
Definition: NoLocalSettings.php:44
Wikimedia\Rdbms\IDatabase\getLag
getLag()
Get replica DB lag.
Wikimedia\Rdbms\LoadBalancer\flushReplicaSnapshots
flushReplicaSnapshots( $fname=__METHOD__)
Commit all replica DB transactions so as to flush any REPEATABLE-READ or SSI snapshot.
Definition: LoadBalancer.php:1321
Wikimedia\Rdbms\LoadBalancer\$perfLogger
LoggerInterface $perfLogger
Definition: LoadBalancer.php:80
$type
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition: hooks.txt:2536
Wikimedia\Rdbms\LoadBalancer\$replLogger
LoggerInterface $replLogger
Definition: LoadBalancer.php:74
Wikimedia\Rdbms\LoadBalancer\doWait
doWait( $index, $open=false, $timeout=null)
Wait for a given replica DB to catch up to the master pos stored in $this.
Definition: LoadBalancer.php:545
Wikimedia\Rdbms\DBError
Database error base class.
Definition: DBError.php:30
Wikimedia\Rdbms\LoadBalancer\hasMasterChanges
hasMasterChanges()
Determine if there are pending changes in a transaction by this thread.
Definition: LoadBalancer.php:1331
DBO_TRX
const DBO_TRX
Definition: defines.php:12
Wikimedia\Rdbms\LoadBalancer\getMaxLag
getMaxLag( $domain=false)
Get the hostname and lag time of the most-lagged replica DB.
Definition: LoadBalancer.php:1510
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:40
Wikimedia\Rdbms\LoadBalancer\$mWaitForPos
bool DBMasterPos $mWaitForPos
False if not set.
Definition: LoadBalancer.php:87
Wikimedia\Rdbms\IDatabase\lastDoneWrites
lastDoneWrites()
Returns the last time the connection may have been used for write queries.
Wikimedia\Rdbms\LoadBalancer\safeWaitForMasterPos
safeWaitForMasterPos(IDatabase $conn, $pos=false, $timeout=10)
Definition: LoadBalancer.php:1563
Wikimedia\Rdbms\LoadBalancer\$mAllowLagged
bool $mAllowLagged
Whether to disregard replica DB lag as a factor in replica DB selection.
Definition: LoadBalancer.php:51
Wikimedia\Rdbms\IDatabase\commit
commit( $fname=__METHOD__, $flush='')
Commits a transaction previously started using begin().
Wikimedia\Rdbms\DatabaseDomain\newUnspecified
static newUnspecified()
Definition: DatabaseDomain.php:93
Wikimedia\Rdbms\LoadBalancer\allowLagged
allowLagged( $mode=null)
Disables/enables lag checks.
Definition: LoadBalancer.php:1452
key
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling but I prefer the flexibility This should also do the output encoding The system allocates a global one in $wgOut Title Represents the title of an and does all the work of translating among various forms such as plain database key
Definition: design.txt:25
WANObjectCache\newEmpty
static newEmpty()
Get an instance that wraps EmptyBagOStuff.
Definition: WANObjectCache.php:200
Wikimedia\Rdbms\Database\runOnTransactionIdleCallbacks
runOnTransactionIdleCallbacks( $trigger)
Actually run and consume any "on transaction idle/resolution" callbacks.
Definition: Database.php:2560
Wikimedia\Rdbms\MaintainableDBConnRef
Helper class to handle automatically marking connections as reusable (via RAII pattern) as well handl...
Definition: MaintainableDBConnRef.php:13
Wikimedia\Rdbms\LoadBalancer\$mConns
Database[][][] $mConns
Map of local/foreignUsed/foreignFree => server index => IDatabase array.
Definition: LoadBalancer.php:45
Wikimedia\Rdbms\LoadBalancer\getReaderIndex
getReaderIndex( $group=false, $domain=false)
Get the index of the reader connection, which may be a replica DB.
Definition: LoadBalancer.php:305
Wikimedia\Rdbms\LoadBalancer\$trxRecurringCallbacks
array[] $trxRecurringCallbacks
Map of (name => callable)
Definition: LoadBalancer.php:101
Wikimedia\Rdbms\Database\runOnTransactionPreCommitCallbacks
runOnTransactionPreCommitCallbacks()
Actually run and consume any "on transaction pre-commit" callbacks.
Definition: Database.php:2610
Wikimedia\Rdbms\LoadBalancer\getRandomNonLagged
getRandomNonLagged(array $loads, $domain=false, $maxLag=INF)
Definition: LoadBalancer.php:256
Wikimedia\Rdbms\LoadBalancer\getReadOnlyReason
getReadOnlyReason( $domain=false, IDatabase $conn=null)
Definition: LoadBalancer.php:1404
Wikimedia\Rdbms\LoadBalancer\pickReaderIndex
pickReaderIndex(array $loads, $domain=false)
Definition: LoadBalancer.php:369
Wikimedia\Rdbms\LoadBalancer\reuseConnection
reuseConnection( $conn)
Mark a foreign connection as being available for reuse under a different DB domain.
Definition: LoadBalancer.php:685
Wikimedia\Rdbms\LoadBalancer\pendingMasterChangeCallers
pendingMasterChangeCallers()
Get the list of callers that have pending master changes.
Definition: LoadBalancer.php:1356
Wikimedia\Rdbms\Database\trxLevel
trxLevel()
Gets the current transaction level.
Definition: Database.php:461
Wikimedia\Rdbms\LoadBalancer\__destruct
__destruct()
Definition: LoadBalancer.php:1655
Wikimedia\Rdbms\LoadBalancer\$mReadIndex
integer $mReadIndex
The generic (not query grouped) replica DB index (of $mServers)
Definition: LoadBalancer.php:85
Wikimedia\Rdbms\LoadBalancer\$mServers
array[] $mServers
Map of (server index => server config array)
Definition: LoadBalancer.php:43
IExpiringStore\TTL_DAY
const TTL_DAY
Definition: IExpiringStore.php:35
Wikimedia\Rdbms\LoadBalancer\hasMasterConnection
hasMasterConnection()
Definition: LoadBalancer.php:1327
Wikimedia\Rdbms\Database\writesOrCallbacksPending
writesOrCallbacksPending()
Returns true if there is a transaction open with possible write queries or transaction pre-commit/idl...
Definition: Database.php:547
Wikimedia\Rdbms\LoadBalancer\suppressTransactionEndCallbacks
suppressTransactionEndCallbacks()
Suppress all pending post-COMMIT/ROLLBACK callbacks.
Definition: LoadBalancer.php:1292
Wikimedia\Rdbms\LoadBalancer\commitMasterChanges
commitMasterChanges( $fname=__METHOD__)
Issue COMMIT on all master connections where writes where done.
Definition: LoadBalancer.php:1210
$limit
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers please use GetContentModels hook to make them known to core if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive $limit
Definition: hooks.txt:1049
Wikimedia\Rdbms\LoadBalancer\forEachOpenConnection
forEachOpenConnection( $callback, array $params=[])
Call a function with each open connection object.
Definition: LoadBalancer.php:1472
Wikimedia\Rdbms\LoadBalancer\$srvCache
BagOStuff $srvCache
Definition: LoadBalancer.php:64
Wikimedia\Rdbms\LoadBalancer\isNonZeroLoad
isNonZeroLoad( $i)
Returns true if the specified index is valid and has non-zero load.
Definition: LoadBalancer.php:1016
Wikimedia\Rdbms\LoadBalancer\__construct
__construct(array $params)
Construct a manager of IDatabase connection objects.
Definition: LoadBalancer.php:129
Wikimedia\Rdbms\LoadBalancer\applyTransactionRoundFlags
applyTransactionRoundFlags(IDatabase $conn)
Definition: LoadBalancer.php:1301
Wikimedia\Rdbms\LoadBalancer\waitForOne
waitForOne( $pos, $timeout=null)
Set the master wait position and wait for a "generic" replica DB to catch up to it.
Definition: LoadBalancer.php:461
$time
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1769
Wikimedia\Rdbms\LoadBalancer\$errorLogger
callable $errorLogger
Exception logger.
Definition: LoadBalancer.php:114
Wikimedia\Rdbms\LoadBalancer\pingAll
pingAll()
Definition: LoadBalancer.php:1461
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
Wikimedia\Rdbms\LoadBalancer\$localDomainIdAlias
string $localDomainIdAlias
Alternate ID string for the domain instead of DatabaseDomain::getId()
Definition: LoadBalancer.php:105
Wikimedia\Rdbms\LoadBalancer\$tableAliases
$tableAliases
Definition: LoadBalancer.php:57
Wikimedia\Rdbms\LoadBalancer\forEachOpenReplicaConnection
forEachOpenReplicaConnection( $callback, array $params=[])
Call a function with each open replica DB connection object.
Definition: LoadBalancer.php:1496
Wikimedia\Rdbms\LoadBalancer\beginMasterChanges
beginMasterChanges( $fname=__METHOD__)
Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set)
Definition: LoadBalancer.php:1178
DB_MASTER
const DB_MASTER
Definition: defines.php:26
Wikimedia\Rdbms\LoadBalancer\$mWaitTimeout
integer $mWaitTimeout
Seconds to spend waiting on replica DB lag to resolve.
Definition: LoadBalancer.php:53
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
Wikimedia\Rdbms\LoadBalancer\commitAll
commitAll( $fname=__METHOD__)
Commit transactions on all open connections.
Definition: LoadBalancer.php:1108
Wikimedia\Rdbms\LoadBalancer
Database connection, tracking, load balancing, and transaction manager for a cluster.
Definition: LoadBalancer.php:41
Wikimedia\Rdbms\LoadBalancer\getLazyConnectionRef
getLazyConnectionRef( $db, $groups=[], $domain=false)
Get a database connection handle reference without connecting yet.
Definition: LoadBalancer.php:741
Wikimedia\Rdbms\LoadBalancer\getScopedPHPBehaviorForCommit
getScopedPHPBehaviorForCommit()
Make PHP ignore user aborts/disconnects until the returned value leaves scope.
Definition: LoadBalancer.php:1644
Wikimedia\Rdbms\LoadBalancer\getConnectionRef
getConnectionRef( $db, $groups=[], $domain=false)
Get a database connection handle reference.
Definition: LoadBalancer.php:735
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2122
Wikimedia\Rdbms\LoadBalancer\getLoadMonitor
getLoadMonitor()
Get a LoadMonitor instance.
Definition: LoadBalancer.php:229
Wikimedia\Rdbms\LoadBalancer\getMaintenanceConnectionRef
getMaintenanceConnectionRef( $db, $groups=[], $domain=false)
Get a maintenance database connection handle reference for migrations and schema changes.
Definition: LoadBalancer.php:747
Wikimedia\Rdbms\LoadBalancer\setTransactionListener
setTransactionListener( $name, callable $callback=null)
Set a callback via IDatabase::setTransactionListener() on all current and future master connections o...
Definition: LoadBalancer.php:1598
Wikimedia\Rdbms\IDatabase\explicitTrxActive
explicitTrxActive()
Wikimedia\Rdbms\LoadBalancer\laggedSlaveUsed
laggedSlaveUsed()
Definition: LoadBalancer.php:1400
Wikimedia\Rdbms\LoadBalancer\setServerInfo
setServerInfo( $i, array $serverInfo)
Sets the server info structure for the given index.
Definition: LoadBalancer.php:1044
Wikimedia\Rdbms\IDatabase\setTransactionListener
setTransactionListener( $name, callable $callback=null)
Run a callback each time any transaction commits or rolls back.
Wikimedia\Rdbms\LoadBalancer\$mLoads
float[] $mLoads
Map of (server index => weight)
Definition: LoadBalancer.php:47
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:81
Wikimedia\Rdbms\IDatabase\rollback
rollback( $fname=__METHOD__, $flush='')
Rollback a transaction previously started using begin().
Wikimedia\Rdbms\Database\setTrxEndCallbackSuppression
setTrxEndCallbackSuppression( $suppress)
Whether to disable running of post-COMMIT/ROLLBACK callbacks.
Definition: Database.php:2547
Wikimedia\Rdbms\IDatabase\pendingWriteCallers
pendingWriteCallers()
Get the list of method names that did write queries for this transaction.
Wikimedia\Rdbms\LoadBalancer\$allReplicasDownMode
bool $allReplicasDownMode
Whether the generic reader fell back to a lagged replica DB.
Definition: LoadBalancer.php:91
Wikimedia\Rdbms\LoadBalancer\rollbackMasterChanges
rollbackMasterChanges( $fname=__METHOD__)
Issue ROLLBACK only on master, only if queries were done on connection.
Definition: LoadBalancer.php:1277
Wikimedia\Rdbms\LoadBalancer\$profiler
object string $profiler
Class name or object With profileIn/profileOut methods.
Definition: LoadBalancer.php:70
Wikimedia\Rdbms\IDatabase\getFlag
getFlag( $flag)
Returns a boolean whether the flag $flag is set for this connection.
Wikimedia\Rdbms\DBUnexpectedError
Definition: DBUnexpectedError.php:27
Wikimedia\Rdbms\IDatabase\masterPosWait
masterPosWait(DBMasterPos $pos, $timeout)
Wait for the replica DB to catch up to a given master position.
Wikimedia\Rdbms\LoadBalancer\approveMasterChanges
approveMasterChanges(array $options)
Perform all pre-commit checks for things like replication safety.
Definition: LoadBalancer.php:1145
Wikimedia\Rdbms\LoadBalancer\$memCache
BagOStuff $memCache
Definition: LoadBalancer.php:66
Wikimedia\Rdbms\LoadBalancer\$chronProt
ChronologyProtector null $chronProt
Definition: LoadBalancer.php:62
Wikimedia\Rdbms\LoadBalancer\$readOnlyReason
string bool $readOnlyReason
Reason the LB is read-only or false if not.
Definition: LoadBalancer.php:95
Wikimedia\Rdbms\DBConnRef
Helper class to handle automatically marking connections as reusable (via RAII pattern) as well handl...
Definition: DBConnRef.php:15
Wikimedia\Rdbms\LoadBalancer\masterRunningReadOnly
masterRunningReadOnly( $domain, IDatabase $conn=null)
Definition: LoadBalancer.php:1427
Wikimedia\Rdbms\LoadBalancer\setTableAliases
setTableAliases(array $aliases)
Make certain table names use their own database, schema, and table prefix when passed into SQL querie...
Definition: LoadBalancer.php:1611
Wikimedia\Rdbms\DBTransactionError
Definition: DBTransactionError.php:27
Wikimedia\Rdbms\LoadBalancer\$localDomain
DatabaseDomain $localDomain
Local Domain ID and default for selectDB() calls.
Definition: LoadBalancer.php:103
$cache
$cache
Definition: mcc.php:33
Wikimedia\Rdbms\DBExpectedError
Base class for the more common types of database errors.
Definition: DBExpectedError.php:35
Wikimedia\Rdbms\Database\factory
static factory( $dbType, $p=[])
Construct a Database subclass instance given a database type and parameters.
Definition: Database.php:334
ArrayUtils\pickRandom
static pickRandom( $weights)
Given an array of non-normalised probabilities, this function will select an element and return the a...
Definition: ArrayUtils.php:66
Wikimedia\Rdbms\LoadBalancer\$chronProtInitialized
boolean $chronProtInitialized
Definition: LoadBalancer.php:119
ArrayUtils
A collection of static methods to play with arrays.
Definition: ArrayUtils.php:28
Wikimedia\Rdbms\ChronologyProtector
Class for ensuring a consistent ordering of events as seen by the user, despite replication.
Definition: ChronologyProtector.php:36
Wikimedia\Rdbms\LoadBalancer\getLaggedReplicaMode
getLaggedReplicaMode( $domain=false)
Definition: LoadBalancer.php:1365
Wikimedia\Rdbms\Database\flushSnapshot
flushSnapshot( $fname=__METHOD__)
Commit any transaction but error out if writes or callbacks are pending.
Definition: Database.php:2873
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
Wikimedia\Rdbms\Database\runTransactionListenerCallbacks
runTransactionListenerCallbacks( $trigger)
Actually run any "transaction listener" callbacks.
Definition: Database.php:2640
Wikimedia\Rdbms\LoadBalancer\$errorConnection
Database $errorConnection
DB connection object that caused a problem.
Definition: LoadBalancer.php:83
Wikimedia\Rdbms\LoadBalancer\$connsOpened
integer $connsOpened
Total connections opened.
Definition: LoadBalancer.php:97
Wikimedia\Rdbms\DBConnectionError
Definition: DBConnectionError.php:26
Wikimedia\Rdbms\LoadBalancer\finalizeMasterChanges
finalizeMasterChanges()
Perform all pre-commit callbacks that remain part of the atomic transactions and disable any post-com...
Definition: LoadBalancer.php:1135
Wikimedia\Rdbms\IDatabase\getLBInfo
getLBInfo( $name=null)
Get properties passed down from the server info array of the load balancer.
Wikimedia\Rdbms\LoadBalancer\haveIndex
haveIndex( $i)
Returns true if the specified index is a valid server index.
Definition: LoadBalancer.php:1012
Wikimedia\Rdbms\LoadBalancer\$disabled
boolean $disabled
Definition: LoadBalancer.php:117
Wikimedia\Rdbms\DBMasterPos\hasReached
hasReached(DBMasterPos $pos)
Wikimedia\Rdbms\LoadBalancer\getLaggedSlaveMode
getLaggedSlaveMode( $domain=false)
Definition: LoadBalancer.php:1387
class
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
Wikimedia\Rdbms\LoadBalancer\laggedReplicaUsed
laggedReplicaUsed()
Checks whether the database for generic connections this request was both:
Definition: LoadBalancer.php:1391
Wikimedia\Rdbms\LoadBalancer\$wanCache
WANObjectCache $wanCache
Definition: LoadBalancer.php:68
Wikimedia\Rdbms\LoadBalancer\reallyOpenConnection
reallyOpenConnection(array $server, $dbNameOverride=false)
Really opens a connection.
Definition: LoadBalancer.php:917
Wikimedia\Rdbms\LoadBalancer\$agent
string $agent
Agent name for query profiling.
Definition: LoadBalancer.php:111
Wikimedia\Rdbms\DatabaseDomain
Class to handle database/prefix specification for IDatabase domains.
Definition: DatabaseDomain.php:28
Wikimedia\Rdbms\TransactionProfiler
Helper class that detects high-contention DB queries via profiling calls.
Definition: TransactionProfiler.php:39
Wikimedia\Rdbms\LoadBalancer\$laggedReplicaMode
bool $laggedReplicaMode
Whether the generic reader fell back to a lagged replica DB.
Definition: LoadBalancer.php:89
Wikimedia\Rdbms\LoadBalancer\$queryLogger
LoggerInterface $queryLogger
Definition: LoadBalancer.php:78
Wikimedia\Rdbms\LoadBalancer\$loadMonitor
ILoadMonitor $loadMonitor
Definition: LoadBalancer.php:60
DBO_DEFAULT
const DBO_DEFAULT
Definition: defines.php:13
Wikimedia\Rdbms\IDatabase\setFlag
setFlag( $flag, $remember=self::REMEMBER_NOTHING)
Set a flag for this connection.
Wikimedia\Rdbms\LoadBalancer\waitForAll
waitForAll( $pos, $timeout=null)
Set the master wait position and wait for ALL replica DBs to catch up to it.
Definition: LoadBalancer.php:488
Wikimedia\Rdbms\LoadBalancer\closeAll
closeAll()
Close all open connections.
Definition: LoadBalancer.php:1072
Wikimedia\Rdbms\LoadBalancer\$connLogger
LoggerInterface $connLogger
Definition: LoadBalancer.php:76
Wikimedia\Rdbms\IDatabase\restoreFlags
restoreFlags( $state=self::RESTORE_PRIOR)
Restore the flags to their prior state before the last setFlag/clearFlag call.
Wikimedia\Rdbms\LoadBalancer\waitFor
waitFor( $pos)
Set the master wait position.
Definition: LoadBalancer.php:444
Wikimedia\Rdbms\LoadBalancer\$mLastError
string $mLastError
The last DB selection or connection error.
Definition: LoadBalancer.php:93
$options
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1049
Wikimedia\Rdbms\LoadBalancer\lastMasterChangeTimestamp
lastMasterChangeTimestamp()
Get the timestamp of the latest write query done by this thread.
Definition: LoadBalancer.php:1340
Wikimedia\Rdbms\LoadBalancer\$cliMode
bool $cliMode
Whether this PHP instance is for a CLI script.
Definition: LoadBalancer.php:109
Wikimedia\Rdbms\ILoadBalancer
Database cluster connection, tracking, load balancing, and transaction manager interface.
Definition: ILoadBalancer.php:79
Wikimedia\Rdbms\LoadBalancer\getServerCount
getServerCount()
Get the number of defined servers (not the number of open connections)
Definition: LoadBalancer.php:1020
Wikimedia\Rdbms\LoadBalancer\closeConnection
closeConnection(IDatabase $conn)
Close a connection.
Definition: LoadBalancer.php:1087
Wikimedia\Rdbms\LoadBalancer\reportConnectionError
reportConnectionError()
Definition: LoadBalancer.php:980
array
the array() calling protocol came about after MediaWiki 1.4rc1.
Wikimedia\Rdbms\LoadBalancer\openConnection
openConnection( $i, $domain=false)
Definition: LoadBalancer.php:761
Wikimedia\Rdbms\LoadBalancer\getConnection
getConnection( $i, $groups=[], $domain=false)
Definition: LoadBalancer.php:616
Wikimedia\Rdbms\LoadBalancer\$mGroupLoads
array[] $mGroupLoads
Map of (group => server index => weight)
Definition: LoadBalancer.php:49
Wikimedia\Rdbms\IDatabase\writesOrCallbacksPending
writesOrCallbacksPending()
Returns true if there is a transaction open with possible write queries or transaction pre-commit/idl...