Path: blob/master/src/infrastructure/cache/PhutilOnDiskKeyValueCache.php
12241 views
<?php12/**3* Interface to a disk cache. Storage persists across requests.4*5* This cache is very slow compared to caches like APC. It is intended as a6* specialized alternative to APC when APC is not available.7*8* This is a highly specialized cache and not appropriate for use as a9* generalized key-value cache for arbitrary application data.10*11* Also note that reading and writing keys from the cache currently involves12* loading and saving the entire cache, no matter how little data you touch.13*14* @task kvimpl Key-Value Cache Implementation15* @task storage Cache Storage16*/17final class PhutilOnDiskKeyValueCache extends PhutilKeyValueCache {1819private $cache = array();20private $cacheFile;21private $lock;22private $wait = 0;232425/* -( Key-Value Cache Implementation )------------------------------------- */262728public function isAvailable() {29return true;30}313233/**34* Set duration (in seconds) to wait for the file lock.35*/36public function setWait($wait) {37$this->wait = $wait;38return $this;39}4041public function getKeys(array $keys) {42$now = time();4344$results = array();45$reloaded = false;46foreach ($keys as $key) {4748// Try to read the value from cache. If we miss, load (or reload) the49// cache.5051while (true) {52if (isset($this->cache[$key])) {53$val = $this->cache[$key];54if (empty($val['ttl']) || $val['ttl'] >= $now) {55$results[$key] = $val['val'];56break;57}58}5960if ($reloaded) {61break;62}6364$this->loadCache($hold_lock = false);65$reloaded = true;66}67}6869return $results;70}717273public function setKeys(array $keys, $ttl = null) {74if ($ttl) {75$ttl_epoch = time() + $ttl;76} else {77$ttl_epoch = null;78}7980$dicts = array();81foreach ($keys as $key => $value) {82$dict = array(83'val' => $value,84);85if ($ttl_epoch) {86$dict['ttl'] = $ttl_epoch;87}88$dicts[$key] = $dict;89}9091$this->loadCache($hold_lock = true);92foreach ($dicts as $key => $dict) {93$this->cache[$key] = $dict;94}95$this->saveCache();9697return $this;98}99100101public function deleteKeys(array $keys) {102$this->loadCache($hold_lock = true);103foreach ($keys as $key) {104unset($this->cache[$key]);105}106$this->saveCache();107108return $this;109}110111112public function destroyCache() {113Filesystem::remove($this->getCacheFile());114return $this;115}116117118/* -( Cache Storage )------------------------------------------------------ */119120121/**122* @task storage123*/124public function setCacheFile($file) {125$this->cacheFile = $file;126return $this;127}128129130/**131* @task storage132*/133private function loadCache($hold_lock) {134if ($this->lock) {135throw new Exception(136pht(137'Trying to %s with a lock!',138__FUNCTION__.'()'));139}140141$lock = PhutilFileLock::newForPath($this->getCacheFile().'.lock');142try {143$lock->lock($this->wait);144} catch (PhutilLockException $ex) {145if ($hold_lock) {146throw $ex;147} else {148$this->cache = array();149return;150}151}152153try {154$this->cache = array();155if (Filesystem::pathExists($this->getCacheFile())) {156$cache = unserialize(Filesystem::readFile($this->getCacheFile()));157if ($cache) {158$this->cache = $cache;159}160}161} catch (Exception $ex) {162$lock->unlock();163throw $ex;164}165166if ($hold_lock) {167$this->lock = $lock;168} else {169$lock->unlock();170}171}172173174/**175* @task storage176*/177private function saveCache() {178if (!$this->lock) {179throw new PhutilInvalidStateException('loadCache');180}181182// We're holding a lock so we're safe to do a write to a well-known file.183// Write to the same directory as the cache so the rename won't imply a184// copy across volumes.185$new = $this->getCacheFile().'.new';186Filesystem::writeFile($new, serialize($this->cache));187Filesystem::rename($new, $this->getCacheFile());188189$this->lock->unlock();190$this->lock = null;191}192193194/**195* @task storage196*/197private function getCacheFile() {198if (!$this->cacheFile) {199throw new PhutilInvalidStateException('setCacheFile');200}201return $this->cacheFile;202}203204}205206207