/** * NOTE: This is an ADVANCED feature that improves performance but adds a lot * of complexity! This is only suitable for production servers because workers * won't pick up changes between when they spawn and when they handle a request. * * Phabricator spends a significant portion of its runtime loading classes * and functions, even with APC enabled. Since we have very rigidly-defined * rules about what can go in a module (specifically: no side effects), it * is safe to load all the libraries *before* we receive a request. * * Normally, SAPIs don't provide a way to do this, but with a patched PHP-FPM * SAPI you can provide a warmup file that it will execute before a request * is received. * * We're limited in what we can do here, since request information won't * exist yet, but we can load class and function definitions, which is what * we're really interested in. * * Once this file exists, the FCGI process will drop into its normal accept loop * and eventually process a request. */ function __warmup__() { $root = dirname(dirname(dirname(dirname(__FILE__)))); require_once $root . '/libphutil/src/__phutil_library_init__.php'; require_once $root . '/arcanist/src/__phutil_library_init__.php'; require_once $root . '/phabricator/src/__phutil_library_init__.php'; // Load every symbol. We could possibly refine this -- we don't need to load // every Controller, for instance. $loader = new PhutilSymbolLoader(); $loader->selectAndLoadSymbols(); define('__WARMUP__', true); }
public function registerLibrary($name, $path) { if (basename($path) != '__phutil_library_init__.php') { throw new PhutilBootloaderException('Only directories with a __phutil_library_init__.php file may be ' . 'registered as libphutil libraries.'); } $path = dirname($path); // Detect attempts to load the same library multiple times from different // locations. This might mean you're doing something silly like trying to // include two different versions of something, or it might mean you're // doing something subtle like running a different version of 'arc' on a // working copy of Arcanist. if (isset($this->registeredLibraries[$name])) { $old_path = $this->registeredLibraries[$name]; if ($old_path != $path) { throw new PhutilLibraryConflictException($name, $old_path, $path); } } $this->registeredLibraries[$name] = $path; // For libphutil v2 libraries, load all functions when we load the library. if (!class_exists('PhutilSymbolLoader', false)) { $root = $this->getLibraryRoot('phutil'); $this->executeInclude($root . '/symbols/PhutilSymbolLoader.php'); } $loader = new PhutilSymbolLoader(); $loader->setLibrary($name)->setType('function'); try { $loader->selectAndLoadSymbols(); } catch (PhutilBootloaderException $ex) { // Ignore this, it happens if a global function's file is removed or // similar. Worst case is that we fatal when calling the function, which // is no worse than fataling here. } catch (PhutilMissingSymbolException $ex) { // Ignore this, it happens if a global function is removed. Everything // else loaded so proceed forward: worst case is a fatal when we // hit a function call to a function which no longer exists, which is // no worse than fataling here. } if (empty($_SERVER['PHUTIL_DISABLE_RUNTIME_EXTENSIONS'])) { $extdir = $path . DIRECTORY_SEPARATOR . 'extensions'; if (Filesystem::pathExists($extdir)) { $extensions = id(new FileFinder($extdir))->withSuffix('php')->withType('f')->withFollowSymlinks(true)->setForceMode('php')->find(); foreach ($extensions as $extension) { $this->loadExtension($name, $path, $extdir . DIRECTORY_SEPARATOR . $extension); } } } return $this; }
private function lintModule($key, $spec, $deps) { $resolvable = array(); $need_classes = array(); $need_functions = array(); $drop_modules = array(); $used = array(); static $types = array('class' => self::LINT_UNDECLARED_CLASS, 'interface' => self::LINT_UNDECLARED_INTERFACE, 'function' => self::LINT_UNDECLARED_FUNCTION); foreach ($types as $type => $lint_code) { foreach ($spec['requires'][$type] as $name => $places) { $declared = $this->checkDependency($type, $name, $deps); if (!$declared) { $module = $this->getModuleDisplayName($key); $message = $this->raiseLintInModule($key, $lint_code, "Module '{$module}' uses {$type} '{$name}' but does not include " . "any module which declares it.", $places); if ($type == 'class' || $type == 'interface') { $loader = new PhutilSymbolLoader(); $loader->setType($type); $loader->setName($name); $symbols = $loader->selectSymbolsWithoutLoading(); if ($symbols) { $class_spec = reset($symbols); try { $loader->selectAndLoadSymbols(); $loaded = true; } catch (PhutilMissingSymbolException $ex) { $loaded = false; } catch (PhutilBootloaderException $ex) { $loaded = false; } if ($loaded) { $resolvable[] = $message; $need_classes[$name] = $class_spec; } else { if (empty($this->unknownClasses[$name])) { $this->unknownClasses[$name] = true; $library = $class_spec['library']; $this->raiseLintInModule($key, self::LINT_UNKNOWN_CLASS, "Class '{$name}' exists in the library map for library " . "'{$library}', but could not be loaded. You may need to " . "rebuild the library map.", $places); } } } else { if (empty($this->unknownClasses[$name])) { $this->unknownClasses[$name] = true; $this->raiseLintInModule($key, self::LINT_UNKNOWN_CLASS, "Class '{$name}' could not be found in any known library. " . "You may need to rebuild the map for the library which " . "contains it.", $places); } } } else { $loader = new PhutilSymbolLoader(); $loader->setType($type); $loader->setName($name); $symbols = $loader->selectSymbolsWithoutLoading(); if ($symbols) { $func_spec = reset($symbols); try { $loader->selectAndLoadSymbols(); $loaded = true; } catch (PhutilMissingSymbolException $ex) { $loaded = false; } catch (PhutilBootloaderException $ex) { $loaded = false; } if ($loaded) { $resolvable[] = $message; $need_functions[$name] = $func_spec; } else { if (empty($this->unknownFunctions[$name])) { $this->unknownFunctions[$name] = true; $library = $func_spec['library']; $this->raiseLintInModule($key, self::LINT_UNKNOWN_FUNCTION, "Function '{$name}' exists in the library map for library " . "'{$library}', but could not be loaded. You may need to " . "rebuild the library map.", $places); } } } else { if (empty($this->unknownFunctions[$name])) { $this->unknownFunctions[$name] = true; $this->raiseLintInModule($key, self::LINT_UNKNOWN_FUNCTION, "Function '{$name}' could not be found in any known " . "library. You may need to rebuild the map for the library " . "which contains it.", $places); } } } } $used[$declared] = true; } } $unused = array_diff_key($deps, $used); foreach ($unused as $unused_module_key => $ignored) { $module = $this->getModuleDisplayName($key); $unused_module = $this->getModuleDisplayName($unused_module_key); $resolvable[] = $this->raiseLintInModule($key, self::LINT_UNUSED_MODULE, "Module '{$module}' requires module '{$unused_module}' but does not " . "use anything it declares.", $spec['requires']['module'][$unused_module_key]); $drop_modules[] = $unused_module_key; } foreach ($spec['requires']['source'] as $file => $where) { if (empty($spec['declares']['source'][$file])) { $module = $this->getModuleDisplayName($key); $resolvable[] = $this->raiseLintInModule($key, self::LINT_UNDECLARED_SOURCE, "Module '{$module}' requires source '{$file}', but it does not " . "exist.", $where); } } foreach ($spec['declares']['source'] as $file => $ignored) { if (empty($spec['requires']['source'][$file])) { $module = $this->getModuleDisplayName($key); $resolvable[] = $this->raiseLintInModule($key, self::LINT_UNUSED_SOURCE, "Module '{$module}' does not include source file '{$file}'.", null); } } if ($resolvable) { $new_file = $this->buildNewModuleInit($key, $spec, $need_classes, $need_functions, $drop_modules); $init_path = $this->getModulePathOnDisk($key) . '/__init__.php'; $root = $this->getEngine()->getWorkingCopy()->getProjectRoot(); $try_path = Filesystem::readablePath($init_path, $root); $full_path = Filesystem::resolvePath($try_path, $root); if (Filesystem::pathExists($full_path)) { $init_path = $try_path; $old_file = Filesystem::readFile($full_path); } else { $old_file = ''; } $this->willLintPath($init_path); $message = $this->raiseLintAtOffset(null, self::LINT_INIT_REBUILD, "This generated phutil '__init__.php' file is suggested to address " . "lint problems with static dependencies in the module.", $old_file, $new_file); $message->setDependentMessages($resolvable); foreach ($resolvable as $resolvable_message) { $resolvable_message->setObsolete(true); } } }
public function registerLibrary($name, $path) { if (basename($path) != '__phutil_library_init__.php') { throw new PhutilBootloaderException('Only directories with a __phutil_library_init__.php file may be ' . 'registered as libphutil libraries.'); } $path = dirname($path); // Detect attempts to load the same library multiple times from different // locations. This might mean you're doing something silly like trying to // include two different versions of something, or it might mean you're // doing something subtle like running a different version of 'arc' on a // working copy of Arcanist. if (isset($this->registeredLibraries[$name])) { $old_path = $this->registeredLibraries[$name]; if ($old_path != $path) { throw new PhutilLibraryConflictException($name, $old_path, $path); } } $this->registeredLibraries[$name] = $path; // TODO: Remove this once we drop libphutil v1 support. $version = $this->getLibraryFormatVersion($name); if ($version == 1) { return $this; } // For libphutil v2 libraries, load all functions when we load the library. if (!class_exists('PhutilSymbolLoader', false)) { $root = $this->getLibraryRoot('phutil'); $this->executeInclude($root . '/symbols/PhutilSymbolLoader.php'); } $loader = new PhutilSymbolLoader(); $loader->setLibrary($name)->setType('function'); try { $loader->selectAndLoadSymbols(); } catch (PhutilMissingSymbolException $ex) { // Ignore this, it happens if a global function is removed. Everything // else loaded so proceed forward: worst case is a fatal when we // hit a function call to a function which no longer exists, which is // no worse than fataling here. } return $this; }