$this->defaultGroup = $conf['defaultGroup'] ?? null;
$this->secret = $conf['secret'] ?? '';
- $this->id = mt_rand();
- $this->ticket = mt_rand();
+ static $nextId, $nextTicket;
+ $this->id = $nextId = ( is_int( $nextId ) ? $nextId++ : mt_rand() );
+ $this->ticket = $nextTicket = ( is_int( $nextTicket ) ? $nextTicket++ : mt_rand() );
}
public function destroy() {
- $this->shutdown( self::SHUTDOWN_NO_CHRONPROT );
- $this->forEachLBCallMethod( 'disable' );
+ /** @noinspection PhpUnusedLocalVariableInspection */
+ $scope = ScopedCallback::newScopedIgnoreUserAbort();
+
+ $this->forEachLBCallMethod( 'disable', [ __METHOD__, $this->id ] );
}
public function getLocalDomainID() {
&$cpIndex = null,
&$cpClientId = null
) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
+ $scope = ScopedCallback::newScopedIgnoreUserAbort();
+
$chronProt = $this->getChronologyProtector();
if ( $mode === self::SHUTDOWN_CHRONPROT_SYNC ) {
$this->shutdownChronologyProtector( $chronProt, $workCallback, 'sync', $cpIndex );
$this->commitMasterChanges( __METHOD__ ); // sanity
}
- /**
- * @see ILBFactory::newMainLB()
- * @param bool $domain
- * @return ILoadBalancer
- */
- abstract public function newMainLB( $domain = false );
-
- /**
- * @see ILBFactory::getMainLB()
- * @param bool $domain
- * @return ILoadBalancer
- */
- abstract public function getMainLB( $domain = false );
-
- /**
- * @see ILBFactory::newExternalLB()
- * @param string $cluster
- * @return ILoadBalancer
- */
- abstract public function newExternalLB( $cluster );
-
- /**
- * @see ILBFactory::getExternalLB()
- * @param string $cluster
- * @return ILoadBalancer
- */
- abstract public function getExternalLB( $cluster );
-
/**
* Call a method of each tracked load balancer
*
}
public function flushReplicaSnapshots( $fname = __METHOD__ ) {
- $this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname ] );
+ if ( $this->trxRoundId !== false && $this->trxRoundId !== $fname ) {
+ $this->queryLogger->warning(
+ "$fname: transaction round '{$this->trxRoundId}' still running",
+ [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
+ );
+ }
+ $this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname, $this->id ] );
}
final public function commitAll( $fname = __METHOD__, array $options = [] ) {
$this->commitMasterChanges( $fname, $options );
- $this->forEachLBCallMethod( 'flushMasterSnapshots', [ $fname ] );
- $this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname ] );
+ $this->forEachLBCallMethod( 'flushMasterSnapshots', [ $fname, $this->id ] );
+ $this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname, $this->id ] );
}
final public function beginMasterChanges( $fname = __METHOD__ ) {
$this->assertTransactionRoundStage( self::ROUND_CURSORY );
+ /** @noinspection PhpUnusedLocalVariableInspection */
+ $scope = ScopedCallback::newScopedIgnoreUserAbort();
+
$this->trxRoundStage = self::ROUND_BEGINNING;
if ( $this->trxRoundId !== false ) {
throw new DBTransactionError(
null,
- "$fname: transaction round '{$this->trxRoundId}' already started."
+ "$fname: transaction round '{$this->trxRoundId}' already started"
);
}
$this->trxRoundId = $fname;
final public function commitMasterChanges( $fname = __METHOD__, array $options = [] ) {
$this->assertTransactionRoundStage( self::ROUND_CURSORY );
+ /** @noinspection PhpUnusedLocalVariableInspection */
+ $scope = ScopedCallback::newScopedIgnoreUserAbort();
+
$this->trxRoundStage = self::ROUND_COMMITTING;
if ( $this->trxRoundId !== false && $this->trxRoundId !== $fname ) {
throw new DBTransactionError(
null,
- "$fname: transaction round '{$this->trxRoundId}' still running."
+ "$fname: transaction round '{$this->trxRoundId}' still running"
);
}
- /** @noinspection PhpUnusedLocalVariableInspection */
- $scope = ScopedCallback::newScopedIgnoreUserAbort(); // try to ignore client aborts
// Run pre-commit callbacks and suppress post-commit callbacks, aborting on failure
do {
$count = 0; // number of callbacks executed this iteration
}
final public function rollbackMasterChanges( $fname = __METHOD__ ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
+ $scope = ScopedCallback::newScopedIgnoreUserAbort();
+
$this->trxRoundStage = self::ROUND_ROLLING_BACK;
$this->trxRoundId = false;
// Actually perform the rollback on all master DB connections and revert DBO_TRX
// time needed to wait on the next clusters.
$masterPositions = array_fill( 0, count( $lbs ), false );
foreach ( $lbs as $i => $lb ) {
- if ( !$lb->hasStreamingReplicaServers() ) {
- continue; // T29975: no replication; avoid getMasterPos() permissions errors
- } elseif (
- $opts['ifWritesSince'] &&
- $lb->lastMasterChangeTimestamp() < $opts['ifWritesSince']
+ if (
+ // No writes to wait on getting replicated
+ !$lb->hasMasterConnection() ||
+ // No replication; avoid getMasterPos() permissions errors (T29975)
+ !$lb->hasStreamingReplicaServers() ||
+ // No writes since the last replication wait
+ (
+ $opts['ifWritesSince'] &&
+ $lb->lastMasterChangeTimestamp() < $opts['ifWritesSince']
+ )
) {
- continue; // no writes since the last wait
+ continue; // no need to wait
}
+
$masterPositions[$i] = $lb->getMasterPos();
}
public function getEmptyTransactionTicket( $fname ) {
if ( $this->hasMasterChanges() ) {
- $this->queryLogger->error( __METHOD__ . ": $fname does not have outer scope.\n" .
- ( new RuntimeException() )->getTraceAsString() );
+ $this->queryLogger->error(
+ __METHOD__ . ": $fname does not have outer scope",
+ [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
+ );
return null;
}
final public function commitAndWaitForReplication( $fname, $ticket, array $opts = [] ) {
if ( $ticket !== $this->ticket ) {
- $this->perfLogger->error( __METHOD__ . ": $fname does not have outer scope.\n" .
- ( new RuntimeException() )->getTraceAsString() );
+ $this->perfLogger->error(
+ __METHOD__ . ": $fname does not have outer scope",
+ [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
+ );
return false;
}
// The transaction owner and any caller with the empty transaction ticket can commit
// so that getEmptyTransactionTicket() callers don't risk seeing DBTransactionError.
if ( $this->trxRoundId !== false && $fname !== $this->trxRoundId ) {
- $this->queryLogger->info( "$fname: committing on behalf of {$this->trxRoundId}." );
+ $this->queryLogger->info( "$fname: committing on behalf of {$this->trxRoundId}" );
$fnameEffective = $this->trxRoundId;
} else {
$fnameEffective = $fname;
} elseif ( $this->memStash instanceof EmptyBagOStuff ) {
// No where to store any DB positions and wait for them to appear
$this->chronProt->setEnabled( false );
- $this->replLogger->info( 'Cannot use ChronologyProtector with EmptyBagOStuff.' );
+ $this->replLogger->info( 'Cannot use ChronologyProtector with EmptyBagOStuff' );
}
- $this->replLogger->debug( __METHOD__ . ': using request info ' .
- json_encode( $this->requestInfo, JSON_PRETTY_PRINT ) );
+ $this->replLogger->debug(
+ __METHOD__ . ': request info ' .
+ json_encode( $this->requestInfo, JSON_PRETTY_PRINT )
+ );
return $this->chronProt;
}
}
/**
- * Base parameters to ILoadBalancer::__construct()
+ * Get parameters to ILoadBalancer::__construct()
+ *
+ * @param int|null $owner Use getOwnershipId() if this is for getMainLB()/getExternalLB()
* @return array
*/
- final protected function baseLoadBalancerParams() {
+ final protected function baseLoadBalancerParams( $owner ) {
if ( $this->trxRoundStage === self::ROUND_COMMIT_CALLBACKS ) {
$initStage = ILoadBalancer::STAGE_POSTCOMMIT_CALLBACKS;
} elseif ( $this->trxRoundStage === self::ROUND_ROLLBACK_CALLBACKS ) {
$this->getChronologyProtector()->applySessionReplicationPosition( $lb );
},
'roundStage' => $initStage,
- 'ownerId' => $this->id
+ 'ownerId' => $owner
];
}
$this->indexAliases = $aliases;
}
- /**
- * @param string $prefix
- * @deprecated Since 1.33
- */
- public function setDomainPrefix( $prefix ) {
- $this->setLocalDomainPrefix( $prefix );
- }
-
public function setLocalDomainPrefix( $prefix ) {
$this->localDomain = new DatabaseDomain(
$this->localDomain->getDatabase(),
}
public function closeAll() {
- $this->forEachLBCallMethod( 'closeAll' );
+ /** @noinspection PhpUnusedLocalVariableInspection */
+ $scope = ScopedCallback::newScopedIgnoreUserAbort();
+
+ $this->forEachLBCallMethod( 'closeAll', [ __METHOD__, $this->id ] );
}
public function setAgentName( $agent ) {
public function setRequestInfo( array $info ) {
if ( $this->chronProt ) {
- throw new LogicException( 'ChronologyProtector already initialized.' );
+ throw new LogicException( 'ChronologyProtector already initialized' );
}
$this->requestInfo = $info + $this->requestInfo;
}
+ /**
+ * @return int Internal instance ID used to assert ownership of ILoadBalancer instances
+ * @since 1.34
+ */
+ final protected function getOwnershipId() {
+ return $this->id;
+ }
+
/**
* @param string $stage
*/