Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 48
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
DeferredUpdatesScopeMediaWikiStack
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 6
506
0.00% covered (danger)
0.00%
0 / 1
 areDatabaseTransactionsActive
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
42
 allowOpportunisticUpdates
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 queueDataUpdate
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 onRunUpdateStart
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
90
 onRunUpdateEnd
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 onRunUpdateFailed
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7namespace MediaWiki\Deferred;
8
9use MediaWiki\MediaWikiServices;
10use Wikimedia\Rdbms\DBTransactionError;
11
12/**
13 * This class decouples DeferredUpdates's awareness of MediaWikiServices to ease unit testing.
14 *
15 * NOTE: As a process-level utility, it is important that MediaWikiServices::getInstance() is
16 * referenced explicitly each time, so as to not cache potentially stale references.
17 * For example after the Installer, or MediaWikiIntegrationTestCase, replace the service container.
18 *
19 * @internal For use by DeferredUpdates only
20 * @since 1.41
21 */
22class DeferredUpdatesScopeMediaWikiStack extends DeferredUpdatesScopeStack {
23
24    private function areDatabaseTransactionsActive(): bool {
25        $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
26        if ( $lbFactory->hasTransactionRound()
27            || !$lbFactory->isReadyForRoundOperations()
28        ) {
29            return true;
30        }
31
32        foreach ( $lbFactory->getAllLBs() as $lb ) {
33            if ( $lb->hasPrimaryChanges() || $lb->explicitTrxActive() ) {
34                return true;
35            }
36        }
37
38        return false;
39    }
40
41    public function allowOpportunisticUpdates(): bool {
42        if ( MW_ENTRY_POINT !== 'cli' ) {
43            // In web req
44            return false;
45        }
46
47        // Run the updates only if they will have outer transaction scope
48        if ( $this->areDatabaseTransactionsActive() ) {
49            // transaction round is active or connection is not ready for commit()
50            return false;
51        }
52
53        return true;
54    }
55
56    public function queueDataUpdate( EnqueueableDataUpdate $update ): void {
57        $spec = $update->getAsJobSpecification();
58        $jobQueueGroupFactory = MediaWikiServices::getInstance()->getJobQueueGroupFactory();
59        $jobQueueGroupFactory->makeJobQueueGroup( $spec['domain'] )->push( $spec['job'] );
60    }
61
62    public function onRunUpdateStart( DeferrableUpdate $update ): void {
63        // Increment a counter metric
64        $type = get_class( $update )
65            . ( $update instanceof DeferrableCallback ? '_' . $update->getOrigin() : '' );
66        $httpMethod = MW_ENTRY_POINT === 'cli' ? 'cli' : strtolower( $_SERVER['REQUEST_METHOD'] ?? 'GET' );
67        $stats = MediaWikiServices::getInstance()->getStatsFactory();
68        $stats->getCounter( 'deferred_updates_total' )
69            ->setLabel( 'http_method', $httpMethod )
70            ->setLabel( 'type', $type )
71            ->increment();
72
73        $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
74        $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
75        if ( !$ticket || $lbFactory->hasTransactionRound() ) {
76            throw new DBTransactionError( null, "A database transaction round is pending." );
77        }
78
79        if ( $update instanceof DataUpdate ) {
80            $update->setTransactionTicket( $ticket );
81        }
82
83        // Designate $update::doUpdate() as the transaction round owner
84        $fnameTrxOwner = ( $update instanceof DeferrableCallback )
85            ? $update->getOrigin()
86            : get_class( $update ) . '::doUpdate';
87
88        // Determine whether the transaction round will be explicit or implicit
89        $useExplicitTrxRound = !(
90            $update instanceof TransactionRoundAwareUpdate &&
91            $update->getTransactionRoundRequirement() == $update::TRX_ROUND_ABSENT
92        );
93        if ( $useExplicitTrxRound ) {
94            // Start a new explicit round
95            $lbFactory->beginPrimaryChanges( $fnameTrxOwner );
96        } else {
97            // Start a new implicit round
98            $lbFactory->commitPrimaryChanges( $fnameTrxOwner );
99        }
100    }
101
102    public function onRunUpdateEnd( DeferrableUpdate $update ): void {
103        $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
104
105        $fnameTrxOwner = ( $update instanceof DeferrableCallback )
106            ? $update->getOrigin()
107            : get_class( $update ) . '::doUpdate';
108
109        // Commit any pending changes from the explicit or implicit transaction round
110        $lbFactory->commitPrimaryChanges( $fnameTrxOwner );
111    }
112
113    public function onRunUpdateFailed( DeferrableUpdate $update ): void {
114        $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
115        $lbFactory->rollbackPrimaryChanges( __METHOD__ );
116    }
117}
118
119/** @deprecated class alias since 1.42 */
120class_alias( DeferredUpdatesScopeMediaWikiStack::class, 'DeferredUpdatesScopeMediaWikiStack' );