mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-25 16:22:43 +01:00
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
This commit is contained in:
parent
e05d8d7061
commit
a88b69a4b6
2 changed files with 164 additions and 0 deletions
|
@ -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',
|
||||
|
|
163
src/applications/cache/PhabricatorCaches.php
vendored
Normal file
163
src/applications/cache/PhabricatorCaches.php
vendored
Normal file
|
@ -0,0 +1,163 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @task setup Setup Cache
|
||||
*/
|
||||
final class PhabricatorCaches {
|
||||
|
||||
|
||||
/* -( Setup Cache )-------------------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* Highly specialized cache for performing setup checks. We use this cache
|
||||
* to determine if we need to run expensive setup checks (e.g., verifying
|
||||
* submodule versions, PATH, the presence of binaries, etc.) when the page
|
||||
* loads. Without it, we would need to run these checks every time.
|
||||
*
|
||||
* Normally, this cache is just APC. In the absence of APC, this cache
|
||||
* degrades into a slow, quirky on-disk cache.
|
||||
*
|
||||
* NOTE: Do not use this cache for anything else! It is not a general-purpose
|
||||
* cache!
|
||||
*
|
||||
* @return PhutilKeyValueCacheStack Most qualified available cache stack.
|
||||
* @task setup
|
||||
*/
|
||||
public static function getSetupCache() {
|
||||
static $cache;
|
||||
if (!$cache) {
|
||||
$caches = self::buildSetupCaches();
|
||||
$cache = id(new PhutilKeyValueCacheStack())
|
||||
->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;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue