aboutsummaryrefslogtreecommitdiffstats
path: root/includes/deferred/DeferredUpdatesScopeMediaWikiStack.php
blob: 3f1240a1fd5a10f19a37ece8158dcdd323a803ec (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
<?php
/**
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * http://www.gnu.org/copyleft/gpl.html
 *
 * @file
 */

namespace MediaWiki\Deferred;

use MediaWiki\MediaWikiServices;
use Wikimedia\Rdbms\DBTransactionError;

/**
 * This class decouples DeferredUpdates's awareness of MediaWikiServices to ease unit testing.
 *
 * NOTE: As a process-level utility, it is important that MediaWikiServices::getInstance() is
 * referenced explicitly each time, so as to not cache potentially stale references.
 * For example after the Installer, or MediaWikiIntegrationTestCase, replace the service container.
 *
 * @internal For use by DeferredUpdates only
 * @since 1.41
 */
class DeferredUpdatesScopeMediaWikiStack extends DeferredUpdatesScopeStack {

	private function areDatabaseTransactionsActive(): bool {
		$lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
		if ( $lbFactory->hasTransactionRound()
			|| !$lbFactory->isReadyForRoundOperations()
		) {
			return true;
		}

		foreach ( $lbFactory->getAllLBs() as $lb ) {
			if ( $lb->hasPrimaryChanges() || $lb->explicitTrxActive() ) {
				return true;
			}
		}

		return false;
	}

	public function allowOpportunisticUpdates(): bool {
		if ( MW_ENTRY_POINT !== 'cli' ) {
			// In web req
			return false;
		}

		// Run the updates only if they will have outer transaction scope
		if ( $this->areDatabaseTransactionsActive() ) {
			// transaction round is active or connection is not ready for commit()
			return false;
		}

		return true;
	}

	public function queueDataUpdate( EnqueueableDataUpdate $update ): void {
		$spec = $update->getAsJobSpecification();
		$jobQueueGroupFactory = MediaWikiServices::getInstance()->getJobQueueGroupFactory();
		$jobQueueGroupFactory->makeJobQueueGroup( $spec['domain'] )->push( $spec['job'] );
	}

	public function onRunUpdateStart( DeferrableUpdate $update ): void {
		// Increment a counter metric
		$type = get_class( $update )
			. ( $update instanceof DeferrableCallback ? '_' . $update->getOrigin() : '' );
		$httpMethod = MW_ENTRY_POINT === 'cli' ? 'cli' : strtolower( $_SERVER['REQUEST_METHOD'] ?? 'GET' );
		$stats = MediaWikiServices::getInstance()->getStatsFactory();
		$stats->getCounter( 'deferred_updates_total' )
			->setLabel( 'http_method', $httpMethod )
			->setLabel( 'type', $type )
			->copyToStatsdAt( "deferred_updates.$httpMethod.$type" )
			->increment();

		$lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
		$ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
		if ( !$ticket || $lbFactory->hasTransactionRound() ) {
			throw new DBTransactionError( null, "A database transaction round is pending." );
		}

		if ( $update instanceof DataUpdate ) {
			$update->setTransactionTicket( $ticket );
		}

		// Designate $update::doUpdate() as the transaction round owner
		$fnameTrxOwner = ( $update instanceof DeferrableCallback )
			? $update->getOrigin()
			: get_class( $update ) . '::doUpdate';

		// Determine whether the transaction round will be explicit or implicit
		$useExplicitTrxRound = !(
			$update instanceof TransactionRoundAwareUpdate &&
			$update->getTransactionRoundRequirement() == $update::TRX_ROUND_ABSENT
		);
		if ( $useExplicitTrxRound ) {
			// Start a new explicit round
			$lbFactory->beginPrimaryChanges( $fnameTrxOwner );
		} else {
			// Start a new implicit round
			$lbFactory->commitPrimaryChanges( $fnameTrxOwner );
		}
	}

	public function onRunUpdateEnd( DeferrableUpdate $update ): void {
		$lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();

		$fnameTrxOwner = ( $update instanceof DeferrableCallback )
			? $update->getOrigin()
			: get_class( $update ) . '::doUpdate';

		// Commit any pending changes from the explicit or implicit transaction round
		$lbFactory->commitPrimaryChanges( $fnameTrxOwner );
	}

	public function onRunUpdateFailed( DeferrableUpdate $update ): void {
		$lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
		$lbFactory->rollbackPrimaryChanges( __METHOD__ );
	}
}

/** @deprecated class alias since 1.42 */
class_alias( DeferredUpdatesScopeMediaWikiStack::class, 'DeferredUpdatesScopeMediaWikiStack' );