Page MenuHomePhabricator

D21071.diff
No OneTemporary

D21071.diff

diff --git a/.arclint b/.arclint
--- a/.arclint
+++ b/.arclint
@@ -62,7 +62,8 @@
"xhpast": {
"type": "xhpast",
"include": "(\\.php$)",
- "standard": "phutil.xhpast"
+ "standard": "phutil.xhpast",
+ "xhpast.php-version": "5.5.0"
}
}
}
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -216,7 +216,16 @@
'ArcanistGoTestResultParserTestCase' => 'unit/parser/__tests__/ArcanistGoTestResultParserTestCase.php',
'ArcanistHLintLinter' => 'lint/linter/ArcanistHLintLinter.php',
'ArcanistHLintLinterTestCase' => 'lint/linter/__tests__/ArcanistHLintLinterTestCase.php',
+ 'ArcanistHardpoint' => 'hardpoint/ArcanistHardpoint.php',
+ 'ArcanistHardpointEngine' => 'hardpoint/ArcanistHardpointEngine.php',
+ 'ArcanistHardpointFutureList' => 'hardpoint/ArcanistHardpointFutureList.php',
+ 'ArcanistHardpointList' => 'hardpoint/ArcanistHardpointList.php',
'ArcanistHardpointLoader' => 'loader/ArcanistHardpointLoader.php',
+ 'ArcanistHardpointObject' => 'hardpoint/ArcanistHardpointObject.php',
+ 'ArcanistHardpointQuery' => 'hardpoint/ArcanistHardpointQuery.php',
+ 'ArcanistHardpointRequest' => 'hardpoint/ArcanistHardpointRequest.php',
+ 'ArcanistHardpointRequestList' => 'hardpoint/ArcanistHardpointRequestList.php',
+ 'ArcanistHardpointTask' => 'hardpoint/ArcanistHardpointTask.php',
'ArcanistHelpWorkflow' => 'toolset/workflow/ArcanistHelpWorkflow.php',
'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistHexadecimalNumericScalarCasingXHPASTLinterRule.php',
'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistHexadecimalNumericScalarCasingXHPASTLinterRuleTestCase.php',
@@ -393,6 +402,7 @@
'ArcanistRuntime' => 'runtime/ArcanistRuntime.php',
'ArcanistRuntimeConfigurationSource' => 'config/source/ArcanistRuntimeConfigurationSource.php',
'ArcanistScalarConfigOption' => 'config/option/ArcanistScalarConfigOption.php',
+ 'ArcanistScalarHardpoint' => 'hardpoint/ArcanistScalarHardpoint.php',
'ArcanistScriptAndRegexLinter' => 'lint/linter/ArcanistScriptAndRegexLinter.php',
'ArcanistSelfClassReferenceXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistSelfClassReferenceXHPASTLinterRule.php',
'ArcanistSelfClassReferenceXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistSelfClassReferenceXHPASTLinterRuleTestCase.php',
@@ -472,6 +482,7 @@
'ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase.php',
'ArcanistVariableVariableXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistVariableVariableXHPASTLinterRule.php',
'ArcanistVariableVariableXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistVariableVariableXHPASTLinterRuleTestCase.php',
+ 'ArcanistVectorHardpoint' => 'hardpoint/ArcanistVectorHardpoint.php',
'ArcanistVersionWorkflow' => 'toolset/workflow/ArcanistVersionWorkflow.php',
'ArcanistWeldWorkflow' => 'workflow/ArcanistWeldWorkflow.php',
'ArcanistWhichWorkflow' => 'workflow/ArcanistWhichWorkflow.php',
@@ -1161,7 +1172,16 @@
'ArcanistGoTestResultParserTestCase' => 'PhutilTestCase',
'ArcanistHLintLinter' => 'ArcanistExternalLinter',
'ArcanistHLintLinterTestCase' => 'ArcanistExternalLinterTestCase',
+ 'ArcanistHardpoint' => 'Phobject',
+ 'ArcanistHardpointEngine' => 'Phobject',
+ 'ArcanistHardpointFutureList' => 'Phobject',
+ 'ArcanistHardpointList' => 'Phobject',
'ArcanistHardpointLoader' => 'Phobject',
+ 'ArcanistHardpointObject' => 'Phobject',
+ 'ArcanistHardpointQuery' => 'Phobject',
+ 'ArcanistHardpointRequest' => 'Phobject',
+ 'ArcanistHardpointRequestList' => 'Phobject',
+ 'ArcanistHardpointTask' => 'Phobject',
'ArcanistHelpWorkflow' => 'ArcanistWorkflow',
'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
@@ -1337,6 +1357,7 @@
'ArcanistRubyLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistRuntimeConfigurationSource' => 'ArcanistDictionaryConfigurationSource',
'ArcanistScalarConfigOption' => 'ArcanistConfigOption',
+ 'ArcanistScalarHardpoint' => 'ArcanistHardpoint',
'ArcanistScriptAndRegexLinter' => 'ArcanistLinter',
'ArcanistSelfClassReferenceXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistSelfClassReferenceXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
@@ -1416,6 +1437,7 @@
'ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistVariableVariableXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistVariableVariableXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
+ 'ArcanistVectorHardpoint' => 'ArcanistHardpoint',
'ArcanistVersionWorkflow' => 'ArcanistWorkflow',
'ArcanistWeldWorkflow' => 'ArcanistArcWorkflow',
'ArcanistWhichWorkflow' => 'ArcanistWorkflow',
diff --git a/src/hardpoint/ArcanistHardpoint.php b/src/hardpoint/ArcanistHardpoint.php
new file mode 100644
--- /dev/null
+++ b/src/hardpoint/ArcanistHardpoint.php
@@ -0,0 +1,32 @@
+<?php
+
+abstract class ArcanistHardpoint
+ extends Phobject {
+
+ private $hardpointKey;
+
+ public function setHardpointKey($hardpoint_key) {
+ $this->hardpointKey = $hardpoint_key;
+ return $this;
+ }
+
+ public function getHardpointKey() {
+ return $this->hardpointKey;
+ }
+
+ abstract public function isVectorHardpoint();
+
+ public function mergeHardpointValues(
+ ArcanistHardpointObject $object,
+ $old,
+ $new) {
+
+ throw new Exception(
+ pht(
+ 'Hardpoint ("%s", of type "%s") does not support merging '.
+ 'values.',
+ $this->getHardpointKey(),
+ get_class($this)));
+ }
+
+}
diff --git a/src/hardpoint/ArcanistHardpointEngine.php b/src/hardpoint/ArcanistHardpointEngine.php
new file mode 100644
--- /dev/null
+++ b/src/hardpoint/ArcanistHardpointEngine.php
@@ -0,0 +1,237 @@
+<?php
+
+final class ArcanistHardpointEngine
+ extends Phobject {
+
+ private $queries;
+ private $queryHardpointMap = array();
+
+ private $requests = array();
+
+ private $futureIterator;
+ private $waitFutures = array();
+
+ public function setQueries(array $queries) {
+ assert_instances_of($queries, 'ArcanistHardpointQuery');
+
+ $this->queries = $queries;
+ $this->queryHardpointMap = null;
+
+ return $this;
+ }
+
+ private function getQueriesForHardpoint($hardpoint) {
+ if ($this->queryHardpointMap === null) {
+ $map = array();
+
+ foreach ($this->queries as $query_key => $query) {
+ $query->setHardpointEngine($this);
+
+ $hardpoints = $query->getHardpoints();
+
+ foreach ($hardpoints as $query_hardpoint) {
+ $map[$query_hardpoint][$query_key] = $query;
+ }
+ }
+
+ $this->queryHardpointMap = $map;
+ }
+
+ return idx($this->queryHardpointMap, $hardpoint, array());
+ }
+
+ public function requestHardpoints(array $objects, array $requests) {
+ assert_instances_of($objects, 'ArcanistHardpointObject');
+
+ $results = array();
+ foreach ($requests as $request) {
+ $request = ArcanistHardpointRequest::newFromSpecification($request)
+ ->setEngine($this)
+ ->setObjects($objects);
+
+ $this->requests[] = $request;
+
+ $this->startRequest($request);
+
+ $results[] = $request;
+ }
+
+ return ArcanistHardpointRequestList::newFromRequests($results);
+ }
+
+ private function startRequest(ArcanistHardpointRequest $request) {
+ $objects = $request->getObjects();
+ $hardpoint = $request->getHardpoint();
+
+ $queries = $this->getQueriesForHardpoint($hardpoint);
+
+ $load = array();
+ foreach ($objects as $object_key => $object) {
+ if (!$object->hasHardpoint($hardpoint)) {
+ throw new Exception(
+ pht(
+ 'Object (with key "%s", of type "%s") has no hardpoint "%s". '.
+ 'Hardpoints on this object are: %s.',
+ $object_key,
+ phutil_describe_type($object),
+ $hardpoint,
+ $object->getHardpointList()->getHardpointListForDisplay()));
+ }
+
+ // If the object already has the hardpoint attached, we don't have to
+ // do anything. Throw the object away.
+
+ if ($object->hasAttachedHardpoint($hardpoint)) {
+ unset($objects[$object_key]);
+ continue;
+ }
+
+ $any_query = false;
+ foreach ($queries as $query_key => $query) {
+ if (!$query->canLoadObject($object)) {
+ continue;
+ }
+
+ $any_query = true;
+ $load[$query_key][$object_key] = $object;
+ }
+
+ if (!$any_query) {
+ throw new Exception(
+ pht(
+ 'No query exists which can load hardpoint "%s" for object '.
+ '(with key "%s" of type "%s").',
+ $hardpoint,
+ $object_key,
+ phutil_describe_type($object)));
+ }
+ }
+
+ if (!$objects) {
+ return;
+ }
+
+ $any_object = head($objects);
+ $list = $object->getHardpointList();
+ $definition = $list->getHardpointDefinition($any_object, $hardpoint);
+
+ $is_vector = ($definition->isVectorHardpoint());
+
+ if ($is_vector) {
+ foreach ($objects as $object) {
+ $object->attachHardpoint($hardpoint, array());
+ }
+ }
+
+ $request->setHardpointDefinition($definition);
+
+ foreach ($load as $query_key => $object_map) {
+ $query = id(clone $queries[$query_key]);
+
+ $task = $request->newTask()
+ ->setQuery($query)
+ ->setObjects($object_map);
+ }
+ }
+
+ public function waitForRequests(array $wait_requests) {
+ foreach ($wait_requests as $wait_key => $wait_request) {
+ if ($wait_request->getEngine() !== $this) {
+ throw new Exception(
+ pht(
+ 'Attempting to wait on a hardpoint request (with index "%s", for '.
+ 'hardpoint "%s") that is part of a different engine.',
+ $wait_key,
+ $wait_request->getHardpoint()));
+ }
+ }
+
+ while (true) {
+ $any_progress = false;
+ foreach ($this->requests as $req_key => $request) {
+ $did_update = $request->updateTasks();
+ if ($did_update) {
+ $any_progress = true;
+ }
+ }
+
+ // If we made progress by directly executing requests, continue
+ // excuting them until we stop making progress. We want to queue all
+ // reachable futures before we wait on futures.
+
+ if ($any_progress) {
+ continue;
+ }
+
+ foreach ($this->requests as $request_key => $request) {
+ if ($request->isComplete()) {
+ unset($this->requests[$request_key]);
+ }
+ }
+
+ if (!$this->requests) {
+ break;
+ }
+
+ $resolved_key = $this->updateFutures();
+
+ if ($resolved_key === null) {
+ throw new Exception(
+ pht(
+ 'Hardpoint engine can not resolve: no request made progress '.
+ 'during the last update cycle and there are no futures '.
+ 'awaiting resolution.'));
+ }
+ }
+ }
+
+ private function updateFutures() {
+ $iterator = $this->futureIterator;
+
+ $is_rewind = false;
+ $wait_futures = $this->waitFutures;
+ if ($wait_futures) {
+ if (!$this->futureIterator) {
+ $iterator = new FutureIterator(array());
+ foreach ($wait_futures as $wait_future) {
+ $iterator->addFuture($wait_future);
+ }
+ $is_rewind = true;
+ $this->futureIterator = $iterator;
+ } else {
+ foreach ($wait_futures as $wait_future) {
+ $iterator->addFuture($wait_future);
+ }
+ }
+ $this->waitFutures = array();
+ }
+
+ $resolved_key = null;
+ if ($iterator) {
+ if ($is_rewind) {
+ $iterator->rewind();
+ } else {
+ $iterator->next();
+ }
+
+ if ($iterator->valid()) {
+ $resolved_key = $iterator->key();
+ } else {
+ $this->futureIterator = null;
+ }
+ }
+
+ return $resolved_key;
+ }
+
+ public function addFutures(array $futures) {
+ assert_instances_of($futures, 'Future');
+ $this->waitFutures += mpull($futures, null, 'getFutureKey');
+
+ // TODO: We could reasonably add these futures to the iterator
+ // immediately and start them here, instead of waiting.
+
+ return $this;
+ }
+
+}
diff --git a/src/hardpoint/ArcanistHardpointFutureList.php b/src/hardpoint/ArcanistHardpointFutureList.php
new file mode 100644
--- /dev/null
+++ b/src/hardpoint/ArcanistHardpointFutureList.php
@@ -0,0 +1,31 @@
+<?php
+
+final class ArcanistHardpointFutureList
+ extends Phobject {
+
+ private $futures;
+ private $sendResult;
+
+ public static function newFromFutures(array $futures) {
+ assert_instances_of($futures, 'Future');
+
+ $object = new self();
+ $object->futures = $futures;
+
+ return $object;
+ }
+
+ public function getFutures() {
+ return $this->futures;
+ }
+
+ public function setSendResult($send_result) {
+ $this->sendResult = $send_result;
+ return $this;
+ }
+
+ public function getSendResult() {
+ return $this->sendResult;
+ }
+
+}
diff --git a/src/hardpoint/ArcanistHardpointList.php b/src/hardpoint/ArcanistHardpointList.php
new file mode 100644
--- /dev/null
+++ b/src/hardpoint/ArcanistHardpointList.php
@@ -0,0 +1,121 @@
+<?php
+
+final class ArcanistHardpointList
+ extends Phobject {
+
+ private $hardpoints = array();
+ private $attached = array();
+ private $data = array();
+
+ public function setHardpoints(array $hardpoints) {
+ assert_instances_of($hardpoints, 'ArcanistHardpoint');
+
+ $map = array();
+ foreach ($hardpoints as $idx => $hardpoint) {
+ $key = $hardpoint->getHardpointKey();
+
+ if (!strlen($key)) {
+ throw new Exception(
+ pht(
+ 'Hardpoint (at index "%s") has no hardpoint key. Each hardpoint '.
+ 'must have a key that is unique among hardpoints on the object.',
+ $idx));
+ }
+
+ if (isset($map[$key])) {
+ throw new Exception(
+ pht(
+ 'Hardpoint (at index "%s") has the same key ("%s") as an earlier '.
+ 'hardpoint. Each hardpoint must have a key that is unique '.
+ 'among hardpoints on the object.'));
+ }
+
+ $map[$key] = $hardpoint;
+ }
+
+ $this->hardpoints = $map;
+
+ return $this;
+ }
+
+ public function hasHardpoint($object, $hardpoint) {
+ return isset($this->hardpoints[$hardpoint]);
+ }
+
+ public function hasAttachedHardpoint($object, $hardpoint) {
+ return isset($this->attached[$hardpoint]);
+ }
+
+ public function getHardpointDefinition($object, $hardpoint) {
+ if (!$this->hasHardpoint($object, $hardpoint)) {
+ throw new Exception(
+ pht(
+ 'Hardpoint ("%s") is not registered on this object (of type "%s") '.
+ 'so the definition object does not exist. Hardpoints are: %s.',
+ $hardpoint,
+ phutil_describe_type($object),
+ $this->getHardpointListForDisplay()));
+ }
+
+ return $this->hardpoints[$hardpoint];
+ }
+
+ public function getHardpoint($object, $hardpoint) {
+ if (!$this->hasHardpoint($object, $hardpoint)) {
+ throw new Exception(
+ pht(
+ 'Hardpoint ("%s") is not registered on this object (of type "%s"). '.
+ 'Hardpoints are: %s.',
+ $hardpoint,
+ phutil_describe_type($object),
+ $this->getHardpointListForDisplay()));
+ }
+
+ if (!$this->hasAttachedHardpoint($object, $hardpoint)) {
+ throw new Exception(
+ pht(
+ 'Hardpoint data (for hardpoint "%s") is not attached.',
+ $hardpoint));
+ }
+
+ return $this->data[$hardpoint];
+ }
+
+ public function setHardpointValue($object, $hardpoint, $value) {
+ if (!$this->hasHardpoint($object, $hardpoint)) {
+ throw new Exception(
+ pht(
+ 'Hardpoint ("%s") is not registered on this object (of type "%s"). '.
+ 'Hardpoints are: %s.',
+ $hardpoint,
+ phutil_describe_type($object),
+ $this->getHardpointListforDisplay()));
+ }
+
+ $this->attached[$hardpoint] = true;
+ $this->data[$hardpoint] = $value;
+ }
+
+ public function attachHardpoint($object, $hardpoint, $value) {
+ if ($this->hasAttachedHardpoint($object, $hardpoint)) {
+ throw new Exception(
+ pht(
+ 'Hardpoint ("%s") already has attached data.',
+ $hardpoint));
+ }
+
+ $this->setHardpointValue($object, $hardpoint, $value);
+ }
+
+ public function getHardpointListForDisplay() {
+ $list = array_keys($this->hardpoints);
+
+ if ($list) {
+ sort($list);
+ return implode(', ', $list);
+ }
+
+ return pht('<none>');
+ }
+
+}
diff --git a/src/hardpoint/ArcanistHardpointObject.php b/src/hardpoint/ArcanistHardpointObject.php
new file mode 100644
--- /dev/null
+++ b/src/hardpoint/ArcanistHardpointObject.php
@@ -0,0 +1,91 @@
+<?php
+
+abstract class ArcanistHardpointObject
+ extends Phobject {
+
+ private $hardpointList;
+
+ final public function getHardpoint($hardpoint) {
+ return $this->getHardpointList()->getHardpoint(
+ $this,
+ $hardpoint);
+ }
+
+ final public function attachHardpoint($hardpoint, $value) {
+ $this->getHardpointList()->attachHardpoint(
+ $this,
+ $hardpoint,
+ $value);
+
+ return $this;
+ }
+
+ final public function mergeHardpoint($hardpoint, $value) {
+ $hardpoint_list = $this->getHardpointList();
+ $hardpoint_def = $hardpoint_list->getHardpointDefinition(
+ $this,
+ $hardpoint);
+
+ $old_value = $this->getHardpoint($hardpoint);
+ $new_value = $hardpoint_def->mergeHardpointValues(
+ $this,
+ $old_value,
+ $value);
+
+ $hardpoint_list->setHardpointValue(
+ $this,
+ $hardpoint,
+ $new_value);
+
+ return $this;
+ }
+
+ final public function hasHardpoint($hardpoint) {
+ return $this->getHardpointList()->hasHardpoint($this, $hardpoint);
+ }
+
+ final public function hasAttachedHardpoint($hardpoint) {
+ return $this->getHardpointList()->hasAttachedHardpoint(
+ $this,
+ $hardpoint);
+ }
+
+ protected function newHardpoints() {
+ return array();
+ }
+
+ final protected function newHardpoint($hardpoint_key) {
+ return id(new ArcanistScalarHardpoint())
+ ->setHardpointKey($hardpoint_key);
+ }
+
+ final protected function newVectorHardpoint($hardpoint_key) {
+ return id(new ArcanistVectorHardpoint())
+ ->setHardpointKey($hardpoint_key);
+ }
+
+
+ final public function getHardpointList() {
+ if ($this->hardpointList === null) {
+ $list = $this->newHardpointList();
+
+ // TODO: Cache the hardpoint list with the class name as a key? If so,
+ // it needs to be purged when the request cache is purged.
+
+ $hardpoints = $this->newHardpoints();
+
+ // TODO: Verify the hardpoints list is structured properly.
+
+ $list->setHardpoints($hardpoints);
+
+ $this->hardpointList = $list;
+ }
+
+ return $this->hardpointList;
+ }
+
+ private function newHardpointList() {
+ return new ArcanistHardpointList();
+ }
+
+}
diff --git a/src/hardpoint/ArcanistHardpointQuery.php b/src/hardpoint/ArcanistHardpointQuery.php
new file mode 100644
--- /dev/null
+++ b/src/hardpoint/ArcanistHardpointQuery.php
@@ -0,0 +1,36 @@
+<?php
+
+abstract class ArcanistHardpointQuery
+ extends Phobject {
+
+ private $hardpointEngine;
+
+ final public function setHardpointEngine(ArcanistHardpointEngine $engine) {
+ $this->hardpointEngine = $engine;
+ return $this;
+ }
+
+ final public function getHardpointEngine() {
+ return $this->hardpointEngine;
+ }
+
+ abstract public function getHardpoints();
+ abstract public function canLoadObject(ArcanistHardpointObject $object);
+ abstract public function loadHardpoint(array $objects, $hardpoint);
+
+ final protected function yieldFuture(Future $future) {
+ return $this->yieldFutures(array($future))
+ ->setSendResult(true);
+ }
+
+ final protected function yieldFutures(array $futures) {
+ return ArcanistHardpointFutureList::newFromFutures($futures);
+ }
+
+ final protected function yieldRequests(array $objects, $requests) {
+ $engine = $this->getHardpointEngine();
+ $requests = $engine->requestHardpoints($objects, $requests);
+ return $requests;
+ }
+
+}
diff --git a/src/hardpoint/ArcanistHardpointRequest.php b/src/hardpoint/ArcanistHardpointRequest.php
new file mode 100644
--- /dev/null
+++ b/src/hardpoint/ArcanistHardpointRequest.php
@@ -0,0 +1,132 @@
+<?php
+
+final class ArcanistHardpointRequest
+ extends Phobject {
+
+ private $engine;
+ private $objects;
+ private $hardpoint;
+ private $hardpointDefinition;
+ private $tasks = array();
+ private $isComplete;
+
+ public static function newFromSpecification($spec) {
+ if ($spec instanceof ArcanistHardpointRequest) {
+ return $spec;
+ }
+
+ if (is_string($spec)) {
+ return id(new self())->setHardpoint($spec);
+ }
+
+ throw new Exception(
+ pht(
+ 'Unknown Hardpoint request specification (of type "%s").',
+ phutil_describe_type($spec)));
+ }
+
+ public function setEngine(ArcanistHardpointEngine $engine) {
+ $this->engine = $engine;
+ return $this;
+ }
+
+ public function getEngine() {
+ return $this->engine;
+ }
+
+ public function setHardpoint($hardpoint) {
+ $this->hardpoint = $hardpoint;
+ return $this;
+ }
+
+ public function getHardpoint() {
+ return $this->hardpoint;
+ }
+
+ public function setObjects(array $objects) {
+ $this->objects = $objects;
+ return $this;
+ }
+
+ public function getObjects() {
+ return $this->objects;
+ }
+
+ public function newTask() {
+ $task = id(new ArcanistHardpointTask())
+ ->setRequest($this);
+
+ $this->tasks[] = $task;
+ $this->isComplete = false;
+
+ return $task;
+ }
+
+ public function isComplete() {
+ return $this->isComplete;
+ }
+
+ public function getTasks() {
+ return $this->tasks;
+ }
+
+ public function updateTasks() {
+ $any_progress = false;
+
+ foreach ($this->tasks as $task) {
+ $did_update = $task->updateTask();
+ if ($did_update) {
+ $any_progress = true;
+ }
+ }
+
+ foreach ($this->tasks as $task_key => $task) {
+ if ($task->isComplete()) {
+ unset($this->tasks[$task_key]);
+ }
+ }
+
+ if (!$this->tasks) {
+
+ // TODO: We can skip or modify this check if the hardpoint is a vector
+ // hardpoint.
+
+ $objects = $this->getObjects();
+ $hardpoint = $this->getHardpoint();
+ foreach ($objects as $object) {
+ if (!$object->hasAttachedHardpoint($hardpoint)) {
+ throw new Exception(
+ pht(
+ 'Unable to load hardpoint "%s" for object (of type "%s"). '.
+ 'All hardpoint query tasks resolved but none attached '.
+ 'a value to the hardpoint.',
+ $hardpoint,
+ phutil_describe_type($object)));
+ }
+ }
+
+ // We may arrive here if a request is queued that can be satisfied
+ // immediately, most often because it requests hardpoints which are
+ // already attached. We don't have to do any work, so we have no tasks
+ // to update or complete and can complete the request immediately.
+ if (!$this->isComplete) {
+ $any_progress = true;
+ }
+
+ $this->isComplete = true;
+ }
+
+ return $any_progress;
+ }
+
+
+ public function setHardpointDefinition($hardpoint_definition) {
+ $this->hardpointDefinition = $hardpoint_definition;
+ return $this;
+ }
+
+ public function getHardpointDefinition() {
+ return $this->hardpointDefinition;
+ }
+
+}
diff --git a/src/hardpoint/ArcanistHardpointRequestList.php b/src/hardpoint/ArcanistHardpointRequestList.php
new file mode 100644
--- /dev/null
+++ b/src/hardpoint/ArcanistHardpointRequestList.php
@@ -0,0 +1,21 @@
+<?php
+
+final class ArcanistHardpointRequestList
+ extends Phobject {
+
+ private $requests;
+
+ public static function newFromRequests(array $requests) {
+ assert_instances_of($requests, 'ArcanistHardpointRequest');
+
+ $object = new self();
+ $object->requests = $requests;
+
+ return $object;
+ }
+
+ public function getRequests() {
+ return $this->requests;
+ }
+
+}
diff --git a/src/hardpoint/ArcanistHardpointTask.php b/src/hardpoint/ArcanistHardpointTask.php
new file mode 100644
--- /dev/null
+++ b/src/hardpoint/ArcanistHardpointTask.php
@@ -0,0 +1,246 @@
+<?php
+
+final class ArcanistHardpointTask
+ extends Phobject {
+
+ private $request;
+ private $query;
+ private $objects;
+
+ private $isComplete;
+ private $generator;
+ private $hasRewound;
+ private $sendFuture;
+
+ private $blockingRequests = array();
+ private $blockingFutures = array();
+
+ public function setRequest(ArcanistHardpointRequest $request) {
+ $this->request = $request;
+ return $this;
+ }
+
+ public function getRequest() {
+ return $this->request;
+ }
+
+ public function setQuery(ArcanistHardpointQuery $query) {
+ $this->query = $query;
+ return $this;
+ }
+
+ public function getQuery() {
+ return $this->query;
+ }
+
+ public function setObjects(array $objects) {
+ $this->objects = $objects;
+ return $this;
+ }
+
+ public function getObjects() {
+ return $this->objects;
+ }
+
+ public function isComplete() {
+ return $this->isComplete;
+ }
+
+ public function updateTask() {
+ if ($this->isComplete()) {
+ return false;
+ }
+
+ // If we're blocked by other requests, we have to wait for them to
+ // resolve.
+ if ($this->getBlockingRequests()) {
+ return false;
+ }
+
+ // If we're blocked by futures, we have to wait for them to resolve.
+ if ($this->getBlockingFutures()) {
+ return false;
+ }
+
+ $query = $this->getQuery();
+
+ // If we've previously produced a generator, iterate it.
+
+ if ($this->generator) {
+ $generator = $this->generator;
+
+ $has_send = false;
+ $send_value = null;
+
+ // If our last iteration generated a single future and it was marked to
+ // be sent back to the generator, resolve the future (it should already
+ // be ready to resolve) and send the result.
+
+ if ($this->sendFuture) {
+ $has_send = true;
+ $future = $this->sendFuture;
+ $this->sendFuture = null;
+
+ $send_value = $future->resolve();
+ }
+
+ if ($has_send && !$this->hasRewound) {
+ throw new Exception(
+ pht(
+ 'Generator has never rewound, but has a value to send. This '.
+ 'is invalid.'));
+ }
+
+ if (!$this->hasRewound) {
+ $this->hasRewound = true;
+ $generator->rewind();
+ } else if ($has_send) {
+ $generator->send($send_value);
+ } else {
+ $generator->next();
+ }
+
+ if ($generator->valid()) {
+ $result = $generator->current();
+
+ if ($result instanceof Future) {
+ $result = new ArcanistHardpointFutureList($result);
+ }
+
+ if ($result instanceof ArcanistHardpointFutureList) {
+ $futures = $result->getFutures();
+ $is_send = $result->getSendResult();
+
+ $this->getRequest()->getEngine()->addFutures($futures);
+
+ foreach ($futures as $future) {
+ $this->blockingFutures[] = $future;
+ }
+
+ if ($is_send) {
+ if (count($futures) === 1) {
+ $this->sendFuture = head($futures);
+ } else {
+ throw new Exception(
+ pht(
+ 'Hardpoint future list is marked to send results to the '.
+ 'generator, but the list does not have exactly one future '.
+ '(it has %s).',
+ phutil_count($futures)));
+ }
+ }
+
+ return true;
+ }
+
+ $is_request = ($result instanceof ArcanistHardpointRequest);
+ $is_request_list = ($result instanceof ArcanistHardpointRequestList);
+ if ($is_request || $is_request_list) {
+ if ($is_request) {
+ $request_list = array($result);
+ } else {
+ $request_list = $result->getRequests();
+ }
+
+ // TODO: Make sure these requests have already been added to the
+ // engine.
+
+ foreach ($request_list as $blocking_request) {
+ $this->blockingRequests[] = $blocking_request;
+ }
+
+ return true;
+ }
+
+ throw new Exception(
+ pht(
+ 'Hardpoint generator (for query "%s") yielded an unexpected '.
+ 'value. Generators may only yield "Future" or '.
+ '"ArcanistHardpointRequest" objects, got "%s".',
+ get_class($query),
+ phutil_describe_type($result)));
+ }
+
+ $this->generator = null;
+ $result = $generator->getReturn();
+
+ $this->attachResult($result);
+
+ return true;
+ }
+
+ $objects = $this->getObjects();
+ $hardpoint = $this->getRequest()->getHardpoint();
+
+ $result = $query->loadHardpoint($objects, $hardpoint);
+ if ($result instanceof Generator) {
+ $this->generator = $result;
+ $this->hasRewound = false;
+
+ // If we produced a generator, we can attempt to iterate it immediately.
+ return $this->updateTask();
+ }
+
+ $this->attachResult($result);
+
+ return true;
+ }
+
+ public function getBlockingRequests() {
+ $blocking = array();
+
+ foreach ($this->blockingRequests as $key => $request) {
+ if (!$request->isComplete()) {
+ $blocking[$key] = $request;
+ }
+ }
+
+ $this->blockingRequests = $blocking;
+
+ return $blocking;
+ }
+
+ public function getBlockingFutures() {
+ $blocking = array();
+
+ foreach ($this->blockingFutures as $key => $future) {
+ if (!$future->hasResult() && !$future->hasException()) {
+ $blocking[$key] = $future;
+ }
+ }
+
+ $this->blockingFutures = $blocking;
+
+ return $blocking;
+ }
+
+ private function attachResult($result) {
+ $objects = $this->getObjects();
+ $hardpoint = $this->getRequest()->getHardpoint();
+
+ $definition = $this->getRequest()->getHardpointDefinition();
+ $is_vector = $definition->isVectorHardpoint();
+
+ foreach ($result as $object_key => $value) {
+ if (!isset($objects[$object_key])) {
+ throw new Exception(
+ pht(
+ 'Bad object key ("%s").',
+ $object_key));
+ }
+
+ $object = $objects[$object_key];
+
+ if ($is_vector) {
+ $object->mergeHardpoint($hardpoint, $value);
+ } else {
+ if (!$object->hasAttachedHardpoint($hardpoint)) {
+ $object->attachHardpoint($hardpoint, $value);
+ }
+ }
+ }
+
+ $this->isComplete = true;
+ }
+
+}
diff --git a/src/hardpoint/ArcanistScalarHardpoint.php b/src/hardpoint/ArcanistScalarHardpoint.php
new file mode 100644
--- /dev/null
+++ b/src/hardpoint/ArcanistScalarHardpoint.php
@@ -0,0 +1,10 @@
+<?php
+
+final class ArcanistScalarHardpoint
+ extends ArcanistHardpoint {
+
+ public function isVectorHardpoint() {
+ return false;
+ }
+
+}
diff --git a/src/hardpoint/ArcanistVectorHardpoint.php b/src/hardpoint/ArcanistVectorHardpoint.php
new file mode 100644
--- /dev/null
+++ b/src/hardpoint/ArcanistVectorHardpoint.php
@@ -0,0 +1,22 @@
+<?php
+
+final class ArcanistVectorHardpoint
+ extends ArcanistHardpoint {
+
+ public function isVectorHardpoint() {
+ return true;
+ }
+
+ public function mergeHardpointValues(
+ ArcanistHardpointObject $object,
+ $old,
+ $new) {
+
+ foreach ($new as $item) {
+ $old[] = $item;
+ }
+
+ return $old;
+ }
+
+}

File Metadata

Mime Type
text/plain
Expires
Sun, Mar 16, 1:04 AM (3 d, 6 h ago)
Storage Engine
amazon-s3
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
phabricator/secure/xz/yl/2uo6ezg2brjt6ijx
Default Alt Text
D21071.diff (31 KB)

Event Timeline