Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F17942786
D21071.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
31 KB
Referenced Files
None
Subscribers
None
D21071.diff
View Options
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
Details
Attached
Mime Type
text/plain
Expires
Aug 1 2025, 7:10 AM (4 w, 4 d ago)
Storage Engine
amazon-s3
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
phabricator/secure/ds/o5/v7foutcws4gmnegp
Default Alt Text
D21071.diff (31 KB)
Attached To
Mode
D21071: Add new "Hardpoint" classes to support request parallelization
Attached
Detach File
Event Timeline
Log In to Comment