+
+ if ( $reportableError ) {
+ throw $reportableError; // throw the first of any GUI errors
+ }
+ }
+
+ /**
+ * @param DeferrableUpdate $update
+ * @param LBFactory $lbFactory
+ * @param integer $stage
+ * @return ErrorPageError|null
+ */
+ private static function runUpdate( DeferrableUpdate $update, LBFactory $lbFactory, $stage ) {
+ $guiError = null;
+ try {
+ $fnameTrxOwner = get_class( $update ) . '::doUpdate';
+ $lbFactory->beginMasterChanges( $fnameTrxOwner );
+ $update->doUpdate();
+ $lbFactory->commitMasterChanges( $fnameTrxOwner );
+ } catch ( Exception $e ) {
+ // Reporting GUI exceptions does not work post-send
+ if ( $e instanceof ErrorPageError && $stage === self::PRESEND ) {
+ $guiError = $e;
+ }
+ MWExceptionHandler::rollbackMasterChangesAndLog( $e );
+ }
+
+ return $guiError;
+ }
+
+ /**
+ * Run all deferred updates immediately if there are no DB writes active
+ *
+ * If $mode is 'run' but there are busy databates, EnqueueableDataUpdate
+ * tasks will be enqueued anyway for the sake of progress.
+ *
+ * @param string $mode Use "enqueue" to use the job queue when possible
+ * @return bool Whether updates were allowed to run
+ * @since 1.28
+ */
+ public static function tryOpportunisticExecute( $mode = 'run' ) {
+ // execute() loop is already running
+ if ( self::$executeContext ) {
+ return false;
+ }
+
+ // Avoiding running updates without them having outer scope
+ if ( !self::getBusyDbConnections() ) {
+ self::doUpdates( $mode );
+ return true;
+ }
+
+ if ( self::pendingUpdatesCount() >= self::BIG_QUEUE_SIZE ) {
+ // If we cannot run the updates with outer transaction context, try to
+ // at least enqueue all the updates that support queueing to job queue
+ self::$preSendUpdates = self::enqueueUpdates( self::$preSendUpdates );
+ self::$postSendUpdates = self::enqueueUpdates( self::$postSendUpdates );
+ }
+
+ return !self::pendingUpdatesCount();
+ }
+
+ /**
+ * Enqueue a job for each EnqueueableDataUpdate item and return the other items
+ *
+ * @param DeferrableUpdate[] $updates A list of deferred update instances
+ * @return DeferrableUpdate[] Remaining updates that do not support being queued
+ */
+ private static function enqueueUpdates( array $updates ) {
+ $remaining = [];
+
+ foreach ( $updates as $update ) {
+ if ( $update instanceof EnqueueableDataUpdate ) {
+ $spec = $update->getAsJobSpecification();
+ JobQueueGroup::singleton( $spec['wiki'] )->push( $spec['job'] );
+ } else {
+ $remaining[] = $update;
+ }
+ }
+
+ return $remaining;
+ }
+
+ /**
+ * @return integer Number of enqueued updates
+ * @since 1.28
+ */
+ public static function pendingUpdatesCount() {
+ return count( self::$preSendUpdates ) + count( self::$postSendUpdates );