transactions for SecondaryDataUpdate
authordaniel <daniel.kinzler@wikimedia.de>
Mon, 30 Apr 2012 14:18:33 +0000 (16:18 +0200)
committerdaniel <daniel.kinzler@wikimedia.de>
Mon, 30 Apr 2012 14:19:32 +0000 (16:19 +0200)
includes/SecondaryDBDataUpdate.php
includes/SecondaryDataUpdate.php
includes/WikiPage.php

index 1adb9a3..babe405 100644 (file)
@@ -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;
                }
index e02b2b3..53da6b4 100644 (file)
@@ -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
                }
        }
 
index 92f1d93..b67f702 100644 (file)
@@ -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();
        }
 
        /**