X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;ds=sidebyside;f=includes%2Flibs%2Frdbms%2Fdatabase%2FDatabase.php;h=452b4f8659339b061627ae3497d3c0570494f9e5;hb=477b83594599ed7b35f3826e4b849ab43cb12ad4;hp=5336b257d4e125de7605e17a22f4c7a893e05970;hpb=9c85176cec5be2054e04521e80486165d4901b3a;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/libs/rdbms/database/Database.php b/includes/libs/rdbms/database/Database.php index 5336b257d4..452b4f8659 100644 --- a/includes/libs/rdbms/database/Database.php +++ b/includes/libs/rdbms/database/Database.php @@ -151,6 +151,11 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware * @var Exception|null The last error that caused the status to become STATUS_TRX_ERROR */ protected $trxStatusCause; + /** + * @var array|null If wasKnownStatementRollbackError() prevented trxStatus from being set, + * the relevant details are stored here. + */ + protected $trxStatusIgnoredCause; /** * Either 1 if a transaction is active or 0 otherwise. * The other Trx fields may not be meaningfull if this is 0. @@ -207,7 +212,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware /** * Array of levels of atomicity within transactions * - * @var array + * @var array List of (name, unique ID, savepoint ID) */ private $trxAtomicLevels = []; /** @@ -1070,6 +1075,9 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) { $this->assertTransactionStatus( $sql, $fname ); + # Avoid fatals if close() was called + $this->assertOpen(); + $priorWritesPending = $this->writesOrCallbacksPending(); $this->lastQuery = $sql; @@ -1120,9 +1128,6 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware $this->queryLogger->debug( "{$this->dbName} {$commentedSql}" ); } - # Avoid fatals if close() was called - $this->assertOpen(); - # Send the query to the server and fetch any corresponding errors $ret = $this->doProfiledQuery( $sql, $commentedSql, $isNonTempWrite, $fname ); $lastError = $this->lastError(); @@ -1148,21 +1153,29 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware } if ( $ret === false ) { - if ( $this->trxLevel && !$this->wasKnownStatementRollbackError() ) { - # Either the query was aborted or all queries after BEGIN where aborted. - if ( $this->explicitTrxActive() || $priorWritesPending ) { - # In the first case, the only options going forward are (a) ROLLBACK, or - # (b) ROLLBACK TO SAVEPOINT (if one was set). If the later case, the only - # option is ROLLBACK, since the snapshots would have been released. - $this->trxStatus = self::STATUS_TRX_ERROR; - $this->trxStatusCause = - $this->makeQueryException( $lastError, $lastErrno, $sql, $fname ); - $tempIgnore = false; // cannot recover + if ( $this->trxLevel ) { + if ( !$this->wasKnownStatementRollbackError() ) { + # Either the query was aborted or all queries after BEGIN where aborted. + if ( $this->explicitTrxActive() || $priorWritesPending ) { + # In the first case, the only options going forward are (a) ROLLBACK, or + # (b) ROLLBACK TO SAVEPOINT (if one was set). If the later case, the only + # option is ROLLBACK, since the snapshots would have been released. + $this->trxStatus = self::STATUS_TRX_ERROR; + $this->trxStatusCause = + $this->makeQueryException( $lastError, $lastErrno, $sql, $fname ); + $tempIgnore = false; // cannot recover + } else { + # Nothing prior was there to lose from the transaction, + # so just roll it back. + $this->doRollback( __METHOD__ . " ($fname)" ); + $this->trxStatus = self::STATUS_TRX_OK; + } + $this->trxStatusIgnoredCause = null; } else { - # Nothing prior was there to lose from the transaction, - # so just roll it back. - $this->doRollback( __METHOD__ . " ($fname)" ); - $this->trxStatus = self::STATUS_TRX_OK; + # We're ignoring an error that caused just the current query to be aborted. + # But log the cause so we can log a deprecation notice if a + # caller actually does ignore it. + $this->trxStatusIgnoredCause = [ $lastError, $lastErrno, $fname ]; } } @@ -1273,16 +1286,24 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware * @throws DBTransactionStateError */ private function assertTransactionStatus( $sql, $fname ) { - if ( - $this->trxStatus < self::STATUS_TRX_OK && - $this->getQueryVerb( $sql ) !== 'ROLLBACK' // transaction/savepoint - ) { + if ( $this->getQueryVerb( $sql ) === 'ROLLBACK' ) { // transaction/savepoint + return; + } + + if ( $this->trxStatus < self::STATUS_TRX_OK ) { throw new DBTransactionStateError( $this, - "Cannot execute query from $fname while transaction status is ERROR. ", + "Cannot execute query from $fname while transaction status is ERROR.", [], $this->trxStatusCause ); + } elseif ( $this->trxStatus === self::STATUS_TRX_OK && $this->trxStatusIgnoredCause ) { + list( $iLastError, $iLastErrno, $iFname ) = $this->trxStatusIgnoredCause; + call_user_func( $this->deprecationLogger, + "Caller from $fname ignored an error originally raised from $iFname: " . + "[$iLastErrno] $iLastError" + ); + $this->trxStatusIgnoredCause = null; } } @@ -3432,57 +3453,104 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware $this->doSavepoint( $savepointId, $fname ); } - $this->trxAtomicLevels[] = [ $fname, $savepointId ]; + $sectionId = new AtomicSectionIdentifier; + $this->trxAtomicLevels[] = [ $fname, $sectionId, $savepointId ]; + + return $sectionId; } final public function endAtomic( $fname = __METHOD__ ) { - if ( !$this->trxLevel ) { - throw new DBUnexpectedError( $this, "No atomic transaction is open (got $fname)." ); + if ( !$this->trxLevel || !$this->trxAtomicLevels ) { + throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)." ); } - list( $savedFname, $savepointId ) = $this->trxAtomicLevels - ? array_pop( $this->trxAtomicLevels ) : [ null, null ]; + // Check if the current section matches $fname + $pos = count( $this->trxAtomicLevels ) - 1; + list( $savedFname, , $savepointId ) = $this->trxAtomicLevels[$pos]; + if ( $savedFname !== $fname ) { - throw new DBUnexpectedError( $this, "Invalid atomic section ended (got $fname)." ); + throw new DBUnexpectedError( + $this, + "Invalid atomic section ended (got $fname but expected $savedFname)." + ); } + // Remove the last section and re-index the array + $this->trxAtomicLevels = array_slice( $this->trxAtomicLevels, 0, $pos ); + if ( !$this->trxAtomicLevels && $this->trxAutomaticAtomic ) { $this->commit( $fname, self::FLUSHING_INTERNAL ); - } elseif ( $savepointId && $savepointId !== 'n/a' ) { + } elseif ( $savepointId !== null && $savepointId !== 'n/a' ) { $this->doReleaseSavepoint( $savepointId, $fname ); } } - final public function cancelAtomic( $fname = __METHOD__ ) { - if ( !$this->trxLevel ) { - throw new DBUnexpectedError( $this, "No atomic transaction is open (got $fname)." ); + final public function cancelAtomic( + $fname = __METHOD__, AtomicSectionIdentifier $sectionId = null + ) { + if ( !$this->trxLevel || !$this->trxAtomicLevels ) { + throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)." ); } - list( $savedFname, $savepointId ) = $this->trxAtomicLevels - ? array_pop( $this->trxAtomicLevels ) : [ null, null ]; - if ( $savedFname !== $fname ) { - throw new DBUnexpectedError( $this, "Invalid atomic section ended (got $fname)." ); + if ( $sectionId !== null ) { + // Find the (last) section with the given $sectionId + $pos = -1; + foreach ( $this->trxAtomicLevels as $i => list( $asFname, $asId, $spId ) ) { + if ( $asId === $sectionId ) { + $pos = $i; + } + } + if ( $pos < 0 ) { + throw new DBUnexpectedError( "Atomic section not found (for $fname)" ); + } + // Remove all descendant sections and re-index the array + $this->trxAtomicLevels = array_slice( $this->trxAtomicLevels, 0, $pos + 1 ); } - if ( !$savepointId ) { - throw new DBUnexpectedError( $this, "Uncancelable atomic section canceled (got $fname)." ); + + // Check if the current section matches $fname + $pos = count( $this->trxAtomicLevels ) - 1; + list( $savedFname, , $savepointId ) = $this->trxAtomicLevels[$pos]; + + if ( $savedFname !== $fname ) { + throw new DBUnexpectedError( + $this, + "Invalid atomic section ended (got $fname but expected $savedFname)." + ); } - if ( !$this->trxAtomicLevels && $this->trxAutomaticAtomic ) { - $this->rollback( $fname, self::FLUSHING_INTERNAL ); - } elseif ( $savepointId !== 'n/a' ) { - $this->doRollbackToSavepoint( $savepointId, $fname ); - $this->trxStatus = self::STATUS_TRX_OK; // no exception; recovered + // Remove the last section and re-index the array + $this->trxAtomicLevels = array_slice( $this->trxAtomicLevels, 0, $pos ); + + if ( $savepointId !== null ) { + // Rollback the transaction to the state just before this atomic section + if ( $savepointId === 'n/a' ) { + $this->rollback( $fname, self::FLUSHING_INTERNAL ); + } else { + $this->doRollbackToSavepoint( $savepointId, $fname ); + $this->trxStatus = self::STATUS_TRX_OK; // no exception; recovered + $this->trxStatusIgnoredCause = null; + } + } elseif ( $this->trxStatus > self::STATUS_TRX_ERROR ) { + // Put the transaction into an error state if it's not already in one + $this->trxStatus = self::STATUS_TRX_ERROR; + $this->trxStatusCause = new DBUnexpectedError( + $this, + "Uncancelable atomic section canceled (got $fname)." + ); } $this->affectedRowCount = 0; // for the sake of consistency } - final public function doAtomicSection( $fname, callable $callback ) { - $this->startAtomic( $fname, self::ATOMIC_CANCELABLE ); + final public function doAtomicSection( + $fname, callable $callback, $cancelable = self::ATOMIC_NOT_CANCELABLE + ) { + $sectionId = $this->startAtomic( $fname, $cancelable ); try { $res = call_user_func_array( $callback, [ $this, $fname ] ); } catch ( Exception $e ) { - $this->cancelAtomic( $fname ); + $this->cancelAtomic( $fname, $sectionId ); + throw $e; } $this->endAtomic( $fname ); @@ -3514,6 +3582,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware $this->doBegin( $fname ); $this->trxStatus = self::STATUS_TRX_OK; + $this->trxStatusIgnoredCause = null; $this->trxAtomicCounter = 0; $this->trxTimestamp = microtime( true ); $this->trxFname = $fname;