Merge "Make doAtomicSection() return the callback result"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 17 Aug 2016 23:01:12 +0000 (23:01 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 17 Aug 2016 23:01:12 +0000 (23:01 +0000)
1  2 
includes/db/Database.php
includes/db/IDatabase.php

diff --combined includes/db/Database.php
@@@ -815,7 -815,7 +815,7 @@@ abstract class DatabaseBase implements 
                if ( !$this->mTrxLevel && $this->getFlag( DBO_TRX )
                        && $this->isTransactableQuery( $sql )
                ) {
 -                      $this->begin( __METHOD__ . " ($fname)" );
 +                      $this->begin( __METHOD__ . " ($fname)", self::TRANSACTION_INTERNAL );
                        $this->mTrxAutomatic = true;
                }
  
  
                $useTrx = !$this->mTrxLevel;
                if ( $useTrx ) {
 -                      $this->begin( $fname );
 +                      $this->begin( $fname, self::TRANSACTION_INTERNAL );
                }
                try {
                        # Update any existing conflicting row(s)
                        throw $e;
                }
                if ( $useTrx ) {
 -                      $this->commit( $fname );
 +                      $this->commit( $fname, self::TRANSACTION_INTERNAL );
                }
  
                return $ok;
                        $this->mTrxPreCommitCallbacks[] = [ $callback, wfGetCaller() ];
                } else {
                        // If no transaction is active, then make one for this callback
 -                      $this->begin( __METHOD__ );
 +                      $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
                        try {
                                call_user_func( $callback );
                                $this->commit( __METHOD__ );
  
        final public function startAtomic( $fname = __METHOD__ ) {
                if ( !$this->mTrxLevel ) {
 -                      $this->begin( $fname );
 +                      $this->begin( $fname, self::TRANSACTION_INTERNAL );
                        $this->mTrxAutomatic = true;
                        // If DBO_TRX is set, a series of startAtomic/endAtomic pairs will result
                        // in all changes being in one transaction to keep requests transactional.
        final public function doAtomicSection( $fname, callable $callback ) {
                $this->startAtomic( $fname );
                try {
-                       call_user_func_array( $callback, [ $this, $fname ] );
+                       $res = call_user_func_array( $callback, [ $this, $fname ] );
                } catch ( Exception $e ) {
                        $this->rollback( $fname );
                        throw $e;
                }
                $this->endAtomic( $fname );
+               return $res;
        }
  
 -      final public function begin( $fname = __METHOD__ ) {
 -              if ( $this->mTrxLevel ) { // implicit commit
 +      final public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT ) {
 +              // Protect against mismatched atomic section, transaction nesting, and snapshot loss
 +              if ( $this->mTrxLevel ) {
                        if ( $this->mTrxAtomicLevels ) {
 -                              // If the current transaction was an automatic atomic one, then we definitely have
 -                              // a problem. Same if there is any unclosed atomic level.
                                $levels = implode( ', ', $this->mTrxAtomicLevels );
 -                              throw new DBUnexpectedError(
 -                                      $this,
 -                                      "Got explicit BEGIN from $fname while atomic section(s) $levels are open."
 -                              );
 +                              $msg = "Got explicit BEGIN from $fname while atomic section(s) $levels are open.";
 +                              throw new DBUnexpectedError( $this, $msg );
                        } elseif ( !$this->mTrxAutomatic ) {
 -                              // We want to warn about inadvertently nested begin/commit pairs, but not about
 -                              // auto-committing implicit transactions that were started by query() via DBO_TRX
 -                              throw new DBUnexpectedError(
 -                                      $this,
 -                                      "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
 -                                              " performing implicit commit!"
 -                              );
 -                      } elseif ( $this->mTrxDoneWrites ) {
 -                              // The transaction was automatic and has done write operations
 -                              throw new DBUnexpectedError(
 -                                      $this,
 -                                      "$fname: Automatic transaction with writes in progress" .
 -                                              " (from {$this->mTrxFname}), performing implicit commit!\n"
 -                              );
 -                      }
 -
 -                      $this->runOnTransactionPreCommitCallbacks();
 -                      $writeTime = $this->pendingWriteQueryDuration();
 -                      $this->doCommit( $fname );
 -                      if ( $this->mTrxDoneWrites ) {
 -                              $this->mDoneWrites = microtime( true );
 -                              $this->getTransactionProfiler()->transactionWritingOut(
 -                                      $this->mServer, $this->mDBname, $this->mTrxShortId, $writeTime );
 +                              $msg = "$fname: Explicit transaction already active (from {$this->mTrxFname}).";
 +                              throw new DBUnexpectedError( $this, $msg );
 +                      } else {
 +                              // @TODO: make this an exception at some point
 +                              $msg = "$fname: Implicit transaction already active (from {$this->mTrxFname}).";
 +                              wfLogDBError( $msg );
 +                              return; // join the main transaction set
                        }
 -
 -                      $this->runOnTransactionIdleCallbacks( self::TRIGGER_COMMIT );
 +              } elseif ( $this->getFlag( DBO_TRX ) && $mode !== self::TRANSACTION_INTERNAL ) {
 +                      // @TODO: make this an exception at some point
 +                      wfLogDBError( "$fname: Implicit transaction expected (DBO_TRX set)." );
 +                      return; // let any writes be in the main transaction
                }
  
                // Avoid fatals if close() was called
                        $levels = implode( ', ', $this->mTrxAtomicLevels );
                        throw new DBUnexpectedError(
                                $this,
 -                              "Got COMMIT while atomic sections $levels are still open"
 +                              "Got COMMIT while atomic sections $levels are still open."
                        );
                }
  
                        } elseif ( !$this->mTrxAutomatic ) {
                                throw new DBUnexpectedError(
                                        $this,
 -                                      "$fname: Flushing an explicit transaction, getting out of sync!"
 +                                      "$fname: Flushing an explicit transaction, getting out of sync."
                                );
                        }
                } else {
                        if ( !$this->mTrxLevel ) {
 -                              wfWarn( "$fname: No transaction to commit, something got out of sync!" );
 +                              wfWarn( "$fname: No transaction to commit, something got out of sync." );
                                return; // nothing to do
                        } elseif ( $this->mTrxAutomatic ) {
 -                              throw new DBUnexpectedError(
 -                                      $this,
 -                                      "$fname: Explicit commit of implicit transaction."
 -                              );
 +                              // @TODO: make this an exception at some point
 +                              wfLogDBError( "$fname: Explicit commit of implicit transaction." );
 +                              return; // wait for the main transaction set commit round
                        }
                }
  
        }
  
        final public function rollback( $fname = __METHOD__, $flush = '' ) {
 -              if ( $flush !== self::FLUSHING_INTERNAL && $flush !== self::FLUSHING_ALL_PEERS ) {
 +              if ( $flush === self::FLUSHING_INTERNAL || $flush === self::FLUSHING_ALL_PEERS ) {
                        if ( !$this->mTrxLevel ) {
 -                              wfWarn( "$fname: No transaction to rollback, something got out of sync!" );
                                return; // nothing to do
                        }
                } else {
                        if ( !$this->mTrxLevel ) {
 +                              wfWarn( "$fname: No transaction to rollback, something got out of sync." );
                                return; // nothing to do
 +                      } elseif ( $this->getFlag( DBO_TRX ) ) {
 +                              throw new DBUnexpectedError(
 +                                      $this,
 +                                      "$fname: Expected mass rollback of all peer databases (DBO_TRX set)."
 +                              );
                        }
                }
  
@@@ -40,11 -40,6 +40,11 @@@ interface IDatabase 
        /** @var int Callback triggered by rollback */
        const TRIGGER_ROLLBACK = 3;
  
 +      /** @var string Transaction is requested by regular caller outside of the DB layer */
 +      const TRANSACTION_EXPLICIT = '';
 +      /** @var string Transaction is requested interally via DBO_TRX/startAtomic() */
 +      const TRANSACTION_INTERNAL = 'implicit';
 +
        /** @var string Transaction operation comes from service managing all DBs */
        const FLUSHING_ALL_PEERS = 'flush';
        /** @var string Transaction operation comes from the database class internally */
         *
         * @param string $fname Caller name (usually __METHOD__)
         * @param callable $callback Callback that issues DB updates
+        * @return mixed $res Result of the callback (since 1.28)
         * @throws DBError
         * @throws RuntimeException
         * @throws UnexpectedValueException
         * automatically because of the DBO_TRX flag.
         *
         * @param string $fname
 +       * @param string $mode A situationally valid IDatabase::TRANSACTION_* constant [optional]
         * @throws DBError
         */
 -      public function begin( $fname = __METHOD__ );
 +      public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT );
  
        /**
         * Commits a transaction previously started using begin().