29use Wikimedia\ScopedCallback;
101 public const ALL = 0;
103 public const PRESEND = 1;
105 public const POSTSEND = 2;
108 public const STAGES = [ self::PRESEND, self::POSTSEND ];
111 private const BIG_QUEUE_SIZE = 100;
114 private static $scopeStack;
119 private static $preventOpportunisticUpdates = 0;
126 return self::$scopeStack;
134 if ( !defined(
'MW_PHPUNIT_TEST' ) ) {
135 throw new LogicException(
'Cannot reconfigure DeferredUpdates outside tests' );
137 self::$scopeStack = $scopeStack;
161 self::getScopeStack()->current()->addUpdate( $update, $stage );
162 self::tryOpportunisticExecute();
189 $type = get_class( $update )
191 $updateId = spl_object_id( $update );
192 $logger->debug(
"DeferredUpdates::run: started $type #{updateId}", [
'updateId' => $updateId ] );
194 $updateException =
null;
196 $startTime = microtime(
true );
198 self::attemptUpdate( $update );
199 }
catch ( Throwable $updateException ) {
200 MWExceptionHandler::logException( $updateException );
202 "Deferred update '{deferred_type}' failed to run.",
204 'deferred_type' => $type,
205 'exception' => $updateException,
208 self::getScopeStack()->onRunUpdateFailed( $update );
210 $walltime = microtime(
true ) - $startTime;
211 $logger->debug(
"DeferredUpdates::run: ended $type #{updateId}, processing time: {walltime}", [
212 'updateId' => $updateId,
213 'walltime' => $walltime,
218 if ( $updateException && $update instanceof EnqueueableDataUpdate ) {
220 self::getScopeStack()->queueDataUpdate( $update );
221 }
catch ( Throwable $jobException ) {
224 "Deferred update '{deferred_type}' failed to enqueue as a job.",
226 'deferred_type' => $type,
227 'exception' => $jobException,
230 self::getScopeStack()->onRunUpdateFailed( $update );
234 return $updateException;
255 public static function doUpdates( $stage = self::ALL ) {
261 $scope = self::getScopeStack()->current();
264 $activeUpdate = $scope->getActiveUpdate();
265 if ( $activeUpdate ) {
266 $class = get_class( $activeUpdate );
268 throw new LogicException(
269 __METHOD__ .
": reached from $class, which is not TransactionRoundAwareUpdate"
272 if ( $activeUpdate->getTransactionRoundRequirement() !== $activeUpdate::TRX_ROUND_ABSENT ) {
273 throw new LogicException(
274 __METHOD__ .
": reached from $class, which does not specify TRX_ROUND_ABSENT"
279 $scope->processUpdates(
281 static function (
DeferrableUpdate $update, $activeStage ) use ( &$guiError, &$exception ) {
282 $scopeStack = self::getScopeStack();
283 $childScope = $scopeStack->descend( $activeStage, $update );
286 $guiError = $guiError ?: ( $e instanceof
ErrorPageError ? $e : null );
287 $exception = $exception ?: $e;
293 $childScope->processUpdates(
297 $guiError = $guiError ?: ( $e instanceof
ErrorPageError ? $e : null );
298 $exception = $exception ?: $e;
302 $scopeStack->ascend();
309 if ( $exception && defined(
'MW_PHPUNIT_TEST' ) ) {
317 if ( $guiError && $stage === self::PRESEND && !headers_sent() ) {
361 if ( self::getRecursiveExecutionStackDepth()
362 || self::$preventOpportunisticUpdates
367 if ( self::getScopeStack()->allowOpportunisticUpdates() ) {
368 self::doUpdates( self::ALL );
372 if ( self::pendingUpdatesCount() >= self::BIG_QUEUE_SIZE ) {
381 $enqueuedUpdates = [];
382 self::getScopeStack()->current()->consumeMatchingUpdates(
384 EnqueueableDataUpdate::class,
385 static function ( EnqueueableDataUpdate $update ) use ( &$enqueuedUpdates ) {
386 self::getScopeStack()->queueDataUpdate( $update );
387 $type = get_class( $update );
388 $enqueuedUpdates[$type] ??= 0;
389 $enqueuedUpdates[$type]++;
392 if ( $enqueuedUpdates ) {
393 LoggerFactory::getInstance(
'DeferredUpdates' )->debug(
394 'Enqueued {enqueuedUpdatesCount} updates as jobs',
396 'enqueuedUpdatesCount' => array_sum( $enqueuedUpdates ),
397 'enqueuedUpdates' => implode(
', ',
398 array_map( fn ( $k, $v ) =>
"$k: $v", array_keys( $enqueuedUpdates ), $enqueuedUpdates ) ),
414 self::$preventOpportunisticUpdates++;
415 return new ScopedCallback(
static function () {
416 self::$preventOpportunisticUpdates--;
430 return self::getScopeStack()->current()->pendingUpdatesCount();
446 return self::getScopeStack()->current()->getPendingUpdates( $stage );
458 self::getScopeStack()->current()->clearPendingUpdates();
468 return self::getScopeStack()->getRecursiveDepth();
484 self::getScopeStack()->onRunUpdateStart( $update );
488 self::getScopeStack()->onRunUpdateEnd( $update );
493class_alias( DeferredUpdates::class,
'DeferredUpdates' );
wfGetCaller( $level=2)
Get the name of the function which called this function wfGetCaller( 1 ) is the function with the wfG...
if(!defined('MW_SETUP_CALLBACK'))
An error page which can definitely be safely rendered using the OutputPage.
Handler class for MWExceptions.
static logException(Throwable $e, $catcher=self::CAUGHT_BY_OTHER, $extraData=[])
Log a throwable to the exception log (if enabled).