wiki.techinc.nl/includes/deferred/DeferredUpdatesScope.php
Aaron Schulz a27be97a84 deferred: remove BIG_QUEUE_SIZE logic that turned updates into jobs
There are enough points where tryOpportunisticExecute() is triggered
that this no longer needed.

Change-Id: Ie27d4ec299df0e6f2754baedc237a473821f0709
2024-08-20 15:14:02 +00:00

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' );