* @param Exception $e
*/
protected function handleException( Exception $e ) {
+ // Bug 63145: Rollback any open database transactions
+ MWExceptionHandler::rollbackMasterChangesAndLog( $e );
+
// Allow extra cleanup and logging
wfRunHooks( 'ApiMain::onException', array( $this, $e ) );
);
}
- if ( $flush != 'flush' ) {
+ if ( $flush !== 'flush' ) {
if ( !$this->mTrxLevel ) {
wfWarn( "$fname: No transaction to commit, something got out of sync!" );
} elseif ( $this->mTrxAutomatic ) {
* No-op on non-transactional databases.
*
* @param string $fname
+ * @param string $flush Flush flag, set to 'flush' to disable warnings about
+ * calling rollback when no transaction is in progress. This will silently
+ * break any ongoing explicit transaction. Only set the flush flag if you
+ * are sure that it is safe to ignore these warnings in your context.
+ * @since 1.23 Added $flush parameter
*/
- final public function rollback( $fname = __METHOD__ ) {
- if ( !$this->mTrxLevel ) {
- wfWarn( "$fname: No transaction to rollback, something got out of sync!" );
+ final public function rollback( $fname = __METHOD__, $flush = '' ) {
+ if ( $flush !== 'flush' ) {
+ if ( !$this->mTrxLevel ) {
+ wfWarn( "$fname: No transaction to rollback, something got out of sync!" );
+ } elseif ( $this->mTrxAutomatic ) {
+ wfWarn( "$fname: Explicit rollback of implicit transaction. Something may be out of sync!" );
+ }
+ } else {
+ if ( !$this->mTrxLevel ) {
+ return; // nothing to do
+ } elseif ( !$this->mTrxAutomatic ) {
+ wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" );
+ }
}
+
$this->doRollback( $fname );
$this->mTrxIdleCallbacks = array(); // cancel
$this->mTrxPreCommitCallbacks = array(); // cancel
function commitMasterChanges() {
$this->forEachLBCallMethod( 'commitMasterChanges' );
}
+
+ /**
+ * Rollback changes on all master connections
+ * @since 1.23
+ */
+ function rollbackMasterChanges() {
+ $this->forEachLBCallMethod( 'rollbackMasterChanges' );
+ }
+
+ /**
+ * Detemine if any master connection has pending changes.
+ * @since 1.23
+ * @return bool
+ */
+ function hasMasterChanges() {
+ $ret = false;
+ $this->forEachLB( function ( $lb ) use ( &$ret ) {
+ $ret = $ret || $lb->hasMasterChanges();
+ } );
+ return $ret;
+ }
}
/**
}
}
+ /**
+ * Issue ROLLBACK only on master, only if queries were done on connection
+ * @since 1.23
+ */
+ function rollbackMasterChanges() {
+ // Always 0, but who knows.. :)
+ $masterIndex = $this->getWriterIndex();
+ foreach ( $this->mConns as $conns2 ) {
+ if ( empty( $conns2[$masterIndex] ) ) {
+ continue;
+ }
+ /** @var DatabaseBase $conn */
+ foreach ( $conns2[$masterIndex] as $conn ) {
+ if ( $conn->trxLevel() && $conn->writesOrCallbacksPending() ) {
+ $conn->rollback( __METHOD__, 'flush' );
+ }
+ }
+ }
+ }
+
+ /**
+ * Determine if there are any pending changes that need to be rolled back
+ * or committed.
+ * @since 1.23
+ * @return bool
+ */
+ function hasMasterChanges() {
+ // Always 0, but who knows.. :)
+ $masterIndex = $this->getWriterIndex();
+ foreach ( $this->mConns as $conns2 ) {
+ if ( empty( $conns2[$masterIndex] ) ) {
+ continue;
+ }
+ /** @var DatabaseBase $conn */
+ foreach ( $conns2[$masterIndex] as $conn ) {
+ if ( $conn->trxLevel() && $conn->writesOrCallbacksPending() ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
/**
* @param $value null
* @return Mixed
}
}
+ /**
+ * If there are any open database transactions, roll them back and log
+ * the stack trace of the exception that should have been caught so the
+ * transaction could be aborted properly.
+ * @since 1.23
+ * @param Exception $e
+ */
+ public static function rollbackMasterChangesAndLog( Exception $e ) {
+ $factory = wfGetLBFactory();
+ if ( $factory->hasMasterChanges() ) {
+ wfDebugLog( 'Bug56269',
+ 'Exception thrown with an uncommited database transaction: ' .
+ MWExceptionHandler::getLogMessage( $e ) . "\n" .
+ $e->getTraceAsString()
+ );
+ $factory->rollbackMasterChanges();
+ }
+ }
+
/**
* Exception handler which simulates the appropriate catch() handling:
*
public static function handle( $e ) {
global $wgFullyInitialised;
+ self::rollbackMasterChangesAndLog( $e );
+
self::report( $e );
// Final cleanup