X-Git-Url: https://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=blobdiff_plain;f=includes%2Fdb%2FDatabase.php;h=3dc6e9213b78efeae22b9daccaf1bbbaff2046d4;hp=6bdcb24cdb443b98eb72ba703e64719a5bac4c82;hb=708c02281e6e8880ae2cebbda7f353ce97841f94;hpb=e5facc46bc170c302438f60849041b0d6be75e82 diff --git a/includes/db/Database.php b/includes/db/Database.php index 6bdcb24cdb..3dc6e9213b 100644 --- a/includes/db/Database.php +++ b/includes/db/Database.php @@ -52,10 +52,14 @@ abstract class DatabaseBase implements IDatabase { protected $mConn = null; protected $mOpened = false; - /** @var callable[] */ + /** @var array[] List of (callable, method name) */ protected $mTrxIdleCallbacks = []; - /** @var callable[] */ + /** @var array[] List of (callable, method name) */ protected $mTrxPreCommitCallbacks = []; + /** @var array[] List of (callable, method name) */ + protected $mTrxEndCallbacks = []; + /** @var bool Whether to suppress triggering of post-commit callbacks */ + protected $suppressPostCommitCallbacks = false; protected $mTablePrefix; protected $mSchema; @@ -692,9 +696,6 @@ abstract class DatabaseBase implements IDatabase { } public function close() { - if ( count( $this->mTrxIdleCallbacks ) ) { // sanity - throw new MWException( "Transaction idle callbacks still pending." ); - } if ( $this->mConn ) { if ( $this->trxLevel() ) { if ( !$this->mTrxAutomatic ) { @@ -707,6 +708,8 @@ abstract class DatabaseBase implements IDatabase { $closed = $this->closeConnection(); $this->mConn = false; + } elseif ( $this->mTrxIdleCallbacks || $this->mTrxEndCallbacks ) { // sanity + throw new MWException( "Transaction callbacks still pending." ); } else { $closed = true; } @@ -2385,14 +2388,19 @@ abstract class DatabaseBase implements IDatabase { * queries. If a deadlock occurs during the processing, the transaction * will be rolled back and the callback function will be called again. * + * Avoid using this method outside of Job or Maintenance classes. + * * Usage: * $dbw->deadlockLoop( callback, ... ); * * Extra arguments are passed through to the specified callback function. + * This method requires that no transactions are already active to avoid + * causing premature commits or exceptions. * * Returns whatever the callback function returned on its successful, * iteration, or false on error, for example if the retry limit was * reached. + * * @return mixed * @throws DBUnexpectedError * @throws Exception @@ -2448,38 +2456,80 @@ abstract class DatabaseBase implements IDatabase { return false; } - final public function onTransactionIdle( $callback ) { + public function serverIsReadOnly() { + return false; + } + + final public function onTransactionResolution( callable $callback ) { + if ( !$this->mTrxLevel ) { + throw new DBUnexpectedError( $this, "No transaction is active." ); + } + $this->mTrxEndCallbacks[] = [ $callback, wfGetCaller() ]; + } + + final public function onTransactionIdle( callable $callback ) { $this->mTrxIdleCallbacks[] = [ $callback, wfGetCaller() ]; if ( !$this->mTrxLevel ) { - $this->runOnTransactionIdleCallbacks(); + $this->runOnTransactionIdleCallbacks( self::TRIGGER_IDLE ); } } - final public function onTransactionPreCommitOrIdle( $callback ) { + final public function onTransactionPreCommitOrIdle( callable $callback ) { if ( $this->mTrxLevel ) { $this->mTrxPreCommitCallbacks[] = [ $callback, wfGetCaller() ]; } else { - $this->onTransactionIdle( $callback ); // this will trigger immediately + // If no transaction is active, then make one for this callback + $this->begin( __METHOD__ ); + try { + call_user_func( $callback ); + $this->commit( __METHOD__ ); + } catch ( Exception $e ) { + $this->rollback( __METHOD__ ); + throw $e; + } } } /** - * Actually any "on transaction idle" callbacks. + * Whether to disable running of post-commit callbacks + * + * This method should not be used outside of Database/LoadBalancer + * + * @param bool $suppress + * @since 1.28 + */ + final public function setPostCommitCallbackSupression( $suppress ) { + $this->suppressPostCommitCallbacks = $suppress; + } + + /** + * Actually run and consume any "on transaction idle/resolution" callbacks. * + * This method should not be used outside of Database/LoadBalancer + * + * @param integer $trigger IDatabase::TRIGGER_* constant * @since 1.20 */ - protected function runOnTransactionIdleCallbacks() { + public function runOnTransactionIdleCallbacks( $trigger ) { + if ( $this->suppressPostCommitCallbacks ) { + return; + } + $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled? $e = $ePrior = null; // last exception do { // callbacks may add callbacks :) - $callbacks = $this->mTrxIdleCallbacks; - $this->mTrxIdleCallbacks = []; // recursion guard + $callbacks = array_merge( + $this->mTrxIdleCallbacks, + $this->mTrxEndCallbacks // include "transaction resolution" callbacks + ); + $this->mTrxIdleCallbacks = []; // consumed (and recursion guard) + $this->mTrxEndCallbacks = []; // consumed (recursion guard) foreach ( $callbacks as $callback ) { try { list( $phpCallback ) = $callback; $this->clearFlag( DBO_TRX ); // make each query its own transaction - call_user_func( $phpCallback ); + call_user_func_array( $phpCallback, [ $trigger ] ); if ( $autoTrx ) { $this->setFlag( DBO_TRX ); // restore automatic begin() } else { @@ -2505,15 +2555,17 @@ abstract class DatabaseBase implements IDatabase { } /** - * Actually any "on transaction pre-commit" callbacks. + * Actually run and consume any "on transaction pre-commit" callbacks. + * + * This method should not be used outside of Database/LoadBalancer * * @since 1.22 */ - protected function runOnTransactionPreCommitCallbacks() { + public function runOnTransactionPreCommitCallbacks() { $e = $ePrior = null; // last exception do { // callbacks may add callbacks :) $callbacks = $this->mTrxPreCommitCallbacks; - $this->mTrxPreCommitCallbacks = []; // recursion guard + $this->mTrxPreCommitCallbacks = []; // consumed (and recursion guard) foreach ( $callbacks as $callback ) { try { list( $phpCallback ) = $callback; @@ -2548,12 +2600,12 @@ abstract class DatabaseBase implements IDatabase { final public function endAtomic( $fname = __METHOD__ ) { if ( !$this->mTrxLevel ) { - throw new DBUnexpectedError( $this, 'No atomic transaction is open.' ); + throw new DBUnexpectedError( $this, "No atomic transaction is open (got $fname)." ); } if ( !$this->mTrxAtomicLevels || array_pop( $this->mTrxAtomicLevels ) !== $fname ) { - throw new DBUnexpectedError( $this, 'Invalid atomic section ended.' ); + throw new DBUnexpectedError( $this, "Invalid atomic section ended (got $fname)." ); } if ( !$this->mTrxAtomicLevels && $this->mTrxAutomaticAtomic ) { @@ -2593,7 +2645,7 @@ abstract class DatabaseBase implements IDatabase { } else { // The transaction was automatic and has done write operations if ( $this->mTrxDoneWrites ) { - wfDebug( "$fname: Automatic transaction with writes in progress" . + wfLogDBError( "$fname: Automatic transaction with writes in progress" . " (from {$this->mTrxFname}), performing implicit commit!\n" ); } @@ -2607,10 +2659,11 @@ abstract class DatabaseBase implements IDatabase { $this->getTransactionProfiler()->transactionWritingOut( $this->mServer, $this->mDBname, $this->mTrxShortId, $writeTime ); } - $this->runOnTransactionIdleCallbacks(); + + $this->runOnTransactionIdleCallbacks( self::TRIGGER_COMMIT ); } - # Avoid fatals if close() was called + // Avoid fatals if close() was called $this->assertOpen(); $this->doBegin( $fname ); @@ -2620,8 +2673,6 @@ abstract class DatabaseBase implements IDatabase { $this->mTrxAutomatic = false; $this->mTrxAutomaticAtomic = false; $this->mTrxAtomicLevels = []; - $this->mTrxIdleCallbacks = []; - $this->mTrxPreCommitCallbacks = []; $this->mTrxShortId = wfRandomString( 12 ); $this->mTrxWriteDuration = 0.0; $this->mTrxWriteCallers = []; @@ -2671,7 +2722,7 @@ abstract class DatabaseBase implements IDatabase { } } - # Avoid fatals if close() was called + // Avoid fatals if close() was called $this->assertOpen(); $this->runOnTransactionPreCommitCallbacks(); @@ -2682,7 +2733,8 @@ abstract class DatabaseBase implements IDatabase { $this->getTransactionProfiler()->transactionWritingOut( $this->mServer, $this->mDBname, $this->mTrxShortId, $writeTime ); } - $this->runOnTransactionIdleCallbacks(); + + $this->runOnTransactionIdleCallbacks( self::TRIGGER_COMMIT ); } /** @@ -2710,17 +2762,19 @@ abstract class DatabaseBase implements IDatabase { } } - # Avoid fatals if close() was called + // Avoid fatals if close() was called $this->assertOpen(); $this->doRollback( $fname ); - $this->mTrxIdleCallbacks = []; // cancel - $this->mTrxPreCommitCallbacks = []; // cancel $this->mTrxAtomicLevels = []; if ( $this->mTrxDoneWrites ) { $this->getTransactionProfiler()->transactionWritingOut( $this->mServer, $this->mDBname, $this->mTrxShortId ); } + + $this->mTrxIdleCallbacks = []; // clear + $this->mTrxPreCommitCallbacks = []; // clear + $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK ); } /** @@ -3294,9 +3348,14 @@ abstract class DatabaseBase implements IDatabase { if ( $this->mTrxLevel && $this->mTrxDoneWrites ) { trigger_error( "Uncommitted DB writes (transaction from {$this->mTrxFname})." ); } - if ( count( $this->mTrxIdleCallbacks ) || count( $this->mTrxPreCommitCallbacks ) ) { + $danglingCallbacks = array_merge( + $this->mTrxIdleCallbacks, + $this->mTrxPreCommitCallbacks, + $this->mTrxEndCallbacks + ); + if ( $danglingCallbacks ) { $callers = []; - foreach ( $this->mTrxIdleCallbacks as $callbackInfo ) { + foreach ( $danglingCallbacks as $callbackInfo ) { $callers[] = $callbackInfo[1]; } $callers = implode( ', ', $callers );