Merge "rdbms: clean up DBO_TRX behavior for onTransaction* callbacks"
[lhc/web/wiklou.git] / includes / libs / rdbms / database / Database.php
index 25938f1..056f189 100644 (file)
@@ -35,6 +35,7 @@ use BagOStuff;
 use HashBagOStuff;
 use LogicException;
 use InvalidArgumentException;
+use UnexpectedValueException;
 use Exception;
 use RuntimeException;
 
@@ -282,6 +283,8 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                                $this->flags |= self::DBO_TRX;
                        }
                }
+               // Disregard deprecated DBO_IGNORE flag (T189999)
+               $this->flags &= ~self::DBO_IGNORE;
 
                $this->sessionVars = $params['variables'];
 
@@ -537,32 +540,6 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                return $res;
        }
 
-       /**
-        * Turns on (false) or off (true) the automatic generation and sending
-        * of a "we're sorry, but there has been a database error" page on
-        * database errors. Default is on (false). When turned off, the
-        * code should use lastErrno() and lastError() to handle the
-        * situation as appropriate.
-        *
-        * Do not use this function outside of the Database classes.
-        *
-        * @param null|bool $ignoreErrors
-        * @return bool The previous value of the flag.
-        */
-       protected function ignoreErrors( $ignoreErrors = null ) {
-               $res = $this->getFlag( self::DBO_IGNORE );
-               if ( $ignoreErrors !== null ) {
-                       // setFlag()/clearFlag() do not allow DBO_IGNORE changes for sanity
-                       if ( $ignoreErrors ) {
-                               $this->flags |= self::DBO_IGNORE;
-                       } else {
-                               $this->flags &= ~self::DBO_IGNORE;
-                       }
-               }
-
-               return $res;
-       }
-
        public function trxLevel() {
                return $this->trxLevel;
        }
@@ -658,6 +635,20 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                );
        }
 
+       /**
+        * @return string|null
+        */
+       final protected function getTransactionRoundId() {
+               // If transaction round participation is enabled, see if one is active
+               if ( $this->getFlag( self::DBO_TRX ) ) {
+                       $id = $this->getLBInfo( 'trxRoundId' );
+
+                       return is_string( $id ) ? $id : null;
+               }
+
+               return null;
+       }
+
        public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL ) {
                if ( !$this->trxLevel ) {
                        return false;
@@ -719,7 +710,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
 
        public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
                if ( ( $flag & self::DBO_IGNORE ) ) {
-                       throw new \UnexpectedValueException( "Modifying DBO_IGNORE is not allowed." );
+                       throw new UnexpectedValueException( "Modifying DBO_IGNORE is not allowed." );
                }
 
                if ( $remember === self::REMEMBER_PRIOR ) {
@@ -730,7 +721,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
 
        public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
                if ( ( $flag & self::DBO_IGNORE ) ) {
-                       throw new \UnexpectedValueException( "Modifying DBO_IGNORE is not allowed." );
+                       throw new UnexpectedValueException( "Modifying DBO_IGNORE is not allowed." );
                }
 
                if ( $remember === self::REMEMBER_PRIOR ) {
@@ -1305,7 +1296,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
         * @throws DBQueryError
         */
        public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
-               if ( $this->ignoreErrors() || $tempIgnore ) {
+               if ( $tempIgnore ) {
                        $this->queryLogger->debug( "SQL ERROR (ignored): $error\n" );
                } else {
                        $sql1line = mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 );
@@ -3098,6 +3089,12 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        }
 
        final public function onTransactionIdle( callable $callback, $fname = __METHOD__ ) {
+               if ( !$this->trxLevel && $this->getTransactionRoundId() ) {
+                       // Start an implicit transaction similar to how query() does
+                       $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
+                       $this->trxAutomatic = true;
+               }
+
                $this->trxIdleCallbacks[] = [ $callback, $fname ];
                if ( !$this->trxLevel ) {
                        $this->runOnTransactionIdleCallbacks( self::TRIGGER_IDLE );
@@ -3105,10 +3102,13 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        }
 
        final public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
-               if ( $this->trxLevel || $this->getFlag( self::DBO_TRX ) ) {
-                       // As long as DBO_TRX is set, writes will accumulate until the load balancer issues
-                       // an implicit commit of all peer databases. This is true even if a transaction has
-                       // not yet been triggered by writes; make sure $callback runs *after* any such writes.
+               if ( !$this->trxLevel && $this->getTransactionRoundId() ) {
+                       // Start an implicit transaction similar to how query() does
+                       $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
+                       $this->trxAutomatic = true;
+               }
+
+               if ( $this->trxLevel ) {
                        $this->trxPreCommitCallbacks[] = [ $callback, $fname ];
                } else {
                        // No transaction is active nor will start implicitly, so make one for this callback
@@ -3417,10 +3417,8 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                $this->trxWriteAdjQueryCount = 0;
                $this->trxWriteCallers = [];
                // First SELECT after BEGIN will establish the snapshot in REPEATABLE-READ.
-               // Get an estimate of the replica DB lag before then, treating estimate staleness
-               // as lag itself just to be safe
-               $status = $this->getApproximateLagStatus();
-               $this->trxReplicaLag = $status['lag'] + ( microtime( true ) - $status['since'] );
+               // Get an estimate of the replication lag before any such queries.
+               $this->trxReplicaLag = $this->getApproximateLagStatus()['lag'];
                // T147697: make explicitTrxActive() return true until begin() finishes. This way, no
                // caller will think its OK to muck around with the transaction just because startAtomic()
                // has not yet completed (e.g. setting trxAtomicLevels).
@@ -3723,7 +3721,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
         * @return array|null ('lag': seconds or false on error, 'since': UNIX timestamp of BEGIN)
         * @since 1.27
         */
-       protected function getTransactionLagStatus() {
+       final protected function getTransactionLagStatus() {
                return $this->trxLevel
                        ? [ 'lag' => $this->trxReplicaLag, 'since' => $this->trxTimestamp() ]
                        : null;