Multi-Content Revisions/Transaction Management

This page was part of the MCR proposal

To provide atomic storage operations, we need a way to maintain a transaction context across multiple stages of saving, and across different storage backends. One options would be a lightweight pair of interfaces:

interface Transaction {

	public function begin();

	public function commit();

	public function abort();
}

interface TransactionBuilder {
	/**
	 * @param callable $callback the action to perform on prepare.
	 */
	public function onPrepareDo( $callback );

	/**
	 * @param callable $callback the action to perform on commit.
	 */
	public function onCommitDo( $callback );

	/**
	 * @param callable $callback the action to perform on abort.
	 */
	public function onAbortDo( $callback );
}

(Code experiment: https://gerrit.wikimedia.org/r/#/c/299521/)

A TransactionManager class that implements the two interfaces would then just call all abort callbacks on abort(), and all prepare and later all commit callbacks on commit().

Services that need transactional context (such as BlobStore) would take a TransactionBuilder as a parameter to the relevant methods. Controllers can take a TransactionBuilder in the constructor, if the respective factory methods that creates the controller takes one as a parameter.

An SQL based storage service would use the TransactionBuilder as follows:

$dbw = $this->dbLoadBalancer->getConnection( DB_MASTER );
$dbw->startAtomic( __METHOD__ );

$dbw-insert( ... );

$method = __METHOD__;
$trx->onCommitDo( function() use ( $dbw, $method ) {
    $dbw->endAtomic( $method );
	// XXX: call $this->dbLoadBalancer->reuseConnection( $dbw ) here?
}, __METHOD__ );
$trx->onAbortDo( function() use ( $dbw ) {
    $dbw->rollback();
}, __METHOD__ );

// XXX: ...or call $this->dbLoadBalancer->reuseConnection( $dbw ) here?