Something went wrong. Try again.
@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
Something went wrong. Try again.
4.4 kB · 205 lines
PHP
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206<?php
/** * Interface to a disk cache. Storage persists across requests. * * This cache is very slow compared to caches like APC. It is intended as a * specialized alternative to APC when APC is not available. * * This is a highly specialized cache and not appropriate for use as a * generalized key-value cache for arbitrary application data. * * Also note that reading and writing keys from the cache currently involves * loading and saving the entire cache, no matter how little data you touch. * * @task kvimpl Key-Value Cache Implementation * @task storage Cache Storage */final class PhutilOnDiskKeyValueCache extends PhutilKeyValueCache {
private $cache = array(); private $cacheFile; private $lock; private $wait = 0;
/* -( Key-Value Cache Implementation )------------------------------------- */
public function isAvailable() { return true; }
/** * Set duration (in seconds) to wait for the file lock. */ public function setWait($wait) { $this->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; }
}