Aaron Schulz has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/227274

Change subject: Avoid some possible deadlocks on account creation
......................................................................

Avoid some possible deadlocks on account creation

* This uses a non-blocking $wgMemc lock to reserve the user
  name in question. This should prevent two threads from
  reaching LOCK IN SHARE MODE and getting stuck on INSERT.
* This adds a BagOStuff::getScopedLock() convenience method.
  It uses less queries than LockManager by being EX only.
  Other callers, like auth plugins may want to use this too.

Bug: T106850
Change-Id: Iecf95206d712367f5d202f76ab0eaa9d7bdabf2b
---
M includes/libs/objectcache/BagOStuff.php
M includes/specials/SpecialUserlogin.php
M tests/phpunit/includes/objectcache/BagOStuffTest.php
3 files changed, 61 insertions(+), 3 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core 
refs/changes/74/227274/1

diff --git a/includes/libs/objectcache/BagOStuff.php 
b/includes/libs/objectcache/BagOStuff.php
index 65ff0ee..c4842a1 100644
--- a/includes/libs/objectcache/BagOStuff.php
+++ b/includes/libs/objectcache/BagOStuff.php
@@ -206,10 +206,12 @@
        /**
         * @param string $key
         * @param int $timeout Lock wait timeout; 0 for non-blocking [optional]
-        * @param int $expiry Lock expiry [optional]
+        * @param int $expiry Lock expiry [optional]; 1 day maximum
         * @return bool Success
         */
        public function lock( $key, $timeout = 6, $expiry = 6 ) {
+               $expiry = min( $expiry ?: INF, 86400 );
+
                $this->clearLastError();
                $timestamp = microtime( true ); // starting UNIX timestamp
                if ( $this->add( "{$key}:lock", 1, $expiry ) ) {
@@ -221,7 +223,6 @@
                $uRTT = ceil( 1e6 * ( microtime( true ) - $timestamp ) ); // 
estimate RTT (us)
                $sleep = 2 * $uRTT; // rough time to do get()+set()
 
-               $locked = false; // lock acquired
                $attempts = 0; // failed attempts
                do {
                        if ( ++$attempts >= 3 && $sleep <= 5e5 ) {
@@ -249,6 +250,41 @@
        }
 
        /**
+        * Get a lightweight exclusive self-unlocking lock
+        *
+        * Note that the same lock cannot be acquired twice.
+        *
+        * This is useful for task de-duplication or to avoid obtrusive
+        * (though non-corrupting) DB errors like INSERT key conflicts
+        * or deadlocks when using LOCK IN SHARE MODE.
+        *
+        * @param string $key
+        * @param int $timeout Lock wait timeout; 0 for non-blocking [optional]
+        * @param int $expiry Lock expiry [optional]; 1 day maximum
+        * @return ScopedLock|null Returns null on failure
+        * @since 1.26
+        */
+       final public function getScopedLock( $key, $timeout = 6, $expiry = 30 ) 
{
+               $expiry = min( $expiry ?: INF, 86400 );
+
+               if ( !$this->lock( $key, $timeout, $expiry ) ) {
+                       return null;
+               }
+
+               $lSince = microtime( true ); // lock timestamp
+               $that = $this;
+
+               return new ScopedCallback( function() use ( $that, $key, 
$lSince, $expiry ) {
+                       $latency = .050; // latency skew (err towards keeping 
lock present)
+                       $age = ( microtime( true ) - $lSince + $latency );
+                       if ( ( $age + $latency ) >= $expiry ) {
+                               return; // expired; it's not "safe" to delete 
the key
+                       }
+                       $that->unlock( $key );
+               } );
+       }
+
+       /**
         * Delete all objects expiring before a certain date.
         * @param string $date The reference date in MW format
         * @param callable|bool $progressCallback Optional, a function which 
will be called
diff --git a/includes/specials/SpecialUserlogin.php 
b/includes/specials/SpecialUserlogin.php
index 8491f89..fb1abc1 100644
--- a/includes/specials/SpecialUserlogin.php
+++ b/includes/specials/SpecialUserlogin.php
@@ -531,7 +531,11 @@
                $u = User::newFromName( $this->mUsername, 'creatable' );
                if ( !$u ) {
                        return Status::newFatal( 'noname' );
-               } elseif ( 0 != $u->idForName( User::READ_LOCKING ) ) {
+               }
+
+               # Make sure the user does not exist already
+               $lock = $wgMemc->getScopedLock( wfMemcKey( 'username', md5( 
$this->mUsername ) ), 0 );
+               if ( !$lock || $u->idForName( User::READ_LOCKING ) ) {
                        return Status::newFatal( 'userexists' );
                }
 
diff --git a/tests/phpunit/includes/objectcache/BagOStuffTest.php 
b/tests/phpunit/includes/objectcache/BagOStuffTest.php
index 4516bb4..f6c7c17 100644
--- a/tests/phpunit/includes/objectcache/BagOStuffTest.php
+++ b/tests/phpunit/includes/objectcache/BagOStuffTest.php
@@ -3,6 +3,7 @@
  * @author Matthias Mullie <[email protected]>
  */
 class BagOStuffTest extends MediaWikiTestCase {
+       /** @var BagOStuff */
        private $cache;
 
        protected function setUp() {
@@ -152,4 +153,21 @@
                $this->cache->delete( $key1 );
                $this->cache->delete( $key2 );
        }
+
+       /**
+        * @covers BagOStuff::getScopedLock
+        */
+       public function testGetScopedLock() {
+               $key = wfMemcKey( 'test' );
+               $value1 = $this->cache->getScopedLock( $key, 0 );
+               $value2 = $this->cache->getScopedLock( $key, 0 );
+
+               $this->assertType( 'ScopedCallback', $value1, 'First lock 
returned callback' );
+               $this->assertNull( $value2, 'Second lock returned no callback' 
);
+
+               unset( $value1 );
+
+               $value3 = $this->cache->getScopedLock( $key, 0 );
+               $this->assertType( 'ScopedCallback', $value3, 'Lock returned 
callback after release' );
+       }
 }

-- 
To view, visit https://gerrit.wikimedia.org/r/227274
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Iecf95206d712367f5d202f76ab0eaa9d7bdabf2b
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Aaron Schulz <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to