[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |