Avoid deadlocks in User::incEditCount
authorAaron Schulz <aschulz@wikimedia.org>
Mon, 13 Apr 2015 23:02:16 +0000 (16:02 -0700)
committerAaron Schulz <aschulz@wikimedia.org>
Tue, 14 Apr 2015 04:59:44 +0000 (04:59 +0000)
* This makes concurrent editing less problematic

Change-Id: I930222d5e831bb3729194abbdcb3cab194c70494

includes/User.php

index f526fe0..9168c33 100644 (file)
@@ -4737,38 +4737,51 @@ class User implements IDBAccessObject {
                return $groups;
        }
 
+       /**
+        * Deferred version of incEditCountImmediate()
+        */
+       public function incEditCount() {
+               $that = $this;
+               wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle( function() use ( $that ) {
+                       $that->incEditCountImmediate();
+               } );
+       }
+
        /**
         * Increment the user's edit-count field.
         * Will have no effect for anonymous users.
+        * @since 1.26
         */
-       public function incEditCount() {
-               if ( !$this->isAnon() ) {
-                       $dbw = wfGetDB( DB_MASTER );
-                       $dbw->update(
-                               'user',
-                               array( 'user_editcount=user_editcount+1' ),
-                               array( 'user_id' => $this->getId() ),
-                               __METHOD__
-                       );
+       public function incEditCountImmediate() {
+               if ( $this->isAnon() ) {
+                       return;
+               }
 
-                       // Lazy initialization check...
-                       if ( $dbw->affectedRows() == 0 ) {
-                               // Now here's a goddamn hack...
-                               $dbr = wfGetDB( DB_SLAVE );
-                               if ( $dbr !== $dbw ) {
-                                       // If we actually have a slave server, the count is
-                                       // at least one behind because the current transaction
-                                       // has not been committed and replicated.
-                                       $this->initEditCount( 1 );
-                               } else {
-                                       // But if DB_SLAVE is selecting the master, then the
-                                       // count we just read includes the revision that was
-                                       // just added in the working transaction.
-                                       $this->initEditCount();
-                               }
+               $dbw = wfGetDB( DB_MASTER );
+               // No rows will be "affected" if user_editcount is NULL
+               $dbw->update(
+                       'user',
+                       array( 'user_editcount=user_editcount+1' ),
+                       array( 'user_id' => $this->getId() ),
+                       __METHOD__
+               );
+               // Lazy initialization check...
+               if ( $dbw->affectedRows() == 0 ) {
+                       // Now here's a goddamn hack...
+                       $dbr = wfGetDB( DB_SLAVE );
+                       if ( $dbr !== $dbw ) {
+                               // If we actually have a slave server, the count is
+                               // at least one behind because the current transaction
+                               // has not been committed and replicated.
+                               $this->initEditCount( 1 );
+                       } else {
+                               // But if DB_SLAVE is selecting the master, then the
+                               // count we just read includes the revision that was
+                               // just added in the working transaction.
+                               $this->initEditCount();
                        }
                }
-               // edit count in user cache too
+               // Edit count in user cache too
                $this->invalidateCache();
        }