From 23aaf85eafceebd27b3c359179156862c9bb1409 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 19 Sep 2018 06:22:25 -0700 Subject: [PATCH] [Wilds] Allow class loading to continue on failure Ref T13098. I think I didn't turn this into an actual revision, but let `ClassMapQuery` optionally continue if it encounters a class load failure. If we don't allow this, it can become very difficult to modify or remove some Arcanist classes since when you, say, remove a Workflow you can no longer make it to "arc liberate" to update the map for the change. This is currently used in roughly one place (in an upcoming diff) to let us get through startup in Runtime and into the "liberate" workflow. --- src/symbols/PhutilClassMapQuery.php | 6 ++++ src/symbols/PhutilSymbolLoader.php | 47 +++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/symbols/PhutilClassMapQuery.php b/src/symbols/PhutilClassMapQuery.php index f0347cef..531e85c2 100644 --- a/src/symbols/PhutilClassMapQuery.php +++ b/src/symbols/PhutilClassMapQuery.php @@ -45,6 +45,7 @@ final class PhutilClassMapQuery extends Phobject { private $filterNull = false; private $uniqueMethod; private $sortMethod; + private $continueOnFailure; // NOTE: If you add more configurable properties here, make sure that // cache key construction in getCacheKey() is updated properly. @@ -162,6 +163,10 @@ final class PhutilClassMapQuery extends Phobject { return $this; } + public function setContinueOnFailure($continue) { + $this->continueOnFailure = $continue; + return $this; + } /* -( Executing the Query )------------------------------------------------ */ @@ -236,6 +241,7 @@ final class PhutilClassMapQuery extends Phobject { $objects = id(new PhutilSymbolLoader()) ->setAncestorClass($ancestor) + ->setContinueOnFailure($this->continueOnFailure) ->loadObjects(); // Apply the "expand" mechanism, if it is configured. diff --git a/src/symbols/PhutilSymbolLoader.php b/src/symbols/PhutilSymbolLoader.php index 1c8fccf9..4c0c99f0 100644 --- a/src/symbols/PhutilSymbolLoader.php +++ b/src/symbols/PhutilSymbolLoader.php @@ -49,6 +49,7 @@ final class PhutilSymbolLoader { private $pathPrefix; private $suppressLoad; + private $continueOnFailure; /** @@ -148,6 +149,11 @@ final class PhutilSymbolLoader { return $this; } + public function setContinueOnFailure($continue) { + $this->continueOnFailure = $continue; + return $this; + } + /* -( Load )--------------------------------------------------------------- */ @@ -250,19 +256,56 @@ final class PhutilSymbolLoader { } if (!$this->suppressLoad) { + + // Loading a class may trigger the autoloader to load more classes + // (usually, the parent class), so we need to keep track of whether we + // are currently loading in "continue on failure" mode. Otherwise, we'll + // fail anyway if we fail to load a parent class. + + // The driving use case for the "continue on failure" mode is to let + // "arc liberate" run so it can rebuild the library map, even if you have + // made changes to Workflow or Config classes which it must load before + // it can operate. If we don't let it continue on failure, it is very + // difficult to remove or move Workflows. + + static $continue_depth = 0; + if ($this->continueOnFailure) { + $continue_depth++; + } + $caught = null; - foreach ($symbols as $symbol) { + foreach ($symbols as $key => $symbol) { try { $this->loadSymbol($symbol); } catch (Exception $ex) { + // If we failed to load this symbol, remove it from the results. + // Otherwise, we may fatal below when trying to reflect it. + unset($symbols[$key]); + $caught = $ex; } } + + $should_continue = ($continue_depth > 0); + + if ($this->continueOnFailure) { + $continue_depth--; + } + if ($caught) { // NOTE: We try to load everything even if we fail to load something, // primarily to make it possible to remove functions from a libphutil // library without breaking library startup. - throw $caught; + if ($should_continue) { + // We may not have `pht()` yet. + fprintf( + STDERR, + "%s: %s\n", + 'IGNORING CLASS LOAD FAILURE', + $caught->getMessage()); + } else { + throw $caught; + } } }