MediaWiki master
DeferredUpdatesScope.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Deferred;
8
20 private $parentScope;
22 private $activeUpdate;
24 private $activeStage;
26 private $queueByStage;
27
33 private function __construct(
34 $activeStage,
35 ?DeferrableUpdate $update,
36 ?DeferredUpdatesScope $parentScope
37 ) {
38 $this->activeStage = $activeStage;
39 $this->activeUpdate = $update;
40 $this->parentScope = $parentScope;
41 $this->queueByStage = array_fill_keys( DeferredUpdates::STAGES, [] );
42 }
43
47 public static function newRootScope() {
48 return new self( null, null, null );
49 }
50
57 public static function newChildScope(
58 $activeStage,
59 DeferrableUpdate $update,
60 DeferredUpdatesScope $parentScope
61 ) {
62 return new self( $activeStage, $update, $parentScope );
63 }
64
70 public function getActiveUpdate() {
71 return $this->activeUpdate;
72 }
73
80 public function addUpdate( DeferrableUpdate $update, $stage ) {
81 // Handle the case where the specified stage must have already passed
82 $stageEffective = max( $stage, $this->activeStage );
83
84 $queue =& $this->queueByStage[$stageEffective];
85
86 if ( $update instanceof MergeableUpdate ) {
87 $class = get_class( $update ); // fully-qualified class
88 if ( isset( $queue[$class] ) ) {
90 $existingUpdate = $queue[$class];
91 '@phan-var MergeableUpdate $existingUpdate';
92 $existingUpdate->merge( $update );
93 // Move the update to the end to handle things like mergeable purge
94 // updates that might depend on the prior updates in the queue running
95 unset( $queue[$class] );
96 $queue[$class] = $existingUpdate;
97 } else {
98 $queue[$class] = $update;
99 }
100 } else {
101 $queue[] = $update;
102 }
103 }
104
110 public function pendingUpdatesCount() {
111 return array_sum( array_map( 'count', $this->queueByStage ) );
112 }
113
120 public function getPendingUpdates( $stage ) {
121 $matchingQueues = [];
122 foreach ( $this->queueByStage as $queueStage => $queue ) {
123 if ( $stage === DeferredUpdates::ALL || $stage === $queueStage ) {
124 $matchingQueues[] = $queue;
125 }
126 }
127
128 return array_merge( ...$matchingQueues );
129 }
130
134 public function clearPendingUpdates() {
135 $this->queueByStage = array_fill_keys( array_keys( $this->queueByStage ), [] );
136 }
137
146 public function processUpdates( $stage, callable $callback ) {
147 if ( $stage === DeferredUpdates::ALL ) {
148 // Do everything, all the way to the last "defer until" stage
149 $activeStage = DeferredUpdates::STAGES[count( DeferredUpdates::STAGES ) - 1];
150 } else {
151 // Handle the case where the specified stage must have already passed
152 $activeStage = max( $stage, $this->activeStage );
153 }
154
155 do {
156 $processed = $this->upmergeUnreadyUpdates( $activeStage );
157 foreach ( range( DeferredUpdates::STAGES[0], $activeStage ) as $queueStage ) {
158 $processed += $this->processStageQueue( $queueStage, $activeStage, $callback );
159 }
160 } while ( $processed > 0 );
161 }
162
173 private function upmergeUnreadyUpdates( $activeStage ) {
174 $reassigned = 0;
175
176 if ( !$this->parentScope ) {
177 return $reassigned;
178 }
179
180 foreach ( $this->queueByStage as $queueStage => $queue ) {
181 foreach ( $queue as $k => $update ) {
182 if ( $update instanceof MergeableUpdate || $queueStage > $activeStage ) {
183 unset( $this->queueByStage[$queueStage][$k] );
184 $this->parentScope->addUpdate( $update, $queueStage );
185 ++$reassigned;
186 }
187 }
188 }
189
190 return $reassigned;
191 }
192
199 private function processStageQueue( $stage, $activeStage, callable $callback ) {
200 $processed = 0;
201
202 // Defensively claim the pending updates in case of recursion
203 $claimedUpdates = $this->queueByStage[$stage];
204 $this->queueByStage[$stage] = [];
205
206 // Keep doing rounds of updates until none get enqueued...
207 while ( $claimedUpdates ) {
208 // Segregate the updates into one for DataUpdate and one for everything else.
209 // This is done for historical reasons; DataUpdate used to have its own static
210 // method for running DataUpdate instances and was called first in DeferredUpdates.
211 // Before that, page updater code directly ran that static method.
212 // @TODO: remove this logic given the existence of RefreshSecondaryDataUpdate
213 $claimedDataUpdates = [];
214 $claimedGenericUpdates = [];
215 foreach ( $claimedUpdates as $claimedUpdate ) {
216 if ( $claimedUpdate instanceof DataUpdate ) {
217 $claimedDataUpdates[] = $claimedUpdate;
218 } else {
219 $claimedGenericUpdates[] = $claimedUpdate;
220 }
221 ++$processed;
222 }
223
224 // Execute the DataUpdate queue followed by the DeferrableUpdate queue...
225 foreach ( $claimedDataUpdates as $claimedDataUpdate ) {
226 $callback( $claimedDataUpdate, $activeStage );
227 }
228 foreach ( $claimedGenericUpdates as $claimedGenericUpdate ) {
229 $callback( $claimedGenericUpdate, $activeStage );
230 }
231
232 // Check for new entries; defensively claim the pending updates in case of recursion
233 $claimedUpdates = $this->queueByStage[$stage];
234 $this->queueByStage[$stage] = [];
235 }
236
237 return $processed;
238 }
239}
240
242class_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.