protected $mConn = null;
protected $mOpened = false;
- /**
- * @since 1.20
- * @var array of Closure
- */
+ /** @var array of Closure */
protected $mTrxIdleCallbacks = array();
+ /** @var array of Closure */
+ protected $mTrxPreCommitCallbacks = array();
protected $mTablePrefix;
protected $mFlags;
/**
* Returns true if there is a transaction open with possible write
- * queries or transaction idle callbacks waiting on it to finish.
+ * queries or transaction pre-commit/idle callbacks waiting on it to finish.
*
* @return bool
*/
public function writesOrCallbacksPending() {
- return $this->mTrxLevel && ( $this->mTrxDoneWrites || $this->mTrxIdleCallbacks );
+ return $this->mTrxLevel && (
+ $this->mTrxDoneWrites || $this->mTrxIdleCallbacks || $this->mTrxPreCommitCallbacks
+ );
}
/**
# Transaction is gone, like it or not
$this->mTrxLevel = 0;
$this->mTrxIdleCallbacks = array(); // cancel
+ $this->mTrxPreCommitCallbacks = array(); // cancel
wfDebug( "Connection lost, reconnecting...\n" );
if ( $this->ping() ) {
* Callbacks must commit any transactions that they begin.
*
* This is useful for updates to different systems or separate transactions are needed.
+ * It can also be used for updates that easily cause deadlocks if locks are held too long.
*
+ * @param Closure $callback
* @since 1.20
+ */
+ final public function onTransactionIdle( Closure $callback ) {
+ $this->mTrxIdleCallbacks[] = $callback;
+ if ( !$this->mTrxLevel ) {
+ $this->runOnTransactionIdleCallbacks();
+ }
+ }
+
+ /**
+ * Run an anonymous function before the current transaction commits or now if there is none.
+ * If there is a transaction and it is rolled back, then the callback is cancelled.
+ * Callbacks must not start nor commit any transactions.
+ *
+ * This is useful for updates that easily cause deadlocks if locks are held too long
+ * but where atomicity is strongly desired for these and some related updates.
*
* @param Closure $callback
+ * @since 1.22
*/
- final public function onTransactionIdle( Closure $callback ) {
+ final public function onTransactionPreCommitOrIdle( Closure $callback ) {
if ( $this->mTrxLevel ) {
- $this->mTrxIdleCallbacks[] = $callback;
+ $this->mTrxPreCommitCallbacks[] = $callback;
} else {
- $callback();
+ $this->onTransactionIdle( $callback ); // this will trigger immediately
}
}
/**
- * Actually run the "on transaction idle" callbacks.
+ * Actually any "on transaction idle" callbacks.
*
* @since 1.20
*/
}
}
+ /**
+ * Actually any "on transaction pre-commit" callbacks.
+ *
+ * @since 1.22
+ */
+ protected function runOnTransactionPreCommitCallbacks() {
+ $e = null; // last exception
+ do { // callbacks may add callbacks :)
+ $callbacks = $this->mTrxPreCommitCallbacks;
+ $this->mTrxPreCommitCallbacks = array(); // recursion guard
+ foreach ( $callbacks as $callback ) {
+ try {
+ $callback();
+ } catch ( Exception $e ) {}
+ }
+ } while ( count( $this->mTrxPreCommitCallbacks ) );
+
+ if ( $e instanceof Exception ) {
+ throw $e; // re-throw any last exception
+ }
+ }
+
/**
* Begin a transaction. If a transaction is already in progress, that transaction will be committed before the
* new transaction is started.
}
}
+ $this->runOnTransactionPreCommitCallbacks();
$this->doCommit( $fname );
$this->runOnTransactionIdleCallbacks();
}
}
}
+ $this->runOnTransactionPreCommitCallbacks();
$this->doCommit( $fname );
$this->runOnTransactionIdleCallbacks();
}
}
$this->doRollback( $fname );
$this->mTrxIdleCallbacks = array(); // cancel
+ $this->mTrxPreCommitCallbacks = array(); // cancel
}
/**
}
public function __destruct() {
- if ( count( $this->mTrxIdleCallbacks ) ) { // sanity
- trigger_error( "Transaction idle callbacks still pending." );
+ if ( count( $this->mTrxIdleCallbacks ) || count( $this->mTrxPreCommitCallbacks ) ) {
+ trigger_error( "Transaction idle or pre-commit callbacks still pending." ); // sanity
}
}
}