Go to the documentation of this file.
30 use Psr\Log\LoggerInterface;
31 use Psr\Log\NullLogger;
35 use Wikimedia\ScopedCallback;
125 [
'replLogger',
'connLogger',
'queryLogger',
'perfLogger' ];
128 $this->localDomain = isset( $conf[
'localDomain'] )
132 $this->maxLag = $conf[
'maxLag'] ??
null;
133 if ( isset( $conf[
'readOnlyReason'] ) && is_string( $conf[
'readOnlyReason'] ) ) {
134 $this->readOnlyReason = $conf[
'readOnlyReason'];
141 foreach ( self::$loggerFields as $key ) {
142 $this->$key = $conf[$key] ??
new NullLogger();
144 $this->errorLogger = $conf[
'errorLogger'] ??
static function ( Throwable $e ) {
145 trigger_error( get_class( $e ) .
': ' . $e->getMessage(), E_USER_WARNING );
147 $this->deprecationLogger = $conf[
'deprecationLogger'] ??
static function ( $msg ) {
148 trigger_error( $msg, E_USER_DEPRECATED );
151 $this->profiler = $conf[
'profiler'] ??
null;
154 $this->requestInfo = [
155 'IPAddress' => $_SERVER[
'REMOTE_ADDR' ] ??
'',
156 'UserAgent' => $_SERVER[
'HTTP_USER_AGENT'] ??
'',
158 'ChronologyProtection' =>
null,
159 'ChronologyClientId' =>
null,
160 'ChronologyPositionIndex' =>
null
163 $this->cliMode = $conf[
'cliMode'] ?? ( PHP_SAPI ===
'cli' || PHP_SAPI ===
'phpdbg' );
164 $this->hostname = $conf[
'hostname'] ?? gethostname();
165 $this->agent = $conf[
'agent'] ??
'';
166 $this->defaultGroup = $conf[
'defaultGroup'] ??
null;
167 $this->secret = $conf[
'secret'] ??
'';
168 $this->replicationWaitTimeout = $this->cliMode ? 60 : 1;
170 static $nextId, $nextTicket;
171 $this->
id = $nextId = ( is_int( $nextId ) ? $nextId++ : mt_rand() );
172 $this->ticket = $nextTicket = ( is_int( $nextTicket ) ? $nextTicket++ : mt_rand() );
177 $scope = ScopedCallback::newScopedIgnoreUserAbort();
183 return $this->localDomain->getId();
197 } elseif ( $domain ===
false || $domain === $this->localDomain->getId() ) {
199 } elseif ( isset( $this->domainAliases[$domain] ) ) {
202 $this->domainAliases[$domain] =
205 return $this->domainAliases[$domain];
208 $cachedDomain = $this->nonLocalDomainCache[$domain] ??
null;
209 if ( $cachedDomain ===
null ) {
211 $this->nonLocalDomainCache = [ $domain => $cachedDomain ];
214 return $cachedDomain;
218 $flags = self::SHUTDOWN_NORMAL,
219 callable $workCallback =
null,
224 $scope = ScopedCallback::newScopedIgnoreUserAbort();
227 if ( ( $flags & self::SHUTDOWN_NO_CHRONPROT ) != self::SHUTDOWN_NO_CHRONPROT ) {
229 $this->replLogger->debug( __METHOD__ .
': finished ChronologyProtector shutdown' );
235 $this->replLogger->debug(
'LBFactory shutdown completed' );
247 $loadBalancer->$methodName( ...
$args );
249 [ $methodName,
$args ]
254 if ( $this->trxRoundId !==
false && $this->trxRoundId !== $fname ) {
255 $this->queryLogger->warning(
256 "$fname: transaction round '{$this->trxRoundId}' still running",
257 [
'trace' => (
new RuntimeException() )->getTraceAsString() ]
263 final public function commitAll( $fname = __METHOD__, array $options = [] ) {
272 $scope = ScopedCallback::newScopedIgnoreUserAbort();
275 if ( $this->trxRoundId !==
false ) {
278 "$fname: transaction round '{$this->trxRoundId}' already started"
281 $this->trxRoundId = $fname;
290 $scope = ScopedCallback::newScopedIgnoreUserAbort();
293 if ( $this->trxRoundId !==
false && $this->trxRoundId !== $fname ) {
296 "$fname: transaction round '{$this->trxRoundId}' still running"
305 }
while ( $count > 0 );
306 $this->trxRoundId =
false;
318 if ( $e instanceof Exception ) {
325 $scope = ScopedCallback::newScopedIgnoreUserAbort();
328 $this->trxRoundId =
false;
360 return ( $this->trxRoundId !==
false );
364 return ( $this->trxRoundStage === self::ROUND_CURSORY );
376 $callersByDB[$masterName] = $callers;
380 if ( count( $callersByDB ) >= 2 ) {
381 $dbs = implode(
', ', array_keys( $callersByDB ) );
382 $msg =
"Multi-DB transaction [{$dbs}]:\n";
383 foreach ( $callersByDB as $db => $callers ) {
384 $msg .=
"$db: " . implode(
'; ', $callers ) .
"\n";
386 $this->queryLogger->info( $msg );
421 'ifWritesSince' => null
425 if ( $opts[
'domain'] ===
false && isset( $opts[
'wiki'] ) ) {
426 $opts[
'domain'] = $opts[
'wiki'];
433 if ( $opts[
'cluster'] !==
false ) {
435 } elseif ( $opts[
'domain'] !==
false ) {
436 $lbs[] = $this->
getMainLB( $opts[
'domain'] );
449 $masterPositions = array_fill( 0, count( $lbs ),
false );
450 foreach ( $lbs as $i => $lb ) {
453 !$lb->hasMasterConnection() ||
455 !$lb->hasStreamingReplicaServers() ||
458 $opts[
'ifWritesSince'] &&
459 $lb->lastMasterChangeTimestamp() < $opts[
'ifWritesSince']
465 $masterPositions[$i] = $lb->getMasterPos();
470 foreach ( $this->replicationWaitCallbacks as $callback ) {
475 foreach ( $lbs as $i => $lb ) {
476 if ( $masterPositions[$i] ) {
478 if ( !$lb->waitForAll( $masterPositions[$i], $opts[
'timeout'] ) ) {
479 $failed[] = $lb->getServerName( $lb->getWriterIndex() );
489 $this->replicationWaitCallbacks[$name] = $callback;
491 unset( $this->replicationWaitCallbacks[$name] );
497 $this->queryLogger->error(
498 __METHOD__ .
": $fname does not have outer scope",
499 [
'trace' => (
new RuntimeException() )->getTraceAsString() ]
509 if (
$ticket !== $this->ticket ) {
510 $this->perfLogger->error(
511 __METHOD__ .
": $fname does not have outer scope",
512 [
'trace' => (
new RuntimeException() )->getTraceAsString() ]
520 if ( $this->trxRoundId !==
false && $fname !== $this->trxRoundId ) {
521 $this->queryLogger->info(
"$fname: committing on behalf of {$this->trxRoundId}" );
524 $fnameEffective = $fname;
531 if ( $fnameEffective !== $fname ) {
535 return $waitSucceeded;
550 if ( $this->chronProt ) {
557 'ip' => $this->requestInfo[
'IPAddress'],
558 'agent' => $this->requestInfo[
'UserAgent'],
559 'clientId' => $this->requestInfo[
'ChronologyClientId'] ?:
null
561 $this->requestInfo[
'ChronologyPositionIndex'],
564 $this->chronProt->setLogger( $this->replLogger );
566 if ( $this->cliMode ) {
567 $this->chronProt->setEnabled(
false );
568 } elseif ( $this->requestInfo[
'ChronologyProtection'] ===
'false' ) {
571 $this->chronProt->setWaitEnabled(
false );
574 $this->chronProt->setEnabled(
false );
575 $this->replLogger->debug(
'Cannot use ChronologyProtector with EmptyBagOStuff' );
578 $this->replLogger->debug(
579 __METHOD__ .
': request info ' .
580 json_encode( $this->requestInfo, JSON_PRETTY_PRINT )
601 $unsavedPositions = $cp->
shutdown( $cpIndex );
602 if ( $unsavedPositions && $workCallback ) {
612 if ( isset( $unsavedPositions[$masterName] ) ) {
613 $lb->
waitForAll( $unsavedPositions[$masterName] );
625 if ( $this->trxRoundStage === self::ROUND_COMMIT_CALLBACKS ) {
627 } elseif ( $this->trxRoundStage === self::ROUND_ROLLBACK_CALLBACKS ) {
655 'roundStage' => $initStage,
664 if ( $this->trxRoundId !==
false ) {
674 $this->tableAliases = $aliases;
678 $this->indexAliases = $aliases;
682 $this->domainAliases = $aliases;
691 $this->localDomain->getDatabase(),
692 $this->localDomain->getSchema(),
713 $scope = ScopedCallback::newScopedIgnoreUserAbort();
728 if ( !$usedCluster ) {
732 return strpos( $url,
'?' ) ===
false ?
"$url?cpPosIndex=$index" :
"$url&cpPosIndex=$index";
747 return "$index@$time#$clientId";
757 static $placeholder = [
'index' =>
null,
'clientId' => null ];
759 if ( $value ===
null ) {
761 } elseif ( !preg_match(
'/^(\d+)@(\d+)#([0-9a-f]{32})$/', $value, $m ) ) {
768 } elseif ( isset( $m[2] ) && $m[2] !==
'' && (
int)$m[2] < $minTimestamp ) {
772 $clientId = ( isset( $m[3] ) && $m[3] !==
'' ) ? $m[3] :
null;
774 return [
'index' => $index,
'clientId' => $clientId ];
778 if ( $this->chronProt ) {
779 throw new LogicException(
'ChronologyProtector already initialized' );
787 $this->replicationWaitTimeout = max( 1, (
int)$seconds );
804 if ( $this->trxRoundStage !== $stage ) {
807 "Transaction round stage must be '$stage' (not '{$this->trxRoundStage}')"
static newFromId( $domain)
A BagOStuff object with no objects in it.
if(ini_get( 'mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
Class representing a cache/ephemeral data store.
static newEmpty()
Get an instance that wraps EmptyBagOStuff.
Multi-datacenter aware caching interface.
Class to handle database/schema/prefix specifications for IDatabase.