public function set($key, $value, $exptime = 0, $flags = 0) { parent::set($key, $value, $exptime, $flags); if (!($flags & self::WRITE_CACHE_ONLY)) { $this->backend->set($key, $value, $exptime, $flags & ~self::WRITE_CACHE_ONLY); } return true; }
/** * @see JobQueueAggregator::doAllGetReadyWikiQueues() */ protected function doGetAllReadyWikiQueues() { $key = $this->getReadyQueueCacheKey(); // If the cache entry wasn't present, is stale, or in .1% of cases otherwise, // regenerate the cache. Use any available stale cache if another process is // currently regenerating the pending DB information. $pendingDbInfo = $this->cache->get($key); if (!is_array($pendingDbInfo) || time() - $pendingDbInfo['timestamp'] > $this->cacheTTL || mt_rand(0, 999) == 0) { if ($this->cache->add("{$key}:rebuild", 1, 1800)) { // lock $pendingDbInfo = array('pendingDBs' => $this->findPendingWikiQueues(), 'timestamp' => time()); for ($attempts = 1; $attempts <= 25; ++$attempts) { if ($this->cache->add("{$key}:lock", 1, 60)) { // lock $this->cache->set($key, $pendingDbInfo); $this->cache->delete("{$key}:lock"); // unlock break; } } $this->cache->delete("{$key}:rebuild"); // unlock } } return is_array($pendingDbInfo) ? $pendingDbInfo['pendingDBs'] : array(); // cache is both empty and locked }
/** * @param ParserOutput $parserOutput * @param WikiPage $page * @param ParserOptions $popts * @param string $cacheTime Time when the cache was generated * @param int $revId Revision ID that was parsed */ public function save($parserOutput, $page, $popts, $cacheTime = null, $revId = null) { $expire = $parserOutput->getCacheExpiry(); if ($expire > 0) { $cacheTime = $cacheTime ?: wfTimestampNow(); if (!$revId) { $revision = $page->getRevision(); $revId = $revision ? $revision->getId() : null; } $optionsKey = new CacheTime(); $optionsKey->mUsedOptions = $parserOutput->getUsedOptions(); $optionsKey->updateCacheExpiry($expire); $optionsKey->setCacheTime($cacheTime); $parserOutput->setCacheTime($cacheTime); $optionsKey->setCacheRevisionId($revId); $parserOutput->setCacheRevisionId($revId); $parserOutputKey = $this->getParserOutputKey($page, $popts->optionsHash($optionsKey->mUsedOptions, $page->getTitle())); // Save the timestamp so that we don't have to load the revision row on view $parserOutput->setTimestamp($page->getTimestamp()); $msg = "Saved in parser cache with key {$parserOutputKey}" . " and timestamp {$cacheTime}" . " and revision id {$revId}" . "\n"; $parserOutput->mText .= "\n<!-- {$msg} -->\n"; wfDebug($msg); // Save the parser output $this->mMemc->set($parserOutputKey, $parserOutput, $expire); // ...and its pointer $this->mMemc->set($this->getOptionsKey($page), $optionsKey, $expire); Hooks::run('ParserCacheSaveComplete', array($this, $parserOutput, $page->getTitle(), $popts, $revId)); } else { wfDebug("Parser output was marked as uncacheable and has not been saved.\n"); } }
protected function doPop() { $key = $this->getCacheKey( 'empty' ); $isEmpty = $this->cache->get( $key ); if ( $isEmpty === 'true' ) { return false; } $partitionsTry = $this->partitionMap; // (partition => weight) while ( count( $partitionsTry ) ) { $partition = ArrayUtils::pickRandom( $partitionsTry ); if ( $partition === false ) { break; // all partitions at 0 weight } $queue = $this->partitionQueues[$partition]; try { $job = $queue->pop(); } catch ( JobQueueError $e ) { $job = false; MWExceptionHandler::logException( $e ); } if ( $job ) { $job->metadata['QueuePartition'] = $partition; return $job; } else { unset( $partitionsTry[$partition] ); // blacklist partition } } $this->cache->set( $key, 'true', JobQueueDB::CACHE_TTL_LONG ); return false; }
protected function doPop() { $partitionsTry = $this->partitionRing->getLiveLocationWeights(); // (partition => weight) $failed = 0; while (count($partitionsTry)) { $partition = ArrayUtils::pickRandom($partitionsTry); if ($partition === false) { break; // all partitions at 0 weight } /** @var JobQueue $queue */ $queue = $this->partitionQueues[$partition]; try { $job = $queue->pop(); } catch (JobQueueError $e) { ++$failed; MWExceptionHandler::logException($e); $job = false; } if ($job) { $job->metadata['QueuePartition'] = $partition; return $job; } else { unset($partitionsTry[$partition]); // blacklist partition } } $this->throwErrorIfAllPartitionsDown($failed); $key = $this->getCacheKey('empty'); $this->cache->set($key, 'true', self::CACHE_TTL_LONG); return false; }
/** * Recycle or destroy any jobs that have been claimed for too long * * @return int Number of jobs recycled/deleted */ public function recycleAndDeleteStaleJobs() { $now = time(); $count = 0; // affected rows $dbw = $this->getMasterDB(); try { if (!$dbw->lock("jobqueue-recycle-{$this->type}", __METHOD__, 1)) { return $count; // already in progress } // Remove claims on jobs acquired for too long if enabled... if ($this->claimTTL > 0) { $claimCutoff = $dbw->timestamp($now - $this->claimTTL); // Get the IDs of jobs that have be claimed but not finished after too long. // These jobs can be recycled into the queue by expiring the claim. Selecting // the IDs first means that the UPDATE can be done by primary key (less deadlocks). $res = $dbw->select('job', 'job_id', array('job_cmd' => $this->type, "job_token != {$dbw->addQuotes('')}", "job_token_timestamp < {$dbw->addQuotes($claimCutoff)}", "job_attempts < {$dbw->addQuotes($this->maxTries)}"), __METHOD__); $ids = array_map(function ($o) { return $o->job_id; }, iterator_to_array($res)); if (count($ids)) { // Reset job_token for these jobs so that other runners will pick them up. // Set the timestamp to the current time, as it is useful to now that the job // was already tried before (the timestamp becomes the "released" time). $dbw->update('job', array('job_token' => '', 'job_token_timestamp' => $dbw->timestamp($now)), array('job_id' => $ids), __METHOD__); $affected = $dbw->affectedRows(); $count += $affected; JobQueue::incrStats('job-recycle', $this->type, $affected, $this->wiki); // The tasks recycled jobs or release delayed jobs into the queue $this->cache->set($this->getCacheKey('empty'), 'false', self::CACHE_TTL_LONG); $this->aggr->notifyQueueNonEmpty($this->wiki, $this->type); } } // Just destroy any stale jobs... $pruneCutoff = $dbw->timestamp($now - self::MAX_AGE_PRUNE); $conds = array('job_cmd' => $this->type, "job_token != {$dbw->addQuotes('')}", "job_token_timestamp < {$dbw->addQuotes($pruneCutoff)}"); if ($this->claimTTL > 0) { // only prune jobs attempted too many times... $conds[] = "job_attempts >= {$dbw->addQuotes($this->maxTries)}"; } // Get the IDs of jobs that are considered stale and should be removed. Selecting // the IDs first means that the UPDATE can be done by primary key (less deadlocks). $res = $dbw->select('job', 'job_id', $conds, __METHOD__); $ids = array_map(function ($o) { return $o->job_id; }, iterator_to_array($res)); if (count($ids)) { $dbw->delete('job', array('job_id' => $ids), __METHOD__); $affected = $dbw->affectedRows(); $count += $affected; JobQueue::incrStats('job-abandon', $this->type, $affected, $this->wiki); } $dbw->unlock("jobqueue-recycle-{$this->type}", __METHOD__); } catch (DBError $e) { $this->throwDBException($e); } return $count; }
/** * Reserve a row with a single UPDATE without holding row locks over RTTs... * * @param string $uuid 32 char hex string * @param int $rand Random unsigned integer (31 bits) * @param bool $gte Search for job_random >= $random (otherwise job_random <= $random) * @return stdClass|bool Row|false */ protected function claimRandom($uuid, $rand, $gte) { $dbw = $this->getMasterDB(); // Check cache to see if the queue has <= OFFSET items $tinyQueue = $this->cache->get($this->getCacheKey('small')); $row = false; // the row acquired $invertedDirection = false; // whether one job_random direction was already scanned // This uses a replication safe method for acquiring jobs. One could use UPDATE+LIMIT // instead, but that either uses ORDER BY (in which case it deadlocks in MySQL) or is // not replication safe. Due to http://bugs.mysql.com/bug.php?id=6980, subqueries cannot // be used here with MySQL. do { if ($tinyQueue) { // queue has <= MAX_OFFSET rows // For small queues, using OFFSET will overshoot and return no rows more often. // Instead, this uses job_random to pick a row (possibly checking both directions). $ineq = $gte ? '>=' : '<='; $dir = $gte ? 'ASC' : 'DESC'; $row = $dbw->selectRow('job', self::selectFields(), array('job_cmd' => $this->type, 'job_token' => '', "job_random {$ineq} {$dbw->addQuotes($rand)}"), __METHOD__, array('ORDER BY' => "job_random {$dir}")); if (!$row && !$invertedDirection) { $gte = !$gte; $invertedDirection = true; continue; // try the other direction } } else { // table *may* have >= MAX_OFFSET rows // Bug 42614: "ORDER BY job_random" with a job_random inequality causes high CPU // in MySQL if there are many rows for some reason. This uses a small OFFSET // instead of job_random for reducing excess claim retries. $row = $dbw->selectRow('job', self::selectFields(), array('job_cmd' => $this->type, 'job_token' => ''), __METHOD__, array('OFFSET' => mt_rand(0, self::MAX_OFFSET))); if (!$row) { $tinyQueue = true; // we know the queue must have <= MAX_OFFSET rows $this->cache->set($this->getCacheKey('small'), 1, 30); continue; // use job_random } } if ($row) { // claim the job $dbw->update('job', array('job_token' => $uuid, 'job_token_timestamp' => $dbw->timestamp(), 'job_attempts = job_attempts+1'), array('job_cmd' => $this->type, 'job_id' => $row->job_id, 'job_token' => ''), __METHOD__); // This might get raced out by another runner when claiming the previously // selected row. The use of job_random should minimize this problem, however. if (!$dbw->affectedRows()) { $row = false; // raced out } } else { break; // nothing to do } } while (!$row); return $row; }
/** * Delete the cached stat info for a file path. * The cache key is salted for a while to prevent race conditions. * Since negatives (404s) are not cached, this does not need to be called when * a file is created at a path were there was none before. * * @param string $path Storage path * @return void */ final protected function deleteFileCache( $path ) { $path = FileBackend::normalizeStoragePath( $path ); if ( $path === null ) { return; // invalid storage path } if ( !$this->memCache->set( $this->fileCacheKey( $path ), 'PURGED', 300 ) ) { trigger_error( "Unable to delete stat cache for file $path." ); } }
/** * Get a hash of a file's contents, either by retrieving a previously- * computed hash from the cache, or by computing a hash from the file. * * @private * @param string $filePath Full path to the file. * @param string $algo Name of selected hashing algorithm. * @return string|bool Hash of file contents, or false if the file could not be read. */ public function getFileContentsHashInternal($filePath, $algo = 'md4') { $mtime = MediaWiki\quietCall('filemtime', $filePath); if ($mtime === false) { return false; } $cacheKey = wfGlobalCacheKey(__CLASS__, $filePath, $mtime, $algo); $hash = $this->cache->get($cacheKey); if ($hash) { return $hash; } $contents = MediaWiki\quietCall('file_get_contents', $filePath); if ($contents === false) { return false; } $hash = hash($algo, $contents); $this->cache->set($cacheKey, $hash, 60 * 60 * 24); // 24h return $hash; }
/** * @see SiteStore::getSites * * @since 1.25 * * @return SiteList */ public function getSites() { if ($this->sites === null) { $this->sites = $this->cache->get($this->getCacheKey()); if (!is_object($this->sites)) { $this->sites = $this->siteStore->getSites(); $this->cache->set($this->getCacheKey(), $this->sites, $this->cacheTimeout); } } return $this->sites; }
/** * Get a hash of a file's contents, either by retrieving a previously- * computed hash from the cache, or by computing a hash from the file. * * @private * @param string $filePath Full path to the file. * @param string $algo Name of selected hashing algorithm. * @return string|bool Hash of file contents, or false if the file could not be read. */ public function getFileContentsHashInternal($filePath, $algo = 'md4') { $mtime = filemtime($filePath); if ($mtime === false) { return false; } $cacheKey = $this->cache->makeGlobalKey(__CLASS__, $filePath, $mtime, $algo); $hash = $this->cache->get($cacheKey); if ($hash) { return $hash; } $contents = file_get_contents($filePath); if ($contents === false) { return false; } $hash = hash($algo, $contents); $this->cache->set($cacheKey, $hash, 60 * 60 * 24); // 24h return $hash; }
/** * Shortcut to update caches. * * @param array $cache Cached messages with a version. * @param string $dest Either "local-only" to save to local caches only * or "all" to save to all caches. * @param string|bool $code Language code (default: false) * @return bool */ protected function saveToCaches(array $cache, $dest, $code = false) { if ($dest === 'all') { $cacheKey = wfMemcKey('messages', $code); $success = $this->mMemc->set($cacheKey, $cache); } else { $success = true; } $this->setValidationHash($code, $cache); $this->saveToLocalCache($code, $cache); return $success; }
/** * Wait for a given slave to catch up to the master pos stored in $this * @param int $index Server index * @param bool $open Check the server even if a new connection has to be made * @param int $timeout Max seconds to wait; default is mWaitTimeout * @return bool */ protected function doWait($index, $open = false, $timeout = null) { $close = false; // close the connection afterwards // Check if we already know that the DB has reached this point $server = $this->getServerName($index); $key = $this->srvCache->makeGlobalKey(__CLASS__, 'last-known-pos', $server); /** @var DBMasterPos $knownReachedPos */ $knownReachedPos = $this->srvCache->get($key); if ($knownReachedPos && $knownReachedPos->hasReached($this->mWaitForPos)) { wfDebugLog('replication', __METHOD__ . ": slave {$server} known to be caught up (pos >= {$knownReachedPos}).\n"); return true; } // Find a connection to wait on, creating one if needed and allowed $conn = $this->getAnyOpenConnection($index); if (!$conn) { if (!$open) { wfDebugLog('replication', __METHOD__ . ": no connection open for {$server}\n"); return false; } else { $conn = $this->openConnection($index, ''); if (!$conn) { wfDebugLog('replication', __METHOD__ . ": failed to connect to {$server}\n"); return false; } // Avoid connection spam in waitForAll() when connections // are made just for the sake of doing this lag check. $close = true; } } wfDebugLog('replication', __METHOD__ . ": Waiting for slave {$server} to catch up...\n"); $timeout = $timeout ?: $this->mWaitTimeout; $result = $conn->masterPosWait($this->mWaitForPos, $timeout); if ($result == -1 || is_null($result)) { // Timed out waiting for slave, use master instead $msg = __METHOD__ . ": Timed out waiting on {$server} pos {$this->mWaitForPos}"; wfDebugLog('replication', "{$msg}\n"); wfDebugLog('DBPerformance', "{$msg}:\n" . wfBacktrace(true)); $ok = false; } else { wfDebugLog('replication', __METHOD__ . ": Done\n"); $ok = true; // Remember that the DB reached this point $this->srvCache->set($key, $this->mWaitForPos, BagOStuff::TTL_DAY); } if ($close) { $this->closeConnection($conn); } return $ok; }
/** * @return array|null Credential map */ protected function getAuthentication() { if ($this->authErrorTimestamp !== null) { if (time() - $this->authErrorTimestamp < 60) { return null; // failed last attempt; don't bother } else { // actually retry this time $this->authErrorTimestamp = null; } } // Session keys expire after a while, so we renew them periodically $reAuth = time() - $this->authSessionTimestamp > $this->authTTL; // Authenticate with proxy and get a session key... if (!$this->authCreds || $reAuth) { $this->authSessionTimestamp = 0; $cacheKey = $this->getCredsCacheKey($this->swiftUser); $creds = $this->srvCache->get($cacheKey); // credentials // Try to use the credential cache if (isset($creds['auth_token']) && isset($creds['storage_url'])) { $this->authCreds = $creds; // Skew the timestamp for worst case to avoid using stale credentials $this->authSessionTimestamp = time() - ceil($this->authTTL / 2); } else { // cache miss list($rcode, $rdesc, $rhdrs, $rbody, $rerr) = $this->http->run(array('method' => 'GET', 'url' => "{$this->swiftAuthUrl}/v1.0", 'headers' => array('x-auth-user' => $this->swiftUser, 'x-auth-key' => $this->swiftKey))); if ($rcode >= 200 && $rcode <= 299) { // OK $this->authCreds = array('auth_token' => $rhdrs['x-auth-token'], 'storage_url' => $rhdrs['x-storage-url']); $this->srvCache->set($cacheKey, $this->authCreds, ceil($this->authTTL / 2)); $this->authSessionTimestamp = time(); } elseif ($rcode === 401) { $this->onError(null, __METHOD__, array(), "Authentication failed.", $rcode); $this->authErrorTimestamp = time(); return null; } else { $this->onError(null, __METHOD__, array(), "HTTP return code: {$rcode}", $rcode); $this->authErrorTimestamp = time(); return null; } } // Ceph RGW does not use <account> in URLs (OpenStack Swift uses "/v1/<account>") if (substr($this->authCreds['storage_url'], -3) === '/v1') { $this->isRGW = true; // take advantage of strong consistency in Ceph } } return $this->authCreds; }
/** * Retrieves number of unread notifications that a user has, would return * $wgEchoMaxNotificationCount + 1 at most * * @param $cached bool Set to false to bypass the cache. * @param $dbSource int use master or slave database to pull count * @return integer: Number of unread notifications. */ public function getNotificationCount($cached = true, $dbSource = DB_SLAVE) { global $wgEchoConfig; //XXCHANGEDXX - allow anons [sc|rs] // if ( $this->mUser->isAnon() ) { // return 0; // } $memcKey = wfMemcKey('echo-notification-count', $this->mUser->getId(), $wgEchoConfig['version']); if ($cached && $this->cache->get($memcKey) !== false) { return (int) $this->cache->get($memcKey); } $count = $this->storage->getNotificationCount($this->mUser, $dbSource); $this->cache->set($memcKey, $count, 86400); return (int) $count; }
/** * Get a connection to the Swift proxy * * @return CF_Connection|false * @throws InvalidResponseException */ protected function getConnection() { if ($this->conn === false) { throw new InvalidResponseException(); // failed last attempt } // Session keys expire after a while, so we renew them periodically if ($this->conn && time() - $this->connStarted > $this->authTTL) { $this->closeConnection(); } // Authenticate with proxy and get a session key... if ($this->conn === null) { $cacheKey = $this->getCredsCacheKey($this->auth->username); $creds = $this->srvCache->get($cacheKey); // credentials if (is_array($creds)) { // cache hit $this->auth->load_cached_credentials($creds['auth_token'], $creds['storage_url'], $creds['cdnm_url']); $this->connStarted = time() - ceil($this->authTTL / 2); // skew for worst case } else { // cache miss try { $this->auth->authenticate(); $creds = $this->auth->export_credentials(); $this->srvCache->set($cacheKey, $creds, ceil($this->authTTL / 2)); // cache $this->connStarted = time(); } catch (AuthenticationException $e) { $this->conn = false; // don't keep re-trying $this->logException($e, __METHOD__, $creds); } catch (InvalidResponseException $e) { $this->conn = false; // don't keep re-trying $this->logException($e, __METHOD__, $creds); } } $this->conn = new CF_Connection($this->auth); } if (!$this->conn) { throw new InvalidResponseException(); // auth/connection problem } return $this->conn; }
/** * @see JobQueue::deduplicateRootJob() * @param $job Job * @return bool */ protected function doDeduplicateRootJob( Job $job ) { if ( !$job->hasRootJobParams() ) { throw new MWException( "Cannot register root job; missing parameters." ); } $params = $job->getRootJobParams(); $key = $this->getRootJobCacheKey( $params['rootJobSignature'] ); // Callers should call batchInsert() and then this function so that if the insert // fails, the de-duplication registration will be aborted. Since the insert is // deferred till "transaction idle", do the same here, so that the ordering is // maintained. Having only the de-duplication registration succeed would cause // jobs to become no-ops without any actual jobs that made them redundant. $timestamp = $this->dupCache->get( $key ); // current last timestamp of this job if ( $timestamp && $timestamp >= $params['rootJobTimestamp'] ) { return true; // a newer version of this root job was enqueued } // Update the timestamp of the last root job started at the location... return $this->dupCache->set( $key, $params['rootJobTimestamp'], JobQueueDB::ROOTJOB_TTL ); }
public function loadFromQueue() { if (!$this->queued) { return; } // See if this queue is in APC $key = wfMemcKey('registration', md5(json_encode($this->queued)), self::CACHE_VERSION); $data = $this->cache->get($key); if ($data) { $this->exportExtractedData($data); } else { $data = $this->readFromQueue($this->queued); $this->exportExtractedData($data); // Do this late since we don't want to extract it since we already // did that, but it should be cached $data['globals']['wgAutoloadClasses'] += $data['autoload']; unset($data['autoload']); $this->cache->set($key, $data, 60 * 60 * 24); } $this->queued = array(); }
public function loadFromQueue() { global $wgVersion; if (!$this->queued) { return; } // A few more things to vary the cache on $versions = ['registration' => self::CACHE_VERSION, 'mediawiki' => $wgVersion]; // See if this queue is in APC $key = wfMemcKey('registration', md5(json_encode($this->queued + $versions))); $data = $this->cache->get($key); if ($data) { $this->exportExtractedData($data); } else { $data = $this->readFromQueue($this->queued); $this->exportExtractedData($data); // Do this late since we don't want to extract it since we already // did that, but it should be cached $data['globals']['wgAutoloadClasses'] += $data['autoload']; unset($data['autoload']); $this->cache->set($key, $data, 60 * 60 * 24); } $this->queued = []; }
/** * Get a message from the MediaWiki namespace, with caching. The key must * first be converted to two-part lang/msg form if necessary. * * Unlike self::get(), this function doesn't resolve fallback chains, and * some callers require this behavior. LanguageConverter::parseCachedTable() * and self::get() are some examples in core. * * @param string $title Message cache key with initial uppercase letter. * @param string $code Code denoting the language to try. * @return string|bool The message, or false if it does not exist or on error */ function getMsgFromNamespace($title, $code) { $this->load($code); if (isset($this->mCache[$code][$title])) { $entry = $this->mCache[$code][$title]; if (substr($entry, 0, 1) === ' ') { // The message exists, so make sure a string // is returned. return (string) substr($entry, 1); } elseif ($entry === '!NONEXISTENT') { return false; } elseif ($entry === '!TOO BIG') { // Fall through and try invididual message cache below } } else { // XXX: This is not cached in process cache, should it? $message = false; Hooks::run('MessagesPreLoad', array($title, &$message)); if ($message !== false) { return $message; } return false; } # Try the individual message cache $titleKey = wfMemcKey('messages', 'individual', $title); $entry = $this->mMemc->get($titleKey); if ($entry) { if (substr($entry, 0, 1) === ' ') { $this->mCache[$code][$title] = $entry; // The message exists, so make sure a string // is returned. return (string) substr($entry, 1); } elseif ($entry === '!NONEXISTENT') { $this->mCache[$code][$title] = '!NONEXISTENT'; return false; } else { # Corrupt/obsolete entry, delete it $this->mMemc->delete($titleKey); } } # Try loading it from the database $revision = Revision::newFromTitle(Title::makeTitle(NS_MEDIAWIKI, $title), false, Revision::READ_LATEST); if ($revision) { $content = $revision->getContent(); if (!$content) { // A possibly temporary loading failure. wfDebugLog('MessageCache', __METHOD__ . ": failed to load message page text for {$title} ({$code})"); $message = null; // no negative caching } else { // XXX: Is this the right way to turn a Content object into a message? // NOTE: $content is typically either WikitextContent, JavaScriptContent or // CssContent. MessageContent is *not* used for storing messages, it's // only used for wrapping them when needed. $message = $content->getWikitextForTransclusion(); if ($message === false || $message === null) { wfDebugLog('MessageCache', __METHOD__ . ": message content doesn't provide wikitext " . "(content model: " . $content->getContentHandler() . ")"); $message = false; // negative caching } else { $this->mCache[$code][$title] = ' ' . $message; $this->mMemc->set($titleKey, ' ' . $message, $this->mExpiry); } } } else { $message = false; // negative caching } if ($message === false) { // negative caching $this->mCache[$code][$title] = '!NONEXISTENT'; $this->mMemc->set($titleKey, '!NONEXISTENT', $this->mExpiry); } return $message; }
/** * Delete the cached stat info for a file path. * The cache key is salted for a while to prevent race conditions. * * @param $path string Storage path */ protected final function deleteFileCache($path) { if (!$this->memCache->set($this->fileCacheKey($path), 'PURGED', 300)) { trigger_error("Unable to delete stat cache for file {$path}."); } }
public function set($key, $value, $exptime = 0) { return $this->writeStore->set($key, $value, $exptime); }
/** * Log a lock request failure to the cache * * @param string $lockDb * @return bool Success */ protected function cacheRecordFailure($lockDb) { return $this->statusCache && $this->safeDelay > 0 ? $this->statusCache->set($this->getMissKey($lockDb), 1, $this->safeDelay) : true; }
/** * Store the wrapper to a cache * * @param BagOStuff $cache * @param string $key * @param int $expiry */ function storeToCache($cache, $key, $expiry = 0) { $this->initialiseDeps(); $cache->set($key, $this, $expiry); }
protected function getServerStates(array $serverIndexes, $domain) { $writerIndex = $this->parent->getWriterIndex(); if (count($serverIndexes) == 1 && reset($serverIndexes) == $writerIndex) { # Single server only, just return zero without caching return ['lagTimes' => [$writerIndex => 0], 'weightScales' => [$writerIndex => 1.0]]; } $key = $this->getCacheKey($serverIndexes); # Randomize TTLs to reduce stampedes (4.0 - 5.0 sec) $ttl = mt_rand(4000000.0, 5000000.0) / 1000000.0; # Keep keys around longer as fallbacks $staleTTL = 60; # (a) Check the local APC cache $value = $this->srvCache->get($key); if ($value && $value['timestamp'] > microtime(true) - $ttl) { $this->replLogger->debug(__METHOD__ . ": got lag times ({$key}) from local cache"); return $value; // cache hit } $staleValue = $value ?: false; # (b) Check the shared cache and backfill APC $value = $this->mainCache->get($key); if ($value && $value['timestamp'] > microtime(true) - $ttl) { $this->srvCache->set($key, $value, $staleTTL); $this->replLogger->debug(__METHOD__ . ": got lag times ({$key}) from main cache"); return $value; // cache hit } $staleValue = $value ?: $staleValue; # (c) Cache key missing or expired; regenerate and backfill if ($this->mainCache->lock($key, 0, 10)) { # Let this process alone update the cache value $cache = $this->mainCache; /** @noinspection PhpUnusedLocalVariableInspection */ $unlocker = new ScopedCallback(function () use($cache, $key) { $cache->unlock($key); }); } elseif ($staleValue) { # Could not acquire lock but an old cache exists, so use it return $staleValue; } $lagTimes = []; $weightScales = []; $movAveRatio = $this->movingAveRatio; foreach ($serverIndexes as $i) { if ($i == $this->parent->getWriterIndex()) { $lagTimes[$i] = 0; // master always has no lag $weightScales[$i] = 1.0; // nominal weight continue; } $conn = $this->parent->getAnyOpenConnection($i); if ($conn) { $close = false; // already open } else { $conn = $this->parent->openConnection($i, $domain); $close = true; // new connection } $lastWeight = isset($staleValue['weightScales'][$i]) ? $staleValue['weightScales'][$i] : 1.0; $coefficient = $this->getWeightScale($i, $conn ?: null); $newWeight = $movAveRatio * $coefficient + (1 - $movAveRatio) * $lastWeight; // Scale from 10% to 100% of nominal weight $weightScales[$i] = max($newWeight, 0.1); if (!$conn) { $lagTimes[$i] = false; $host = $this->parent->getServerName($i); $this->replLogger->error(__METHOD__ . ": host {$host} is unreachable"); continue; } if ($conn->getLBInfo('is static')) { $lagTimes[$i] = 0; } else { $lagTimes[$i] = $conn->getLag(); if ($lagTimes[$i] === false) { $host = $this->parent->getServerName($i); $this->replLogger->error(__METHOD__ . ": host {$host} is not replicating?"); } } if ($close) { # Close the connection to avoid sleeper connections piling up. # Note that the caller will pick one of these DBs and reconnect, # which is slightly inefficient, but this only matters for the lag # time cache miss cache, which is far less common that cache hits. $this->parent->closeConnection($conn); } } # Add a timestamp key so we know when it was cached $value = ['lagTimes' => $lagTimes, 'weightScales' => $weightScales, 'timestamp' => microtime(true)]; $this->mainCache->set($key, $value, $staleTTL); $this->srvCache->set($key, $value, $staleTTL); $this->replLogger->info(__METHOD__ . ": re-calculated lag times ({$key})"); return $value; }
public function getLagTimes($serverIndexes, $wiki) { if (count($serverIndexes) == 1 && reset($serverIndexes) == 0) { # Single server only, just return zero without caching return array(0 => 0); } $key = $this->getLagTimeCacheKey(); # Randomize TTLs to reduce stampedes (4.0 - 5.0 sec) $ttl = mt_rand(4000000.0, 5000000.0) / 1000000.0; # Keep keys around longer as fallbacks $staleTTL = 60; # (a) Check the local APC cache $value = $this->srvCache->get($key); if ($value && $value['timestamp'] > microtime(true) - $ttl) { wfDebugLog('replication', __METHOD__ . ": got lag times ({$key}) from local cache"); return $value['lagTimes']; // cache hit } $staleValue = $value ?: false; # (b) Check the shared cache and backfill APC $value = $this->mainCache->get($key); if ($value && $value['timestamp'] > microtime(true) - $ttl) { $this->srvCache->set($key, $value, $staleTTL); wfDebugLog('replication', __METHOD__ . ": got lag times ({$key}) from main cache"); return $value['lagTimes']; // cache hit } $staleValue = $value ?: $staleValue; # (c) Cache key missing or expired; regenerate and backfill if ($this->mainCache->lock($key, 0, 10)) { # Let this process alone update the cache value $cache = $this->mainCache; /** @noinspection PhpUnusedLocalVariableInspection */ $unlocker = new ScopedCallback(function () use($cache, $key) { $cache->unlock($key); }); } elseif ($staleValue) { # Could not acquire lock but an old cache exists, so use it return $staleValue['lagTimes']; } $lagTimes = array(); foreach ($serverIndexes as $i) { if ($i == 0) { # Master $lagTimes[$i] = 0; } elseif (false !== ($conn = $this->parent->getAnyOpenConnection($i))) { $lagTimes[$i] = $conn->getLag(); } elseif (false !== ($conn = $this->parent->openConnection($i, $wiki))) { $lagTimes[$i] = $conn->getLag(); # Close the connection to avoid sleeper connections piling up. # Note that the caller will pick one of these DBs and reconnect, # which is slightly inefficient, but this only matters for the lag # time cache miss cache, which is far less common that cache hits. $this->parent->closeConnection($conn); } } # Add a timestamp key so we know when it was cached $value = array('lagTimes' => $lagTimes, 'timestamp' => microtime(true)); $this->mainCache->set($key, $value, $staleTTL); $this->srvCache->set($key, $value, $staleTTL); wfDebugLog('replication', __METHOD__ . ": re-calculated lag times ({$key})"); return $value['lagTimes']; }