[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/cache/ -> PhabricatorCaches.php (source)

   1  <?php
   2  
   3  /**
   4   * @task immutable  Immutable Cache
   5   * @task setup      Setup Cache
   6   * @task compress   Compression
   7   */
   8  final class PhabricatorCaches {
   9  
  10    public static function getNamespace() {
  11      return PhabricatorEnv::getEnvConfig('phabricator.cache-namespace');
  12    }
  13  
  14    private static function newStackFromCaches(array $caches) {
  15      $caches = self::addNamespaceToCaches($caches);
  16      $caches = self::addProfilerToCaches($caches);
  17      return id(new PhutilKeyValueCacheStack())
  18        ->setCaches($caches);
  19    }
  20  
  21  
  22  /* -(  Local Cache  )-------------------------------------------------------- */
  23  
  24  
  25    /**
  26     * Gets an immutable cache stack.
  27     *
  28     * This stack trades mutability away for improved performance. Normally, it is
  29     * APC + DB.
  30     *
  31     * In the general case with multiple web frontends, this stack can not be
  32     * cleared, so it is only appropriate for use if the value of a given key is
  33     * permanent and immutable.
  34     *
  35     * @return PhutilKeyValueCacheStack Best immutable stack available.
  36     * @task immutable
  37     */
  38    public static function getImmutableCache() {
  39      static $cache;
  40      if (!$cache) {
  41        $caches = self::buildImmutableCaches();
  42        $cache = self::newStackFromCaches($caches);
  43      }
  44      return $cache;
  45    }
  46  
  47  
  48    /**
  49     * Build the immutable cache stack.
  50     *
  51     * @return list<PhutilKeyValueCache> List of caches.
  52     * @task immutable
  53     */
  54    private static function buildImmutableCaches() {
  55      $caches = array();
  56  
  57      $apc = new PhutilAPCKeyValueCache();
  58      if ($apc->isAvailable()) {
  59        $caches[] = $apc;
  60      }
  61  
  62      $caches[] = new PhabricatorKeyValueDatabaseCache();
  63  
  64      return $caches;
  65    }
  66  
  67  
  68  /* -(  Repository Graph Cache  )--------------------------------------------- */
  69  
  70  
  71    public static function getRepositoryGraphL1Cache() {
  72      static $cache;
  73      if (!$cache) {
  74        $caches = self::buildRepositoryGraphL1Caches();
  75        $cache = self::newStackFromCaches($caches);
  76      }
  77      return $cache;
  78    }
  79  
  80    private static function buildRepositoryGraphL1Caches() {
  81      $caches = array();
  82  
  83      $request = new PhutilInRequestKeyValueCache();
  84      $request->setLimit(32);
  85      $caches[] = $request;
  86  
  87      $apc = new PhutilAPCKeyValueCache();
  88      if ($apc->isAvailable()) {
  89        $caches[] = $apc;
  90      }
  91  
  92      return $caches;
  93    }
  94  
  95    public static function getRepositoryGraphL2Cache() {
  96      static $cache;
  97      if (!$cache) {
  98        $caches = self::buildRepositoryGraphL2Caches();
  99        $cache = self::newStackFromCaches($caches);
 100      }
 101      return $cache;
 102    }
 103  
 104    private static function buildRepositoryGraphL2Caches() {
 105      $caches = array();
 106      $caches[] = new PhabricatorKeyValueDatabaseCache();
 107      return $caches;
 108    }
 109  
 110  
 111  /* -(  Setup Cache  )-------------------------------------------------------- */
 112  
 113  
 114    /**
 115     * Highly specialized cache for performing setup checks. We use this cache
 116     * to determine if we need to run expensive setup checks when the page
 117     * loads. Without it, we would need to run these checks every time.
 118     *
 119     * Normally, this cache is just APC. In the absence of APC, this cache
 120     * degrades into a slow, quirky on-disk cache.
 121     *
 122     * NOTE: Do not use this cache for anything else! It is not a general-purpose
 123     * cache!
 124     *
 125     * @return PhutilKeyValueCacheStack Most qualified available cache stack.
 126     * @task setup
 127     */
 128    public static function getSetupCache() {
 129      static $cache;
 130      if (!$cache) {
 131        $caches = self::buildSetupCaches();
 132        $cache = self::newStackFromCaches($caches);
 133      }
 134      return $cache;
 135    }
 136  
 137  
 138    /**
 139     * @task setup
 140     */
 141    private static function buildSetupCaches() {
 142      // In most cases, we should have APC. This is an ideal cache for our
 143      // purposes -- it's fast and empties on server restart.
 144      $apc = new PhutilAPCKeyValueCache();
 145      if ($apc->isAvailable()) {
 146        return array($apc);
 147      }
 148  
 149      // If we don't have APC, build a poor approximation on disk. This is still
 150      // much better than nothing; some setup steps are quite slow.
 151      $disk_path = self::getSetupCacheDiskCachePath();
 152      if ($disk_path) {
 153        $disk = new PhutilOnDiskKeyValueCache();
 154        $disk->setCacheFile($disk_path);
 155        $disk->setWait(0.1);
 156        if ($disk->isAvailable()) {
 157          return array($disk);
 158        }
 159      }
 160  
 161      return array();
 162    }
 163  
 164  
 165    /**
 166     * @task setup
 167     */
 168    private static function getSetupCacheDiskCachePath() {
 169      // The difficulty here is in choosing a path which will change on server
 170      // restart (we MUST have this property), but as rarely as possible
 171      // otherwise (we desire this property to give the cache the best hit rate
 172      // we can).
 173  
 174      // In some setups, the parent PID is more stable and longer-lived that the
 175      // PID (e.g., under apache, our PID will be a worker while the ppid will
 176      // be the main httpd process). If we're confident we're running under such
 177      // a setup, we can try to use the PPID as the basis for our cache instead
 178      // of our own PID.
 179      $use_ppid = false;
 180  
 181      switch (php_sapi_name()) {
 182        case 'cli-server':
 183          // This is the PHP5.4+ built-in webserver. We should use the pid
 184          // (the server), not the ppid (probably a shell or something).
 185          $use_ppid = false;
 186          break;
 187        case 'fpm-fcgi':
 188          // We should be safe to use PPID here.
 189          $use_ppid = true;
 190          break;
 191        case 'apache2handler':
 192          // We're definitely safe to use the PPID.
 193          $use_ppid = true;
 194          break;
 195      }
 196  
 197      $pid_basis = getmypid();
 198      if ($use_ppid) {
 199        if (function_exists('posix_getppid')) {
 200          $parent_pid = posix_getppid();
 201          // On most systems, normal processes can never have PIDs lower than 100,
 202          // so something likely went wrong if we we get one of these.
 203          if ($parent_pid > 100) {
 204            $pid_basis = $parent_pid;
 205          }
 206        }
 207      }
 208  
 209      // If possible, we also want to know when the process launched, so we can
 210      // drop the cache if a process restarts but gets the same PID an earlier
 211      // process had. "/proc" is not available everywhere (e.g., not on OSX), but
 212      // check if we have it.
 213      $epoch_basis = null;
 214      $stat = @stat("/proc/{$pid_basis}");
 215      if ($stat !== false) {
 216        $epoch_basis = $stat['ctime'];
 217      }
 218  
 219      $tmp_dir = sys_get_temp_dir();
 220  
 221      $tmp_path = $tmp_dir.DIRECTORY_SEPARATOR.'phabricator-setup';
 222      if (!file_exists($tmp_path)) {
 223        @mkdir($tmp_path);
 224      }
 225  
 226      $is_ok = self::testTemporaryDirectory($tmp_path);
 227      if (!$is_ok) {
 228        $tmp_path = $tmp_dir;
 229        $is_ok = self::testTemporaryDirectory($tmp_path);
 230        if (!$is_ok) {
 231          // We can't find anywhere to write the cache, so just bail.
 232          return null;
 233        }
 234      }
 235  
 236      $tmp_name = 'setup-'.$pid_basis;
 237      if ($epoch_basis) {
 238        $tmp_name .= '.'.$epoch_basis;
 239      }
 240      $tmp_name .= '.cache';
 241  
 242      return $tmp_path.DIRECTORY_SEPARATOR.$tmp_name;
 243    }
 244  
 245  
 246    /**
 247     * @task setup
 248     */
 249    private static function testTemporaryDirectory($dir) {
 250      if (!@file_exists($dir)) {
 251        return false;
 252      }
 253      if (!@is_dir($dir)) {
 254        return false;
 255      }
 256      if (!@is_writable($dir)) {
 257        return false;
 258      }
 259  
 260      return true;
 261    }
 262  
 263    private static function addProfilerToCaches(array $caches) {
 264      foreach ($caches as $key => $cache) {
 265        $pcache = new PhutilKeyValueCacheProfiler($cache);
 266        $pcache->setProfiler(PhutilServiceProfiler::getInstance());
 267        $caches[$key] = $pcache;
 268      }
 269      return $caches;
 270    }
 271  
 272    private static function addNamespaceToCaches(array $caches) {
 273      $namespace = PhabricatorCaches::getNamespace();
 274      if (!$namespace) {
 275        return $caches;
 276      }
 277  
 278      foreach ($caches as $key => $cache) {
 279        $ncache = new PhutilKeyValueCacheNamespace($cache);
 280        $ncache->setNamespace($namespace);
 281        $caches[$key] = $ncache;
 282      }
 283  
 284      return $caches;
 285    }
 286  
 287  
 288    /**
 289     * Deflate a value, if deflation is available and has an impact.
 290     *
 291     * If the value is larger than 1KB, we have `gzdeflate()`, we successfully
 292     * can deflate it, and it benefits from deflation, we deflate it. Otherwise
 293     * we leave it as-is.
 294     *
 295     * Data can later be inflated with @{method:inflateData}.
 296     *
 297     * @param string String to attempt to deflate.
 298     * @return string|null Deflated string, or null if it was not deflated.
 299     * @task compress
 300     */
 301    public static function maybeDeflateData($value) {
 302      $len = strlen($value);
 303      if ($len <= 1024) {
 304        return null;
 305      }
 306  
 307      if (!function_exists('gzdeflate')) {
 308        return null;
 309      }
 310  
 311      $deflated = gzdeflate($value);
 312      if ($deflated === false) {
 313        return null;
 314      }
 315  
 316      $deflated_len = strlen($deflated);
 317      if ($deflated_len >= ($len / 2)) {
 318        return null;
 319      }
 320  
 321      return $deflated;
 322    }
 323  
 324  
 325    /**
 326     * Inflate data previously deflated by @{method:maybeDeflateData}.
 327     *
 328     * @param string Deflated data, from @{method:maybeDeflateData}.
 329     * @return string Original, uncompressed data.
 330     * @task compress
 331     */
 332    public static function inflateData($value) {
 333      if (!function_exists('gzinflate')) {
 334        throw new Exception(
 335          pht('gzinflate() is not available; unable to read deflated data!'));
 336      }
 337  
 338      $value = gzinflate($value);
 339      if ($value === false) {
 340        throw new Exception(pht('Failed to inflate data!'));
 341      }
 342  
 343      return $value;
 344    }
 345  
 346  
 347  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1