Make onTransactionIdle() safer for multi-DB commits
[lhc/web/wiklou.git] / includes / db / loadbalancer / LBFactory.php
index 549a8b5..053f9f8 100644 (file)
@@ -120,6 +120,15 @@ abstract class LBFactory implements DestructibleService {
                return $class;
        }
 
+       /**
+        * Shut down, close connections and destroy the cached instance.
+        *
+        * @deprecated since 1.27, use LBFactory::destroy()
+        */
+       public static function destroyInstance() {
+               self::singleton()->destroy();
+       }
+
        /**
         * Create a new load balancer object. The resulting object will be untracked,
         * not chronology-protected, and the caller is responsible for cleaning it up.
@@ -195,15 +204,12 @@ abstract class LBFactory implements DestructibleService {
         * 1. To commit changes to the masters.
         * 2. To release the snapshot on all connections, master and slave.
         * @param string $fname Caller name
+        * @param array $options Options map:
+        *   - maxWriteDuration: abort if more than this much time was spent in write queries
         */
-       public function commitAll( $fname = __METHOD__ ) {
-               $this->logMultiDbTransaction();
-
-               $start = microtime( true );
+       public function commitAll( $fname = __METHOD__, array $options = [] ) {
+               $this->commitMasterChanges( $fname, $options );
                $this->forEachLBCallMethod( 'commitAll', [ $fname ] );
-               $timeMs = 1000 * ( microtime( true ) - $start );
-
-               RequestContext::getMain()->getStats()->timing( "db.commit-all", $timeMs );
        }
 
        /**
@@ -213,26 +219,18 @@ abstract class LBFactory implements DestructibleService {
         *   - maxWriteDuration: abort if more than this much time was spent in write queries
         */
        public function commitMasterChanges( $fname = __METHOD__, array $options = [] ) {
-               $limit = isset( $options['maxWriteDuration'] ) ? $options['maxWriteDuration'] : 0;
-
-               $this->logMultiDbTransaction();
-               $this->forEachLB( function ( LoadBalancer $lb ) use ( $limit ) {
-                       $lb->forEachOpenConnection( function ( IDatabase $db ) use ( $limit ) {
-                               $time = $db->pendingWriteQueryDuration();
-                               if ( $limit > 0 && $time > $limit ) {
-                                       throw new DBTransactionError(
-                                               $db,
-                                               wfMessage( 'transaction-duration-limit-exceeded', $time, $limit )->text()
-                                       );
-                               }
-                       } );
-               } );
-
-               $start = microtime( true );
+               // Perform all pre-commit callbacks, aborting on failure
+               $this->forEachLBCallMethod( 'runMasterPreCommitCallbacks' );
+               // Perform all 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
+               $this->forEachLBCallMethod( 'commitMasterChanges', [ $fname ] );
+               // Run all post-commit callbacks
+               $this->forEachLBCallMethod( 'runMasterPostCommitCallbacks' );
+               // Commit any dangling DBO_TRX transactions from callbacks on one DB to another DB
                $this->forEachLBCallMethod( 'commitMasterChanges', [ $fname ] );
-               $timeMs = 1000 * ( microtime( true ) - $start );
-
-               RequestContext::getMain()->getStats()->timing( "db.commit-masters", $timeMs );
        }
 
        /**
@@ -247,7 +245,7 @@ abstract class LBFactory implements DestructibleService {
        /**
         * Log query info if multi DB transactions are going to be committed now
         */
-       private function logMultiDbTransaction() {
+       private function logIfMultiDbTransaction() {
                $callersByDB = [];
                $this->forEachLB( function ( LoadBalancer $lb ) use ( &$callersByDB ) {
                        $masterName = $lb->getServerName( $lb->getWriterIndex() );
@@ -447,6 +445,15 @@ abstract class LBFactory implements DestructibleService {
                        }
                } );
        }
+
+       /**
+        * Close all open database connections on all open load balancers.
+        * @since 1.28
+        */
+       public function closeAll() {
+               $this->forEachLBCallMethod( 'closeAll', [] );
+       }
+
 }
 
 /**
@@ -454,7 +461,8 @@ abstract class LBFactory implements DestructibleService {
  */
 class DBAccessError extends MWException {
        public function __construct() {
-               parent::__construct( 'The storage backend is disabled!' );
+               parent::__construct( "Mediawiki tried to access the database via wfGetDB(). " .
+                       "This is not allowed, because database access has been disabled." );
        }
 }