From a88b69a4b67c9e55cb7af6f86e79576e543c521e Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 25 Dec 2012 06:09:51 -0800 Subject: [PATCH] Add a "setup" cache Summary: See T2062. This cache allows us to essentially implement this sort of block: if (this_code_has_not_run_since_the_last_server_restart()) { ... } This will let us do setup checks automatically (i.e., without a specialized setup mode) without imposing hundreds of milliseconds of `git submodule status` and similar checks on every page load, even if an install does not have APC. Broadly, the major goals here are: - Reduce user errors and support costs related to misconfiguration (e.g., failure to update submodules). - Simplify setup and configuration (remove 'phabricator.setup', remove/reduce PHABRICATOR_ENV). - Move as much configuration to the web as possible (required for SaaS). Test Plan: Added this block to webroot/index.php: $cache = PhabricatorCaches::getSetupCache(); $result = $cache->getKeys(array('x')); if (empty($result['x'])) { phlog('Cache miss + set.'); $cache->setKeys(array('x' => 'y')); } else { phlog('Cache hit.'); } Verified it used APC correctly. Disabled APC and verified it degraded to a reasonable disk-based behavior. If we miss both of these we end up with no actual caching, but that's the best we can do. This code will also run too early in setup for it to be appropriate to raise exceptions out of this pathway -- later on, we can raise a warning that APC is not installed. Reviewers: btrahan, vrana Reviewed By: btrahan CC: aran Maniphest Tasks: T2227, T2062 Differential Revision: https://secure.phabricator.com/D4281 --- src/__phutil_library_map__.php | 1 + src/applications/cache/PhabricatorCaches.php | 163 +++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 src/applications/cache/PhabricatorCaches.php 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; + } + +}