User:Werdna/CentralAuth investigation

I'm looking into it. In my own testing, CentralAuthUser got into an infinite loop when browsing as a non-anonymous unmerged account. Debug log said this:

Global User: cache miss for UnmergedUser, version , expected 1
Loading state for global user UnmergedUser from DB
LoadBalancer::openForeignConnection: reusing connection 0/centralauth
Global User: cache miss for UnmergedUser, version , expected 1
Loading state for global user UnmergedUser from DB
...

until I manually killed the php-cgi process.

I sent the backtrace off to the debug log, and got the following extract:
CentralAuthUser.php line 139 calls wfBacktrace()
CentralAuthUser.php line 266 calls CentralAuthUser::loadState()
CentralAuthUser.php line 289 calls CentralAuthUser::getCacheObject()
CentralAuthUser.php line 158 calls CentralAuthUser::saveToCache()
CentralAuthUser.php line 266 calls CentralAuthUser::loadState()
CentralAuthUser.php line 289 calls CentralAuthUser::getCacheObject()
CentralAuthUser.php line 158 calls CentralAuthUser::saveToCache()
CentralAuthUser.php line 266 calls CentralAuthUser::loadState()

It seems that, in getCacheObject, it wants to run loadState again. Which is all fine and dandy if loadState knows it's already been called, but it doesn't seem to know that. It checks this using the following lines:

		} elseif ( isset( $this->mGlobalId ) ) {
			// Already loaded
			return;
		}

So, obviously, mGlobalId is not being set properly. But it should be set by this line:

$this->mGlobalId = 0; // executed if we can't find a row in loadState();

When I tried dumping the whole object to the debug log before and after this line, I found this:

BEFORE: CentralAuthUser Object
(
    [mName] => UnmergedUser
    [mStateDirty] => 
    [mVersion] => 1
    [mDelayInvalidation] => 0
    [mWasAnon] => 
    [mIsAttached] => 
)
AFTER: CentralAuthUser Object
(
    [mName] => UnmergedUser
    [mStateDirty] => 
    [mVersion] => 1
    [mDelayInvalidation] => 0
    [mWasAnon] => 
    [mIsAttached] => 
    [mGlobalId] => 0
)

Unusual, since mGlobalId should have been set by the previous error. I looked around for anywhere that this might be unset, and the only candidate function was resetState(). So, I added a debug line there with a backtrace to see where and why this was happening.

Resetting state, BACKTRACE:
CentralAuthUser.php line 93 calls wfBacktrace()
CentralAuthUser.php line 294 calls CentralAuthUser::resetState()
CentralAuthUser.php line 160 calls CentralAuthUser::saveToCache()

Deducing from the code, it calls resetState() if mFromMaster is not set to true. Unfortunately, if a row is not found, then the following essential line isn't executed:

$this->mFromMaster = $fromMaster;

And we end up in an infinite loop :-(

Patch to solve this problem edit

Index: CentralAuthUser.php
===================================================================
--- CentralAuthUser.php (revision 34386)
+++ CentralAuthUser.php (working copy)
@@ -211,6 +211,7 @@
                } else {
                        $this->mGlobalId = 0;
                        $this->mIsAttached = false;
+                       $this->mFromMaster = $fromMaster;
                }
        }