use MediaWiki\Revision\SlotRecord;
use MediaWiki\User\UserIdentity;
use MessageCache;
+use MWCallableUpdate;
use ParserCache;
use ParserOptions;
use ParserOutput;
}
// Defer the getCannonicalParserOutput() call triggered by getSecondaryDataUpdates()
- DeferredUpdates::addCallableUpdate( function () {
- $this->doSecondaryDataUpdates( [
- // T52785 do not update any other pages on a null edit
- 'recursive' => $this->options['changed']
- ] );
- } );
+ // by wrapping the code that schedules the secondary updates in a callback itself
+ $wrapperUpdate = new MWCallableUpdate(
+ function () {
+ $this->doSecondaryDataUpdates( [
+ // T52785 do not update any other pages on a null edit
+ 'recursive' => $this->options['changed']
+ ] );
+ },
+ __METHOD__
+ );
+ $wrapperUpdate->setTransactionRoundRequirement( $wrapperUpdate::TRX_ROUND_ABSENT );
+ DeferredUpdates::addUpdate( $wrapperUpdate );
// TODO: MCR: check if *any* changed slot supports categories!
if ( $this->rcWatchCategoryMembership
* current page or otherwise depend on it (default: false)
* - defer: one of the DeferredUpdates constants, or false to run immediately after waiting
* for replication of the changes from the SecondaryDataUpdates hooks (default: false)
- * - transactionTicket: a transaction ticket from LBFactory::getEmptyTransactionTicket(),
- * only when defer is false (default: null)
* @since 1.32
*/
public function doSecondaryDataUpdates( array $options = [] ) {
$this->assertHasRevision( __METHOD__ );
- $options += [
- 'recursive' => false,
- 'defer' => false,
- 'transactionTicket' => null,
- ];
+ $options += [ 'recursive' => false, 'defer' => false ];
$deferValues = [ false, DeferredUpdates::PRESEND, DeferredUpdates::POSTSEND ];
if ( !in_array( $options['defer'], $deferValues, true ) ) {
throw new InvalidArgumentException( 'Invalid value for defer: ' . $options['defer'] );
}
- Assert::parameterType(
- 'integer|null', $options['transactionTicket'], '$options[\'transactionTicket\']' );
-
$updates = $this->getSecondaryDataUpdates( $options['recursive'] );
$triggeringUser = $this->options['triggeringUser'] ?? $this->user;
$causeAction = $this->options['causeAction'] ?? 'unknown';
$causeAgent = $this->options['causeAgent'] ?? 'unknown';
$legacyRevision = new Revision( $this->revision );
- $ticket = $options['transactionTicket'];
-
- if ( $options['defer'] === false && $ticket !== null ) {
- // For legacy hook handlers doing updates via LinksUpdateConstructed, make sure
- // any pending writes they made get flushed before the doUpdate() calls below.
- // This avoids snapshot-clearing errors in LinksUpdate::acquirePageLock().
- $this->loadbalancerFactory->commitAndWaitForReplication( __METHOD__, $ticket );
- }
foreach ( $updates as $update ) {
if ( $update instanceof DataUpdate ) {
$update->setRevision( $legacyRevision );
$update->setTriggeringUser( $triggeringUser );
}
+ }
- if ( $options['defer'] === false ) {
- if ( $update instanceof DataUpdate && $ticket !== null ) {
- $update->setTransactionTicket( $ticket );
- }
- $update->doUpdate();
- } else {
+ if ( $options['defer'] === false ) {
+ // T221577: flush any transaction; each update needs outer transaction scope
+ $this->loadbalancerFactory->commitMasterChanges( __METHOD__ );
+ foreach ( $updates as $update ) {
+ DeferredUpdates::attemptUpdate( $update, $this->loadbalancerFactory );
+ }
+ } else {
+ foreach ( $updates as $update ) {
DeferredUpdates::addUpdate( $update, $options['defer'] );
}
}
$lbFactory = $services->getDBLoadBalancerFactory();
$method = RequestContext::getMain()->getRequest()->getMethod();
- $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
-
/** @var ErrorPageError $reportableError */
$reportableError = null;
/** @var DeferrableUpdate[] $updates Snapshot of queue */
$updatesByType = [ 'data' => [], 'generic' => [] ];
foreach ( $updates as $du ) {
if ( $du instanceof DataUpdate ) {
- $du->setTransactionTicket( $ticket );
$updatesByType['data'][] = $du;
} else {
$updatesByType['generic'][] = $du;
$firstKey = key( self::$executeContext['subqueue'] );
unset( self::$executeContext['subqueue'][$firstKey] );
- if ( $subUpdate instanceof DataUpdate ) {
- $subUpdate->setTransactionTicket( $ticket );
- }
-
$guiError = self::handleUpdate( $subUpdate, $lbFactory, $mode, $stage );
$reportableError = $reportableError ?: $guiError;
}
* @since 1.34
*/
public static function attemptUpdate( DeferrableUpdate $update, ILBFactory $lbFactory ) {
+ if ( $update instanceof DataUpdate ) {
+ $update->setTransactionTicket( $lbFactory->getEmptyTransactionTicket( __METHOD__ ) );
+ }
+
if (
$update instanceof TransactionRoundAwareUpdate &&
$update->getTransactionRoundRequirement() == $update::TRX_ROUND_ABSENT
/**
* Deferrable Update for closure/callback
*/
-class MWCallableUpdate implements DeferrableUpdate, DeferrableCallback {
- /** @var callable|null */
+class MWCallableUpdate
+ implements DeferrableUpdate, DeferrableCallback, TransactionRoundAwareUpdate
+{
+ /** @var callable|null Callback, or null if it was cancelled */
private $callback;
- /** @var string */
+ /** @var string Calling method name */
private $fname;
+ /** @var int One of the class TRX_ROUND_* constants */
+ private $trxRoundRequirement = self::TRX_ROUND_PRESENT;
/**
* @param callable $callback
public function getOrigin() {
return $this->fname;
}
+
+ /**
+ * @since 1.34
+ * @param int $mode One of the class TRX_ROUND_* constants
+ */
+ public function setTransactionRoundRequirement( $mode ) {
+ $this->trxRoundRequirement = $mode;
+ }
+
+ public function getTransactionRoundRequirement() {
+ return $this->trxRoundRequirement;
+ }
}