24use Psr\Log\LoggerInterface;
25use Psr\Log\NullLogger;
26use Wikimedia\ScopedCallback;
31use UnexpectedValueException;
32use InvalidArgumentException;
126 const CONN_HELD_WARN_THRESHOLD = 10;
129 const MAX_LAG_DEFAULT = 10;
131 const MAX_WAIT_DEFAULT = 10;
133 const TTL_CACHE_READONLY = 5;
144 if ( !isset(
$params[
'servers'] ) ) {
145 throw new InvalidArgumentException( __CLASS__ .
': missing servers parameter' );
147 $this->
servers = $params[
'servers'];
148 foreach ( $this->
servers as $i => $server ) {
150 $this->
servers[$i][
'master'] =
true;
152 $this->
servers[$i][
'replica'] =
true;
161 $this->waitTimeout = isset(
$params[
'waitTimeout'] )
163 : self::MAX_WAIT_DEFAULT;
165 $this->readIndex = -1;
168 self::KEY_LOCAL => [],
169 self::KEY_FOREIGN_INUSE => [],
170 self::KEY_FOREIGN_FREE => [],
172 self::KEY_LOCAL_NOROUND => [],
173 self::KEY_FOREIGN_INUSE_NOROUND => [],
174 self::KEY_FOREIGN_FREE_NOROUND => []
177 $this->waitForPos =
false;
180 if ( isset(
$params[
'readOnlyReason'] ) && is_string(
$params[
'readOnlyReason'] ) ) {
181 $this->readOnlyReason =
$params[
'readOnlyReason'];
184 if ( isset(
$params[
'maxLag'] ) ) {
185 $this->maxLag =
$params[
'maxLag'];
188 if ( isset(
$params[
'loadMonitor'] ) ) {
189 $this->loadMonitorConfig =
$params[
'loadMonitor'];
191 $this->loadMonitorConfig = [
'class' =>
'LoadMonitorNull' ];
193 $this->loadMonitorConfig += [
'lagWarnThreshold' =>
$this->maxLag ];
195 foreach (
$params[
'servers'] as $i => $server ) {
196 $this->loads[$i] = $server[
'load'];
197 if ( isset( $server[
'groupLoads'] ) ) {
198 foreach ( $server[
'groupLoads'] as $group => $ratio ) {
199 if ( !isset( $this->groupLoads[$group] ) ) {
200 $this->groupLoads[$group] = [];
202 $this->groupLoads[$group][$i] = $ratio;
207 if ( isset(
$params[
'srvCache'] ) ) {
208 $this->srvCache =
$params[
'srvCache'];
212 if ( isset(
$params[
'wanCache'] ) ) {
213 $this->wanCache =
$params[
'wanCache'];
215 $this->wanCache = WANObjectCache::newEmpty();
217 $this->profiler = isset(
$params[
'profiler'] ) ?
$params[
'profiler'] :
null;
218 if ( isset(
$params[
'trxProfiler'] ) ) {
219 $this->trxProfiler =
$params[
'trxProfiler'];
224 $this->errorLogger = isset(
$params[
'errorLogger'] )
226 :
function ( Exception
$e ) {
227 trigger_error( get_class(
$e ) .
': ' .
$e->getMessage(), E_USER_WARNING );
229 $this->deprecationLogger = isset(
$params[
'deprecationLogger'] )
231 :
function ( $msg ) {
232 trigger_error( $msg, E_USER_DEPRECATED );
235 foreach ( [
'replLogger',
'connLogger',
'queryLogger',
'perfLogger' ] as $key ) {
236 $this->$key = isset(
$params[$key] ) ?
$params[$key] :
new NullLogger();
239 $this->host = isset(
$params[
'hostname'] )
241 : ( gethostname() ?:
'unknown' );
242 $this->cliMode = isset(
$params[
'cliMode'] )
244 : ( PHP_SAPI ===
'cli' || PHP_SAPI ===
'phpdbg' );
247 if ( isset(
$params[
'chronologyCallback'] ) ) {
248 $this->chronologyCallback =
$params[
'chronologyCallback'];
260 return $this->localDomain->getId();
269 if ( !isset( $this->loadMonitor ) ) {
271 'LoadMonitor' => LoadMonitor::class,
272 'LoadMonitorNull' => LoadMonitorNull::class,
273 'LoadMonitorMySQL' => LoadMonitorMySQL::class,
276 $class = $this->loadMonitorConfig[
'class'];
277 if ( isset( $compat[$class] ) ) {
278 $class = $compat[$class];
281 $this->loadMonitor =
new $class(
282 $this, $this->srvCache, $this->wanCache, $this->loadMonitorConfig );
283 $this->loadMonitor->setLogger( $this->replLogger );
298 # Unset excessively lagged servers
299 foreach ( $lags as $i => $lag ) {
301 # How much lag this server nominally is allowed to have
302 $maxServerLag = isset( $this->
servers[$i][
'max lag'] )
303 ? $this->
servers[$i][
'max lag']
305 # Constrain that futher by $maxLag argument
306 $maxServerLag = min( $maxServerLag,
$maxLag );
309 if ( $lag ===
false && !is_infinite( $maxServerLag ) ) {
310 $this->replLogger->error(
312 ": server {host} is not replicating?", [
'host' =>
$host ] );
314 } elseif ( $lag > $maxServerLag ) {
315 $this->replLogger->debug(
317 ": server {host} has {lag} seconds of lag (>= {maxlag})",
318 [
'host' =>
$host,
'lag' => $lag,
'maxlag' => $maxServerLag ]
325 # Find out if all the replica DBs with non-zero load are lagged
327 foreach (
$loads as $load ) {
331 # No appropriate DB servers except maybe the master and some replica DBs with zero load
332 # Do NOT use the master
333 # Instead, this function will return false, triggering read-only mode,
334 # and a lagged replica DB will be used instead.
338 if ( count(
$loads ) == 0 ) {
342 # Return a random representative of the remainder
347 if ( count( $this->
servers ) == 1 ) {
350 } elseif ( $group ===
false && $this->readIndex >= 0 ) {
355 if ( $group !==
false ) {
357 if ( isset( $this->groupLoads[$group] ) ) {
358 $loads = $this->groupLoads[$group];
361 $this->connLogger->info( __METHOD__ .
": no loads for group $group" );
375 if ( $i ===
false ) {
385 if ( !$this->
doWait( $i ) ) {
390 if ( $this->readIndex <= 0 && $this->loads[$i] > 0 && $group ===
false ) {
392 $this->readIndex = $i;
395 $this->laggedReplicaMode =
true;
400 $this->connLogger->debug( __METHOD__ .
": using server $serverName for group '$group'" );
412 throw new InvalidArgumentException(
"Empty server array given to LoadBalancer" );
422 while ( count( $currentLoads ) ) {
427 if ( $this->waitForPos && $this->waitForPos->asOfTime() ) {
431 $ago = microtime(
true ) - $this->waitForPos->asOfTime();
435 if ( $i ===
false ) {
439 if ( $i ===
false && count( $currentLoads ) != 0 ) {
441 $this->replLogger->error(
442 __METHOD__ .
": all replica DBs lagged. Switch to read-only mode" );
448 if ( $i ===
false ) {
452 $this->connLogger->debug( __METHOD__ .
": pickRandom() returned false" );
454 return [
false,
false ];
458 $this->connLogger->debug( __METHOD__ .
": Using reader #$i: $serverName..." );
462 $this->connLogger->warning( __METHOD__ .
": Failed connecting to $i/$domain" );
463 unset( $currentLoads[$i] );
470 if ( $domain !==
false ) {
479 if ( !count( $currentLoads ) ) {
480 $this->connLogger->error( __METHOD__ .
": all servers down" );
489 $this->waitForPos = $pos;
493 if ( !$this->
doWait( $i ) ) {
494 $this->laggedReplicaMode =
true;
506 $this->waitForPos = $pos;
513 $readLoads = array_filter( $readLoads );
518 $ok = $this->
doWait( $i,
true, $timeout );
523 # Restore the old position, as this is not used for lag-protection but for throttling
524 $this->waitForPos = $oldPos;
535 $this->waitForPos = $pos;
536 $serverCount = count( $this->
servers );
539 for ( $i = 1; $i < $serverCount; $i++ ) {
540 if ( $this->loads[$i] > 0 ) {
541 $start = microtime(
true );
542 $ok = $this->
doWait( $i,
true, $timeout ) && $ok;
543 $timeout -= intval( microtime(
true ) - $start );
544 if ( $timeout <= 0 ) {
550 # Restore the old position, as this is not used for lag-protection but for throttling
551 $this->waitForPos = $oldPos;
565 if ( !$this->waitForPos || $pos->hasReached( $this->waitForPos ) ) {
566 $this->waitForPos = $pos;
571 $autocommit = ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT );
572 foreach ( $this->conns as $connsByServer ) {
573 if ( !isset( $connsByServer[$i] ) ) {
577 foreach ( $connsByServer[$i] as $conn ) {
578 if ( !$autocommit || $conn->getLBInfo(
'autoCommitOnly' ) ) {
594 protected function doWait( $index, $open =
false, $timeout =
null ) {
599 $key = $this->srvCache->makeGlobalKey( __CLASS__,
'last-known-pos', $server,
'v1' );
601 $knownReachedPos = $this->srvCache->get( $key );
604 $knownReachedPos->
hasReached( $this->waitForPos )
606 $this->replLogger->debug(
608 ': replica DB {dbserver} known to be caught up (pos >= $knownReachedPos).',
609 [
'dbserver' => $server ]
619 $this->replLogger->debug(
620 __METHOD__ .
': no connection open for {dbserver}',
621 [
'dbserver' => $server ]
628 $this->replLogger->warning(
629 __METHOD__ .
': failed to connect to {dbserver}',
630 [
'dbserver' => $server ]
641 $this->replLogger->info(
643 ': waiting for replica DB {dbserver} to catch up...',
644 [
'dbserver' => $server ]
647 $result = $conn->masterPosWait( $this->waitForPos, $timeout );
649 if ( $result ===
null ) {
650 $this->replLogger->warning(
651 __METHOD__ .
': Errored out waiting on {host} pos {pos}',
654 'pos' => $this->waitForPos,
655 'trace' => (
new RuntimeException() )->getTraceAsString()
659 } elseif ( $result == -1 ) {
660 $this->replLogger->warning(
661 __METHOD__ .
': Timed out waiting on {host} pos {pos}',
664 'pos' => $this->waitForPos,
665 'trace' => (
new RuntimeException() )->getTraceAsString()
670 $this->replLogger->debug( __METHOD__ .
": done waiting" );
673 $this->srvCache->set( $key, $this->waitForPos, BagOStuff::TTL_DAY );
683 public function getConnection( $i, $groups = [], $domain =
false, $flags = 0 ) {
684 if ( $i ===
null || $i ===
false ) {
685 throw new InvalidArgumentException(
'Attempt to call ' . __METHOD__ .
686 ' with invalid server index' );
689 if ( $this->localDomain->equals( $domain ) || $domain === $this->localDomainIdAlias ) {
693 if ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) === self::CONN_TRX_AUTOCOMMIT ) {
699 if ( $attributes[Database::ATTR_DB_LEVEL_LOCKING] ) {
704 $flags &= ~self::CONN_TRX_AUTOCOMMIT;
705 $this->connLogger->info( __METHOD__ .
706 ': ignoring CONN_TRX_AUTOCOMMIT to avoid deadlocks.' );
710 $groups = ( $groups ===
false || $groups === [] )
714 $masterOnly = ( $i == self::DB_MASTER || $i == $this->
getWriterIndex() );
717 if ( $i == self::DB_MASTER ) {
719 } elseif ( $i == self::DB_REPLICA ) {
720 # Try to find an available server in any the query groups (in order)
721 foreach ( $groups as $group ) {
723 if ( $groupIndex !==
false ) {
730 # Operation-based index
731 if ( $i == self::DB_REPLICA ) {
732 $this->lastError =
'Unknown error';
733 # Try the general server pool if $groups are unavailable.
734 $i = ( $groups === [
false ] )
737 # Couldn't find a working server in getReaderIndex()?
738 if ( $i ===
false ) {
746 # Now we have an explicit index into the servers array
754 # Profile any new connections that happen
755 if ( $this->connsOpened > $oldConnsOpened ) {
756 $host = $conn->getServer();
757 $dbname = $conn->getDBname();
758 $this->trxProfiler->recordConnection(
$host, $dbname, $masterOnly );
762 # Make master-requested DB handles inherit any read-only mode setting
763 $conn->setLBInfo(
'readOnlyReason', $this->
getReadOnlyReason( $domain, $conn ) );
770 $serverIndex = $conn->
getLBInfo(
'serverIndex' );
771 $refCount = $conn->
getLBInfo(
'foreignPoolRefCount' );
772 if ( $serverIndex ===
null || $refCount ===
null ) {
784 } elseif ( $conn instanceof
DBConnRef ) {
787 $this->connLogger->error(
788 __METHOD__ .
": got DBConnRef instance.\n" .
789 (
new RuntimeException() )->getTraceAsString() );
794 if ( $this->disabled ) {
798 if ( $conn->
getLBInfo(
'autoCommitOnly' ) ) {
807 if ( !isset( $this->conns[$connInUseKey][$serverIndex][$domain] ) ) {
808 throw new InvalidArgumentException( __METHOD__ .
809 ": connection $serverIndex/$domain not found; it may have already been freed." );
810 } elseif ( $this->conns[$connInUseKey][$serverIndex][$domain] !== $conn ) {
811 throw new InvalidArgumentException( __METHOD__ .
812 ": connection $serverIndex/$domain mismatched; it may have already been freed." );
815 $conn->
setLBInfo(
'foreignPoolRefCount', --$refCount );
816 if ( $refCount <= 0 ) {
817 $this->conns[$connFreeKey][$serverIndex][$domain] = $conn;
818 unset( $this->conns[$connInUseKey][$serverIndex][$domain] );
819 if ( !$this->conns[$connInUseKey][$serverIndex] ) {
820 unset( $this->conns[$connInUseKey][$serverIndex] );
822 $this->connLogger->debug( __METHOD__ .
": freed connection $serverIndex/$domain" );
824 $this->connLogger->debug( __METHOD__ .
825 ": reference count for $serverIndex/$domain reduced to $refCount" );
838 return new DBConnRef( $this, [ $db, $groups, $domain, $flags ] );
845 $this, $this->
getConnection( $db, $groups, $domain, $flags ) );
849 if ( $this->localDomain->equals( $domain ) || $domain === $this->localDomainIdAlias ) {
853 if ( !$this->connectionAttempted && $this->chronologyCallback ) {
854 $this->connLogger->debug( __METHOD__ .
': calling initLB() before first connection.' );
856 $this->connectionAttempted =
true;
857 call_user_func( $this->chronologyCallback, $this );
864 $autoCommit = ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT );
866 if ( $domain !==
false ) {
872 if ( isset( $this->conns[$connKey][$i][0] ) ) {
873 $conn = $this->conns[$connKey][$i][0];
875 if ( !isset( $this->
servers[$i] ) || !is_array( $this->
servers[$i] ) ) {
876 throw new InvalidArgumentException(
"No server with index '$i'." );
880 $server[
'serverIndex'] = $i;
881 $server[
'autoCommitOnly'] = $autoCommit;
882 if ( $this->localDomain->getDatabase() !==
null ) {
884 $server[
'tablePrefix'] = $this->localDomain->getTablePrefix();
888 if ( $conn->isOpen() ) {
889 $this->connLogger->debug(
890 __METHOD__ .
": connected to database $i at '$host'." );
891 $this->conns[$connKey][$i][0] = $conn;
893 $this->connLogger->warning(
894 __METHOD__ .
": failed to connect to database $i at '$host'." );
895 $this->errorConnection = $conn;
906 $this->errorConnection = $conn;
910 if ( $autoCommit && $conn instanceof
IDatabase ) {
911 $conn->clearFlag( $conn::DBO_TRX );
940 $dbName = $domainInstance->getDatabase();
941 $prefix = $domainInstance->getTablePrefix();
942 $autoCommit = ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT );
952 if ( isset( $this->conns[$connInUseKey][$i][$domain] ) ) {
954 $conn = $this->conns[$connInUseKey][$i][$domain];
955 $this->connLogger->debug( __METHOD__ .
": reusing connection $i/$domain" );
956 } elseif ( isset( $this->conns[$connFreeKey][$i][$domain] ) ) {
958 $conn = $this->conns[$connFreeKey][$i][$domain];
959 unset( $this->conns[$connFreeKey][$i][$domain] );
960 $this->conns[$connInUseKey][$i][$domain] = $conn;
961 $this->connLogger->debug( __METHOD__ .
": reusing free connection $i/$domain" );
962 } elseif ( !empty( $this->conns[$connFreeKey][$i] ) ) {
964 $conn = reset( $this->conns[$connFreeKey][$i] );
965 $oldDomain =
key( $this->conns[$connFreeKey][$i] );
966 if ( strlen( $dbName ) && !$conn->selectDB( $dbName ) ) {
967 $this->lastError =
"Error selecting database '$dbName' on server " .
968 $conn->getServer() .
" from client host {$this->host}";
969 $this->errorConnection = $conn;
972 $conn->tablePrefix( $prefix );
973 unset( $this->conns[$connFreeKey][$i][$oldDomain] );
975 $this->conns[$connInUseKey][$i][$conn->getDomainId()] = $conn;
976 $this->connLogger->debug( __METHOD__ .
977 ": reusing free connection from $oldDomain for $domain" );
980 if ( !isset( $this->
servers[$i] ) || !is_array( $this->
servers[$i] ) ) {
981 throw new InvalidArgumentException(
"No server with index '$i'." );
985 $server[
'serverIndex'] = $i;
986 $server[
'foreignPoolRefCount'] = 0;
987 $server[
'foreign'] =
true;
988 $server[
'autoCommitOnly'] = $autoCommit;
990 if ( !$conn->isOpen() ) {
991 $this->connLogger->warning( __METHOD__ .
": connection error for $i/$domain" );
992 $this->errorConnection = $conn;
995 $conn->tablePrefix( $prefix );
997 $this->conns[$connInUseKey][$i][$conn->getDomainID()] = $conn;
998 $this->connLogger->debug( __METHOD__ .
": opened new connection for $i/$domain" );
1004 if ( !$domainInstance->isCompatible( $conn->getDomainID() ) ) {
1005 throw new UnexpectedValueException(
1006 "Got connection to '{$conn->getDomainID()}', but expected '$domain'." );
1009 $refCount = $conn->getLBInfo(
'foreignPoolRefCount' );
1010 $conn->setLBInfo(
'foreignPoolRefCount', $refCount + 1 );
1019 isset( $this->
servers[$i][
'driver'] ) ? $this->
servers[$i][
'driver'] : null
1031 if ( !is_int( $index ) ) {
1050 if ( $this->disabled ) {
1055 if ( $domainOverride->getDatabase() ===
null ) {
1058 if ( $server[
'type'] ===
'mysql' ) {
1062 $server[
'dbname'] =
null;
1065 $server[
'dbname'] = $domainOverride->getDatabase();
1066 $server[
'schema'] = $domainOverride->getSchema();
1071 $server[
'clusterMasterHost'] = $masterName;
1074 if ( ++$this->connsOpened >= self::CONN_HELD_WARN_THRESHOLD ) {
1075 $this->perfLogger->warning( __METHOD__ .
": " .
1076 "{$this->connsOpened}+ connections made (master=$masterName)" );
1092 $server[
'flags'] = isset( $server[
'flags'] ) ? $server[
'flags'] : IDatabase::DBO_DEFAULT;
1103 $db->setLBInfo( $server );
1104 $db->setLazyMasterHandle(
1107 $db->setTableAliases( $this->tableAliases );
1108 $db->setIndexAliases( $this->indexAliases );
1111 if ( $this->trxRoundId !==
false ) {
1114 foreach ( $this->trxRecurringCallbacks as $name => $callback ) {
1115 $db->setTransactionListener( $name, $callback );
1128 'method' => __METHOD__,
1133 $context[
'db_server'] = $conn->getServer();
1134 $this->connLogger->warning(
1135 __METHOD__ .
": connection error: {last_error} ({db_server})",
1140 $conn->reportConnectionError(
"{$this->lastError} ({$context['db_server']})" );
1143 $this->connLogger->error(
1145 ": LB failure with no last connection. Connection error: {last_error}",
1159 return array_key_exists( $i, $this->
servers );
1163 return array_key_exists( $i, $this->
servers ) && $this->loads[$i] != 0;
1167 return count( $this->
servers );
1171 if ( isset( $this->
servers[$i][
'hostName'] ) ) {
1172 $name = $this->
servers[$i][
'hostName'];
1173 } elseif ( isset( $this->
servers[$i][
'host'] ) ) {
1174 $name = $this->
servers[$i][
'host'];
1179 return ( $name !=
'' ) ?
$name :
'localhost';
1183 if ( isset( $this->
servers[$i] ) ) {
1191 return isset( $this->
servers[$i][
'type'] ) ? $this->
servers[$i][
'type'] :
'unknown';
1195 # If this entire request was served from a replica DB without opening a connection to the
1196 # master (however unlikely that may be), then we can fetch the position from the replica DB.
1198 if ( !$masterConn ) {
1199 $serverCount = count( $this->
servers );
1200 for ( $i = 1; $i < $serverCount; $i++ ) {
1203 return $conn->getReplicaPos();
1207 return $masterConn->getMasterPos();
1215 $this->disabled =
true;
1221 $this->connLogger->debug(
1222 __METHOD__ .
": closing connection to database '$host'." );
1227 self::KEY_LOCAL => [],
1228 self::KEY_FOREIGN_INUSE => [],
1229 self::KEY_FOREIGN_FREE => [],
1230 self::KEY_LOCAL_NOROUND => [],
1231 self::KEY_FOREIGN_INUSE_NOROUND => [],
1232 self::KEY_FOREIGN_FREE_NOROUND => []
1234 $this->connsOpened = 0;
1238 $serverIndex = $conn->
getLBInfo(
'serverIndex' );
1239 foreach ( $this->conns as
$type => $connsByServer ) {
1240 if ( !isset( $connsByServer[$serverIndex] ) ) {
1244 foreach ( $connsByServer[$serverIndex] as $i => $trackedConn ) {
1245 if ( $conn === $trackedConn ) {
1247 $this->connLogger->debug(
1248 __METHOD__ .
": closing connection to database $i at '$host'." );
1249 unset( $this->conns[
$type][$serverIndex][$i] );
1262 $restore = ( $this->trxRoundId !==
false );
1263 $this->trxRoundId =
false;
1269 call_user_func( $this->errorLogger,
$e );
1270 $failures[] =
"{$conn->getServer()}: {$e->getMessage()}";
1272 if ( $restore && $conn->
getLBInfo(
'master' ) ) {
1281 "Commit failed on server(s) " . implode(
"\n", array_unique( $failures ) )
1297 $limit = isset(
$options[
'maxWriteDuration'] ) ?
$options[
'maxWriteDuration'] : 0;
1303 throw new DBTransactionError(
1305 "Explicit transaction still active. A caller may have caught an error."
1311 if ( $limit > 0 &&
$time > $limit ) {
1314 "Transaction spent $time second(s) in writes, exceeding the limit of $limit.",
1323 "A connection to the {$conn->getDBname()} database was lost before commit."
1330 if ( $this->trxRoundId !==
false ) {
1333 "$fname: Transaction round '{$this->trxRoundId}' already started."
1336 $this->trxRoundId =
$fname;
1345 call_user_func( $this->errorLogger,
$e );
1346 $failures[] =
"{$conn->getServer()}: {$e->getMessage()}";
1356 "$fname: Flush failed on server(s) " . implode(
"\n", array_unique( $failures ) )
1367 $restore = ( $this->trxRoundId !==
false );
1368 $this->trxRoundId =
false;
1373 $conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
1374 } elseif ( $restore ) {
1378 call_user_func( $this->errorLogger,
$e );
1379 $failures[] =
"{$conn->getServer()}: {$e->getMessage()}";
1390 "$fname: Commit failed on server(s) " . implode(
"\n", array_unique( $failures ) )
1404 $this->queryLogger->warning( __METHOD__ .
": found writes pending." );
1415 $conn->runOnTransactionIdleCallbacks( $type );
1416 } catch ( Exception $ex ) {
1421 }
catch ( Exception $ex ) {
1430 $restore = ( $this->trxRoundId !==
false );
1431 $this->trxRoundId =
false;
1458 if ( $conn->
getLBInfo(
'autoCommitOnly' ) ) {
1462 if ( $conn->
getFlag( $conn::DBO_DEFAULT ) ) {
1465 $conn->
setFlag( $conn::DBO_TRX, $conn::REMEMBER_PRIOR );
1468 if ( $conn->
getFlag( $conn::DBO_TRX ) ) {
1469 $conn->
setLBInfo(
'trxRoundId', $this->trxRoundId );
1477 if ( $conn->
getLBInfo(
'autoCommitOnly' ) ) {
1481 if ( $conn->
getFlag( $conn::DBO_TRX ) ) {
1482 $conn->
setLBInfo(
'trxRoundId',
false );
1485 if ( $conn->
getFlag( $conn::DBO_DEFAULT ) ) {
1506 return (
bool)$pending;
1519 $age = ( $age === null ) ? $this->waitTimeout : $age;
1536 if ( !$this->laggedReplicaMode && $this->
getServerCount() > 1 ) {
1539 $conn = $this->
getConnection( self::DB_REPLICA,
false, $domain );
1543 $this->allReplicasDownMode =
true;
1544 $this->laggedReplicaMode =
true;
1574 if ( $this->readOnlyReason !==
false ) {
1577 if ( $this->allReplicasDownMode ) {
1578 return 'The database has been automatically locked ' .
1579 'until the replica database servers become available';
1581 return 'The database has been automatically locked ' .
1582 'while the replica database servers catch up to the master.';
1585 return 'The database master is running in read-only mode.';
1600 return (
bool)
$cache->getWithSetCallback(
1601 $cache->makeGlobalKey( __CLASS__,
'server-read-only', $masterServer ),
1602 self::TTL_CACHE_READONLY,
1603 function () use ( $domain, $conn ) {
1604 $old = $this->trxProfiler->setSilenced(
true );
1606 $dbw = $conn ?: $this->
getConnection( self::DB_MASTER, [], $domain );
1607 $readOnly = (int)$dbw->serverIsReadOnly();
1614 $this->trxProfiler->setSilenced( $old );
1617 [
'pcTTL' => $cache::TTL_PROC_LONG,
'busyValue' => 0 ]
1622 if ( $mode ===
null ) {
1633 if ( !$conn->
ping() ) {
1642 foreach ( $this->conns as $connsByServer ) {
1643 foreach ( $connsByServer as $serverConns ) {
1644 foreach ( $serverConns as $conn ) {
1645 $mergedParams = array_merge( [ $conn ],
$params );
1646 call_user_func_array( $callback, $mergedParams );
1654 foreach ( $this->conns as $connsByServer ) {
1655 if ( isset( $connsByServer[$masterIndex] ) ) {
1657 foreach ( $connsByServer[$masterIndex] as $conn ) {
1658 $mergedParams = array_merge( [ $conn ],
$params );
1659 call_user_func_array( $callback, $mergedParams );
1666 foreach ( $this->conns as $connsByServer ) {
1667 foreach ( $connsByServer as $i => $serverConns ) {
1671 foreach ( $serverConns as $conn ) {
1672 $mergedParams = array_merge( [ $conn ],
$params );
1673 call_user_func_array( $callback, $mergedParams );
1689 foreach ( $lagTimes as $i => $lag ) {
1690 if ( $this->loads[$i] > 0 && $lag >
$maxLag ) {
1705 $knownLagTimes = [];
1706 $indexesWithLag = [];
1707 foreach ( $this->
servers as $i => $server ) {
1708 if ( empty( $server[
'is static'] ) ) {
1709 $indexesWithLag[] = $i;
1711 $knownLagTimes[$i] = 0;
1715 return $this->
getLoadMonitor()->getLagTimes( $indexesWithLag, $domain ) + $knownLagTimes;
1742 if ( $masterConn ) {
1743 $pos = $masterConn->getMasterPos();
1746 $pos = $masterConn->getMasterPos();
1753 if ( $result == -1 || is_null( $result ) ) {
1754 $msg = __METHOD__ .
': timed out waiting on {host} pos {pos}';
1755 $this->replLogger->warning( $msg, [
1758 'trace' => (
new RuntimeException() )->getTraceAsString()
1762 $this->replLogger->debug( __METHOD__ .
': done waiting' );
1767 $this->replLogger->error(
1768 __METHOD__ .
': could not get master pos for {host}',
1771 'trace' => (
new RuntimeException() )->getTraceAsString()
1781 $this->trxRecurringCallbacks[
$name] = $callback;
1783 unset( $this->trxRecurringCallbacks[$name] );
1786 function (
IDatabase $conn ) use ( $name, $callback ) {
1793 $this->tableAliases = $aliases;
1797 $this->indexAliases = $aliases;
1806 if ( $conn->
getLBInfo(
'foreignPoolRefCount' ) > 0 ) {
1807 $domainsInUse[] = $conn->getDomainID();
1812 if ( $domainsInUse ) {
1813 $domains = implode(
', ', $domainsInUse );
1815 "Foreign domain connections are still in use ($domains)." );
1818 $oldDomain = $this->localDomain->getId();
1820 $this->localDomain->getDatabase(),
1821 $this->localDomain->getSchema(),
1827 $db->tablePrefix( $prefix );
1836 $this->localDomain = $domain;
1839 if ( $this->localDomain->getTablePrefix() !=
'' ) {
1840 $this->localDomainIdAlias =
1841 $this->localDomain->
getDatabase() .
'-' . $this->localDomain->getTablePrefix();
1843 $this->localDomainIdAlias = $this->localDomain->getDatabase();
1854 if ( PHP_SAPI !=
'cli' ) {
1855 $old = ignore_user_abort(
true );
1856 return new ScopedCallback(
function () use ( $old ) {
1857 ignore_user_abort( $old );
1870class_alias( LoadBalancer::class,
'LoadBalancer' );
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings,...
A collection of static methods to play with arrays.
static pickRandom( $weights)
Given an array of non-normalised probabilities, this function will select an element and return the a...
interface is intended to be more or less compatible with the PHP memcached client.
A BagOStuff object with no objects in it.
Multi-datacenter aware caching interface.
Class to handle database/prefix specification for IDatabase domains.
static newFromId( $domain)
Helper class to handle automatically marking connections as reusable (via RAII pattern) as well handl...
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
when a variable name is used in a function
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
the array() calling protocol came about after MediaWiki 1.4rc1.
see documentation in includes Linker php for Linker::makeImageLink & $time
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Allows to change the fields on the form that will be generated $name
processing should stop and the error should be shown to the user * false
returning false will NOT prevent logging $e
storage can be distributed across multiple servers