/** * Wraps the complex pool counter interface to force the single call pattern * that Cirrus always uses. * @param $type same as type parameter on PoolCounter::factory * @param $user the user * @param $workCallback callback when pool counter is aquired. Called with * no parameters. * @param $errorCallback optional callback called on errors. Called with * the error string and the key as parameters. If left undefined defaults * to a function that returns a fatal status and logs an warning. */ public static function doPoolCounterWork($type, $user, $workCallback, $errorCallback = null) { global $wgCirrusSearchPoolCounterKey; // By default the pool counter allows you to lock the same key with // multiple types. That might be useful but it isn't how Cirrus thinks. // Instead, all keys are scoped to their type. if (!$user) { // We don't want to even use the pool counter if there isn't a user. return $workCallback(); } $perUserKey = md5($user->getName()); $perUserKey = "nowait:CirrusSearch:_per_user:{$perUserKey}"; $globalKey = "{$type}:{$wgCirrusSearchPoolCounterKey}"; if ($errorCallback === null) { $errorCallback = function ($error, $key, $userName) { $forUserName = $userName ? "for {userName} " : ''; LoggerFactory::getInstance('CirrusSearch')->warning("Pool error {$forUserName}on {key}: {error}", array('userName' => $userName, 'key' => $key, 'error' => $error)); return Status::newFatal('cirrussearch-backend-error'); }; } $errorHandler = function ($key) use($errorCallback, $user) { return function ($status) use($errorCallback, $key, $user) { $status = $status->getErrorsArray(); // anon usernames are needed within the logs to determine if // specific ips (such as large #'s of users behind a proxy) // need to be whitelisted. We do not need this information // for logged in users and do not store it. $userName = $user->isAnon() ? $user->getName() : ''; return $errorCallback($status[0][0], $key, $userName); }; }; $doPerUserWork = function () use($type, $globalKey, $workCallback, $errorHandler) { // Now that we have the per user lock lets get the operation lock. // Note that this could block, causing the user to wait in line with their lock held. $work = new PoolCounterWorkViaCallback($type, $globalKey, array('doWork' => $workCallback, 'error' => $errorHandler($globalKey))); return $work->execute(); }; $work = new PoolCounterWorkViaCallback('CirrusSearch-PerUser', $perUserKey, array('doWork' => $doPerUserWork, 'error' => function ($status) use($errorHandler, $perUserKey, $doPerUserWork) { $errorCallback = $errorHandler($perUserKey); $errorResult = $errorCallback($status); if (Util::isUserPoolCounterActive()) { return $errorResult; } else { return $doPerUserWork(); } })); return $work->execute(); }