1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-23 07:12:40 +01:00

Add new "Hardpoint" classes to support request parallelization

Summary:
Depends on D21070. Ref T11968. Adds "yield"-aware query classes for parallelizing service calls.

These will replace the similar (but not yield-aware) "Hardpoint" classes introduced previously. This is an API change but most of the old classes still exist and still do the same thing, just with more "yield" statements.

This just adds a bunch of new code with no callers and no API updates.

Test Plan: See future changes.

Maniphest Tasks: T11968

Differential Revision: https://secure.phabricator.com/D21071
This commit is contained in:
epriestley 2020-04-08 08:38:52 -07:00
parent 0b3cd39230
commit 85141c4d90
13 changed files with 1003 additions and 1 deletions

View file

@ -62,7 +62,8 @@
"xhpast": { "xhpast": {
"type": "xhpast", "type": "xhpast",
"include": "(\\.php$)", "include": "(\\.php$)",
"standard": "phutil.xhpast" "standard": "phutil.xhpast",
"xhpast.php-version": "5.5.0"
} }
} }
} }

View file

@ -216,7 +216,16 @@ phutil_register_library_map(array(
'ArcanistGoTestResultParserTestCase' => 'unit/parser/__tests__/ArcanistGoTestResultParserTestCase.php', 'ArcanistGoTestResultParserTestCase' => 'unit/parser/__tests__/ArcanistGoTestResultParserTestCase.php',
'ArcanistHLintLinter' => 'lint/linter/ArcanistHLintLinter.php', 'ArcanistHLintLinter' => 'lint/linter/ArcanistHLintLinter.php',
'ArcanistHLintLinterTestCase' => 'lint/linter/__tests__/ArcanistHLintLinterTestCase.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', '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', 'ArcanistHelpWorkflow' => 'toolset/workflow/ArcanistHelpWorkflow.php',
'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistHexadecimalNumericScalarCasingXHPASTLinterRule.php', 'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistHexadecimalNumericScalarCasingXHPASTLinterRule.php',
'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistHexadecimalNumericScalarCasingXHPASTLinterRuleTestCase.php', 'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistHexadecimalNumericScalarCasingXHPASTLinterRuleTestCase.php',
@ -393,6 +402,7 @@ phutil_register_library_map(array(
'ArcanistRuntime' => 'runtime/ArcanistRuntime.php', 'ArcanistRuntime' => 'runtime/ArcanistRuntime.php',
'ArcanistRuntimeConfigurationSource' => 'config/source/ArcanistRuntimeConfigurationSource.php', 'ArcanistRuntimeConfigurationSource' => 'config/source/ArcanistRuntimeConfigurationSource.php',
'ArcanistScalarConfigOption' => 'config/option/ArcanistScalarConfigOption.php', 'ArcanistScalarConfigOption' => 'config/option/ArcanistScalarConfigOption.php',
'ArcanistScalarHardpoint' => 'hardpoint/ArcanistScalarHardpoint.php',
'ArcanistScriptAndRegexLinter' => 'lint/linter/ArcanistScriptAndRegexLinter.php', 'ArcanistScriptAndRegexLinter' => 'lint/linter/ArcanistScriptAndRegexLinter.php',
'ArcanistSelfClassReferenceXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistSelfClassReferenceXHPASTLinterRule.php', 'ArcanistSelfClassReferenceXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistSelfClassReferenceXHPASTLinterRule.php',
'ArcanistSelfClassReferenceXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistSelfClassReferenceXHPASTLinterRuleTestCase.php', 'ArcanistSelfClassReferenceXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistSelfClassReferenceXHPASTLinterRuleTestCase.php',
@ -472,6 +482,7 @@ phutil_register_library_map(array(
'ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase.php', 'ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase.php',
'ArcanistVariableVariableXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistVariableVariableXHPASTLinterRule.php', 'ArcanistVariableVariableXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistVariableVariableXHPASTLinterRule.php',
'ArcanistVariableVariableXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistVariableVariableXHPASTLinterRuleTestCase.php', 'ArcanistVariableVariableXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistVariableVariableXHPASTLinterRuleTestCase.php',
'ArcanistVectorHardpoint' => 'hardpoint/ArcanistVectorHardpoint.php',
'ArcanistVersionWorkflow' => 'toolset/workflow/ArcanistVersionWorkflow.php', 'ArcanistVersionWorkflow' => 'toolset/workflow/ArcanistVersionWorkflow.php',
'ArcanistWeldWorkflow' => 'workflow/ArcanistWeldWorkflow.php', 'ArcanistWeldWorkflow' => 'workflow/ArcanistWeldWorkflow.php',
'ArcanistWhichWorkflow' => 'workflow/ArcanistWhichWorkflow.php', 'ArcanistWhichWorkflow' => 'workflow/ArcanistWhichWorkflow.php',
@ -1161,7 +1172,16 @@ phutil_register_library_map(array(
'ArcanistGoTestResultParserTestCase' => 'PhutilTestCase', 'ArcanistGoTestResultParserTestCase' => 'PhutilTestCase',
'ArcanistHLintLinter' => 'ArcanistExternalLinter', 'ArcanistHLintLinter' => 'ArcanistExternalLinter',
'ArcanistHLintLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistHLintLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistHardpoint' => 'Phobject',
'ArcanistHardpointEngine' => 'Phobject',
'ArcanistHardpointFutureList' => 'Phobject',
'ArcanistHardpointList' => 'Phobject',
'ArcanistHardpointLoader' => 'Phobject', 'ArcanistHardpointLoader' => 'Phobject',
'ArcanistHardpointObject' => 'Phobject',
'ArcanistHardpointQuery' => 'Phobject',
'ArcanistHardpointRequest' => 'Phobject',
'ArcanistHardpointRequestList' => 'Phobject',
'ArcanistHardpointTask' => 'Phobject',
'ArcanistHelpWorkflow' => 'ArcanistWorkflow', 'ArcanistHelpWorkflow' => 'ArcanistWorkflow',
'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
@ -1337,6 +1357,7 @@ phutil_register_library_map(array(
'ArcanistRubyLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistRubyLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistRuntimeConfigurationSource' => 'ArcanistDictionaryConfigurationSource', 'ArcanistRuntimeConfigurationSource' => 'ArcanistDictionaryConfigurationSource',
'ArcanistScalarConfigOption' => 'ArcanistConfigOption', 'ArcanistScalarConfigOption' => 'ArcanistConfigOption',
'ArcanistScalarHardpoint' => 'ArcanistHardpoint',
'ArcanistScriptAndRegexLinter' => 'ArcanistLinter', 'ArcanistScriptAndRegexLinter' => 'ArcanistLinter',
'ArcanistSelfClassReferenceXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistSelfClassReferenceXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistSelfClassReferenceXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistSelfClassReferenceXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
@ -1416,6 +1437,7 @@ phutil_register_library_map(array(
'ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistVariableVariableXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistVariableVariableXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistVariableVariableXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistVariableVariableXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistVectorHardpoint' => 'ArcanistHardpoint',
'ArcanistVersionWorkflow' => 'ArcanistWorkflow', 'ArcanistVersionWorkflow' => 'ArcanistWorkflow',
'ArcanistWeldWorkflow' => 'ArcanistArcWorkflow', 'ArcanistWeldWorkflow' => 'ArcanistArcWorkflow',
'ArcanistWhichWorkflow' => 'ArcanistWorkflow', 'ArcanistWhichWorkflow' => 'ArcanistWorkflow',

View file

@ -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)));
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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>');
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,10 @@
<?php
final class ArcanistScalarHardpoint
extends ArcanistHardpoint {
public function isVectorHardpoint() {
return false;
}
}

View file

@ -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;
}
}