25use Psr\Log\LoggerInterface;
26use Psr\Log\NullLogger;
27use Wikimedia\ScopedCallback;
32use InvalidArgumentException;
120 const CONN_HELD_WARN_THRESHOLD = 10;
123 const MAX_LAG_DEFAULT = 10;
125 const TTL_CACHE_READONLY = 5;
136 if ( !isset(
$params[
'servers'] ) ) {
137 throw new InvalidArgumentException( __CLASS__ .
': missing servers parameter' );
139 $this->mServers =
$params[
'servers'];
140 foreach ( $this->mServers as $i => $server ) {
142 $this->mServers[$i][
'master'] =
true;
144 $this->mServers[$i][
'replica'] =
true;
148 $this->localDomain = isset(
$params[
'localDomain'] )
153 if ( $this->localDomain->getTablePrefix() !=
'' ) {
154 $this->localDomainIdAlias =
155 $this->localDomain->getDatabase() .
'-' . $this->localDomain->getTablePrefix();
157 $this->localDomainIdAlias = $this->localDomain->getDatabase();
160 $this->mWaitTimeout = isset(
$params[
'waitTimeout'] ) ?
$params[
'waitTimeout'] : 10;
162 $this->mReadIndex = -1;
165 self::KEY_LOCAL => [],
166 self::KEY_FOREIGN_INUSE => [],
167 self::KEY_FOREIGN_FREE => [],
169 self::KEY_LOCAL_NOROUND => [],
170 self::KEY_FOREIGN_INUSE_NOROUND => [],
171 self::KEY_FOREIGN_FREE_NOROUND => []
174 $this->mWaitForPos =
false;
175 $this->mAllowLagged =
false;
177 if ( isset(
$params[
'readOnlyReason'] ) && is_string(
$params[
'readOnlyReason'] ) ) {
178 $this->readOnlyReason =
$params[
'readOnlyReason'];
181 if ( isset(
$params[
'loadMonitor'] ) ) {
182 $this->loadMonitorConfig =
$params[
'loadMonitor'];
184 $this->loadMonitorConfig = [
'class' =>
'LoadMonitorNull' ];
187 foreach (
$params[
'servers'] as $i => $server ) {
188 $this->mLoads[$i] = $server[
'load'];
189 if ( isset( $server[
'groupLoads'] ) ) {
190 foreach ( $server[
'groupLoads'] as $group => $ratio ) {
191 if ( !isset( $this->mGroupLoads[$group] ) ) {
192 $this->mGroupLoads[$group] = [];
194 $this->mGroupLoads[$group][$i] = $ratio;
199 if ( isset(
$params[
'srvCache'] ) ) {
200 $this->srvCache =
$params[
'srvCache'];
204 if ( isset(
$params[
'wanCache'] ) ) {
205 $this->wanCache =
$params[
'wanCache'];
207 $this->wanCache = WANObjectCache::newEmpty();
209 $this->profiler = isset(
$params[
'profiler'] ) ?
$params[
'profiler'] :
null;
210 if ( isset(
$params[
'trxProfiler'] ) ) {
211 $this->trxProfiler =
$params[
'trxProfiler'];
216 $this->errorLogger = isset(
$params[
'errorLogger'] )
218 :
function ( Exception
$e ) {
219 trigger_error( get_class(
$e ) .
': ' .
$e->getMessage(), E_USER_WARNING );
222 foreach ( [
'replLogger',
'connLogger',
'queryLogger',
'perfLogger' ] as $key ) {
223 $this->$key = isset(
$params[$key] ) ?
$params[$key] :
new NullLogger();
226 $this->host = isset(
$params[
'hostname'] )
228 : ( gethostname() ?:
'unknown' );
229 $this->cliMode = isset(
$params[
'cliMode'] ) ?
$params[
'cliMode'] : PHP_SAPI ===
'cli';
232 if ( isset(
$params[
'chronologyProtector'] ) ) {
233 $this->chronProt =
$params[
'chronologyProtector'];
243 if ( !isset( $this->loadMonitor ) ) {
245 'LoadMonitor' => LoadMonitor::class,
246 'LoadMonitorNull' => LoadMonitorNull::class,
247 'LoadMonitorMySQL' => LoadMonitorMySQL::class,
250 $class = $this->loadMonitorConfig[
'class'];
251 if ( isset( $compat[$class] ) ) {
252 $class = $compat[$class];
255 $this->loadMonitor =
new $class(
256 $this, $this->srvCache, $this->wanCache, $this->loadMonitorConfig );
257 $this->loadMonitor->setLogger( $this->replLogger );
272 # Unset excessively lagged servers
273 foreach ( $lags as $i => $lag ) {
275 # How much lag this server nominally is allowed to have
276 $maxServerLag = isset( $this->mServers[$i][
'max lag'] )
277 ? $this->mServers[$i][
'max lag']
278 : self::MAX_LAG_DEFAULT;
279 # Constrain that futher by $maxLag argument
280 $maxServerLag = min( $maxServerLag, $maxLag );
283 if ( $lag ===
false && !is_infinite( $maxServerLag ) ) {
284 $this->replLogger->error(
285 "Server {host} is not replicating?", [
'host' =>
$host ] );
287 } elseif ( $lag > $maxServerLag ) {
288 $this->replLogger->debug(
290 ": server {host} has {lag} seconds of lag (>= {maxlag})",
291 [
'host' =>
$host,
'lag' => $lag,
'maxlag' => $maxServerLag ]
298 # Find out if all the replica DBs with non-zero load are lagged
300 foreach ( $loads as $load ) {
304 # No appropriate DB servers except maybe the master and some replica DBs with zero load
305 # Do NOT use the master
306 # Instead, this function will return false, triggering read-only mode,
307 # and a lagged replica DB will be used instead.
311 if ( count( $loads ) == 0 ) {
315 # Return a random representative of the remainder
320 if ( count( $this->mServers ) == 1 ) {
323 } elseif ( $group ===
false && $this->mReadIndex >= 0 ) {
328 if ( $group !==
false ) {
330 if ( isset( $this->mGroupLoads[$group] ) ) {
331 $loads = $this->mGroupLoads[$group];
334 $this->connLogger->info( __METHOD__ .
": no loads for group $group" );
348 if ( $i ===
false ) {
358 if ( !$this->
doWait( $i ) ) {
363 if ( $this->mReadIndex <= 0 && $this->mLoads[$i] > 0 && $group ===
false ) {
365 $this->mReadIndex = $i;
368 $this->laggedReplicaMode =
true;
373 $this->connLogger->debug( __METHOD__ .
": using server $serverName for group '$group'" );
384 if ( !count( $loads ) ) {
385 throw new InvalidArgumentException(
"Empty server array given to LoadBalancer" );
394 $currentLoads = $loads;
395 while ( count( $currentLoads ) ) {
400 if ( $this->mWaitForPos && $this->mWaitForPos->asOfTime() ) {
404 $ago = microtime(
true ) - $this->mWaitForPos->asOfTime();
408 if ( $i ===
false ) {
412 if ( $i ===
false && count( $currentLoads ) != 0 ) {
414 $this->replLogger->error(
"All replica DBs lagged. Switch to read-only mode" );
420 if ( $i ===
false ) {
424 $this->connLogger->debug( __METHOD__ .
": pickRandom() returned false" );
426 return [
false,
false ];
430 $this->connLogger->debug( __METHOD__ .
": Using reader #$i: $serverName..." );
434 $this->connLogger->warning( __METHOD__ .
": Failed connecting to $i/$domain" );
435 unset( $currentLoads[$i] );
442 if ( $domain !==
false ) {
451 if ( !count( $currentLoads ) ) {
452 $this->connLogger->error(
"All servers down" );
461 $this->mWaitForPos = $pos;
465 if ( !$this->
doWait( $i ) ) {
466 $this->laggedReplicaMode =
true;
478 $this->mWaitForPos = $pos;
485 $readLoads = array_filter( $readLoads );
490 $ok = $this->
doWait( $i,
true, $timeout );
495 # Restore the old position, as this is not used for lag-protection but for throttling
496 $this->mWaitForPos = $oldPos;
505 $this->mWaitForPos = $pos;
506 $serverCount = count( $this->mServers );
509 for ( $i = 1; $i < $serverCount; $i++ ) {
510 if ( $this->mLoads[$i] > 0 ) {
511 $ok = $this->
doWait( $i,
true, $timeout ) && $ok;
515 # Restore the old position, as this is not used for lag-protection but for throttling
516 $this->mWaitForPos = $oldPos;
530 if ( !$this->mWaitForPos || $pos->hasReached( $this->mWaitForPos ) ) {
531 $this->mWaitForPos = $pos;
540 foreach ( $this->mConns as $connsByServer ) {
541 if ( !empty( $connsByServer[$i] ) ) {
543 $serverConns = $connsByServer[$i];
545 return reset( $serverConns );
559 protected function doWait( $index, $open =
false, $timeout =
null ) {
564 $key = $this->srvCache->makeGlobalKey( __CLASS__,
'last-known-pos', $server,
'v1' );
566 $knownReachedPos = $this->srvCache->get( $key );
569 $knownReachedPos->
hasReached( $this->mWaitForPos )
571 $this->replLogger->debug( __METHOD__ .
572 ": replica DB $server known to be caught up (pos >= $knownReachedPos)." );
580 $this->replLogger->debug( __METHOD__ .
": no connection open for $server" );
586 $this->replLogger->warning( __METHOD__ .
": failed to connect to $server" );
596 $this->replLogger->info( __METHOD__ .
": Waiting for replica DB $server to catch up..." );
598 $result = $conn->masterPosWait( $this->mWaitForPos, $timeout );
600 if ( $result == -1 || is_null( $result ) ) {
602 $this->replLogger->warning(
603 __METHOD__ .
": Timed out waiting on {host} pos {$this->mWaitForPos}",
604 [
'host' => $server ]
608 $this->replLogger->info( __METHOD__ .
": Done" );
611 $this->srvCache->set( $key, $this->mWaitForPos, BagOStuff::TTL_DAY );
622 if ( $i ===
null || $i ===
false ) {
623 throw new InvalidArgumentException(
'Attempt to call ' . __METHOD__ .
624 ' with invalid server index' );
627 if ( $this->localDomain->equals( $domain ) || $domain === $this->localDomainIdAlias ) {
631 $groups = ( $groups ===
false || $groups === [] )
635 $masterOnly = ( $i == self::DB_MASTER || $i == $this->
getWriterIndex() );
638 if ( $i == self::DB_MASTER ) {
641 # Try to find an available server in any the query groups (in order)
642 foreach ( $groups as $group ) {
644 if ( $groupIndex !==
false ) {
651 # Operation-based index
652 if ( $i == self::DB_REPLICA ) {
653 $this->mLastError =
'Unknown error';
654 # Try the general server pool if $groups are unavailable.
655 $i = ( $groups === [
false ] )
658 # Couldn't find a working server in getReaderIndex()?
659 if ( $i ===
false ) {
667 # Now we have an explicit index into the servers array
675 # Profile any new connections that happen
676 if ( $this->connsOpened > $oldConnsOpened ) {
677 $host = $conn->getServer();
678 $dbname = $conn->getDBname();
679 $this->trxProfiler->recordConnection(
$host, $dbname, $masterOnly );
683 # Make master-requested DB handles inherit any read-only mode setting
684 $conn->setLBInfo(
'readOnlyReason', $this->
getReadOnlyReason( $domain, $conn ) );
691 $serverIndex = $conn->getLBInfo(
'serverIndex' );
692 $refCount = $conn->getLBInfo(
'foreignPoolRefCount' );
693 if ( $serverIndex ===
null || $refCount ===
null ) {
705 } elseif ( $conn instanceof
DBConnRef ) {
708 $this->connLogger->error( __METHOD__ .
": got DBConnRef instance.\n" .
709 (
new RuntimeException() )->getTraceAsString() );
714 if ( $this->disabled ) {
718 if ( $conn->getLBInfo(
'autoCommitOnly' ) ) {
726 $domain = $conn->getDomainID();
727 if ( !isset( $this->mConns[$connInUseKey][$serverIndex][$domain] ) ) {
728 throw new InvalidArgumentException( __METHOD__ .
729 ": connection $serverIndex/$domain not found; it may have already been freed." );
730 } elseif ( $this->mConns[$connInUseKey][$serverIndex][$domain] !== $conn ) {
731 throw new InvalidArgumentException( __METHOD__ .
732 ": connection $serverIndex/$domain mismatched; it may have already been freed." );
735 $conn->setLBInfo(
'foreignPoolRefCount', --$refCount );
736 if ( $refCount <= 0 ) {
737 $this->mConns[$connFreeKey][$serverIndex][$domain] = $conn;
738 unset( $this->mConns[$connInUseKey][$serverIndex][$domain] );
739 if ( !$this->mConns[$connInUseKey][$serverIndex] ) {
740 unset( $this->mConns[$connInUseKey][$serverIndex] );
742 $this->connLogger->debug( __METHOD__ .
": freed connection $serverIndex/$domain" );
744 $this->connLogger->debug( __METHOD__ .
745 ": reference count for $serverIndex/$domain reduced to $refCount" );
769 if ( $this->localDomain->equals( $domain ) || $domain === $this->localDomainIdAlias ) {
773 if ( !$this->chronProtInitialized && $this->chronProt ) {
774 $this->connLogger->debug( __METHOD__ .
': calling initLB() before first connection.' );
776 $this->chronProtInitialized =
true;
777 $this->chronProt->initLB( $this );
784 $autoCommit = ( (
$flags & self::CONN_TRX_AUTO ) == self::CONN_TRX_AUTO );
786 if ( $domain !==
false ) {
792 if ( isset( $this->mConns[$connKey][$i][0] ) ) {
793 $conn = $this->mConns[$connKey][$i][0];
795 if ( !isset( $this->mServers[$i] ) || !is_array( $this->mServers[$i] ) ) {
796 throw new InvalidArgumentException(
"No server with index '$i'." );
799 $server = $this->mServers[$i];
800 $server[
'serverIndex'] = $i;
801 $server[
'autoCommitOnly'] = $autoCommit;
804 if ( $conn->isOpen() ) {
805 $this->connLogger->debug(
"Connected to database $i at '$host'." );
806 $this->mConns[$connKey][$i][0] = $conn;
808 $this->connLogger->warning(
"Failed to connect to database $i at '$host'." );
809 $this->errorConnection = $conn;
820 $this->errorConnection = $conn;
824 if ( $autoCommit && $conn instanceof
IDatabase ) {
825 $conn->clearFlag( $conn::DBO_TRX );
854 $dbName = $domainInstance->getDatabase();
855 $prefix = $domainInstance->getTablePrefix();
856 $autoCommit = ( (
$flags & self::CONN_TRX_AUTO ) == self::CONN_TRX_AUTO );
866 if ( isset( $this->mConns[$connInUseKey][$i][$domain] ) ) {
868 $conn = $this->mConns[$connInUseKey][$i][$domain];
869 $this->connLogger->debug( __METHOD__ .
": reusing connection $i/$domain" );
870 } elseif ( isset( $this->mConns[$connFreeKey][$i][$domain] ) ) {
872 $conn = $this->mConns[$connFreeKey][$i][$domain];
873 unset( $this->mConns[$connFreeKey][$i][$domain] );
874 $this->mConns[$connInUseKey][$i][$domain] = $conn;
875 $this->connLogger->debug( __METHOD__ .
": reusing free connection $i/$domain" );
876 } elseif ( !empty( $this->mConns[$connFreeKey][$i] ) ) {
878 $conn = reset( $this->mConns[$connFreeKey][$i] );
879 $oldDomain =
key( $this->mConns[$connFreeKey][$i] );
882 if ( strlen( $dbName ) && !$conn->selectDB( $dbName ) ) {
883 $this->mLastError =
"Error selecting database '$dbName' on server " .
884 $conn->getServer() .
" from client host {$this->host}";
885 $this->errorConnection = $conn;
888 $conn->tablePrefix( $prefix );
889 unset( $this->mConns[$connFreeKey][$i][$oldDomain] );
890 $this->mConns[$connInUseKey][$i][$domain] = $conn;
891 $this->connLogger->debug( __METHOD__ .
892 ": reusing free connection from $oldDomain for $domain" );
895 if ( !isset( $this->mServers[$i] ) || !is_array( $this->mServers[$i] ) ) {
896 throw new InvalidArgumentException(
"No server with index '$i'." );
899 $server = $this->mServers[$i];
900 $server[
'serverIndex'] = $i;
901 $server[
'foreignPoolRefCount'] = 0;
902 $server[
'foreign'] =
true;
903 $server[
'autoCommitOnly'] = $autoCommit;
905 if ( !$conn->isOpen() ) {
906 $this->connLogger->warning( __METHOD__ .
": connection error for $i/$domain" );
907 $this->errorConnection = $conn;
910 $conn->tablePrefix( $prefix );
911 $this->mConns[$connInUseKey][$i][$domain] = $conn;
912 $this->connLogger->debug( __METHOD__ .
": opened new connection for $i/$domain" );
918 $refCount = $conn->getLBInfo(
'foreignPoolRefCount' );
919 $conn->setLBInfo(
'foreignPoolRefCount', $refCount + 1 );
933 if ( !is_integer( $index ) ) {
952 if ( $this->disabled ) {
956 if ( $dbNameOverride !==
false ) {
957 $server[
'dbname'] = $dbNameOverride;
962 $server[
'clusterMasterHost'] = $masterName;
965 if ( ++$this->connsOpened >= self::CONN_HELD_WARN_THRESHOLD ) {
966 $this->perfLogger->warning( __METHOD__ .
": " .
967 "{$this->connsOpened}+ connections made (master=$masterName)" );
982 $server[
'flags'] = isset( $server[
'flags'] ) ? $server[
'flags'] : IDatabase::DBO_DEFAULT;
993 $db->setLBInfo( $server );
994 $db->setLazyMasterHandle(
997 $db->setTableAliases( $this->tableAliases );
1000 if ( $this->trxRoundId !==
false ) {
1003 foreach ( $this->trxRecurringCallbacks as $name => $callback ) {
1004 $db->setTransactionListener( $name, $callback );
1017 'method' => __METHOD__,
1022 $context[
'db_server'] = $conn->getServer();
1023 $this->connLogger->warning(
1024 "Connection error: {last_error} ({db_server})",
1029 $conn->reportConnectionError(
"{$this->mLastError} ({$context['db_server']})" );
1032 $this->connLogger->error(
1033 "LB failure with no last connection. Connection error: {last_error}",
1047 return array_key_exists( $i, $this->mServers );
1051 return array_key_exists( $i, $this->mServers ) && $this->mLoads[$i] != 0;
1055 return count( $this->mServers );
1059 if ( isset( $this->mServers[$i][
'hostName'] ) ) {
1060 $name = $this->mServers[$i][
'hostName'];
1061 } elseif ( isset( $this->mServers[$i][
'host'] ) ) {
1062 $name = $this->mServers[$i][
'host'];
1067 return ( $name !=
'' ) ?
$name :
'localhost';
1071 return isset( $this->mServers[$i][
'type'] ) ? $this->mServers[$i][
'type'] :
'unknown';
1079 if ( isset( $this->mServers[$i] ) ) {
1080 return $this->mServers[$i];
1091 $this->mServers[$i] = $serverInfo;
1095 # If this entire request was served from a replica DB without opening a connection to the
1096 # master (however unlikely that may be), then we can fetch the position from the replica DB.
1098 if ( !$masterConn ) {
1099 $serverCount = count( $this->mServers );
1100 for ( $i = 1; $i < $serverCount; $i++ ) {
1103 return $conn->getReplicaPos();
1107 return $masterConn->getMasterPos();
1115 $this->disabled =
true;
1121 $this->connLogger->debug(
"Closing connection to database '$host'." );
1126 self::KEY_LOCAL => [],
1127 self::KEY_FOREIGN_INUSE => [],
1128 self::KEY_FOREIGN_FREE => [],
1129 self::KEY_LOCAL_NOROUND => [],
1130 self::KEY_FOREIGN_INUSE_NOROUND => [],
1131 self::KEY_FOREIGN_FREE_NOROUND => []
1133 $this->connsOpened = 0;
1137 $serverIndex = $conn->
getLBInfo(
'serverIndex' );
1138 foreach ( $this->mConns as
$type => $connsByServer ) {
1139 if ( !isset( $connsByServer[$serverIndex] ) ) {
1143 foreach ( $connsByServer[$serverIndex] as $i => $trackedConn ) {
1144 if ( $conn === $trackedConn ) {
1146 $this->connLogger->debug(
"Closing connection to database $i at '$host'." );
1147 unset( $this->mConns[
$type][$serverIndex][$i] );
1160 $restore = ( $this->trxRoundId !==
false );
1161 $this->trxRoundId =
false;
1167 call_user_func( $this->errorLogger,
$e );
1168 $failures[] =
"{$conn->getServer()}: {$e->getMessage()}";
1170 if ( $restore && $conn->
getLBInfo(
'master' ) ) {
1179 "Commit failed on server(s) " . implode(
"\n", array_unique( $failures ) )
1195 $limit = isset(
$options[
'maxWriteDuration'] ) ?
$options[
'maxWriteDuration'] : 0;
1201 throw new DBTransactionError(
1203 "Explicit transaction still active. A caller may have caught an error."
1209 if ( $limit > 0 &&
$time > $limit ) {
1212 "Transaction spent $time second(s) in writes, exceeding the limit of $limit.",
1221 "A connection to the {$conn->getDBname()} database was lost before commit."
1228 if ( $this->trxRoundId !==
false ) {
1231 "$fname: Transaction round '{$this->trxRoundId}' already started."
1234 $this->trxRoundId =
$fname;
1243 call_user_func( $this->errorLogger,
$e );
1244 $failures[] =
"{$conn->getServer()}: {$e->getMessage()}";
1254 "$fname: Flush failed on server(s) " . implode(
"\n", array_unique( $failures ) )
1265 $restore = ( $this->trxRoundId !==
false );
1266 $this->trxRoundId =
false;
1271 $conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
1272 } elseif ( $restore ) {
1276 call_user_func( $this->errorLogger,
$e );
1277 $failures[] =
"{$conn->getServer()}: {$e->getMessage()}";
1288 "$fname: Commit failed on server(s) " . implode(
"\n", array_unique( $failures ) )
1301 $this->queryLogger->info( __METHOD__ .
": found writes/callbacks pending." );
1312 $conn->runOnTransactionIdleCallbacks( $type );
1313 } catch ( Exception $ex ) {
1318 }
catch ( Exception $ex ) {
1327 $restore = ( $this->trxRoundId !==
false );
1328 $this->trxRoundId =
false;
1332 $conn->rollback( $fname, $conn::FLUSHING_ALL_PEERS );
1351 if ( $conn->
getLBInfo(
'autoCommitOnly' ) ) {
1355 if ( $conn->
getFlag( $conn::DBO_DEFAULT ) ) {
1358 $conn->
setFlag( $conn::DBO_TRX, $conn::REMEMBER_PRIOR );
1369 if ( $conn->
getLBInfo(
'autoCommitOnly' ) ) {
1373 if ( $conn->
getFlag( $conn::DBO_DEFAULT ) ) {
1394 return (
bool)$pending;
1407 $age = ( $age === null ) ? $this->mWaitTimeout : $age;
1424 if ( !$this->laggedReplicaMode && $this->
getServerCount() > 1 ) {
1427 $conn = $this->
getConnection( self::DB_REPLICA,
false, $domain );
1431 $this->allReplicasDownMode =
true;
1432 $this->laggedReplicaMode =
true;
1462 if ( $this->readOnlyReason !==
false ) {
1465 if ( $this->allReplicasDownMode ) {
1466 return 'The database has been automatically locked ' .
1467 'until the replica database servers become available';
1469 return 'The database has been automatically locked ' .
1470 'while the replica database servers catch up to the master.';
1473 return 'The database master is running in read-only mode.';
1488 return (
bool)
$cache->getWithSetCallback(
1489 $cache->makeGlobalKey( __CLASS__,
'server-read-only', $masterServer ),
1490 self::TTL_CACHE_READONLY,
1491 function () use ( $domain, $conn ) {
1492 $old = $this->trxProfiler->setSilenced(
true );
1494 $dbw = $conn ?: $this->
getConnection( self::DB_MASTER, [], $domain );
1495 $readOnly = (int)$dbw->serverIsReadOnly();
1502 $this->trxProfiler->setSilenced( $old );
1505 [
'pcTTL' => $cache::TTL_PROC_LONG,
'busyValue' => 0 ]
1510 if ( $mode ===
null ) {
1513 $this->mAllowLagged = $mode;
1521 if ( !$conn->
ping() ) {
1530 foreach ( $this->mConns as $connsByServer ) {
1531 foreach ( $connsByServer as $serverConns ) {
1532 foreach ( $serverConns as $conn ) {
1533 $mergedParams = array_merge( [ $conn ],
$params );
1534 call_user_func_array( $callback, $mergedParams );
1542 foreach ( $this->mConns as $connsByServer ) {
1543 if ( isset( $connsByServer[$masterIndex] ) ) {
1545 foreach ( $connsByServer[$masterIndex] as $conn ) {
1546 $mergedParams = array_merge( [ $conn ],
$params );
1547 call_user_func_array( $callback, $mergedParams );
1554 foreach ( $this->mConns as $connsByServer ) {
1555 foreach ( $connsByServer as $i => $serverConns ) {
1559 foreach ( $serverConns as $conn ) {
1560 $mergedParams = array_merge( [ $conn ],
$params );
1561 call_user_func_array( $callback, $mergedParams );
1573 return [
$host, $maxLag, $maxIndex ];
1577 foreach ( $lagTimes as $i => $lag ) {
1578 if ( $this->mLoads[$i] > 0 && $lag > $maxLag ) {
1580 $host = $this->mServers[$i][
'host'];
1585 return [
$host, $maxLag, $maxIndex ];
1593 $knownLagTimes = [];
1594 $indexesWithLag = [];
1595 foreach ( $this->mServers as $i => $server ) {
1596 if ( empty( $server[
'is static'] ) ) {
1597 $indexesWithLag[] = $i;
1599 $knownLagTimes[$i] = 0;
1603 return $this->
getLoadMonitor()->getLagTimes( $indexesWithLag, $domain ) + $knownLagTimes;
1628 if ( $masterConn ) {
1629 $pos = $masterConn->getMasterPos();
1632 $pos = $masterConn->getMasterPos();
1639 if ( $result == -1 || is_null( $result ) ) {
1640 $msg = __METHOD__ .
": Timed out waiting on {$conn->getServer()} pos {$pos}";
1641 $this->replLogger->warning(
"$msg" );
1644 $this->replLogger->info( __METHOD__ .
": Done" );
1649 $this->replLogger->error(
"Could not get master pos for {$conn->getServer()}." );
1657 $this->trxRecurringCallbacks[
$name] = $callback;
1659 unset( $this->trxRecurringCallbacks[$name] );
1662 function (
IDatabase $conn ) use ( $name, $callback ) {
1669 $this->tableAliases = $aliases;
1678 if ( $conn->
getLBInfo(
'foreignPoolRefCount' ) > 0 ) {
1679 $domainsInUse[] = $conn->getDomainID();
1684 if ( $domainsInUse ) {
1685 $domains = implode(
', ', $domainsInUse );
1687 "Foreign domain connections are still in use ($domains)." );
1691 $this->localDomain->getDatabase(),
1708 if ( PHP_SAPI !=
'cli' ) {
1709 $old = ignore_user_abort(
true );
1710 return new ScopedCallback(
function () use ( $old ) {
1711 ignore_user_abort( $old );
1724class_alias( LoadBalancer::class,
'LoadBalancer' );
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined.
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
it s the revision text itself In either if gzip is the revision text is gzipped $flags
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