3 use MediaWiki\Deferred\DeferrableUpdate
;
4 use MediaWiki\Deferred\DeferredUpdates
;
5 use MediaWiki\Deferred\MergeableUpdate
;
6 use MediaWiki\Deferred\MWCallableUpdate
;
7 use MediaWiki\Deferred\TransactionRoundDefiningUpdate
;
11 * @covers \MediaWiki\Deferred\DeferredUpdates
12 * @covers \MediaWiki\Deferred\DeferredUpdatesScopeStack
13 * @covers \MediaWiki\Deferred\DeferredUpdatesScope
15 class DeferredUpdatesTest
extends MediaWikiIntegrationTestCase
{
17 public function testAddAndRun() {
18 $update = $this->getMockBuilder( DeferrableUpdate
::class )
19 ->onlyMethods( [ 'doUpdate' ] )->getMock();
20 $update->expects( $this->once() )->method( 'doUpdate' );
22 DeferredUpdates
::addUpdate( $update );
23 DeferredUpdates
::doUpdates();
26 public function testAddMergeable() {
27 $cleanup = DeferredUpdates
::preventOpportunisticUpdates();
29 $update1 = $this->getMockBuilder( MergeableUpdate
::class )
30 ->onlyMethods( [ 'merge', 'doUpdate' ] )->getMock();
31 $update1->expects( $this->once() )->method( 'merge' );
32 $update1->expects( $this->never() )->method( 'doUpdate' );
34 $update2 = $this->getMockBuilder( MergeableUpdate
::class )
35 ->onlyMethods( [ 'merge', 'doUpdate' ] )->getMock();
36 $update2->expects( $this->never() )->method( 'merge' );
37 $update2->expects( $this->never() )->method( 'doUpdate' );
39 DeferredUpdates
::addUpdate( $update1 );
40 DeferredUpdates
::addUpdate( $update2 );
43 public function testAddCallableUpdate() {
45 DeferredUpdates
::addCallableUpdate( static function () use ( &$ran ) {
48 // Opportunistic updates should be enabled, so the updates should have already executed
49 $this->assertSame( 0, DeferredUpdates
::pendingUpdatesCount() );
51 $this->assertSame( 1, $ran, 'Update ran' );
54 public function testGetPendingUpdates() {
55 $cleanup = DeferredUpdates
::preventOpportunisticUpdates();
57 $pre = DeferredUpdates
::PRESEND
;
58 $post = DeferredUpdates
::POSTSEND
;
59 $all = DeferredUpdates
::ALL
;
61 $update = $this->createMock( DeferrableUpdate
::class );
62 $update->expects( $this->never() )
63 ->method( 'doUpdate' );
65 DeferredUpdates
::addUpdate( $update, $pre );
66 $this->assertCount( 1, DeferredUpdates
::getPendingUpdates( $pre ) );
67 $this->assertSame( [], DeferredUpdates
::getPendingUpdates( $post ) );
68 $this->assertCount( 1, DeferredUpdates
::getPendingUpdates( $all ) );
69 $this->assertCount( 1, DeferredUpdates
::getPendingUpdates() );
70 DeferredUpdates
::clearPendingUpdates();
71 $this->assertSame( [], DeferredUpdates
::getPendingUpdates() );
73 DeferredUpdates
::addUpdate( $update, $post );
74 $this->assertSame( [], DeferredUpdates
::getPendingUpdates( $pre ) );
75 $this->assertCount( 1, DeferredUpdates
::getPendingUpdates( $post ) );
76 $this->assertCount( 1, DeferredUpdates
::getPendingUpdates( $all ) );
77 $this->assertCount( 1, DeferredUpdates
::getPendingUpdates() );
78 DeferredUpdates
::clearPendingUpdates();
79 $this->assertSame( [], DeferredUpdates
::getPendingUpdates() );
82 public function testDoUpdatesWeb() {
83 $cleanup = DeferredUpdates
::preventOpportunisticUpdates();
86 '1' => "deferred update 1;\n",
87 '2' => "deferred update 2;\n",
88 '2-1' => "deferred update 1 within deferred update 2;\n",
89 '2-2' => "deferred update 2 within deferred update 2;\n",
90 '3' => "deferred update 3;\n",
91 '3-1' => "deferred update 1 within deferred update 3;\n",
92 '3-2' => "deferred update 2 within deferred update 3;\n",
93 '3-1-1' => "deferred update 1 within deferred update 1 within deferred update 3;\n",
94 '3-2-1' => "deferred update 1 within deferred update 2 with deferred update 3;\n",
96 DeferredUpdates
::addCallableUpdate(
97 static function () use ( $updates ) {
101 DeferredUpdates
::addCallableUpdate(
102 static function () use ( $updates ) {
104 DeferredUpdates
::addCallableUpdate(
105 static function () use ( $updates ) {
106 echo $updates['2-1'];
109 DeferredUpdates
::addCallableUpdate(
110 static function () use ( $updates ) {
111 echo $updates['2-2'];
116 DeferredUpdates
::addCallableUpdate(
117 static function () use ( $updates ) {
119 DeferredUpdates
::addCallableUpdate(
120 static function () use ( $updates ) {
121 echo $updates['3-1'];
122 DeferredUpdates
::addCallableUpdate(
123 static function () use ( $updates ) {
124 echo $updates['3-1-1'];
129 DeferredUpdates
::addCallableUpdate(
130 static function () use ( $updates ) {
131 echo $updates['3-2'];
132 DeferredUpdates
::addCallableUpdate(
133 static function () use ( $updates ) {
134 echo $updates['3-2-1'];
142 $this->assertEquals( 3, DeferredUpdates
::pendingUpdatesCount() );
144 $this->expectOutputString( implode( '', $updates ) );
146 DeferredUpdates
::doUpdates();
150 DeferredUpdates
::addCallableUpdate(
151 static function () use ( &$x ) {
154 DeferredUpdates
::PRESEND
156 DeferredUpdates
::addCallableUpdate(
157 static function () use ( &$y ) {
160 DeferredUpdates
::POSTSEND
163 $this->assertNull( $x, "Update not run yet" );
164 $this->assertNull( $y, "Update not run yet" );
166 DeferredUpdates
::doUpdates( DeferredUpdates
::PRESEND
);
167 $this->assertEquals( "Sherity", $x, "PRESEND update ran" );
168 $this->assertNull( $y, "POSTSEND update not run yet" );
170 DeferredUpdates
::doUpdates( DeferredUpdates
::POSTSEND
);
171 $this->assertEquals( "Marychu", $y, "POSTSEND update ran" );
174 public function testDoUpdatesCLI() {
176 '1' => "deferred update 1;\n",
177 '2' => "deferred update 2;\n",
178 '2-1' => "deferred update 1 within deferred update 2;\n",
179 '2-2' => "deferred update 2 within deferred update 2;\n",
180 '3' => "deferred update 3;\n",
181 '3-1' => "deferred update 1 within deferred update 3;\n",
182 '3-2' => "deferred update 2 within deferred update 3;\n",
183 '3-1-1' => "deferred update 1 within deferred update 1 within deferred update 3;\n",
184 '3-2-1' => "deferred update 1 within deferred update 2 with deferred update 3;\n",
188 $lbFactory = $this->getServiceContainer()->getDBLoadBalancerFactory();
189 $lbFactory->commitPrimaryChanges( __METHOD__
);
191 DeferredUpdates
::addCallableUpdate(
192 static function () use ( $updates ) {
196 DeferredUpdates
::addCallableUpdate(
197 static function () use ( $updates ) {
199 DeferredUpdates
::addCallableUpdate(
200 static function () use ( $updates ) {
201 echo $updates['2-1'];
204 DeferredUpdates
::addCallableUpdate(
205 static function () use ( $updates ) {
206 echo $updates['2-2'];
211 DeferredUpdates
::addCallableUpdate(
212 static function () use ( $updates ) {
214 DeferredUpdates
::addCallableUpdate(
215 static function () use ( $updates ) {
216 echo $updates['3-1'];
217 DeferredUpdates
::addCallableUpdate(
218 static function () use ( $updates ) {
219 echo $updates['3-1-1'];
224 DeferredUpdates
::addCallableUpdate(
225 static function () use ( $updates ) {
226 echo $updates['3-2'];
227 DeferredUpdates
::addCallableUpdate(
228 static function () use ( $updates ) {
229 echo $updates['3-2-1'];
237 $this->expectOutputString( implode( '', $updates ) );
239 // Opportunistic updates should be enabled, so the updates should have already executed
240 $this->assertSame( 0, DeferredUpdates
::pendingUpdatesCount() );
243 public function testPresendAddOnPostsendRun() {
247 $lbFactory = $this->getServiceContainer()->getDBLoadBalancerFactory();
248 $lbFactory->commitPrimaryChanges( __METHOD__
);
250 DeferredUpdates
::addCallableUpdate(
251 static function () use ( &$x, &$y ) {
253 DeferredUpdates
::addCallableUpdate(
254 static function () use ( &$y ) {
257 DeferredUpdates
::PRESEND
260 DeferredUpdates
::POSTSEND
263 // Opportunistic updates should be enabled, so the updates should have already executed
264 $this->assertSame( 0, DeferredUpdates
::pendingUpdatesCount() );
266 $this->assertTrue( $x, "Outer POSTSEND update ran" );
267 $this->assertTrue( $y, "Nested PRESEND update ran" );
270 public function testRunUpdateTransactionScope() {
271 $cleanup = DeferredUpdates
::preventOpportunisticUpdates();
273 $lbFactory = $this->getServiceContainer()->getDBLoadBalancerFactory();
274 $this->assertFalse( $lbFactory->hasTransactionRound(), 'Initial state' );
277 DeferredUpdates
::addCallableUpdate( function () use ( &$ran, $lbFactory ) {
279 $this->assertTrue( $lbFactory->hasTransactionRound(), 'Has transaction' );
281 DeferredUpdates
::doUpdates();
283 $this->assertSame( 1, $ran, 'Update ran' );
284 $this->assertFalse( $lbFactory->hasTransactionRound(), 'Final state' );
288 * @covers \MediaWiki\Deferred\TransactionRoundDefiningUpdate
290 public function testRunOuterScopeUpdate() {
291 $cleanup = DeferredUpdates
::preventOpportunisticUpdates();
293 $lbFactory = $this->getServiceContainer()->getDBLoadBalancerFactory();
294 $this->assertFalse( $lbFactory->hasTransactionRound(), 'Initial state' );
297 DeferredUpdates
::addUpdate( new TransactionRoundDefiningUpdate(
298 function () use ( &$ran, $lbFactory ) {
300 $this->assertFalse( $lbFactory->hasTransactionRound(), 'No transaction' );
303 DeferredUpdates
::doUpdates();
305 $this->assertSame( 1, $ran, 'Update ran' );
308 public function testTryOpportunisticExecute() {
310 $callback1 = static function () use ( &$calls ) {
313 $callback2 = static function () use ( &$calls ) {
317 $lbFactory = $this->getServiceContainer()->getDBLoadBalancerFactory();
318 $lbFactory->beginPrimaryChanges( __METHOD__
);
320 DeferredUpdates
::addCallableUpdate( $callback1 );
321 $this->assertEquals( [], $calls );
323 DeferredUpdates
::tryOpportunisticExecute();
324 $this->assertEquals( [], $calls );
326 $dbw = $this->getDb();
327 $dbw->onTransactionCommitOrIdle( function () use ( &$calls, $callback2 ) {
328 DeferredUpdates
::addCallableUpdate( $callback2 );
329 $this->assertEquals( [], $calls );
332 $this->assertSame( 1, $dbw->trxLevel() );
333 $this->assertEquals( [], $calls );
335 $lbFactory->commitPrimaryChanges( __METHOD__
);
337 $this->assertEquals( [ 'oti' ], $calls );
339 DeferredUpdates
::tryOpportunisticExecute();
340 $this->assertEquals( [ 'oti', 1, 2 ], $calls );
344 * @covers \MediaWiki\Deferred\MWCallableUpdate
346 public function testCallbackUpdateRounds() {
347 $lbFactory = $this->getServiceContainer()->getDBLoadBalancerFactory();
351 // This confirms that DeferredUpdates sets the transaction owner in LBFactory
352 // based on MWCallableUpdate::getOrigin, thus allowing the callback to control
353 // over the transaction and e.g. perform a commit.
354 DeferredUpdates
::attemptUpdate(
355 new MWCallableUpdate(
356 static function () use ( $lbFactory, $fname, &$called ) {
357 $lbFactory->commitPrimaryChanges( $fname );
364 $this->assertTrue( $called, "Callback ran" );
367 public function testNestedExecution() {
368 $cleanup = DeferredUpdates
::preventOpportunisticUpdates();
375 DeferredUpdates
::clearPendingUpdates();
377 $this->assertSame( 0, DeferredUpdates
::pendingUpdatesCount() );
378 $this->assertSame( 0, DeferredUpdates
::getRecursiveExecutionStackDepth() );
380 // T249069: TransactionRoundDefiningUpdate => JobRunner => DeferredUpdates::doUpdates()
381 DeferredUpdates
::addUpdate( new TransactionRoundDefiningUpdate(
382 function () use ( &$res, &$resSub, &$resSubSub, &$resA ) {
385 $this->assertSame( 0, DeferredUpdates
::pendingUpdatesCount() );
386 $this->assertSame( 1, DeferredUpdates
::getRecursiveExecutionStackDepth() );
388 // Add update to subqueue of in-progress top-queue job
389 DeferredUpdates
::addCallableUpdate( function () use ( &$resSub, &$resSubSub ) {
392 $this->assertSame( 0, DeferredUpdates
::pendingUpdatesCount() );
393 $this->assertSame( 2, DeferredUpdates
::getRecursiveExecutionStackDepth() );
395 // Add update to subqueue of in-progress top-queue job (not recursive)
396 DeferredUpdates
::addCallableUpdate( static function () use ( &$resSubSub ) {
400 $this->assertSame( 1, DeferredUpdates
::pendingUpdatesCount() );
403 $this->assertSame( 1, DeferredUpdates
::pendingUpdatesCount() );
404 $this->assertSame( 1, DeferredUpdates
::getRecursiveExecutionStackDepth() );
406 if ( $resSub === null && $resA === null && $resSubSub === null ) {
410 DeferredUpdates
::doUpdates();
414 $this->assertSame( 1, DeferredUpdates
::pendingUpdatesCount() );
415 $this->assertSame( 0, DeferredUpdates
::getRecursiveExecutionStackDepth() );
417 DeferredUpdates
::addCallableUpdate( static function () use ( &$resA ) {
421 $this->assertSame( 2, DeferredUpdates
::pendingUpdatesCount() );
422 $this->assertSame( 0, DeferredUpdates
::getRecursiveExecutionStackDepth() );
424 $this->assertNull( $resA );
425 $this->assertNull( $res );
426 $this->assertNull( $resSub );
427 $this->assertNull( $resSubSub );
429 DeferredUpdates
::doUpdates();
431 $this->assertSame( 0, DeferredUpdates
::pendingUpdatesCount() );
432 $this->assertSame( 0, DeferredUpdates
::getRecursiveExecutionStackDepth() );
433 $this->assertSame( 418, $res );
434 $this->assertSame( 'a', $resSub );
435 $this->assertSame( 'b', $resSubSub );
436 $this->assertSame( 93, $resA );