Merge "bidi-isolate usernames in Linker::userLink"
[lhc/web/wiklou.git] / includes / db / loadbalancer / LBFactory.php
index b320544..2e6f602 100644 (file)
@@ -44,8 +44,12 @@ abstract class LBFactory implements DestructibleService {
 
        /** @var mixed */
        protected $ticket;
+       /** @var string|bool String if a requested DBO_TRX transaction round is active */
+       protected $trxRoundId = false;
        /** @var string|bool Reason all LBs are read-only or false if not */
        protected $readOnlyReason = false;
+       /** @var callable[] */
+       protected $replicationWaitCallbacks = [];
 
        const SHUTDOWN_NO_CHRONPROT = 1; // don't save ChronologyProtector positions (for async code)
 
@@ -196,9 +200,12 @@ abstract class LBFactory implements DestructibleService {
        /**
         * Prepare all tracked load balancers for shutdown
         * @param integer $flags Supports SHUTDOWN_* flags
-        * STUB
         */
        public function shutdown( $flags = 0 ) {
+               if ( !( $flags & self::SHUTDOWN_NO_CHRONPROT ) ) {
+                       $this->shutdownChronologyProtector( $this->chronProt );
+               }
+               $this->commitMasterChanges( __METHOD__ ); // sanity
        }
 
        /**
@@ -216,6 +223,31 @@ abstract class LBFactory implements DestructibleService {
                );
        }
 
+       /**
+        * Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set)
+        *
+        * The DBO_TRX setting will be reverted to the default in each of these methods:
+        *   - commitMasterChanges()
+        *   - rollbackMasterChanges()
+        *   - commitAll()
+        * This allows for custom transaction rounds from any outer transaction scope.
+        *
+        * @param string $fname
+        * @throws DBTransactionError
+        * @since 1.28
+        */
+       public function beginMasterChanges( $fname = __METHOD__ ) {
+               if ( $this->trxRoundId !== false ) {
+                       throw new DBTransactionError(
+                               null,
+                               "Transaction round '{$this->trxRoundId}' already started."
+                       );
+               }
+               $this->trxRoundId = $fname;
+               // Set DBO_TRX flags on all appropriate DBs
+               $this->forEachLBCallMethod( 'beginMasterChanges', [ $fname ] );
+       }
+
        /**
         * Commit on all connections. Done for two reasons:
         * 1. To commit changes to the masters.
@@ -237,19 +269,20 @@ abstract class LBFactory implements DestructibleService {
         * @throws Exception
         */
        public function commitMasterChanges( $fname = __METHOD__, array $options = [] ) {
-               // Perform all pre-commit callbacks, aborting on failure
-               $this->forEachLBCallMethod( 'runMasterPreCommitCallbacks' );
-               // Perform all pre-commit checks, aborting on failure
+               // Run pre-commit callbacks and suppress post-commit callbacks, aborting on failure
+               $this->forEachLBCallMethod( 'finalizeMasterChanges' );
+               $this->trxRoundId = false;
+               // Perform pre-commit checks, aborting on failure
                $this->forEachLBCallMethod( 'approveMasterChanges', [ $options ] );
                // Log the DBs and methods involved in multi-DB transactions
                $this->logIfMultiDbTransaction();
-               // Actually perform the commit on all master DB connections
+               // Actually perform the commit on all master DB connections and revert DBO_TRX
                $this->forEachLBCallMethod( 'commitMasterChanges', [ $fname ] );
                // Run all post-commit callbacks
                /** @var Exception $e */
                $e = null; // first callback exception
                $this->forEachLB( function ( LoadBalancer $lb ) use ( &$e ) {
-                       $ex = $lb->runMasterPostCommitCallbacks();
+                       $ex = $lb->runMasterPostTrxCallbacks( IDatabase::TRIGGER_COMMIT );
                        $e = $e ?: $ex;
                } );
                // Commit any dangling DBO_TRX transactions from callbacks on one DB to another DB
@@ -266,7 +299,13 @@ abstract class LBFactory implements DestructibleService {
         * @since 1.23
         */
        public function rollbackMasterChanges( $fname = __METHOD__ ) {
+               $this->trxRoundId = false;
+               $this->forEachLBCallMethod( 'suppressTransactionEndCallbacks' );
                $this->forEachLBCallMethod( 'rollbackMasterChanges', [ $fname ] );
+               // Run all post-rollback callbacks
+               $this->forEachLB( function ( LoadBalancer $lb ) {
+                       $lb->runMasterPostTrxCallbacks( IDatabase::TRIGGER_ROLLBACK );
+               } );
        }
 
        /**
@@ -365,6 +404,10 @@ abstract class LBFactory implements DestructibleService {
                        'ifWritesSince' => null
                ];
 
+               foreach ( $this->replicationWaitCallbacks as $callback ) {
+                       $callback();
+               }
+
                // Figure out which clusters need to be checked
                /** @var LoadBalancer[] $lbs */
                $lbs = [];
@@ -417,6 +460,23 @@ abstract class LBFactory implements DestructibleService {
                }
        }
 
+       /**
+        * Add a callback to be run in every call to waitForReplication() before waiting
+        *
+        * Callbacks must clear any transactions that they start
+        *
+        * @param string $name Callback name
+        * @param callable|null $callback Use null to unset a callback
+        * @since 1.28
+        */
+       public function setWaitForReplicationListener( $name, callable $callback = null ) {
+               if ( $callback ) {
+                       $this->replicationWaitCallbacks[$name] = $callback;
+               } else {
+                       unset( $this->replicationWaitCallbacks[$name] );
+               }
+       }
+
        /**
         * Get a token asserting that no transaction writes are active
         *
@@ -511,6 +571,15 @@ abstract class LBFactory implements DestructibleService {
                } );
        }
 
+       /**
+        * @param LoadBalancer $lb
+        */
+       protected function initLoadBalancer( LoadBalancer $lb ) {
+               if ( $this->trxRoundId !== false ) {
+                       $lb->beginMasterChanges( $this->trxRoundId ); // set DBO_TRX
+               }
+       }
+
        /**
         * Close all open database connections on all open load balancers.
         * @since 1.28
@@ -520,19 +589,3 @@ abstract class LBFactory implements DestructibleService {
        }
 
 }
-
-/**
- * Exception class for attempted DB access
- */
-class DBAccessError extends MWException {
-       public function __construct() {
-               parent::__construct( "Mediawiki tried to access the database via wfGetDB(). " .
-                       "This is not allowed, because database access has been disabled." );
-       }
-}
-
-/**
- * Exception class for replica DB wait timeouts
- */
-class DBReplicationWaitError extends Exception {
-}