MediaWiki master
DeferredUpdatesScope.php
Go to the documentation of this file.
1<?php
21namespace MediaWiki\Deferred;
22
34 private $parentScope;
36 private $activeUpdate;
38 private $activeStage;
40 private $queueByStage;
41
47 private function __construct(
48 $activeStage,
49 ?DeferrableUpdate $update,
50 ?DeferredUpdatesScope $parentScope
51 ) {
52 $this->activeStage = $activeStage;
53 $this->activeUpdate = $update;
54 $this->parentScope = $parentScope;
55 $this->queueByStage = array_fill_keys( DeferredUpdates::STAGES, [] );
56 }
57
61 public static function newRootScope() {
62 return new self( null, null, null );
63 }
64
71 public static function newChildScope(
72 $activeStage,
73 DeferrableUpdate $update,
74 DeferredUpdatesScope $parentScope
75 ) {
76 return new self( $activeStage, $update, $parentScope );
77 }
78
84 public function getActiveUpdate() {
85 return $this->activeUpdate;
86 }
87
94 public function addUpdate( DeferrableUpdate $update, $stage ) {
95 // Handle the case where the specified stage must have already passed
96 $stageEffective = max( $stage, $this->activeStage );
97
98 $queue =& $this->queueByStage[$stageEffective];
99
100 if ( $update instanceof MergeableUpdate ) {
101 $class = get_class( $update ); // fully-qualified class
102 if ( isset( $queue[$class] ) ) {
104 $existingUpdate = $queue[$class];
105 '@phan-var MergeableUpdate $existingUpdate';
106 $existingUpdate->merge( $update );
107 // Move the update to the end to handle things like mergeable purge
108 // updates that might depend on the prior updates in the queue running
109 unset( $queue[$class] );
110 $queue[$class] = $existingUpdate;
111 } else {
112 $queue[$class] = $update;
113 }
114 } else {
115 $queue[] = $update;
116 }
117 }
118
124 public function pendingUpdatesCount() {
125 return array_sum( array_map( 'count', $this->queueByStage ) );
126 }
127
134 public function getPendingUpdates( $stage ) {
135 $matchingQueues = [];
136 foreach ( $this->queueByStage as $queueStage => $queue ) {
137 if ( $stage === DeferredUpdates::ALL || $stage === $queueStage ) {
138 $matchingQueues[] = $queue;
139 }
140 }
141
142 return array_merge( ...$matchingQueues );
143 }
144
148 public function clearPendingUpdates() {
149 $this->queueByStage = array_fill_keys( array_keys( $this->queueByStage ), [] );
150 }
151
159 public function consumeMatchingUpdates( $stage, $class, callable $callback ) {
160 // T268840: defensively claim the pending updates in case of recursion
161 $claimedUpdates = [];
162 foreach ( $this->queueByStage as $queueStage => $queue ) {
163 if ( $stage === DeferredUpdates::ALL || $stage === $queueStage ) {
164 foreach ( $queue as $k => $update ) {
165 if ( $update instanceof $class ) {
166 $claimedUpdates[] = $update;
167 unset( $this->queueByStage[$queueStage][$k] );
168 }
169 }
170 }
171 }
172 // Execute the callback for each update
173 foreach ( $claimedUpdates as $update ) {
174 $callback( $update );
175 }
176 }
177
186 public function processUpdates( $stage, callable $callback ) {
187 if ( $stage === DeferredUpdates::ALL ) {
188 // Do everything, all the way to the last "defer until" stage
189 $activeStage = DeferredUpdates::STAGES[count( DeferredUpdates::STAGES ) - 1];
190 } else {
191 // Handle the case where the specified stage must have already passed
192 $activeStage = max( $stage, $this->activeStage );
193 }
194
195 do {
196 $processed = $this->upmergeUnreadyUpdates( $activeStage );
197 foreach ( range( DeferredUpdates::STAGES[0], $activeStage ) as $queueStage ) {
198 $processed += $this->processStageQueue( $queueStage, $activeStage, $callback );
199 }
200 } while ( $processed > 0 );
201 }
202
213 private function upmergeUnreadyUpdates( $activeStage ) {
214 $reassigned = 0;
215
216 if ( !$this->parentScope ) {
217 return $reassigned;
218 }
219
220 foreach ( $this->queueByStage as $queueStage => $queue ) {
221 foreach ( $queue as $k => $update ) {
222 if ( $update instanceof MergeableUpdate || $queueStage > $activeStage ) {
223 unset( $this->queueByStage[$queueStage][$k] );
224 $this->parentScope->addUpdate( $update, $queueStage );
225 ++$reassigned;
226 }
227 }
228 }
229
230 return $reassigned;
231 }
232
239 private function processStageQueue( $stage, $activeStage, callable $callback ) {
240 $processed = 0;
241
242 // Defensively claim the pending updates in case of recursion
243 $claimedUpdates = $this->queueByStage[$stage];
244 $this->queueByStage[$stage] = [];
245
246 // Keep doing rounds of updates until none get enqueued...
247 while ( $claimedUpdates ) {
248 // Segregate the updates into one for DataUpdate and one for everything else.
249 // This is done for historical reasons; DataUpdate used to have its own static
250 // method for running DataUpdate instances and was called first in DeferredUpdates.
251 // Before that, page updater code directly ran that static method.
252 // @TODO: remove this logic given the existence of RefreshSecondaryDataUpdate
253 $claimedDataUpdates = [];
254 $claimedGenericUpdates = [];
255 foreach ( $claimedUpdates as $claimedUpdate ) {
256 if ( $claimedUpdate instanceof DataUpdate ) {
257 $claimedDataUpdates[] = $claimedUpdate;
258 } else {
259 $claimedGenericUpdates[] = $claimedUpdate;
260 }
261 ++$processed;
262 }
263
264 // Execute the DataUpdate queue followed by the DeferrableUpdate queue...
265 foreach ( $claimedDataUpdates as $claimedDataUpdate ) {
266 $callback( $claimedDataUpdate, $activeStage );
267 }
268 foreach ( $claimedGenericUpdates as $claimedGenericUpdate ) {
269 $callback( $claimedGenericUpdate, $activeStage );
270 }
271
272 // Check for new entries; defensively claim the pending updates in case of recursion
273 $claimedUpdates = $this->queueByStage[$stage];
274 $this->queueByStage[$stage] = [];
275 }
276
277 return $processed;
278 }
279}
280
282class_alias( DeferredUpdatesScope::class, 'DeferredUpdatesScope' );
DeferredUpdates helper class for managing DeferrableUpdate::doUpdate() nesting levels caused by neste...
processUpdates( $stage, callable $callback)
Iteratively, reassign unready pending updates to the parent scope (if applicable) and process the rea...
consumeMatchingUpdates( $stage, $class, callable $callback)
Remove pending updates of the specified stage/class and pass them to a callback.
static newChildScope( $activeStage, DeferrableUpdate $update, DeferredUpdatesScope $parentScope)
pendingUpdatesCount()
Get the number of pending updates within this scope.
getActiveUpdate()
Get the deferred update that owns this scope (root scope has none)
getPendingUpdates( $stage)
Get pending updates within this scope with the given "defer until" stage.
clearPendingUpdates()
Cancel all pending updates within this scope.
addUpdate(DeferrableUpdate $update, $stage)
Enqueue a deferred update within this scope using the specified "defer until" time.
Interface that deferrable updates should implement.
Interface that deferrable updates can implement to signal that updates can be combined.