@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add an "Abort Older Builds" build step to Harbormaster

Summary:
Ref T13124. See PHI531. When a revision is updated, builds against the older diff tend to stop being relevant. Add an option to abort outstanding older builds automatically.

At least for now, I'm adding this as a build step instead of some kind of special checkbox. An alternate implementation would be some kind of "Edit Options" action on plans with a checkbox like `[X] When this build starts, abort older builds.`

I think adding it as a build step is a bit simpler, and likely to lead to greater consistency and flexibility down the road, make it easier to add options, etc., and since we don't really have any other current use cases for "a bunch of checkboxes". This might change eventually if we add a bunch of checkboxes for some other reason.

The actual step activates //before// the build queues, so it doesn't need to wait in queue before it can actually act. T13088 discusses some plans here if this sticks.

Test Plan:
- Created a "Sleep for 120 seconds" build plan and triggered it with Herald.
- Added an "Abort Older Builds" step.
- Updated a revision several times in a row, saw older builds abort.

Reviewers: amckinley

Reviewed By: amckinley

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13124

Differential Revision: https://secure.phabricator.com/D19376

+247 -35
+4
src/__phutil_library_map__.php
··· 1263 1263 'FundInitiativeTransactionType' => 'applications/fund/xaction/FundInitiativeTransactionType.php', 1264 1264 'FundInitiativeViewController' => 'applications/fund/controller/FundInitiativeViewController.php', 1265 1265 'FundSchemaSpec' => 'applications/fund/storage/FundSchemaSpec.php', 1266 + 'HarbormasterAbortOlderBuildsBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterAbortOlderBuildsBuildStepImplementation.php', 1266 1267 'HarbormasterArcLintBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterArcLintBuildStepImplementation.php', 1267 1268 'HarbormasterArcUnitBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterArcUnitBuildStepImplementation.php', 1268 1269 'HarbormasterArtifact' => 'applications/harbormaster/artifact/HarbormasterArtifact.php', ··· 1358 1359 'HarbormasterCircleCIBuildableInterface' => 'applications/harbormaster/interface/HarbormasterCircleCIBuildableInterface.php', 1359 1360 'HarbormasterCircleCIHookController' => 'applications/harbormaster/controller/HarbormasterCircleCIHookController.php', 1360 1361 'HarbormasterConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php', 1362 + 'HarbormasterControlBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterControlBuildStepGroup.php', 1361 1363 'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php', 1362 1364 'HarbormasterCreateArtifactConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php', 1363 1365 'HarbormasterCreatePlansCapability' => 'applications/harbormaster/capability/HarbormasterCreatePlansCapability.php', ··· 6636 6638 'FundInitiativeTransactionType' => 'PhabricatorModularTransactionType', 6637 6639 'FundInitiativeViewController' => 'FundController', 6638 6640 'FundSchemaSpec' => 'PhabricatorConfigSchemaSpec', 6641 + 'HarbormasterAbortOlderBuildsBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 6639 6642 'HarbormasterArcLintBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 6640 6643 'HarbormasterArcUnitBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 6641 6644 'HarbormasterArtifact' => 'Phobject', ··· 6771 6774 'HarbormasterCircleCIBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 6772 6775 'HarbormasterCircleCIHookController' => 'HarbormasterController', 6773 6776 'HarbormasterConduitAPIMethod' => 'ConduitAPIMethod', 6777 + 'HarbormasterControlBuildStepGroup' => 'HarbormasterBuildStepGroup', 6774 6778 'HarbormasterController' => 'PhabricatorController', 6775 6779 'HarbormasterCreateArtifactConduitAPIMethod' => 'HarbormasterConduitAPIMethod', 6776 6780 'HarbormasterCreatePlansCapability' => 'PhabricatorPolicyCapability',
+13
src/applications/harbormaster/constants/HarbormasterBuildStatus.php
··· 98 98 ); 99 99 } 100 100 101 + public static function getIncompleteStatusConstants() { 102 + $map = self::getBuildStatusSpecMap(); 103 + 104 + $constants = array(); 105 + foreach ($map as $constant => $spec) { 106 + if (!$spec['isComplete']) { 107 + $constants[] = $constant; 108 + } 109 + } 110 + 111 + return $constants; 112 + } 113 + 101 114 public static function getCompletedStatusConstants() { 102 115 return array( 103 116 self::STATUS_PASSED,
+1 -12
src/applications/harbormaster/controller/HarbormasterBuildActionController.php
··· 51 51 } 52 52 53 53 if ($request->isDialogFormPost() && $can_issue) { 54 - $editor = id(new HarbormasterBuildTransactionEditor()) 55 - ->setActor($viewer) 56 - ->setContentSourceFromRequest($request) 57 - ->setContinueOnNoEffect(true) 58 - ->setContinueOnMissingFields(true); 59 - 60 - $xaction = id(new HarbormasterBuildTransaction()) 61 - ->setTransactionType(HarbormasterBuildTransaction::TYPE_COMMAND) 62 - ->setNewValue($action); 63 - 64 - $editor->applyTransactions($build, array($xaction)); 65 - 54 + $build->sendMessage($viewer, $action); 66 55 return id(new AphrontRedirectResponse())->setURI($return_uri); 67 56 } 68 57
+14 -23
src/applications/harbormaster/query/HarbormasterBuildStepQuery.php
··· 22 22 return $this; 23 23 } 24 24 25 - protected function loadPage() { 26 - $table = new HarbormasterBuildStep(); 27 - $conn_r = $table->establishConnection('r'); 25 + public function newResultObject() { 26 + return new HarbormasterBuildStep(); 27 + } 28 28 29 - $data = queryfx_all( 30 - $conn_r, 31 - 'SELECT * FROM %T %Q %Q %Q', 32 - $table->getTableName(), 33 - $this->buildWhereClause($conn_r), 34 - $this->buildOrderClause($conn_r), 35 - $this->buildLimitClause($conn_r)); 36 - 37 - return $table->loadAllFromArray($data); 29 + protected function loadPage() { 30 + return $this->loadStandardPage($this->newResultObject()); 38 31 } 39 32 40 - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { 41 - $where = array(); 33 + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { 34 + $where = parent::buildWhereClauseParts($conn); 42 35 43 - if ($this->ids) { 36 + if ($this->ids !== null) { 44 37 $where[] = qsprintf( 45 - $conn_r, 38 + $conn, 46 39 'id IN (%Ld)', 47 40 $this->ids); 48 41 } 49 42 50 - if ($this->phids) { 43 + if ($this->phids !== null) { 51 44 $where[] = qsprintf( 52 - $conn_r, 45 + $conn, 53 46 'phid in (%Ls)', 54 47 $this->phids); 55 48 } 56 49 57 - if ($this->buildPlanPHIDs) { 50 + if ($this->buildPlanPHIDs !== null) { 58 51 $where[] = qsprintf( 59 - $conn_r, 52 + $conn, 60 53 'buildPlanPHID in (%Ls)', 61 54 $this->buildPlanPHIDs); 62 55 } 63 56 64 - $where[] = $this->buildPagingClause($conn_r); 65 - 66 - return $this->formatWhereClause($where); 57 + return $where; 67 58 } 68 59 69 60 protected function willFilterPage(array $page) {
+135
src/applications/harbormaster/step/HarbormasterAbortOlderBuildsBuildStepImplementation.php
··· 1 + <?php 2 + 3 + final class HarbormasterAbortOlderBuildsBuildStepImplementation 4 + extends HarbormasterBuildStepImplementation { 5 + 6 + public function getName() { 7 + return pht('Abort Older Builds'); 8 + } 9 + 10 + public function getGenericDescription() { 11 + return pht( 12 + 'When building a revision, abort copies of this build plan which are '. 13 + 'currently running against older diffs.'); 14 + } 15 + 16 + public function getBuildStepGroupKey() { 17 + return HarbormasterControlBuildStepGroup::GROUPKEY; 18 + } 19 + 20 + public function getEditInstructions() { 21 + return pht(<<<EOTEXT 22 + When run against a revision, this build step will abort any older copies of 23 + the same build plan which are currently running against older diffs. 24 + 25 + There are some nuances to the behavior: 26 + 27 + - if this build step is triggered manually, it won't abort anything; 28 + - this build step won't abort manual builds; 29 + - this build step won't abort anything if the diff it is building isn't 30 + the active diff when it runs. 31 + 32 + Build results on outdated diffs often aren't very important, so this may 33 + reduce build queue load without any substantial cost. 34 + EOTEXT 35 + ); 36 + } 37 + 38 + public function willStartBuild( 39 + PhabricatorUser $viewer, 40 + HarbormasterBuildable $buildable, 41 + HarbormasterBuild $build, 42 + HarbormasterBuildPlan $plan, 43 + HarbormasterBuildStep $step) { 44 + 45 + if ($buildable->getIsManualBuildable()) { 46 + // Don't abort anything if this is a manual buildable. 47 + return; 48 + } 49 + 50 + $object_phid = $buildable->getBuildablePHID(); 51 + if (phid_get_type($object_phid) !== DifferentialDiffPHIDType::TYPECONST) { 52 + // If this buildable isn't building a diff, bail out. For example, we 53 + // might be building a commit. In this case, this step has no effect. 54 + return; 55 + } 56 + 57 + $diff = id(new DifferentialDiffQuery()) 58 + ->setViewer($viewer) 59 + ->withPHIDs(array($object_phid)) 60 + ->executeOne(); 61 + if (!$diff) { 62 + return; 63 + } 64 + 65 + $revision_id = $diff->getRevisionID(); 66 + 67 + $revision = id(new DifferentialRevisionQuery()) 68 + ->setViewer($viewer) 69 + ->withIDs(array($revision_id)) 70 + ->executeOne(); 71 + if (!$revision) { 72 + return; 73 + } 74 + 75 + $active_phid = $revision->getActiveDiffPHID(); 76 + if ($active_phid !== $object_phid) { 77 + // If we aren't building the active diff, bail out. 78 + return; 79 + } 80 + 81 + $diffs = id(new DifferentialDiffQuery()) 82 + ->setViewer($viewer) 83 + ->withRevisionIDs(array($revision_id)) 84 + ->execute(); 85 + $abort_diff_phids = array(); 86 + foreach ($diffs as $diff) { 87 + if ($diff->getPHID() !== $active_phid) { 88 + $abort_diff_phids[] = $diff->getPHID(); 89 + } 90 + } 91 + 92 + if (!$abort_diff_phids) { 93 + return; 94 + } 95 + 96 + // We're fetching buildables even if they have "passed" or "failed" 97 + // because they may still have ongoing builds. At the time of writing 98 + // only "failed" buildables may still be ongoing, but it seems likely that 99 + // "passed" buildables may be ongoing in the future. 100 + 101 + $abort_buildables = id(new HarbormasterBuildableQuery()) 102 + ->setViewer($viewer) 103 + ->withBuildablePHIDs($abort_diff_phids) 104 + ->withManualBuildables(false) 105 + ->execute(); 106 + if (!$abort_buildables) { 107 + return; 108 + } 109 + 110 + $statuses = HarbormasterBuildStatus::getIncompleteStatusConstants(); 111 + 112 + $abort_builds = id(new HarbormasterBuildQuery()) 113 + ->setViewer($viewer) 114 + ->withBuildablePHIDs(mpull($abort_buildables, 'getPHID')) 115 + ->withBuildPlanPHIDs(array($plan->getPHID())) 116 + ->withBuildStatuses($statuses) 117 + ->execute(); 118 + if (!$abort_builds) { 119 + return; 120 + } 121 + 122 + foreach ($abort_builds as $abort_build) { 123 + $abort_build->sendMessage( 124 + $viewer, 125 + HarbormasterBuildCommand::COMMAND_ABORT); 126 + } 127 + } 128 + 129 + public function execute( 130 + HarbormasterBuild $build, 131 + HarbormasterBuildTarget $build_target) { 132 + return; 133 + } 134 + 135 + }
+9
src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php
··· 308 308 'enabled in configuration.')); 309 309 } 310 310 311 + public function willStartBuild( 312 + PhabricatorUser $viewer, 313 + HarbormasterBuildable $buildable, 314 + HarbormasterBuild $build, 315 + HarbormasterBuildPlan $plan, 316 + HarbormasterBuildStep $step) { 317 + return; 318 + } 319 + 311 320 312 321 /* -( Automatic Targets )-------------------------------------------------- */ 313 322
+20
src/applications/harbormaster/stepgroup/HarbormasterControlBuildStepGroup.php
··· 1 + <?php 2 + 3 + final class HarbormasterControlBuildStepGroup 4 + extends HarbormasterBuildStepGroup { 5 + 6 + const GROUPKEY = 'harbormaster.control'; 7 + 8 + public function getGroupName() { 9 + return pht('Flow Control'); 10 + } 11 + 12 + public function getGroupOrder() { 13 + return 5000; 14 + } 15 + 16 + public function shouldShowIfEmpty() { 17 + return false; 18 + } 19 + 20 + }
+9
src/applications/harbormaster/storage/HarbormasterBuildable.php
··· 141 141 142 142 $build->save(); 143 143 144 + $steps = id(new HarbormasterBuildStepQuery()) 145 + ->setViewer($viewer) 146 + ->withBuildPlanPHIDs(array($plan->getPHID())) 147 + ->execute(); 148 + 149 + foreach ($steps as $step) { 150 + $step->willStartBuild($viewer, $this, $build, $plan); 151 + } 152 + 144 153 PhabricatorWorker::scheduleTask( 145 154 'HarbormasterBuildWorker', 146 155 array(
+29
src/applications/harbormaster/storage/build/HarbormasterBuild.php
··· 356 356 } 357 357 } 358 358 359 + public function sendMessage(PhabricatorUser $viewer, $command) { 360 + // TODO: This should not be an editor transaction, but there are plans to 361 + // merge BuildCommand into BuildMessage which should moot this. As this 362 + // exists today, it can race against BuildEngine. 363 + 364 + // This is a bogus content source, but this whole flow should be obsolete 365 + // soon. 366 + $content_source = PhabricatorContentSource::newForSource( 367 + PhabricatorConsoleContentSource::SOURCECONST); 368 + 369 + $editor = id(new HarbormasterBuildTransactionEditor()) 370 + ->setActor($viewer) 371 + ->setContentSource($content_source) 372 + ->setContinueOnNoEffect(true) 373 + ->setContinueOnMissingFields(true); 374 + 375 + $viewer_phid = $viewer->getPHID(); 376 + if (!$viewer_phid) { 377 + $acting_phid = id(new PhabricatorHarbormasterApplication())->getPHID(); 378 + $editor->setActingAsPHID($acting_phid); 379 + } 380 + 381 + $xaction = id(new HarbormasterBuildTransaction()) 382 + ->setTransactionType(HarbormasterBuildTransaction::TYPE_COMMAND) 383 + ->setNewValue($command); 384 + 385 + $editor->applyTransactions($this, array($xaction)); 386 + } 387 + 359 388 360 389 /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 361 390
+13
src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php
··· 100 100 return ($this->getStepAutoKey() !== null); 101 101 } 102 102 103 + public function willStartBuild( 104 + PhabricatorUser $viewer, 105 + HarbormasterBuildable $buildable, 106 + HarbormasterBuild $build, 107 + HarbormasterBuildPlan $plan) { 108 + return $this->getStepImplementation()->willStartBuild( 109 + $viewer, 110 + $buildable, 111 + $build, 112 + $plan, 113 + $this); 114 + } 115 + 103 116 104 117 /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 105 118