Make onTransactionPreCommitOrIdle() atomic when immediate
authorAaron Schulz <aschulz@wikimedia.org>
Sun, 24 Jul 2016 18:22:59 +0000 (11:22 -0700)
committerAaron Schulz <aschulz@wikimedia.org>
Mon, 25 Jul 2016 19:15:31 +0000 (12:15 -0700)
One of the main uses of this method is to tack on some DB
updates to the end of atomic transactions to reduce lock
contention problems. In this case, multiple updates in the
callback are atomic. However, if no transaction is active,
then such updates were previously not atomic. Wrap them in
transactions now.

Change-Id: I18c56e65063a61000c4dfd1979bf972800b173ac

includes/db/Database.php
includes/db/IDatabase.php

index 9b73584..df83529 100644 (file)
@@ -2468,7 +2468,15 @@ abstract class DatabaseBase implements IDatabase {
                if ( $this->mTrxLevel ) {
                        $this->mTrxPreCommitCallbacks[] = [ $callback, wfGetCaller() ];
                } else {
-                       $this->onTransactionIdle( $callback ); // this will trigger immediately
+                       // If no transaction is active, then make one for this callback
+                       $this->begin( __METHOD__ );
+                       try {
+                               call_user_func( $callback );
+                               $this->commit( __METHOD__ );
+                       } catch ( Exception $e ) {
+                               $this->rollback( __METHOD__ );
+                               throw $e;
+                       }
                }
        }
 
index c024632..aa2a980 100644 (file)
@@ -1221,7 +1221,7 @@ interface IDatabase {
        public function getMasterPos();
 
        /**
-        * Run an anonymous function as soon as the current transaction commits or rolls back.
+        * Run a callback as soon as the current transaction commits or rolls back.
         * An error is thrown if no transaction is pending. Queries in the function will run in
         * AUTO-COMMIT mode unless there are begin() calls. Callbacks must commit any transactions
         * that they begin.
@@ -1238,7 +1238,7 @@ interface IDatabase {
        public function onTransactionResolution( callable $callback );
 
        /**
-        * Run an anonymous function as soon as there is no transaction pending.
+        * Run a callback as soon as there is no transaction pending.
         * If there is a transaction and it is rolled back, then the callback is cancelled.
         * Queries in the function will run in AUTO-COMMIT mode unless there are begin() calls.
         * Callbacks must commit any transactions that they begin.
@@ -1259,9 +1259,10 @@ interface IDatabase {
        public function onTransactionIdle( callable $callback );
 
        /**
-        * Run an anonymous function before the current transaction commits or now if there is none.
+        * Run a callback 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.
+        * Callbacks must not start nor commit any transactions. If no transaction is active,
+        * then a transaction will wrap the callback.
         *
         * This is useful for updates that easily cause deadlocks if locks are held too long
         * but where atomicity is strongly desired for these updates and some related updates.