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
160 public function processUpdates( $stage, callable $callback ) {
161 if ( $stage === DeferredUpdates::ALL ) {
162 // Do everything, all the way to the last "defer until" stage
163 $activeStage = DeferredUpdates::STAGES[count( DeferredUpdates::STAGES ) - 1];
164 } else {
165 // Handle the case where the specified stage must have already passed
166 $activeStage = max( $stage, $this->activeStage );
167 }
168
169 do {
170 $processed = $this->upmergeUnreadyUpdates( $activeStage );
171 foreach ( range( DeferredUpdates::STAGES[0], $activeStage ) as $queueStage ) {
172 $processed += $this->processStageQueue( $queueStage, $activeStage, $callback );
173 }
174 } while ( $processed > 0 );
175 }
176
187 private function upmergeUnreadyUpdates( $activeStage ) {
188 $reassigned = 0;
189
190 if ( !$this->parentScope ) {
191 return $reassigned;
192 }
193
194 foreach ( $this->queueByStage as $queueStage => $queue ) {
195 foreach ( $queue as $k => $update ) {
196 if ( $update instanceof MergeableUpdate || $queueStage > $activeStage ) {
197 unset( $this->queueByStage[$queueStage][$k] );
198 $this->parentScope->addUpdate( $update, $queueStage );
199 ++$reassigned;
200 }
201 }
202 }
203
204 return $reassigned;
205 }
206
213 private function processStageQueue( $stage, $activeStage, callable $callback ) {
214 $processed = 0;
215
216 // Defensively claim the pending updates in case of recursion
217 $claimedUpdates = $this->queueByStage[$stage];
218 $this->queueByStage[$stage] = [];
219
220 // Keep doing rounds of updates until none get enqueued...
221 while ( $claimedUpdates ) {
222 // Segregate the updates into one for DataUpdate and one for everything else.
223 // This is done for historical reasons; DataUpdate used to have its own static
224 // method for running DataUpdate instances and was called first in DeferredUpdates.
225 // Before that, page updater code directly ran that static method.
226 // @TODO: remove this logic given the existence of RefreshSecondaryDataUpdate
227 $claimedDataUpdates = [];
228 $claimedGenericUpdates = [];
229 foreach ( $claimedUpdates as $claimedUpdate ) {
230 if ( $claimedUpdate instanceof DataUpdate ) {
231 $claimedDataUpdates[] = $claimedUpdate;
232 } else {
233 $claimedGenericUpdates[] = $claimedUpdate;
234 }
235 ++$processed;
236 }
237
238 // Execute the DataUpdate queue followed by the DeferrableUpdate queue...
239 foreach ( $claimedDataUpdates as $claimedDataUpdate ) {
240 $callback( $claimedDataUpdate, $activeStage );
241 }
242 foreach ( $claimedGenericUpdates as $claimedGenericUpdate ) {
243 $callback( $claimedGenericUpdate, $activeStage );
244 }
245
246 // Check for new entries; defensively claim the pending updates in case of recursion
247 $claimedUpdates = $this->queueByStage[$stage];
248 $this->queueByStage[$stage] = [];
249 }
250
251 return $processed;
252 }
253}
254
256class_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...
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.