Merge "mw.action.history.diff: Remove higher than necessary specific selectors"
[lhc/web/wiklou.git] / includes / deferred / DeferredUpdates.php
index 24c7930..9f5b31a 100644 (file)
  *
  * @file
  */
+use Wikimedia\Rdbms\IDatabase;
 use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\LBFactory;
+use Wikimedia\Rdbms\LoadBalancer;
 
 /**
  * Class for managing the deferred updates
@@ -27,8 +30,11 @@ use MediaWiki\MediaWikiServices;
  * In web request mode, deferred updates can be run at the end of the request, either before or
  * after the HTTP response has been sent. In either case, they run after the DB commit step. If
  * an update runs after the response is sent, it will not block clients. If sent before, it will
- * run synchronously. If such an update works via queueing, it will be more likely to complete by
- * the time the client makes their next request after this one.
+ * run synchronously. These two modes are defined via PRESEND and POSTSEND constants, the latter
+ * being the default for addUpdate() and addCallableUpdate().
+ *
+ * Updates that work through this system will be more likely to complete by the time the client
+ * makes their next request after this one than with the JobQueue system.
  *
  * In CLI mode, updates run immediately if no DB writes are pending. Otherwise, they run when:
  *   - a) Any waitForReplication() call if no writes are pending on any DB
@@ -36,7 +42,11 @@ use MediaWiki\MediaWikiServices;
  *   - c) EnqueueableDataUpdate tasks may enqueue on commit of Maintenance::getDB( DB_MASTER )
  *   - d) At the completion of Maintenance::execute()
  *
- * When updates are deferred, they use a FIFO queue (one for pre-send and one for post-send).
+ * When updates are deferred, they go into one two FIFO "top-queues" (one for pre-send and one
+ * for post-send). Updates enqueued *during* doUpdate() of a "top" update go into the "sub-queue"
+ * for that update. After that method finishes, the sub-queue is run until drained. This continues
+ * for each top-queue job until the entire top queue is drained. This happens for the pre-send
+ * top-queue, and later on, the post-send top-queue, in execute().
  *
  * @since 1.19
  */
@@ -119,6 +129,15 @@ class DeferredUpdates {
                }
        }
 
+       /**
+        * @param bool $value Whether to just immediately run updates in addUpdate()
+        * @since 1.28
+        * @deprecated 1.29 Causes issues in Web-executed jobs - see T165714 and T100085.
+        */
+       public static function setImmediateMode( $value ) {
+               wfDeprecated( __METHOD__, '1.29' );
+       }
+
        /**
         * @param DeferrableUpdate[] $queue
         * @param DeferrableUpdate $update
@@ -139,18 +158,22 @@ class DeferredUpdates {
        }
 
        /**
+        * Immediately run/queue a list of updates
+        *
         * @param DeferrableUpdate[] &$queue List of DeferrableUpdate objects
         * @param string $mode Use "enqueue" to use the job queue when possible
         * @param integer $stage Class constant (PRESEND, POSTSEND) (since 1.28)
         * @throws ErrorPageError Happens on top-level calls
         * @throws Exception Happens on second-level calls
         */
-       public static function execute( array &$queue, $mode, $stage ) {
+       protected static function execute( array &$queue, $mode, $stage ) {
                $services = MediaWikiServices::getInstance();
                $stats = $services->getStatsdDataFactory();
                $lbFactory = $services->getDBLoadBalancerFactory();
                $method = RequestContext::getMain()->getRequest()->getMethod();
 
+               $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
+
                /** @var ErrorPageError $reportableError */
                $reportableError = null;
                /** @var DeferrableUpdate[] $updates Snapshot of queue */
@@ -173,7 +196,13 @@ class DeferredUpdates {
                        // Order will be DataUpdate followed by generic DeferrableUpdate tasks
                        $updatesByType = [ 'data' => [], 'generic' => [] ];
                        foreach ( $updates as $du ) {
-                               $updatesByType[$du instanceof DataUpdate ? 'data' : 'generic'][] = $du;
+                               if ( $du instanceof DataUpdate ) {
+                                       $du->setTransactionTicket( $ticket );
+                                       $updatesByType['data'][] = $du;
+                               } else {
+                                       $updatesByType['generic'][] = $du;
+                               }
+
                                $name = ( $du instanceof DeferrableCallback )
                                        ? get_class( $du ) . '-' . $du->getOrigin()
                                        : get_class( $du );
@@ -197,6 +226,10 @@ class DeferredUpdates {
                                                $firstKey = key( self::$executeContext['subqueue'] );
                                                unset( self::$executeContext['subqueue'][$firstKey] );
 
+                                               if ( $subUpdate instanceof DataUpdate ) {
+                                                       $subUpdate->setTransactionTicket( $ticket );
+                                               }
+
                                                $guiError = self::runUpdate( $subUpdate, $lbFactory, $stage );
                                                $reportableError = $reportableError ?: $guiError;
                                        }
@@ -221,9 +254,10 @@ class DeferredUpdates {
        private static function runUpdate( DeferrableUpdate $update, LBFactory $lbFactory, $stage ) {
                $guiError = null;
                try {
-                       $lbFactory->beginMasterChanges( __METHOD__ );
+                       $fnameTrxOwner = get_class( $update ) . '::doUpdate';
+                       $lbFactory->beginMasterChanges( $fnameTrxOwner );
                        $update->doUpdate();
-                       $lbFactory->commitMasterChanges( __METHOD__ );
+                       $lbFactory->commitMasterChanges( $fnameTrxOwner );
                } catch ( Exception $e ) {
                        // Reporting GUI exceptions does not work post-send
                        if ( $e instanceof ErrorPageError && $stage === self::PRESEND ) {
@@ -296,6 +330,21 @@ class DeferredUpdates {
                return count( self::$preSendUpdates ) + count( self::$postSendUpdates );
        }
 
+       /**
+        * @param integer $stage DeferredUpdates constant (PRESEND, POSTSEND, or ALL)
+        * @since 1.29
+        */
+       public static function getPendingUpdates( $stage = self::ALL ) {
+               $updates = [];
+               if ( $stage === self::ALL || $stage === self::PRESEND ) {
+                       $updates = array_merge( $updates, self::$preSendUpdates );
+               }
+               if ( $stage === self::ALL || $stage === self::POSTSEND ) {
+                       $updates = array_merge( $updates, self::$postSendUpdates );
+               }
+               return $updates;
+       }
+
        /**
         * Clear all pending updates without performing them. Generally, you don't
         * want or need to call this. Unit tests need it though.