/** * @covers WANObjectCache::set() */ public function testWritePending() { $value = 1; $key = wfRandomString(); $opts = ['pending' => true]; $this->cache->set($key, $value, 30, $opts); $this->assertEquals(false, $this->cache->get($key), "Pending value not written."); }
/** * 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; }
/** * @covers WANObjectCache::delete() */ public function testDelete() { $key = wfRandomString(); $value = wfRandomString(); $this->cache->set($key, $value); $curTTL = null; $v = $this->cache->get($key, $curTTL); $this->assertEquals($value, $v, "Key was created with value"); $this->assertGreaterThan(0, $curTTL, "Existing key has current TTL > 0"); $this->cache->delete($key); $curTTL = null; $v = $this->cache->get($key, $curTTL); $this->assertFalse($v, "Deleted key has false value"); $this->assertLessThan(0, $curTTL, "Deleted key has current TTL < 0"); $this->cache->set($key, $value . 'more'); $this->assertFalse($v, "Deleted key is tombstoned and has false value"); $this->assertLessThan(0, $curTTL, "Deleted key is tombstoned and has current TTL < 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->wanCache->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->wanCache->delete($titleKey); } } # Try loading it from the database $revision = Revision::newFromTitle(Title::makeTitle(NS_MEDIAWIKI, $title)); 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->getModel() . ")"); $message = false; // negative caching } else { $this->mCache[$code][$title] = ' ' . $message; $this->wanCache->set($titleKey, ' ' . $message, $this->mExpiry); } } } else { $message = false; // negative caching } if ($message === false) { // negative caching $this->mCache[$code][$title] = '!NONEXISTENT'; $this->wanCache->set($titleKey, '!NONEXISTENT', $this->mExpiry); } return $message; }
public function testMcRouterSupport() { $localBag = $this->getMock('EmptyBagOStuff', ['set', 'delete']); $localBag->expects($this->never())->method('set'); $localBag->expects($this->never())->method('delete'); $wanCache = new WANObjectCache(['cache' => $localBag, 'pool' => 'testcache-hash', 'relayer' => new EventRelayerNull([])]); $valFunc = function () { return 1; }; // None of these should use broadcasting commands (e.g. SET, DELETE) $wanCache->get('x'); $wanCache->get('x', $ctl, ['check1']); $wanCache->getMulti(['x', 'y']); $wanCache->getMulti(['x', 'y'], $ctls, ['check2']); $wanCache->getWithSetCallback('p', 30, $valFunc); $wanCache->getCheckKeyTime('zzz'); }
/** * Get the md5 used to validate the local disk cache * * @param string $code * @return array (hash or false, bool expiry status) */ protected function getValidationHash($code) { $curTTL = null; $value = $this->wanCache->get(wfMemcKey('messages', $code, 'hash'), $curTTL, array(wfMemcKey('messages', $code))); $expired = $curTTL === null || $curTTL < 0; return array($value, $expired); }