There are enough points where tryOpportunisticExecute() is triggered that this no longer needed. Change-Id: Ie27d4ec299df0e6f2754baedc237a473821f0709
256 lines
8.7 KiB
PHP
256 lines
8.7 KiB
PHP
<?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;
|
|
|
|
/**
|
|
* DeferredUpdates helper class for managing DeferrableUpdate::doUpdate() nesting levels
|
|
* caused by nested calls to DeferredUpdates::doUpdates()
|
|
*
|
|
* @see DeferredUpdates
|
|
* @see DeferredUpdatesScopeStack
|
|
* @internal For use by DeferredUpdates and DeferredUpdatesScopeStack only
|
|
* @since 1.36
|
|
*/
|
|
class DeferredUpdatesScope {
|
|
/** @var DeferredUpdatesScope|null Parent scope (root scope as none) */
|
|
private $parentScope;
|
|
/** @var DeferrableUpdate|null Deferred update that owns this scope (root scope has none) */
|
|
private $activeUpdate;
|
|
/** @var int|null Active processing stage in DeferredUpdates::STAGES (if any) */
|
|
private $activeStage;
|
|
/** @var DeferrableUpdate[][] Stage-ordered (stage => merge class or position => update) map */
|
|
private $queueByStage;
|
|
|
|
/**
|
|
* @param int|null $activeStage One of DeferredUpdates::STAGES or null
|
|
* @param DeferrableUpdate|null $update
|
|
* @param DeferredUpdatesScope|null $parentScope
|
|
*/
|
|
private function __construct(
|
|
$activeStage,
|
|
?DeferrableUpdate $update,
|
|
?DeferredUpdatesScope $parentScope
|
|
) {
|
|
$this->activeStage = $activeStage;
|
|
$this->activeUpdate = $update;
|
|
$this->parentScope = $parentScope;
|
|
$this->queueByStage = array_fill_keys( DeferredUpdates::STAGES, [] );
|
|
}
|
|
|
|
/**
|
|
* @return DeferredUpdatesScope Scope for the case of no in-progress deferred update
|
|
*/
|
|
public static function newRootScope() {
|
|
return new self( null, null, null );
|
|
}
|
|
|
|
/**
|
|
* @param int $activeStage The in-progress stage; one of DeferredUpdates::STAGES
|
|
* @param DeferrableUpdate $update The deferred update that owns this scope
|
|
* @param DeferredUpdatesScope $parentScope The parent scope of this scope
|
|
* @return DeferredUpdatesScope Scope for the case of an in-progress deferred update
|
|
*/
|
|
public static function newChildScope(
|
|
$activeStage,
|
|
DeferrableUpdate $update,
|
|
DeferredUpdatesScope $parentScope
|
|
) {
|
|
return new self( $activeStage, $update, $parentScope );
|
|
}
|
|
|
|
/**
|
|
* Get the deferred update that owns this scope (root scope has none)
|
|
*
|
|
* @return DeferrableUpdate|null
|
|
*/
|
|
public function getActiveUpdate() {
|
|
return $this->activeUpdate;
|
|
}
|
|
|
|
/**
|
|
* Enqueue a deferred update within this scope using the specified "defer until" time
|
|
*
|
|
* @param DeferrableUpdate $update
|
|
* @param int $stage One of DeferredUpdates::STAGES
|
|
*/
|
|
public function addUpdate( DeferrableUpdate $update, $stage ) {
|
|
// Handle the case where the specified stage must have already passed
|
|
$stageEffective = max( $stage, $this->activeStage );
|
|
|
|
$queue =& $this->queueByStage[$stageEffective];
|
|
|
|
if ( $update instanceof MergeableUpdate ) {
|
|
$class = get_class( $update ); // fully-qualified class
|
|
if ( isset( $queue[$class] ) ) {
|
|
/** @var MergeableUpdate $existingUpdate */
|
|
$existingUpdate = $queue[$class];
|
|
'@phan-var MergeableUpdate $existingUpdate';
|
|
$existingUpdate->merge( $update );
|
|
// Move the update to the end to handle things like mergeable purge
|
|
// updates that might depend on the prior updates in the queue running
|
|
unset( $queue[$class] );
|
|
$queue[$class] = $existingUpdate;
|
|
} else {
|
|
$queue[$class] = $update;
|
|
}
|
|
} else {
|
|
$queue[] = $update;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the number of pending updates within this scope
|
|
*
|
|
* @return int
|
|
*/
|
|
public function pendingUpdatesCount() {
|
|
return array_sum( array_map( 'count', $this->queueByStage ) );
|
|
}
|
|
|
|
/**
|
|
* Get pending updates within this scope with the given "defer until" stage
|
|
*
|
|
* @param int $stage One of DeferredUpdates::STAGES or DeferredUpdates::ALL
|
|
* @return DeferrableUpdate[]
|
|
*/
|
|
public function getPendingUpdates( $stage ) {
|
|
$matchingQueues = [];
|
|
foreach ( $this->queueByStage as $queueStage => $queue ) {
|
|
if ( $stage === DeferredUpdates::ALL || $stage === $queueStage ) {
|
|
$matchingQueues[] = $queue;
|
|
}
|
|
}
|
|
|
|
return array_merge( ...$matchingQueues );
|
|
}
|
|
|
|
/**
|
|
* Cancel all pending updates within this scope
|
|
*/
|
|
public function clearPendingUpdates() {
|
|
$this->queueByStage = array_fill_keys( array_keys( $this->queueByStage ), [] );
|
|
}
|
|
|
|
/**
|
|
* Iteratively, reassign unready pending updates to the parent scope (if applicable) and
|
|
* process the ready pending updates in stage-order with the callback, repeating the process
|
|
* until there is nothing left to do
|
|
*
|
|
* @param int $stage One of DeferredUpdates::STAGES or DeferredUpdates::ALL
|
|
* @param callable $callback Processing function with arguments (update, effective stage)
|
|
*/
|
|
public function processUpdates( $stage, callable $callback ) {
|
|
if ( $stage === DeferredUpdates::ALL ) {
|
|
// Do everything, all the way to the last "defer until" stage
|
|
$activeStage = DeferredUpdates::STAGES[count( DeferredUpdates::STAGES ) - 1];
|
|
} else {
|
|
// Handle the case where the specified stage must have already passed
|
|
$activeStage = max( $stage, $this->activeStage );
|
|
}
|
|
|
|
do {
|
|
$processed = $this->upmergeUnreadyUpdates( $activeStage );
|
|
foreach ( range( DeferredUpdates::STAGES[0], $activeStage ) as $queueStage ) {
|
|
$processed += $this->processStageQueue( $queueStage, $activeStage, $callback );
|
|
}
|
|
} while ( $processed > 0 );
|
|
}
|
|
|
|
/**
|
|
* If this is a child scope, then reassign unready pending updates to the parent scope:
|
|
* - MergeableUpdate instances will be reassigned to the parent scope on account of their
|
|
* de-duplication/melding semantics. They are normally only processed in the root scope.
|
|
* - DeferrableUpdate instances with a "defer until" stage later than the specified stage
|
|
* will be reassigned to the parent scope since they are not ready.
|
|
*
|
|
* @param int $activeStage One of DeferredUpdates::STAGES
|
|
* @return int Number of updates moved
|
|
*/
|
|
private function upmergeUnreadyUpdates( $activeStage ) {
|
|
$reassigned = 0;
|
|
|
|
if ( !$this->parentScope ) {
|
|
return $reassigned;
|
|
}
|
|
|
|
foreach ( $this->queueByStage as $queueStage => $queue ) {
|
|
foreach ( $queue as $k => $update ) {
|
|
if ( $update instanceof MergeableUpdate || $queueStage > $activeStage ) {
|
|
unset( $this->queueByStage[$queueStage][$k] );
|
|
$this->parentScope->addUpdate( $update, $queueStage );
|
|
++$reassigned;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $reassigned;
|
|
}
|
|
|
|
/**
|
|
* @param int $stage One of DeferredUpdates::STAGES
|
|
* @param int $activeStage One of DeferredUpdates::STAGES
|
|
* @param callable $callback Processing function with arguments (update, effective stage)
|
|
* @return int Number of updates processed
|
|
*/
|
|
private function processStageQueue( $stage, $activeStage, callable $callback ) {
|
|
$processed = 0;
|
|
|
|
// Defensively claim the pending updates in case of recursion
|
|
$claimedUpdates = $this->queueByStage[$stage];
|
|
$this->queueByStage[$stage] = [];
|
|
|
|
// Keep doing rounds of updates until none get enqueued...
|
|
while ( $claimedUpdates ) {
|
|
// Segregate the updates into one for DataUpdate and one for everything else.
|
|
// This is done for historical reasons; DataUpdate used to have its own static
|
|
// method for running DataUpdate instances and was called first in DeferredUpdates.
|
|
// Before that, page updater code directly ran that static method.
|
|
// @TODO: remove this logic given the existence of RefreshSecondaryDataUpdate
|
|
$claimedDataUpdates = [];
|
|
$claimedGenericUpdates = [];
|
|
foreach ( $claimedUpdates as $claimedUpdate ) {
|
|
if ( $claimedUpdate instanceof DataUpdate ) {
|
|
$claimedDataUpdates[] = $claimedUpdate;
|
|
} else {
|
|
$claimedGenericUpdates[] = $claimedUpdate;
|
|
}
|
|
++$processed;
|
|
}
|
|
|
|
// Execute the DataUpdate queue followed by the DeferrableUpdate queue...
|
|
foreach ( $claimedDataUpdates as $claimedDataUpdate ) {
|
|
$callback( $claimedDataUpdate, $activeStage );
|
|
}
|
|
foreach ( $claimedGenericUpdates as $claimedGenericUpdate ) {
|
|
$callback( $claimedGenericUpdate, $activeStage );
|
|
}
|
|
|
|
// Check for new entries; defensively claim the pending updates in case of recursion
|
|
$claimedUpdates = $this->queueByStage[$stage];
|
|
$this->queueByStage[$stage] = [];
|
|
}
|
|
|
|
return $processed;
|
|
}
|
|
}
|
|
|
|
/** @deprecated class alias since 1.42 */
|
|
class_alias( DeferredUpdatesScope::class, 'DeferredUpdatesScope' );
|