diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d268311173..1b1949924e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -651,6 +651,7 @@ phutil_register_library_map(array( 'PhabricatorBuiltinPatchList' => 'infrastructure/storage/patch/PhabricatorBuiltinPatchList.php', 'PhabricatorButtonsExample' => 'applications/uiexample/examples/PhabricatorButtonsExample.php', 'PhabricatorCacheDAO' => 'applications/cache/storage/PhabricatorCacheDAO.php', + 'PhabricatorCaches' => 'applications/cache/PhabricatorCaches.php', 'PhabricatorCalendarBrowseController' => 'applications/calendar/controller/PhabricatorCalendarBrowseController.php', 'PhabricatorCalendarController' => 'applications/calendar/controller/PhabricatorCalendarController.php', 'PhabricatorCalendarDAO' => 'applications/calendar/storage/PhabricatorCalendarDAO.php', diff --git a/src/applications/cache/PhabricatorCaches.php b/src/applications/cache/PhabricatorCaches.php new file mode 100644 index 0000000000..b00836756f --- /dev/null +++ b/src/applications/cache/PhabricatorCaches.php @@ -0,0 +1,163 @@ +setCaches($caches) + ->setProfiler(PhutilServiceProfiler::getInstance()); + } + return $cache; + } + + + /** + * @task setup + */ + private static function buildSetupCaches() { + // In most cases, we should have APC. This is an ideal cache for our + // purposes -- it's fast and empties on server restart. + $apc = new PhutilKeyValueCacheAPC(); + if ($apc->isAvailable()) { + return array($apc); + } + + // If we don't have APC, build a poor approximation on disk. This is still + // much better than nothing; some setup steps are quite slow. + $disk_path = self::getSetupCacheDiskCachePath(); + if ($disk_path) { + $disk = new PhutilKeyValueCacheOnDisk(); + $disk->setCacheFile($disk_path); + if ($disk->isAvailable()) { + return array($disk); + } + } + + return array(); + } + + + /** + * @task setup + */ + private static function getSetupCacheDiskCachePath() { + // The difficulty here is in choosing a path which will change on server + // restart (we MUST have this property), but as rarely as possible + // otherwise (we desire this property to give the cache the best hit rate + // we can). + + // In some setups, the parent PID is more stable and longer-lived that the + // PID (e.g., under apache, our PID will be a worker while the ppid will + // be the main httpd process). If we're confident we're running under such + // a setup, we can try to use the PPID as the basis for our cache instead + // of our own PID. + $use_ppid = false; + + switch (php_sapi_name()) { + case 'cli-server': + // This is the PHP5.4+ built-in webserver. We should use the pid + // (the server), not the ppid (probably a shell or something). + $use_ppid = false; + break; + case 'fpm-fcgi': + // We should be safe to use PPID here. + $use_ppid = true; + break; + case 'apache2handler': + // We're definitely safe to use the PPID. + $use_ppid = true; + break; + } + + $pid_basis = getmypid(); + if ($use_ppid) { + if (function_exists('posix_getppid')) { + $parent_pid = posix_getppid(); + // On most systems, normal processes can never have PIDs lower than 100, + // so something likely went wrong if we we get one of these. + if ($parent_pid > 100) { + $pid_basis = $parent_pid; + } + } + } + + // If possible, we also want to know when the process launched, so we can + // drop the cache if a process restarts but gets the same PID an earlier + // process had. "/proc" is not available everywhere (e.g., not on OSX), but + // check if we have it. + $epoch_basis = null; + $stat = @stat("/proc/{$pid_basis}"); + if ($stat !== false) { + $epoch_basis = $stat['ctime']; + } + + $tmp_dir = sys_get_temp_dir(); + + $tmp_path = $tmp_dir.DIRECTORY_SEPARATOR.'phabricator-setup'; + if (!file_exists($tmp_path)) { + @mkdir($tmp_path); + } + + $is_ok = self::testTemporaryDirectory($tmp_path); + if (!$is_ok) { + $tmp_path = $tmp_dir; + $is_ok = self::testTemporaryDirectory($tmp_path); + if (!$is_ok) { + // We can't find anywhere to write the cache, so just bail. + return null; + } + } + + $tmp_name = 'setup-'.$pid_basis; + if ($epoch_basis) { + $tmp_name .= '.'.$epoch_basis; + } + $tmp_name .= '.cache'; + + return $tmp_path.DIRECTORY_SEPARATOR.$tmp_name; + } + + + /** + * @task setup + */ + private static function testTemporaryDirectory($dir) { + if (!@file_exists($dir)) { + return false; + } + if (!@is_dir($dir)) { + return false; + } + if (!@is_writable($dir)) { + return false; + } + + return true; + } + +}