From 3ab0c7d4d7b5b788b2f57a95408e5984d83f0c51 Mon Sep 17 00:00:00 2001 From: Aaron Schulz Date: Tue, 22 Dec 2015 18:18:59 -0800 Subject: [PATCH] Added IDatabase::doAtomicSection() convenience method This can replace startAtomic/endAtomic pairs. Bug: T122115 Change-Id: I01c8e4773ec2b42dbe87a5508a10b997be103c11 --- includes/db/DBConnRef.php | 4 +++ includes/db/Database.php | 15 ++++++++++ includes/db/IDatabase.php | 28 +++++++++++++++++ includes/specialpage/QueryPage.php | 48 +++++++++++++++++++----------- 4 files changed, 78 insertions(+), 17 deletions(-) diff --git a/includes/db/DBConnRef.php b/includes/db/DBConnRef.php index 5443eeb902..3cac22a13c 100644 --- a/includes/db/DBConnRef.php +++ b/includes/db/DBConnRef.php @@ -429,6 +429,10 @@ class DBConnRef implements IDatabase { return $this->__call( __FUNCTION__, func_get_args() ); } + public function doAtomicSection( $fname, $callback ) { + return $this->__call( __FUNCTION__, func_get_args() ); + } + public function begin( $fname = __METHOD__ ) { return $this->__call( __FUNCTION__, func_get_args() ); } diff --git a/includes/db/Database.php b/includes/db/Database.php index 31e26531d8..abd4e3af59 100644 --- a/includes/db/Database.php +++ b/includes/db/Database.php @@ -3446,6 +3446,21 @@ abstract class DatabaseBase implements IDatabase { } } + final public function doAtomicSection( $fname, $callback ) { + if ( !is_callable( $callback ) ) { + throw new UnexpectedValueException( "Invalid callback." ); + }; + + $this->startAtomic( $fname ); + try { + call_user_func_array( $callback, array( $this, $fname ) ); + } catch ( Exception $e ) { + $this->rollback( $fname ); + throw $e; + } + $this->endAtomic( $fname ); + } + /** * Begin a transaction. If a transaction is already in progress, * that transaction will be committed before the new transaction is started. diff --git a/includes/db/IDatabase.php b/includes/db/IDatabase.php index 4674c17bed..31b2758c33 100644 --- a/includes/db/IDatabase.php +++ b/includes/db/IDatabase.php @@ -1268,6 +1268,34 @@ interface IDatabase { */ public function endAtomic( $fname = __METHOD__ ); + /** + * Run a callback to do an atomic set of updates for this database + * + * The $callback takes the following arguments: + * - This database object + * - The value of $fname + * + * If any exception occurs in the callback, then rollback() will be called and the error will + * be re-thrown. It may also be that the rollback itself fails with an exception before then. + * In any case, such errors are expected to terminate the request, without any outside caller + * attempting to catch errors and commit anyway. Note that any rollback undoes all prior + * atomic section and uncommitted updates, which trashes the current request, requiring an + * error to be displayed. + * + * This can be an alternative to explicit startAtomic()/endAtomic() calls. + * + * @see DatabaseBase::startAtomic + * @see DatabaseBase::endAtomic + * + * @param string $fname Caller name (usually __METHOD__) + * @param callable $callback Callback that issues DB updates + * @throws DBError + * @throws RuntimeException + * @throws UnexpectedValueException + * @since 1.27 + */ + public function doAtomicSection( $fname, $callback ); + /** * Begin a transaction. If a transaction is already in progress, * that transaction will be committed before the new transaction is started. diff --git a/includes/specialpage/QueryPage.php b/includes/specialpage/QueryPage.php index 9755e8e0e0..2d25710614 100644 --- a/includes/specialpage/QueryPage.php +++ b/includes/specialpage/QueryPage.php @@ -325,25 +325,39 @@ abstract class QueryPage extends SpecialPage { $value = 0; } - $vals[] = array( 'qc_type' => $this->getName(), - 'qc_namespace' => $row->namespace, - 'qc_title' => $row->title, - 'qc_value' => $value ); + $vals[] = array( + 'qc_type' => $this->getName(), + 'qc_namespace' => $row->namespace, + 'qc_title' => $row->title, + 'qc_value' => $value + ); } - $dbw->startAtomic( __METHOD__ ); - # Clear out any old cached data - $dbw->delete( 'querycache', array( 'qc_type' => $this->getName() ), $fname ); - # Save results into the querycache table on the master - if ( count( $vals ) ) { - $dbw->insert( 'querycache', $vals, __METHOD__ ); - } - # Update the querycache_info record for the page - $dbw->delete( 'querycache_info', array( 'qci_type' => $this->getName() ), $fname ); - $dbw->insert( 'querycache_info', - array( 'qci_type' => $this->getName(), 'qci_timestamp' => $dbw->timestamp() ), - $fname ); - $dbw->endAtomic( __METHOD__ ); + $that = $this; + $dbw->doAtomicSection( + __METHOD__, + function ( IDatabase $dbw, $fname ) use ( $that, $vals ) { + # Clear out any old cached data + $dbw->delete( 'querycache', + array( 'qc_type' => $that->getName() ), + $fname + ); + # Save results into the querycache table on the master + if ( count( $vals ) ) { + $dbw->insert( 'querycache', $vals, $fname ); + } + # Update the querycache_info record for the page + $dbw->delete( 'querycache_info', + array( 'qci_type' => $that->getName() ), + $fname + ); + $dbw->insert( 'querycache_info', + array( 'qci_type' => $that->getName(), + 'qci_timestamp' => $dbw->timestamp() ), + $fname + ); + } + ); } } catch ( DBError $e ) { if ( !$ignoreErrors ) { -- 2.20.1