From 30b0800e4a1a719155574adbdf26307c5ed8b6d4 Mon Sep 17 00:00:00 2001 From: daniel Date: Mon, 30 Apr 2012 16:18:33 +0200 Subject: [PATCH] transactions for SecondaryDataUpdate --- includes/SecondaryDBDataUpdate.php | 47 +++++++++++++++--- includes/SecondaryDataUpdate.php | 77 ++++++++++++++++++++++++++++-- includes/WikiPage.php | 13 +++-- 3 files changed, 123 insertions(+), 14 deletions(-) diff --git a/includes/SecondaryDBDataUpdate.php b/includes/SecondaryDBDataUpdate.php index 1adb9a3524..babe4057b3 100644 --- a/includes/SecondaryDBDataUpdate.php +++ b/includes/SecondaryDBDataUpdate.php @@ -26,16 +26,17 @@ abstract class SecondaryDBDataUpdate extends SecondaryDataUpdate { * @private */ var $mDb, //!< Database connection reference - $mOptions; //!< SELECT options to be used (array) + $mOptions, //!< SELECT options to be used (array) + $mHasTransaction;//!< bool whether a transaction is open on this object (internal use only!) /**@}}*/ /** * Constructor - **/ - public function __construct( ) { + **/ + public function __construct( ) { global $wgAntiLockFlags; - parent::__construct( ); + parent::__construct( ); if ( $wgAntiLockFlags & ALF_NO_LINK_LOCK ) { $this->mOptions = array(); @@ -43,15 +44,49 @@ abstract class SecondaryDBDataUpdate extends SecondaryDataUpdate { $this->mOptions = array( 'FOR UPDATE' ); } $this->mDb = wfGetDB( DB_MASTER ); + $this->mHasTransaction = false; } /** - * Invalidate the cache of a list of pages from a single namespace + * Begin a database transaction. + * + * Because nested transactions are not supportred by the Database class, this implementation + * checked Database::trxLevel() and only opens a transaction if none is yet active. + */ + public function beginTransaction() { + // NOTE: nested transactions are not supported, only start a transaction if none is open + if ( $this->mDb->trxLevel() === 0 ) { + $this->mDb->begin( get_class( $this ) . '::beginTransaction' ); + $this->mHasTransaction = true; + } + } + + /** + * Commit the database transaction started via beginTransaction (if any). + */ + public function commitTransaction() { + if ( $this->mHasTransaction ) { + $this->mDb->commit( get_class( $this ) . '::commitTransaction' ); + } + } + + /** + * Abort the database transaction started via beginTransaction (if any). + */ + public function abortTransaction() { + if ( $this->mHasTransaction ) { + $this->mDb->rollback( get_class( $this ) . '::abortTransaction' ); + } + } + + /** + * Invalidate the cache of a list of pages from a single namespace. + * This is intended for use by subclasses. * * @param $namespace Integer * @param $dbkeys Array */ - public function invalidatePages( $namespace, $dbkeys ) { + protected function invalidatePages( $namespace, $dbkeys ) { if ( !count( $dbkeys ) ) { return; } diff --git a/includes/SecondaryDataUpdate.php b/includes/SecondaryDataUpdate.php index e02b2b3104..53da6b49b0 100644 --- a/includes/SecondaryDataUpdate.php +++ b/includes/SecondaryDataUpdate.php @@ -33,16 +33,85 @@ abstract class SecondaryDataUpdate implements DeferrableUpdate { } /** - * Conveniance method, calls doUpdate() on every element in the array. + * Begin an appropriate transaction, if any. + * This default implementation does nothing. + */ + public function beginTransaction() { + //noop + } + + /** + * Commit the transaction started via beginTransaction, if any. + * This default implementation does nothing. + */ + public function commitTransaction() { + //noop + } + + /** + * Abort / roll back the transaction started via beginTransaction, if any. + * This default implementation does nothing. + */ + public function rollbackTransaction() { + //noop + } + + /** + * Conveniance method, calls doUpdate() on every SecondaryDataUpdate in the array. + * + * This methods supports transactions logic by first calling beginTransaction() + * on all updates in the array, then calling doUpdate() on each, and, if all goes well, + * then calling commitTransaction() on each update. If an error occurrs, + * rollbackTransaction() will be called on any update object that had beginTranscation() + * called but not yet commitTransaction(). + * + * This allows for limited transactional logic across multiple baceknds for storing + * secondary data. * * @static - * @param $updates array + * @param $updates array a list of SecondaryDataUpdate instances */ public static function runUpdates( $updates ) { if ( empty( $updates ) ) return; # nothing to do - foreach ( $updates as $update ) { - $update->doUpdate(); + $open_transactions = array(); + $exception = null; + + /** + * @var $update SecondaryDataUpdate + * @var $trans SecondaryDataUpdate + */ + + try { + // begin transactions + foreach ( $updates as $update ) { + $update->beginTransaction(); + $open_transactions[] = $update; + } + + // do work + foreach ( $updates as $update ) { + $update->doUpdate(); + } + + // commit transactions + while ( count( $open_transactions ) > 0 ) { + $trans = array_pop( $open_transactions ); + $trans->commitTransaction(); + } + } catch ( Exception $ex ) { + $exception = $ex; + wfDebug( "Caught exception, will rethrow after rollback: " . $ex->getMessage() ); + } + + // rollback remaining transactions + while ( count( $open_transactions ) > 0 ) { + $trans = array_pop( $open_transactions ); + $trans->rollbackTransaction(); + } + + if ( $exception ) { + throw $exception; // rethrow after cleanup } } diff --git a/includes/WikiPage.php b/includes/WikiPage.php index 92f1d93b44..b67f702fd5 100644 --- a/includes/WikiPage.php +++ b/includes/WikiPage.php @@ -384,11 +384,16 @@ class WikiPage extends Page { if ( $this->exists() ) { # look at the revision's actual content model $rev = $this->getRevision(); - return $rev->getContentModelName(); - } else { - # use the default model for this page - return $this->mTitle->getContentModelName(); + + if ( $rev !== null ) { + return $rev->getContentModelName(); + } else { + wfWarn( "Page exists but has no revision!" ); + } } + + # use the default model for this page + return $this->mTitle->getContentModelName(); } /** -- 2.20.1