Ejemplo n.º 1
0
 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
 }
Ejemplo n.º 3
0
 /**
  * @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");
     }
 }
Ejemplo n.º 4
0
	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;
	}
Ejemplo n.º 5
0
 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;
 }
Ejemplo n.º 6
0
 /**
  * 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;
 }
Ejemplo n.º 7
0
 /**
  * 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;
 }
Ejemplo n.º 8
0
	/**
	 * 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." );
		}
	}
Ejemplo n.º 9
0
 /**
  * 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;
 }
Ejemplo n.º 11
0
 /**
  * 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;
 }
Ejemplo n.º 12
0
 /**
  * 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;
 }
Ejemplo n.º 13
0
 /**
  * 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;
 }
Ejemplo n.º 14
0
 /**
  * @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;
 }
Ejemplo n.º 15
0
 /**
  * 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;
 }
Ejemplo n.º 16
0
 /**
  * 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;
 }
Ejemplo n.º 17
0
	/**
	 * @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 );
	}
Ejemplo n.º 18
0
 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();
 }
Ejemplo n.º 19
0
 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 = [];
 }
Ejemplo n.º 20
0
 /**
  * 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;
 }
Ejemplo n.º 21
0
 /**
  * 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}.");
     }
 }
Ejemplo n.º 22
0
 public function set($key, $value, $exptime = 0)
 {
     return $this->writeStore->set($key, $value, $exptime);
 }
Ejemplo n.º 23
0
 /**
  * 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;
 }
Ejemplo n.º 24
0
 /**
  * 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);
 }
Ejemplo n.º 25
0
 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;
 }
Ejemplo n.º 26
0
 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'];
 }