const ROUND_BEGINNING = 'within-begin';
const ROUND_COMMITTING = 'within-commit';
const ROUND_ROLLING_BACK = 'within-rollback';
+ const ROUND_COMMIT_CALLBACKS = 'within-commit-callbacks';
+ const ROUND_ROLLBACK_CALLBACKS = 'within-rollback-callbacks';
private static $loggerFields =
[ 'replLogger', 'connLogger', 'queryLogger', 'perfLogger' ];
'IPAddress' => isset( $_SERVER[ 'REMOTE_ADDR' ] ) ? $_SERVER[ 'REMOTE_ADDR' ] : '',
'UserAgent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '',
'ChronologyProtection' => 'true',
+ // phpcs:ignore MediaWiki.Usage.SuperGlobalsUsage.SuperGlobals -- library can't use $wgRequest
'ChronologyPositionIndex' => isset( $_GET['cpPosIndex'] ) ? $_GET['cpPosIndex'] : null
];
/**
* @see ILBFactory::newMainLB()
* @param bool $domain
- * @return LoadBalancer
+ * @return ILoadBalancer
*/
abstract public function newMainLB( $domain = false );
/**
* @see ILBFactory::getMainLB()
* @param bool $domain
- * @return LoadBalancer
+ * @return ILoadBalancer
*/
abstract public function getMainLB( $domain = false );
/**
* @see ILBFactory::newExternalLB()
* @param string $cluster
- * @return LoadBalancer
+ * @return ILoadBalancer
*/
abstract public function newExternalLB( $cluster );
/**
* @see ILBFactory::getExternalLB()
* @param string $cluster
- * @return LoadBalancer
+ * @return ILoadBalancer
*/
abstract public function getExternalLB( $cluster );
final public function commitAll( $fname = __METHOD__, array $options = [] ) {
$this->commitMasterChanges( $fname, $options );
- $this->forEachLBCallMethod( 'commitAll', [ $fname ] );
+ $this->forEachLBCallMethod( 'flushMasterSnapshots', [ $fname ] );
+ $this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname ] );
}
final public function beginMasterChanges( $fname = __METHOD__ ) {
/** @noinspection PhpUnusedLocalVariableInspection */
$scope = $this->getScopedPHPBehaviorForCommit(); // try to ignore client aborts
// Run pre-commit callbacks and suppress post-commit callbacks, aborting on failure
- $this->forEachLBCallMethod( 'finalizeMasterChanges' );
+ do {
+ $count = 0; // number of callbacks executed this iteration
+ $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$count ) {
+ $count += $lb->finalizeMasterChanges();
+ } );
+ } while ( $count > 0 );
$this->trxRoundId = false;
// Perform pre-commit checks, aborting on failure
$this->forEachLBCallMethod( 'approveMasterChanges', [ $options ] );
// Actually perform the commit on all master DB connections and revert DBO_TRX
$this->forEachLBCallMethod( 'commitMasterChanges', [ $fname ] );
// Run all post-commit callbacks in a separate step
+ $this->trxRoundStage = self::ROUND_COMMIT_CALLBACKS;
$e = $this->executePostTransactionCallbacks();
$this->trxRoundStage = self::ROUND_CURSORY;
// Throw any last post-commit callback error
// Actually perform the rollback on all master DB connections and revert DBO_TRX
$this->forEachLBCallMethod( 'rollbackMasterChanges', [ $fname ] );
// Run all post-commit callbacks in a separate step
+ $this->trxRoundStage = self::ROUND_ROLLBACK_CALLBACKS;
$this->executePostTransactionCallbacks();
$this->trxRoundStage = self::ROUND_CURSORY;
}
}
/**
- * Base parameters to LoadBalancer::__construct()
+ * Base parameters to ILoadBalancer::__construct()
* @return array
*/
final protected function baseLoadBalancerParams() {
+ if ( $this->trxRoundStage === self::ROUND_COMMIT_CALLBACKS ) {
+ $initStage = ILoadBalancer::STAGE_POSTCOMMIT_CALLBACKS;
+ } elseif ( $this->trxRoundStage === self::ROUND_ROLLBACK_CALLBACKS ) {
+ $initStage = ILoadBalancer::STAGE_POSTROLLBACK_CALLBACKS;
+ } else {
+ $initStage = null;
+ }
+
return [
'localDomain' => $this->localDomain,
'readOnlyReason' => $this->readOnlyReason,
// Defer ChronologyProtector construction in case setRequestInfo() ends up
// being called later (but before the first connection attempt) (T192611)
$this->getChronologyProtector()->initLB( $lb );
- }
+ },
+ 'roundStage' => $initStage
];
}
return strpos( $url, '?' ) === false ? "$url?cpPosIndex=$index" : "$url&cpPosIndex=$index";
}
+ /**
+ * @param int $index Write index
+ * @param int $time UNIX timestamp
+ * @return string Timestamp-qualified write index of the form "<index>.<timestamp>"
+ * @since 1.32
+ */
+ public static function makeCookieValueFromCPIndex( $index, $time ) {
+ return $index . '@' . $time;
+ }
+
+ /**
+ * @param string $value String possibly of the form "<index>" or "<index>@<timestamp>"
+ * @param int $minTimestamp Lowest UNIX timestamp of non-expired values (if present)
+ * @return int|null Write index or null if $value is empty or expired
+ * @since 1.32
+ */
+ public static function getCPIndexFromCookieValue( $value, $minTimestamp ) {
+ if ( !preg_match( '/^(\d+)(?:@(\d+))?$/', $value, $m ) ) {
+ return null;
+ }
+
+ $index = (int)$m[1];
+
+ if ( isset( $m[2] ) && $m[2] !== '' && (int)$m[2] < $minTimestamp ) {
+ return null; // expired
+ }
+
+ return ( $index > 0 ) ? $index : null;
+ }
+
public function setRequestInfo( array $info ) {
if ( $this->chronProt ) {
throw new LogicException( 'ChronologyProtector already initialized.' );
}
}
+/**
+ * @deprecated since 1.29
+ */
class_alias( LBFactory::class, 'LBFactory' );