Merge "Converted User object cache to the WAN cache"
[lhc/web/wiklou.git] / includes / User.php
index b122a9f..7902119 100644 (file)
@@ -410,15 +410,14 @@ class User implements IDBAccessObject {
         * @since 1.25
         */
        protected function loadFromCache() {
-               global $wgMemc;
-
                if ( $this->mId == 0 ) {
                        $this->loadDefaults();
                        return false;
                }
 
+               $cache = ObjectCache::getMainWANInstance();
                $key = wfMemcKey( 'user', 'id', $this->mId );
-               $data = $wgMemc->get( $key );
+               $data = $cache->get( $key );
                if ( !is_array( $data ) || $data['mVersion'] < self::VERSION ) {
                        // Object is expired
                        return false;
@@ -440,8 +439,6 @@ class User implements IDBAccessObject {
         * This method should not be called outside the User class
         */
        public function saveToCache() {
-               global $wgMemc;
-
                $this->load();
                $this->loadGroups();
                $this->loadOptions();
@@ -451,6 +448,8 @@ class User implements IDBAccessObject {
                        return;
                }
 
+               $cache = ObjectCache::getMainWANInstance();
+
                // The cache needs good consistency due to its high TTL, so the user
                // should have been loaded from the master to avoid lag amplification.
                if ( !( $this->queryFlagsUsed & self::READ_LATEST ) ) {
@@ -465,7 +464,7 @@ class User implements IDBAccessObject {
                $data['mVersion'] = self::VERSION;
                $key = wfMemcKey( 'user', 'id', $this->mId );
 
-               $wgMemc->set( $key, $data );
+               $cache->set( $key, $data );
        }
 
        /** @name newFrom*() static factory methods */
@@ -1431,37 +1430,85 @@ class User implements IDBAccessObject {
        public function addAutopromoteOnceGroups( $event ) {
                global $wgAutopromoteOnceLogInRC, $wgAuth;
 
-               $toPromote = array();
-               if ( !wfReadOnly() && $this->getId() ) {
-                       $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
-                       if ( count( $toPromote ) ) {
-                               $oldGroups = $this->getGroups(); // previous groups
+               if ( wfReadOnly() || !$this->getId() ) {
+                       return array();
+               }
 
-                               foreach ( $toPromote as $group ) {
-                                       $this->addGroup( $group );
-                               }
-                               // update groups in external authentication database
-                               $wgAuth->updateExternalDBGroups( $this, $toPromote );
+               $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
+               if ( !count( $toPromote ) ) {
+                       return array();
+               }
 
-                               $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
+               if ( !$this->checkAndSetTouched() ) {
+                       return array(); // raced out (bug T48834)
+               }
 
-                               $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
-                               $logEntry->setPerformer( $this );
-                               $logEntry->setTarget( $this->getUserPage() );
-                               $logEntry->setParameters( array(
-                                       '4::oldgroups' => $oldGroups,
-                                       '5::newgroups' => $newGroups,
-                               ) );
-                               $logid = $logEntry->insert();
-                               if ( $wgAutopromoteOnceLogInRC ) {
-                                       $logEntry->publish( $logid );
-                               }
-                       }
+               $oldGroups = $this->getGroups(); // previous groups
+               foreach ( $toPromote as $group ) {
+                       $this->addGroup( $group );
+               }
+
+               // update groups in external authentication database
+               $wgAuth->updateExternalDBGroups( $this, $toPromote );
+
+               $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
+
+               $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
+               $logEntry->setPerformer( $this );
+               $logEntry->setTarget( $this->getUserPage() );
+               $logEntry->setParameters( array(
+                       '4::oldgroups' => $oldGroups,
+                       '5::newgroups' => $newGroups,
+               ) );
+               $logid = $logEntry->insert();
+               if ( $wgAutopromoteOnceLogInRC ) {
+                       $logEntry->publish( $logid );
                }
 
                return $toPromote;
        }
 
+       /**
+        * Bump user_touched if it didn't change since this object was loaded
+        *
+        * On success, the mTouched field is updated.
+        * The user serialization cache is always cleared.
+        *
+        * @return bool Whether user_touched was actually updated
+        * @since 1.26
+        */
+       protected function checkAndSetTouched() {
+               $this->load();
+
+               if ( !$this->mId ) {
+                       return false; // anon
+               }
+
+               // Get a new user_touched that is higher than the old one
+               $oldTouched = $this->mTouched;
+               $newTouched = $this->newTouchedTimestamp();
+
+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->update( 'user',
+                       array( 'user_touched' => $dbw->timestamp( $newTouched ) ),
+                       array(
+                               'user_id' => $this->mId,
+                               'user_touched' => $dbw->timestamp( $oldTouched ) // CAS check
+                       ),
+                       __METHOD__
+               );
+               $success = ( $dbw->affectedRows() > 0 );
+
+               if ( $success ) {
+                       $this->mTouched = $newTouched;
+               }
+
+               // Clears on failure too since that is desired if the cache is stale
+               $this->clearSharedCache();
+
+               return $success;
+       }
+
        /**
         * Clear various cached data stored in this object. The cache of the user table
         * data (i.e. self::$mCacheVars) is not cleared unless $reloadFrom is given.
@@ -2204,8 +2251,6 @@ class User implements IDBAccessObject {
         *   page. Ignored if null or !$val.
         */
        public function setNewtalk( $val, $curRev = null ) {
-               global $wgMemc;
-
                if ( wfReadOnly() ) {
                        return;
                }
@@ -2227,12 +2272,6 @@ class User implements IDBAccessObject {
                        $changed = $this->deleteNewtalk( $field, $id );
                }
 
-               if ( $this->isAnon() ) {
-                       // Anons have a separate memcached space, since
-                       // user records aren't kept for them.
-                       $key = wfMemcKey( 'newtalk', 'ip', $id );
-                       $wgMemc->set( $key, $val ? 1 : 0, 1800 );
-               }
                if ( $changed ) {
                        $this->invalidateCache();
                }
@@ -2262,11 +2301,11 @@ class User implements IDBAccessObject {
         * Called implicitly from invalidateCache() and saveSettings().
         */
        public function clearSharedCache() {
-               global $wgMemc;
-
                $this->load();
                if ( $this->mId ) {
-                       $wgMemc->delete( wfMemcKey( 'user', 'id', $this->mId ) );
+                       $cache = ObjectCache::getMainWANInstance();
+
+                       $cache->delete( wfMemcKey( 'user', 'id', $this->mId ) );
                }
        }
 
@@ -2293,15 +2332,14 @@ class User implements IDBAccessObject {
         * @since 1.25
         */
        public function touch() {
-               global $wgMemc;
-
                $this->load();
 
                if ( $this->mId ) {
+                       $this->mQuickTouched = $this->newTouchedTimestamp();
+
+                       $cache = ObjectCache::getMainWANInstance();
                        $key = wfMemcKey( 'user-quicktouched', 'id', $this->mId );
-                       $timestamp = $this->newTouchedTimestamp();
-                       $wgMemc->set( $key, $timestamp );
-                       $this->mQuickTouched = $timestamp;
+                       $cache->touchCheckKey( $key );
                }
        }
 
@@ -2319,16 +2357,16 @@ class User implements IDBAccessObject {
         * @return string TS_MW Timestamp
         */
        public function getTouched() {
-               global $wgMemc;
-
                $this->load();
 
                if ( $this->mId ) {
                        if ( $this->mQuickTouched === null ) {
+                               $cache = ObjectCache::getMainWANInstance();
                                $key = wfMemcKey( 'user-quicktouched', 'id', $this->mId );
-                               $timestamp = $wgMemc->get( $key );
+
+                               $timestamp = $cache->getCheckKeyTime( $key );
                                if ( $timestamp ) {
-                                       $this->mQuickTouched = $timestamp;
+                                       $this->mQuickTouched = wfTimestamp( TS_MW, $timestamp );
                                } else {
                                        # Set the timestamp to get HTTP 304 cache hits
                                        $this->touch();
@@ -2341,6 +2379,17 @@ class User implements IDBAccessObject {
                return $this->mTouched;
        }
 
+       /**
+        * Get the user_touched timestamp field (time of last DB updates)
+        * @return string TS_MW Timestamp
+        * @since 1.26
+        */
+       protected function getDBTouched() {
+               $this->load();
+
+               return $this->mTouched;
+       }
+
        /**
         * @return Password
         * @since 1.24
@@ -3396,7 +3445,9 @@ class User implements IDBAccessObject {
                        $force = 'force';
                }
 
-               $this->getWatchedItem( $title )->resetNotificationTimestamp( $force, $oldid );
+               $this->getWatchedItem( $title )->resetNotificationTimestamp(
+                       $force, $oldid, WatchedItem::DEFERRED
+               );
        }
 
        /**