wiki.techinc.nl/tests/phpunit/includes/deferred/DeferredUpdatesTest.php

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

477 lines
15 KiB
PHP
Raw Normal View History

<?php
use MediaWiki\Deferred\DeferrableUpdate;
use MediaWiki\Deferred\DeferredUpdates;
use MediaWiki\Deferred\EnqueueableDataUpdate;
use MediaWiki\Deferred\MergeableUpdate;
use MediaWiki\Deferred\MWCallableUpdate;
use MediaWiki\Deferred\TransactionRoundDefiningUpdate;
use MediaWiki\Logger\LoggerFactory;
/**
* @group Database
* @covers \MediaWiki\Deferred\DeferredUpdates
* @covers \MediaWiki\Deferred\DeferredUpdatesScopeStack
* @covers \MediaWiki\Deferred\DeferredUpdatesScope
*/
class DeferredUpdatesTest extends MediaWikiIntegrationTestCase {
public function testAddAndRun() {
$update = $this->getMockBuilder( DeferrableUpdate::class )
->onlyMethods( [ 'doUpdate' ] )->getMock();
$update->expects( $this->once() )->method( 'doUpdate' );
DeferredUpdates::addUpdate( $update );
DeferredUpdates::doUpdates();
}
public function testAddMergeable() {
$cleanup = DeferredUpdates::preventOpportunisticUpdates();
$update1 = $this->getMockBuilder( MergeableUpdate::class )
->onlyMethods( [ 'merge', 'doUpdate' ] )->getMock();
$update1->expects( $this->once() )->method( 'merge' );
$update1->expects( $this->never() )->method( 'doUpdate' );
$update2 = $this->getMockBuilder( MergeableUpdate::class )
->onlyMethods( [ 'merge', 'doUpdate' ] )->getMock();
$update2->expects( $this->never() )->method( 'merge' );
$update2->expects( $this->never() )->method( 'doUpdate' );
DeferredUpdates::addUpdate( $update1 );
DeferredUpdates::addUpdate( $update2 );
}
public function testAddCallableUpdate() {
$ran = 0;
DeferredUpdates::addCallableUpdate( static function () use ( &$ran ) {
$ran++;
} );
// Opportunistic updates should be enabled, so the updates should have already executed
$this->assertSame( 0, DeferredUpdates::pendingUpdatesCount() );
$this->assertSame( 1, $ran, 'Update ran' );
}
public function testGetPendingUpdates() {
$cleanup = DeferredUpdates::preventOpportunisticUpdates();
$pre = DeferredUpdates::PRESEND;
$post = DeferredUpdates::POSTSEND;
$all = DeferredUpdates::ALL;
$update = $this->createMock( DeferrableUpdate::class );
$update->expects( $this->never() )
->method( 'doUpdate' );
DeferredUpdates::addUpdate( $update, $pre );
$this->assertCount( 1, DeferredUpdates::getPendingUpdates( $pre ) );
$this->assertSame( [], DeferredUpdates::getPendingUpdates( $post ) );
$this->assertCount( 1, DeferredUpdates::getPendingUpdates( $all ) );
$this->assertCount( 1, DeferredUpdates::getPendingUpdates() );
DeferredUpdates::clearPendingUpdates();
$this->assertSame( [], DeferredUpdates::getPendingUpdates() );
DeferredUpdates::addUpdate( $update, $post );
$this->assertSame( [], DeferredUpdates::getPendingUpdates( $pre ) );
$this->assertCount( 1, DeferredUpdates::getPendingUpdates( $post ) );
$this->assertCount( 1, DeferredUpdates::getPendingUpdates( $all ) );
$this->assertCount( 1, DeferredUpdates::getPendingUpdates() );
DeferredUpdates::clearPendingUpdates();
$this->assertSame( [], DeferredUpdates::getPendingUpdates() );
}
public function testDoUpdatesWeb() {
$cleanup = DeferredUpdates::preventOpportunisticUpdates();
$updates = [
'1' => "deferred update 1;\n",
'2' => "deferred update 2;\n",
'2-1' => "deferred update 1 within deferred update 2;\n",
'2-2' => "deferred update 2 within deferred update 2;\n",
'3' => "deferred update 3;\n",
'3-1' => "deferred update 1 within deferred update 3;\n",
'3-2' => "deferred update 2 within deferred update 3;\n",
'3-1-1' => "deferred update 1 within deferred update 1 within deferred update 3;\n",
'3-2-1' => "deferred update 1 within deferred update 2 with deferred update 3;\n",
];
DeferredUpdates::addCallableUpdate(
static function () use ( $updates ) {
echo $updates['1'];
}
);
DeferredUpdates::addCallableUpdate(
static function () use ( $updates ) {
echo $updates['2'];
DeferredUpdates::addCallableUpdate(
static function () use ( $updates ) {
echo $updates['2-1'];
}
);
DeferredUpdates::addCallableUpdate(
static function () use ( $updates ) {
echo $updates['2-2'];
}
);
}
);
DeferredUpdates::addCallableUpdate(
static function () use ( $updates ) {
echo $updates['3'];
DeferredUpdates::addCallableUpdate(
static function () use ( $updates ) {
echo $updates['3-1'];
DeferredUpdates::addCallableUpdate(
static function () use ( $updates ) {
echo $updates['3-1-1'];
}
);
}
);
DeferredUpdates::addCallableUpdate(
static function () use ( $updates ) {
echo $updates['3-2'];
DeferredUpdates::addCallableUpdate(
static function () use ( $updates ) {
echo $updates['3-2-1'];
}
);
}
);
}
);
$this->assertEquals( 3, DeferredUpdates::pendingUpdatesCount() );
$this->expectOutputString( implode( '', $updates ) );
DeferredUpdates::doUpdates();
$x = null;
$y = null;
DeferredUpdates::addCallableUpdate(
static function () use ( &$x ) {
$x = 'Sherity';
},
DeferredUpdates::PRESEND
);
DeferredUpdates::addCallableUpdate(
static function () use ( &$y ) {
$y = 'Marychu';
},
DeferredUpdates::POSTSEND
);
$this->assertNull( $x, "Update not run yet" );
$this->assertNull( $y, "Update not run yet" );
DeferredUpdates::doUpdates( DeferredUpdates::PRESEND );
$this->assertEquals( "Sherity", $x, "PRESEND update ran" );
$this->assertNull( $y, "POSTSEND update not run yet" );
DeferredUpdates::doUpdates( DeferredUpdates::POSTSEND );
$this->assertEquals( "Marychu", $y, "POSTSEND update ran" );
}
public function testDoUpdatesCLI() {
$updates = [
'1' => "deferred update 1;\n",
'2' => "deferred update 2;\n",
'2-1' => "deferred update 1 within deferred update 2;\n",
'2-2' => "deferred update 2 within deferred update 2;\n",
'3' => "deferred update 3;\n",
'3-1' => "deferred update 1 within deferred update 3;\n",
'3-2' => "deferred update 2 within deferred update 3;\n",
'3-1-1' => "deferred update 1 within deferred update 1 within deferred update 3;\n",
'3-2-1' => "deferred update 1 within deferred update 2 with deferred update 3;\n",
];
// clear anything
$lbFactory = $this->getServiceContainer()->getDBLoadBalancerFactory();
$lbFactory->commitPrimaryChanges( __METHOD__ );
DeferredUpdates::addCallableUpdate(
static function () use ( $updates ) {
echo $updates['1'];
}
);
DeferredUpdates::addCallableUpdate(
static function () use ( $updates ) {
echo $updates['2'];
DeferredUpdates::addCallableUpdate(
static function () use ( $updates ) {
echo $updates['2-1'];
}
);
DeferredUpdates::addCallableUpdate(
static function () use ( $updates ) {
echo $updates['2-2'];
}
);
}
);
DeferredUpdates::addCallableUpdate(
static function () use ( $updates ) {
echo $updates['3'];
DeferredUpdates::addCallableUpdate(
static function () use ( $updates ) {
echo $updates['3-1'];
DeferredUpdates::addCallableUpdate(
static function () use ( $updates ) {
echo $updates['3-1-1'];
}
);
}
);
DeferredUpdates::addCallableUpdate(
static function () use ( $updates ) {
echo $updates['3-2'];
DeferredUpdates::addCallableUpdate(
static function () use ( $updates ) {
echo $updates['3-2-1'];
}
);
}
);
}
);
$this->expectOutputString( implode( '', $updates ) );
// Opportunistic updates should be enabled, so the updates should have already executed
$this->assertSame( 0, DeferredUpdates::pendingUpdatesCount() );
}
public function testPresendAddOnPostsendRun() {
$x = false;
$y = false;
// clear anything
$lbFactory = $this->getServiceContainer()->getDBLoadBalancerFactory();
$lbFactory->commitPrimaryChanges( __METHOD__ );
DeferredUpdates::addCallableUpdate(
static function () use ( &$x, &$y ) {
$x = true;
DeferredUpdates::addCallableUpdate(
static function () use ( &$y ) {
$y = true;
},
DeferredUpdates::PRESEND
);
},
DeferredUpdates::POSTSEND
);
// Opportunistic updates should be enabled, so the updates should have already executed
$this->assertSame( 0, DeferredUpdates::pendingUpdatesCount() );
$this->assertTrue( $x, "Outer POSTSEND update ran" );
$this->assertTrue( $y, "Nested PRESEND update ran" );
}
public function testRunUpdateTransactionScope() {
$cleanup = DeferredUpdates::preventOpportunisticUpdates();
$lbFactory = $this->getServiceContainer()->getDBLoadBalancerFactory();
$this->assertFalse( $lbFactory->hasTransactionRound(), 'Initial state' );
$ran = 0;
DeferredUpdates::addCallableUpdate( function () use ( &$ran, $lbFactory ) {
$ran++;
$this->assertTrue( $lbFactory->hasTransactionRound(), 'Has transaction' );
} );
DeferredUpdates::doUpdates();
$this->assertSame( 1, $ran, 'Update ran' );
$this->assertFalse( $lbFactory->hasTransactionRound(), 'Final state' );
}
/**
* @covers \MediaWiki\Deferred\TransactionRoundDefiningUpdate
*/
public function testRunOuterScopeUpdate() {
$cleanup = DeferredUpdates::preventOpportunisticUpdates();
$lbFactory = $this->getServiceContainer()->getDBLoadBalancerFactory();
$this->assertFalse( $lbFactory->hasTransactionRound(), 'Initial state' );
$ran = 0;
DeferredUpdates::addUpdate( new TransactionRoundDefiningUpdate(
function () use ( &$ran, $lbFactory ) {
$ran++;
$this->assertFalse( $lbFactory->hasTransactionRound(), 'No transaction' );
} )
);
DeferredUpdates::doUpdates();
$this->assertSame( 1, $ran, 'Update ran' );
}
public function testTryOpportunisticExecute() {
$calls = [];
$callback1 = static function () use ( &$calls ) {
$calls[] = 1;
};
$callback2 = static function () use ( &$calls ) {
$calls[] = 2;
};
$lbFactory = $this->getServiceContainer()->getDBLoadBalancerFactory();
$lbFactory->beginPrimaryChanges( __METHOD__ );
DeferredUpdates::addCallableUpdate( $callback1 );
$this->assertEquals( [], $calls );
DeferredUpdates::tryOpportunisticExecute();
$this->assertEquals( [], $calls );
$dbw = $this->getDb();
$dbw->onTransactionCommitOrIdle( function () use ( &$calls, $callback2 ) {
DeferredUpdates::addCallableUpdate( $callback2 );
$this->assertEquals( [], $calls );
$calls[] = 'oti';
} );
$this->assertSame( 1, $dbw->trxLevel() );
$this->assertEquals( [], $calls );
$lbFactory->commitPrimaryChanges( __METHOD__ );
$this->assertEquals( [ 'oti' ], $calls );
DeferredUpdates::tryOpportunisticExecute();
$this->assertEquals( [ 'oti', 1, 2 ], $calls );
}
public function testTryOpportunisticExecute_enqueue() {
$this->setLogger( 'DeferredUpdates', new TestLogger( true, null, true ) );
$lbFactory = $this->getServiceContainer()->getDBLoadBalancerFactory();
$lbFactory->beginPrimaryChanges( __METHOD__ );
for ( $i = 1; $i <= 50; $i++ ) {
DeferredUpdates::addCallableUpdate( fn () => null );
}
$enqueueableUpdate = new class ( fn () => null, $this->getDb()->getDomainID() )
extends MWCallableUpdate
implements EnqueueableDataUpdate
{
private $domainId;
public function __construct( callable $callback, $domainId ) {
parent::__construct( $callback );
$this->domainId = $domainId;
}
public function getAsJobSpecification() {
return [ 'domain' => $this->domainId, 'job' => new JobSpecification( 'foo', [] ) ];
}
};
DeferredUpdates::addUpdate( $enqueueableUpdate );
for ( $i = 1; $i <= 50; $i++ ) {
DeferredUpdates::addCallableUpdate( fn () => null );
}
DeferredUpdates::tryOpportunisticExecute();
$lbFactory->commitPrimaryChanges( __METHOD__ );
$log = LoggerFactory::getInstance( 'DeferredUpdates' )->getBuffer();
$this->assertSame( 'Enqueued {enqueuedUpdatesCount} updates as jobs', $log[0][1] );
$this->assertSame( 1, $log[0][2]['enqueuedUpdatesCount'] );
$this->assertSame( get_class( $enqueueableUpdate ) . ': 1', $log[0][2]['enqueuedUpdates'] );
}
/**
* @covers \MediaWiki\Deferred\MWCallableUpdate
*/
public function testCallbackUpdateRounds() {
$lbFactory = $this->getServiceContainer()->getDBLoadBalancerFactory();
$fname = __METHOD__;
$called = false;
// This confirms that DeferredUpdates sets the transaction owner in LBFactory
// based on MWCallableUpdate::getOrigin, thus allowing the callback to control
// over the transaction and e.g. perform a commit.
DeferredUpdates::attemptUpdate(
new MWCallableUpdate(
static function () use ( $lbFactory, $fname, &$called ) {
$lbFactory->flushReplicaSnapshots( $fname );
$lbFactory->commitPrimaryChanges( $fname );
$called = true;
},
$fname
)
);
$this->assertTrue( $called, "Callback ran" );
}
public function testNestedExecution() {
$cleanup = DeferredUpdates::preventOpportunisticUpdates();
$res = null;
$resSub = null;
$resSubSub = null;
$resA = null;
deferred: make DeferredUpdates::doUpdates() recursion more uniform Follow-up ba6490aa1eb. That patch worked around $executeContext iteration errors due to the lack of recursion support in doUpdates()/handleUpdateQueue(). Errors were triggered by MediaWiki::schedulePostSendJobs() enqueueing deferred updates that invoke JobRunner::run(), which, in turn, invoke doUpdates() for each job via JobRunner:: doExecuteJob(). The doUpdates() method was changed to operate on the sub-queue for the in-progress DeferrableUpdate. Further improve the recursion logic: * Use classes for the scope stack and scope queues. * Put *all* updates added during a DeferrableUpdate::doUpdate() call into the subqueue, regardless of "defer until" stage or whether it implements MergeableUpdate. Now, doUpdate() can run *everything* it enqueued instead of a non-obvious subset. Note that the TransactionRoundDefiningUpdate that invokes JobRunner was already a POSTSEND update before ba6490aa1eb; the only effect of this change is that MergeableUpdate instances from jobs will once again run in doExecuteJob(). * Make recursive DeferredUpdates::doUpdate() calls error out immediately unless the DeferrableUpdate responsible is a TransactionRoundAwareUpdate with the TRX_ROUND_ABSENT flag. This covers the schedulePostSendJobs() scenario and only prohibits insane call patterns. Failing early avoids the risk of handleUpdateQueue() dropping all the updates due to the same DBTransactionError error in DeferredUpdates::attemptUpdate(). * Avoid recursion loop in tryOpportunisticExecute() when JobQueueDB is in use and a large number of tasks are pending. This happened due to methods like onTransactionPreCommitOrIdle() being used within JobQueueDB. Mark DeferredUpdates::doUpdates()/tryOpportunisticExecute() as @internal and create a Maintenance::shutdown() method to avoid a direct call in the doMaintenance.php file. Bug: T249069 Bug: T268840 Change-Id: Ib369f0e74243a48ababdb9cd83b155c9a0f5e741
2020-05-06 14:37:00 +00:00
DeferredUpdates::clearPendingUpdates();
$this->assertSame( 0, DeferredUpdates::pendingUpdatesCount() );
$this->assertSame( 0, DeferredUpdates::getRecursiveExecutionStackDepth() );
// T249069: TransactionRoundDefiningUpdate => JobRunner => DeferredUpdates::doUpdates()
DeferredUpdates::addUpdate( new TransactionRoundDefiningUpdate(
function () use ( &$res, &$resSub, &$resSubSub, &$resA ) {
$res = 1;
deferred: make DeferredUpdates::doUpdates() recursion more uniform Follow-up ba6490aa1eb. That patch worked around $executeContext iteration errors due to the lack of recursion support in doUpdates()/handleUpdateQueue(). Errors were triggered by MediaWiki::schedulePostSendJobs() enqueueing deferred updates that invoke JobRunner::run(), which, in turn, invoke doUpdates() for each job via JobRunner:: doExecuteJob(). The doUpdates() method was changed to operate on the sub-queue for the in-progress DeferrableUpdate. Further improve the recursion logic: * Use classes for the scope stack and scope queues. * Put *all* updates added during a DeferrableUpdate::doUpdate() call into the subqueue, regardless of "defer until" stage or whether it implements MergeableUpdate. Now, doUpdate() can run *everything* it enqueued instead of a non-obvious subset. Note that the TransactionRoundDefiningUpdate that invokes JobRunner was already a POSTSEND update before ba6490aa1eb; the only effect of this change is that MergeableUpdate instances from jobs will once again run in doExecuteJob(). * Make recursive DeferredUpdates::doUpdate() calls error out immediately unless the DeferrableUpdate responsible is a TransactionRoundAwareUpdate with the TRX_ROUND_ABSENT flag. This covers the schedulePostSendJobs() scenario and only prohibits insane call patterns. Failing early avoids the risk of handleUpdateQueue() dropping all the updates due to the same DBTransactionError error in DeferredUpdates::attemptUpdate(). * Avoid recursion loop in tryOpportunisticExecute() when JobQueueDB is in use and a large number of tasks are pending. This happened due to methods like onTransactionPreCommitOrIdle() being used within JobQueueDB. Mark DeferredUpdates::doUpdates()/tryOpportunisticExecute() as @internal and create a Maintenance::shutdown() method to avoid a direct call in the doMaintenance.php file. Bug: T249069 Bug: T268840 Change-Id: Ib369f0e74243a48ababdb9cd83b155c9a0f5e741
2020-05-06 14:37:00 +00:00
$this->assertSame( 0, DeferredUpdates::pendingUpdatesCount() );
$this->assertSame( 1, DeferredUpdates::getRecursiveExecutionStackDepth() );
// Add update to subqueue of in-progress top-queue job
DeferredUpdates::addCallableUpdate( function () use ( &$resSub, &$resSubSub ) {
$resSub = 'a';
deferred: make DeferredUpdates::doUpdates() recursion more uniform Follow-up ba6490aa1eb. That patch worked around $executeContext iteration errors due to the lack of recursion support in doUpdates()/handleUpdateQueue(). Errors were triggered by MediaWiki::schedulePostSendJobs() enqueueing deferred updates that invoke JobRunner::run(), which, in turn, invoke doUpdates() for each job via JobRunner:: doExecuteJob(). The doUpdates() method was changed to operate on the sub-queue for the in-progress DeferrableUpdate. Further improve the recursion logic: * Use classes for the scope stack and scope queues. * Put *all* updates added during a DeferrableUpdate::doUpdate() call into the subqueue, regardless of "defer until" stage or whether it implements MergeableUpdate. Now, doUpdate() can run *everything* it enqueued instead of a non-obvious subset. Note that the TransactionRoundDefiningUpdate that invokes JobRunner was already a POSTSEND update before ba6490aa1eb; the only effect of this change is that MergeableUpdate instances from jobs will once again run in doExecuteJob(). * Make recursive DeferredUpdates::doUpdate() calls error out immediately unless the DeferrableUpdate responsible is a TransactionRoundAwareUpdate with the TRX_ROUND_ABSENT flag. This covers the schedulePostSendJobs() scenario and only prohibits insane call patterns. Failing early avoids the risk of handleUpdateQueue() dropping all the updates due to the same DBTransactionError error in DeferredUpdates::attemptUpdate(). * Avoid recursion loop in tryOpportunisticExecute() when JobQueueDB is in use and a large number of tasks are pending. This happened due to methods like onTransactionPreCommitOrIdle() being used within JobQueueDB. Mark DeferredUpdates::doUpdates()/tryOpportunisticExecute() as @internal and create a Maintenance::shutdown() method to avoid a direct call in the doMaintenance.php file. Bug: T249069 Bug: T268840 Change-Id: Ib369f0e74243a48ababdb9cd83b155c9a0f5e741
2020-05-06 14:37:00 +00:00
$this->assertSame( 0, DeferredUpdates::pendingUpdatesCount() );
$this->assertSame( 2, DeferredUpdates::getRecursiveExecutionStackDepth() );
// Add update to subqueue of in-progress top-queue job (not recursive)
DeferredUpdates::addCallableUpdate( static function () use ( &$resSubSub ) {
$resSubSub = 'b';
} );
deferred: make DeferredUpdates::doUpdates() recursion more uniform Follow-up ba6490aa1eb. That patch worked around $executeContext iteration errors due to the lack of recursion support in doUpdates()/handleUpdateQueue(). Errors were triggered by MediaWiki::schedulePostSendJobs() enqueueing deferred updates that invoke JobRunner::run(), which, in turn, invoke doUpdates() for each job via JobRunner:: doExecuteJob(). The doUpdates() method was changed to operate on the sub-queue for the in-progress DeferrableUpdate. Further improve the recursion logic: * Use classes for the scope stack and scope queues. * Put *all* updates added during a DeferrableUpdate::doUpdate() call into the subqueue, regardless of "defer until" stage or whether it implements MergeableUpdate. Now, doUpdate() can run *everything* it enqueued instead of a non-obvious subset. Note that the TransactionRoundDefiningUpdate that invokes JobRunner was already a POSTSEND update before ba6490aa1eb; the only effect of this change is that MergeableUpdate instances from jobs will once again run in doExecuteJob(). * Make recursive DeferredUpdates::doUpdate() calls error out immediately unless the DeferrableUpdate responsible is a TransactionRoundAwareUpdate with the TRX_ROUND_ABSENT flag. This covers the schedulePostSendJobs() scenario and only prohibits insane call patterns. Failing early avoids the risk of handleUpdateQueue() dropping all the updates due to the same DBTransactionError error in DeferredUpdates::attemptUpdate(). * Avoid recursion loop in tryOpportunisticExecute() when JobQueueDB is in use and a large number of tasks are pending. This happened due to methods like onTransactionPreCommitOrIdle() being used within JobQueueDB. Mark DeferredUpdates::doUpdates()/tryOpportunisticExecute() as @internal and create a Maintenance::shutdown() method to avoid a direct call in the doMaintenance.php file. Bug: T249069 Bug: T268840 Change-Id: Ib369f0e74243a48ababdb9cd83b155c9a0f5e741
2020-05-06 14:37:00 +00:00
$this->assertSame( 1, DeferredUpdates::pendingUpdatesCount() );
} );
deferred: make DeferredUpdates::doUpdates() recursion more uniform Follow-up ba6490aa1eb. That patch worked around $executeContext iteration errors due to the lack of recursion support in doUpdates()/handleUpdateQueue(). Errors were triggered by MediaWiki::schedulePostSendJobs() enqueueing deferred updates that invoke JobRunner::run(), which, in turn, invoke doUpdates() for each job via JobRunner:: doExecuteJob(). The doUpdates() method was changed to operate on the sub-queue for the in-progress DeferrableUpdate. Further improve the recursion logic: * Use classes for the scope stack and scope queues. * Put *all* updates added during a DeferrableUpdate::doUpdate() call into the subqueue, regardless of "defer until" stage or whether it implements MergeableUpdate. Now, doUpdate() can run *everything* it enqueued instead of a non-obvious subset. Note that the TransactionRoundDefiningUpdate that invokes JobRunner was already a POSTSEND update before ba6490aa1eb; the only effect of this change is that MergeableUpdate instances from jobs will once again run in doExecuteJob(). * Make recursive DeferredUpdates::doUpdate() calls error out immediately unless the DeferrableUpdate responsible is a TransactionRoundAwareUpdate with the TRX_ROUND_ABSENT flag. This covers the schedulePostSendJobs() scenario and only prohibits insane call patterns. Failing early avoids the risk of handleUpdateQueue() dropping all the updates due to the same DBTransactionError error in DeferredUpdates::attemptUpdate(). * Avoid recursion loop in tryOpportunisticExecute() when JobQueueDB is in use and a large number of tasks are pending. This happened due to methods like onTransactionPreCommitOrIdle() being used within JobQueueDB. Mark DeferredUpdates::doUpdates()/tryOpportunisticExecute() as @internal and create a Maintenance::shutdown() method to avoid a direct call in the doMaintenance.php file. Bug: T249069 Bug: T268840 Change-Id: Ib369f0e74243a48ababdb9cd83b155c9a0f5e741
2020-05-06 14:37:00 +00:00
$this->assertSame( 1, DeferredUpdates::pendingUpdatesCount() );
$this->assertSame( 1, DeferredUpdates::getRecursiveExecutionStackDepth() );
if ( $resSub === null && $resA === null && $resSubSub === null ) {
$res = 418;
}
deferred: make DeferredUpdates::doUpdates() recursion more uniform Follow-up ba6490aa1eb. That patch worked around $executeContext iteration errors due to the lack of recursion support in doUpdates()/handleUpdateQueue(). Errors were triggered by MediaWiki::schedulePostSendJobs() enqueueing deferred updates that invoke JobRunner::run(), which, in turn, invoke doUpdates() for each job via JobRunner:: doExecuteJob(). The doUpdates() method was changed to operate on the sub-queue for the in-progress DeferrableUpdate. Further improve the recursion logic: * Use classes for the scope stack and scope queues. * Put *all* updates added during a DeferrableUpdate::doUpdate() call into the subqueue, regardless of "defer until" stage or whether it implements MergeableUpdate. Now, doUpdate() can run *everything* it enqueued instead of a non-obvious subset. Note that the TransactionRoundDefiningUpdate that invokes JobRunner was already a POSTSEND update before ba6490aa1eb; the only effect of this change is that MergeableUpdate instances from jobs will once again run in doExecuteJob(). * Make recursive DeferredUpdates::doUpdate() calls error out immediately unless the DeferrableUpdate responsible is a TransactionRoundAwareUpdate with the TRX_ROUND_ABSENT flag. This covers the schedulePostSendJobs() scenario and only prohibits insane call patterns. Failing early avoids the risk of handleUpdateQueue() dropping all the updates due to the same DBTransactionError error in DeferredUpdates::attemptUpdate(). * Avoid recursion loop in tryOpportunisticExecute() when JobQueueDB is in use and a large number of tasks are pending. This happened due to methods like onTransactionPreCommitOrIdle() being used within JobQueueDB. Mark DeferredUpdates::doUpdates()/tryOpportunisticExecute() as @internal and create a Maintenance::shutdown() method to avoid a direct call in the doMaintenance.php file. Bug: T249069 Bug: T268840 Change-Id: Ib369f0e74243a48ababdb9cd83b155c9a0f5e741
2020-05-06 14:37:00 +00:00
DeferredUpdates::doUpdates();
}
) );
deferred: make DeferredUpdates::doUpdates() recursion more uniform Follow-up ba6490aa1eb. That patch worked around $executeContext iteration errors due to the lack of recursion support in doUpdates()/handleUpdateQueue(). Errors were triggered by MediaWiki::schedulePostSendJobs() enqueueing deferred updates that invoke JobRunner::run(), which, in turn, invoke doUpdates() for each job via JobRunner:: doExecuteJob(). The doUpdates() method was changed to operate on the sub-queue for the in-progress DeferrableUpdate. Further improve the recursion logic: * Use classes for the scope stack and scope queues. * Put *all* updates added during a DeferrableUpdate::doUpdate() call into the subqueue, regardless of "defer until" stage or whether it implements MergeableUpdate. Now, doUpdate() can run *everything* it enqueued instead of a non-obvious subset. Note that the TransactionRoundDefiningUpdate that invokes JobRunner was already a POSTSEND update before ba6490aa1eb; the only effect of this change is that MergeableUpdate instances from jobs will once again run in doExecuteJob(). * Make recursive DeferredUpdates::doUpdate() calls error out immediately unless the DeferrableUpdate responsible is a TransactionRoundAwareUpdate with the TRX_ROUND_ABSENT flag. This covers the schedulePostSendJobs() scenario and only prohibits insane call patterns. Failing early avoids the risk of handleUpdateQueue() dropping all the updates due to the same DBTransactionError error in DeferredUpdates::attemptUpdate(). * Avoid recursion loop in tryOpportunisticExecute() when JobQueueDB is in use and a large number of tasks are pending. This happened due to methods like onTransactionPreCommitOrIdle() being used within JobQueueDB. Mark DeferredUpdates::doUpdates()/tryOpportunisticExecute() as @internal and create a Maintenance::shutdown() method to avoid a direct call in the doMaintenance.php file. Bug: T249069 Bug: T268840 Change-Id: Ib369f0e74243a48ababdb9cd83b155c9a0f5e741
2020-05-06 14:37:00 +00:00
$this->assertSame( 1, DeferredUpdates::pendingUpdatesCount() );
$this->assertSame( 0, DeferredUpdates::getRecursiveExecutionStackDepth() );
DeferredUpdates::addCallableUpdate( static function () use ( &$resA ) {
$resA = 93;
} );
deferred: make DeferredUpdates::doUpdates() recursion more uniform Follow-up ba6490aa1eb. That patch worked around $executeContext iteration errors due to the lack of recursion support in doUpdates()/handleUpdateQueue(). Errors were triggered by MediaWiki::schedulePostSendJobs() enqueueing deferred updates that invoke JobRunner::run(), which, in turn, invoke doUpdates() for each job via JobRunner:: doExecuteJob(). The doUpdates() method was changed to operate on the sub-queue for the in-progress DeferrableUpdate. Further improve the recursion logic: * Use classes for the scope stack and scope queues. * Put *all* updates added during a DeferrableUpdate::doUpdate() call into the subqueue, regardless of "defer until" stage or whether it implements MergeableUpdate. Now, doUpdate() can run *everything* it enqueued instead of a non-obvious subset. Note that the TransactionRoundDefiningUpdate that invokes JobRunner was already a POSTSEND update before ba6490aa1eb; the only effect of this change is that MergeableUpdate instances from jobs will once again run in doExecuteJob(). * Make recursive DeferredUpdates::doUpdate() calls error out immediately unless the DeferrableUpdate responsible is a TransactionRoundAwareUpdate with the TRX_ROUND_ABSENT flag. This covers the schedulePostSendJobs() scenario and only prohibits insane call patterns. Failing early avoids the risk of handleUpdateQueue() dropping all the updates due to the same DBTransactionError error in DeferredUpdates::attemptUpdate(). * Avoid recursion loop in tryOpportunisticExecute() when JobQueueDB is in use and a large number of tasks are pending. This happened due to methods like onTransactionPreCommitOrIdle() being used within JobQueueDB. Mark DeferredUpdates::doUpdates()/tryOpportunisticExecute() as @internal and create a Maintenance::shutdown() method to avoid a direct call in the doMaintenance.php file. Bug: T249069 Bug: T268840 Change-Id: Ib369f0e74243a48ababdb9cd83b155c9a0f5e741
2020-05-06 14:37:00 +00:00
$this->assertSame( 2, DeferredUpdates::pendingUpdatesCount() );
$this->assertSame( 0, DeferredUpdates::getRecursiveExecutionStackDepth() );
$this->assertNull( $resA );
$this->assertNull( $res );
$this->assertNull( $resSub );
$this->assertNull( $resSubSub );
DeferredUpdates::doUpdates();
deferred: make DeferredUpdates::doUpdates() recursion more uniform Follow-up ba6490aa1eb. That patch worked around $executeContext iteration errors due to the lack of recursion support in doUpdates()/handleUpdateQueue(). Errors were triggered by MediaWiki::schedulePostSendJobs() enqueueing deferred updates that invoke JobRunner::run(), which, in turn, invoke doUpdates() for each job via JobRunner:: doExecuteJob(). The doUpdates() method was changed to operate on the sub-queue for the in-progress DeferrableUpdate. Further improve the recursion logic: * Use classes for the scope stack and scope queues. * Put *all* updates added during a DeferrableUpdate::doUpdate() call into the subqueue, regardless of "defer until" stage or whether it implements MergeableUpdate. Now, doUpdate() can run *everything* it enqueued instead of a non-obvious subset. Note that the TransactionRoundDefiningUpdate that invokes JobRunner was already a POSTSEND update before ba6490aa1eb; the only effect of this change is that MergeableUpdate instances from jobs will once again run in doExecuteJob(). * Make recursive DeferredUpdates::doUpdate() calls error out immediately unless the DeferrableUpdate responsible is a TransactionRoundAwareUpdate with the TRX_ROUND_ABSENT flag. This covers the schedulePostSendJobs() scenario and only prohibits insane call patterns. Failing early avoids the risk of handleUpdateQueue() dropping all the updates due to the same DBTransactionError error in DeferredUpdates::attemptUpdate(). * Avoid recursion loop in tryOpportunisticExecute() when JobQueueDB is in use and a large number of tasks are pending. This happened due to methods like onTransactionPreCommitOrIdle() being used within JobQueueDB. Mark DeferredUpdates::doUpdates()/tryOpportunisticExecute() as @internal and create a Maintenance::shutdown() method to avoid a direct call in the doMaintenance.php file. Bug: T249069 Bug: T268840 Change-Id: Ib369f0e74243a48ababdb9cd83b155c9a0f5e741
2020-05-06 14:37:00 +00:00
$this->assertSame( 0, DeferredUpdates::pendingUpdatesCount() );
$this->assertSame( 0, DeferredUpdates::getRecursiveExecutionStackDepth() );
$this->assertSame( 418, $res );
$this->assertSame( 'a', $resSub );
$this->assertSame( 'b', $resSubSub );
$this->assertSame( 93, $resA );
}
}