protected $mTrxPreCommitCallbacks = [];
/** @var array[] List of (callable, method name) */
protected $mTrxEndCallbacks = [];
- /** @var bool Whether to suppress triggering of post-commit callbacks */
- protected $suppressPostCommitCallbacks = false;
+ /** @var array[] Map of (name => (callable, method name)) */
+ protected $mTrxRecurringCallbacks = [];
+ /** @var bool Whether to suppress triggering of transaction end callbacks */
+ protected $mTrxEndCallbacksSuppressed = false;
/** @var string */
protected $mTablePrefix;
try {
// Handle callbacks in mTrxEndCallbacks
$this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
+ $this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK );
return null;
} catch ( Exception $e ) {
// Already logged; move on...
$ok = $this->insert( $table, $rows, $fname, [ 'IGNORE' ] ) && $ok;
} catch ( Exception $e ) {
if ( $useTrx ) {
- $this->rollback( $fname );
+ $this->rollback( $fname, self::FLUSHING_INTERNAL );
}
throw $e;
}
if ( $useTrx ) {
- $this->commit( $fname, self::TRANSACTION_INTERNAL );
+ $this->commit( $fname, self::FLUSHING_INTERNAL );
}
return $ok;
$this->mTrxPreCommitCallbacks[] = [ $callback, wfGetCaller() ];
} else {
// If no transaction is active, then make one for this callback
- $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
+ $this->startAtomic( __METHOD__ );
try {
call_user_func( $callback );
- $this->commit( __METHOD__ );
+ $this->endAtomic( __METHOD__ );
} catch ( Exception $e ) {
- $this->rollback( __METHOD__ );
+ $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
throw $e;
}
}
}
+ final public function setTransactionListener( $name, callable $callback = null ) {
+ if ( $callback ) {
+ $this->mTrxRecurringCallbacks[$name] = [ $callback, wfGetCaller() ];
+ } else {
+ unset( $this->mTrxRecurringCallbacks[$name] );
+ }
+ }
+
/**
- * Whether to disable running of post-commit callbacks
+ * Whether to disable running of post-COMMIT/ROLLBACK 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;
+ final public function setTrxEndCallbackSuppression( $suppress ) {
+ $this->mTrxEndCallbacksSuppressed = $suppress;
}
/**
* @throws Exception
*/
public function runOnTransactionIdleCallbacks( $trigger ) {
- if ( $this->suppressPostCommitCallbacks ) {
+ if ( $this->mTrxEndCallbacksSuppressed ) {
return;
}
// Some callbacks may use startAtomic/endAtomic, so make sure
// their transactions are ended so other callbacks don't fail
if ( $this->trxLevel() ) {
- $this->rollback( __METHOD__ );
+ $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
}
}
}
}
}
+ /**
+ * Actually run any "transaction listener" callbacks.
+ *
+ * This method should not be used outside of Database/LoadBalancer
+ *
+ * @param integer $trigger IDatabase::TRIGGER_* constant
+ * @throws Exception
+ * @since 1.20
+ */
+ public function runTransactionListenerCallbacks( $trigger ) {
+ if ( $this->mTrxEndCallbacksSuppressed ) {
+ return;
+ }
+
+ /** @var Exception $e */
+ $e = null; // first exception
+
+ foreach ( $this->mTrxRecurringCallbacks as $callback ) {
+ try {
+ list( $phpCallback ) = $callback;
+ $phpCallback( $trigger, $this );
+ } catch ( Exception $ex ) {
+ MWExceptionHandler::logException( $ex );
+ $e = $e ?: $ex;
+ }
+ }
+
+ if ( $e instanceof Exception ) {
+ throw $e; // re-throw any first exception
+ }
+ }
+
final public function startAtomic( $fname = __METHOD__ ) {
if ( !$this->mTrxLevel ) {
$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.
if ( !$this->getFlag( DBO_TRX ) ) {
try {
$res = call_user_func_array( $callback, [ $this, $fname ] );
} catch ( Exception $e ) {
- $this->rollback( $fname );
+ $this->rollback( $fname, self::FLUSHING_INTERNAL );
throw $e;
}
$this->endAtomic( $fname );
// @TODO: make this an exception at some point
$msg = "$fname: Implicit transaction already active (from {$this->mTrxFname}).";
wfLogDBError( $msg );
+ wfWarn( $msg );
return; // join the main transaction set
}
} 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)." );
+ $msg = "$fname: Implicit transaction expected (DBO_TRX set).";
+ wfLogDBError( $msg );
+ wfWarn( $msg );
return; // let any writes be in the main transaction
}
$this->mTrxTimestamp = microtime( true );
$this->mTrxFname = $fname;
$this->mTrxDoneWrites = false;
- $this->mTrxAutomatic = false;
+ $this->mTrxAutomatic = ( $mode === self::TRANSACTION_INTERNAL );
$this->mTrxAutomaticAtomic = false;
$this->mTrxAtomicLevels = [];
$this->mTrxShortId = wfRandomString( 12 );
return; // nothing to do
} elseif ( $this->mTrxAutomatic ) {
// @TODO: make this an exception at some point
- wfLogDBError( "$fname: Explicit commit of implicit transaction." );
+ $msg = "$fname: Explicit commit of implicit transaction.";
+ wfLogDBError( $msg );
+ wfWarn( $msg );
return; // wait for the main transaction set commit round
}
}
}
$this->runOnTransactionIdleCallbacks( self::TRIGGER_COMMIT );
+ $this->runTransactionListenerCallbacks( self::TRIGGER_COMMIT );
}
/**
$this->mTrxIdleCallbacks = []; // clear
$this->mTrxPreCommitCallbacks = []; // clear
$this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
+ $this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK );
}
/**
}
}
+ public function clearSnapshot( $fname = __METHOD__ ) {
+ if ( $this->writesOrCallbacksPending() || $this->explicitTrxActive() ) {
+ // This only flushes transactions to clear snapshots, not to write data
+ throw new DBUnexpectedError(
+ $this,
+ "$fname: Cannot COMMIT to clear snapshot because writes are pending."
+ );
+ }
+
+ $this->commit( $fname, self::FLUSHING_INTERNAL );
+ }
+
public function explicitTrxActive() {
return $this->mTrxLevel && ( $this->mTrxAtomicLevels || !$this->mTrxAutomatic );
}