wait = $wait; return $this; } public function getKeys(array $keys) { $now = time(); $results = array(); $reloaded = false; foreach ($keys as $key) { // Try to read the value from cache. If we miss, load (or reload) the // cache. while (true) { if (isset($this->cache[$key])) { $val = $this->cache[$key]; if (empty($val['ttl']) || $val['ttl'] >= $now) { $results[$key] = $val['val']; break; } } if ($reloaded) { break; } $this->loadCache($hold_lock = false); $reloaded = true; } } return $results; } public function setKeys(array $keys, $ttl = null) { if ($ttl) { $ttl_epoch = time() + $ttl; } else { $ttl_epoch = null; } $dicts = array(); foreach ($keys as $key => $value) { $dict = array( 'val' => $value, ); if ($ttl_epoch) { $dict['ttl'] = $ttl_epoch; } $dicts[$key] = $dict; } $this->loadCache($hold_lock = true); foreach ($dicts as $key => $dict) { $this->cache[$key] = $dict; } $this->saveCache(); return $this; } public function deleteKeys(array $keys) { $this->loadCache($hold_lock = true); foreach ($keys as $key) { unset($this->cache[$key]); } $this->saveCache(); return $this; } public function destroyCache() { Filesystem::remove($this->getCacheFile()); return $this; } /* -( Cache Storage )------------------------------------------------------ */ /** * @task storage */ public function setCacheFile($file) { $this->cacheFile = $file; return $this; } /** * @task storage */ private function loadCache($hold_lock) { if ($this->lock) { throw new Exception( pht( 'Trying to %s with a lock!', __FUNCTION__.'()')); } $lock = PhutilFileLock::newForPath($this->getCacheFile().'.lock'); try { $lock->lock($this->wait); } catch (PhutilLockException $ex) { if ($hold_lock) { throw $ex; } else { $this->cache = array(); return; } } try { $this->cache = array(); if (Filesystem::pathExists($this->getCacheFile())) { $cache = unserialize(Filesystem::readFile($this->getCacheFile())); if ($cache) { $this->cache = $cache; } } } catch (Exception $ex) { $lock->unlock(); throw $ex; } if ($hold_lock) { $this->lock = $lock; } else { $lock->unlock(); } } /** * @task storage */ private function saveCache() { if (!$this->lock) { throw new PhutilInvalidStateException('loadCache'); } // We're holding a lock so we're safe to do a write to a well-known file. // Write to the same directory as the cache so the rename won't imply a // copy across volumes. $new = $this->getCacheFile().'.new'; Filesystem::writeFile($new, serialize($this->cache)); Filesystem::rename($new, $this->getCacheFile()); $this->lock->unlock(); $this->lock = null; } /** * @task storage */ private function getCacheFile() { if (!$this->cacheFile) { throw new PhutilInvalidStateException('setCacheFile'); } return $this->cacheFile; } }