24 use Psr\Log\LoggerInterface;
25 use Psr\Log\NullLogger;
26 use Wikimedia\ScopedCallback;
31 use UnexpectedValueException;
32 use 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'];
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 ) ) {
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
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 ) {
595 $timeout = max( 1, intval( $timeout ?: $this->waitTimeout ) );
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 );
650 $this->replLogger->warning(
651 __METHOD__ .
': Errored out waiting on {host} pos {pos}',
654 'pos' => $this->waitForPos,
655 'trace' => (
new RuntimeException() )->getTraceAsString()
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" );
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 === [] )
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
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" );
830 $domain = ( $domain !==
false ) ? $domain : $this->localDomain;
836 $domain = ( $domain !==
false ) ? $domain : $this->localDomain;
838 return new DBConnRef( $this, [ $db, $groups, $domain, $flags ] );
842 $domain = ( $domain !==
false ) ? $domain : $this->localDomain;
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 ) {
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 ) {
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)" );
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'] ) ) {
1173 } elseif ( isset( $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' ) ) {
1469 $conn->
setLBInfo(
'trxRoundId', $this->trxRoundId );
1477 if ( $conn->
getLBInfo(
'autoCommitOnly' ) ) {
1482 $conn->
setLBInfo(
'trxRoundId',
false );
1506 return (
bool)$pending;
1519 $age = ( $age === null ) ? $this->waitTimeout : $age;
1536 if ( !$this->laggedReplicaMode && $this->
getServerCount() > 1 ) {
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,
1604 $old = $this->trxProfiler->setSilenced(
true );
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;
1733 $timeout = max( 1, $timeout ?: $this->waitTimeout );
1742 if ( $masterConn ) {
1743 $pos = $masterConn->getMasterPos();
1746 $pos = $masterConn->getMasterPos();
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] );
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 );