diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -334,10 +334,15 @@ ), array( 'objectPHID' => $this->getPHID(), - 'delayUntil' => $epoch, + 'delayUntil' => ($epoch ? (int)$epoch : null), )); } + public function setAwakenTaskIDs(array $ids) { + $this->setAttribute('internal.awakenTaskIDs', $ids); + return $this; + } + private function didActivate() { $viewer = PhabricatorUser::getOmnipotentUser(); $need_update = false; @@ -359,6 +364,11 @@ if ($expires) { $this->scheduleUpdate($expires); } + + $awaken_ids = $this->getAttribute('internal.awakenTaskIDs'); + if (is_array($awaken_ids) && $awaken_ids) { + PhabricatorWorker::awakenTaskIDs($awaken_ids); + } } diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php --- a/src/applications/drydock/storage/DrydockResource.php +++ b/src/applications/drydock/storage/DrydockResource.php @@ -218,7 +218,7 @@ ), array( 'objectPHID' => $this->getPHID(), - 'delayUntil' => $epoch, + 'delayUntil' => ($epoch ? (int)$epoch : null), )); } diff --git a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php --- a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php @@ -6,6 +6,16 @@ abstract class HarbormasterBuildStepImplementation extends Phobject { private $settings; + private $currentWorkerTaskID; + + public function setCurrentWorkerTaskID($id) { + $this->currentWorkerTaskID = $id; + return $this; + } + + public function getCurrentWorkerTaskID() { + return $this->currentWorkerTaskID; + } public static function getImplementations() { return id(new PhutilClassMapQuery()) diff --git a/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php --- a/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php @@ -54,6 +54,11 @@ ->setAttribute('repositoryPHID', $repository_phid) ->setAttribute('commit', $commit); + $task_id = $this->getCurrentWorkerTaskID(); + if ($task_id) { + $lease->setAwakenTaskIDs(array($task_id)); + } + $lease->queueForActivation(); $build_target diff --git a/src/applications/harbormaster/worker/HarbormasterTargetWorker.php b/src/applications/harbormaster/worker/HarbormasterTargetWorker.php --- a/src/applications/harbormaster/worker/HarbormasterTargetWorker.php +++ b/src/applications/harbormaster/worker/HarbormasterTargetWorker.php @@ -59,6 +59,7 @@ } $implementation = $target->getImplementation(); + $implementation->setCurrentWorkerTaskID($this->getCurrentWorkerTaskID()); $implementation->execute($build, $target); $next_status = HarbormasterBuildTarget::STATUS_PASSED; diff --git a/src/infrastructure/daemon/workers/PhabricatorWorker.php b/src/infrastructure/daemon/workers/PhabricatorWorker.php --- a/src/infrastructure/daemon/workers/PhabricatorWorker.php +++ b/src/infrastructure/daemon/workers/PhabricatorWorker.php @@ -8,6 +8,7 @@ private $data; private static $runAllTasksInProcess = false; private $queuedTasks = array(); + private $currentWorkerTask; // NOTE: Lower priority numbers execute first. The priority numbers have to // have the same ordering that IDs do (lowest first) so MySQL can use a @@ -18,6 +19,10 @@ const PRIORITY_BULK = 3000; const PRIORITY_IMPORT = 4000; + /** + * Special owner indicating that the task has yielded. + */ + const YIELD_OWNER = '(yield)'; /* -( Configuring Retries and Failures )----------------------------------- */ @@ -77,6 +82,23 @@ return null; } + public function setCurrentWorkerTask(PhabricatorWorkerTask $task) { + $this->currentWorkerTask = $task; + return $this; + } + + public function getCurrentWorkerTask() { + return $this->currentWorkerTask; + } + + public function getCurrentWorkerTaskID() { + $task = $this->getCurrentWorkerTask(); + if (!$task) { + return null; + } + return $task->getID(); + } + abstract protected function doWork(); final public function __construct($data) { @@ -105,6 +127,14 @@ $data, $options = array()) { + PhutilTypeSpec::checkMap( + $options, + array( + 'priority' => 'optional int|null', + 'objectPHID' => 'optional string|null', + 'delayUntil' => 'optional int|null', + )); + $priority = idx($options, 'priority'); if ($priority === null) { $priority = self::PRIORITY_DEFAULT; @@ -208,4 +238,49 @@ return $this->queuedTasks; } + + /** + * Awaken tasks that have yielded. + * + * Reschedules the specified tasks if they are currently queued in a yielded, + * unleased, unretried state so they'll execute sooner. This can let the + * queue avoid unnecessary waits. + * + * This method does not provide any assurances about when these tasks will + * execute, or even guarantee that it will have any effect at all. + * + * @param list List of task IDs to try to awaken. + * @return void + */ + final public static function awakenTaskIDs(array $ids) { + if (!$ids) { + return; + } + + $table = new PhabricatorWorkerActiveTask(); + $conn_w = $table->establishConnection('w'); + + // NOTE: At least for now, we're keeping these tasks yielded, just + // pretending that they threw a shorter yield than they really did. + + // Overlap the windows here to handle minor client/server time differences + // and because it's likely correct to push these tasks to the head of their + // respective priorities. There is a good chance they are ready to execute. + $window = phutil_units('1 hour in seconds'); + $epoch_ago = (PhabricatorTime::getNow() - $window); + + queryfx( + $conn_w, + 'UPDATE %T SET leaseExpires = %d + WHERE id IN (%Ld) + AND leaseOwner = %s + AND leaseExpires > %d + AND failureCount = 0', + $table->getTableName(), + $epoch_ago, + $ids, + self::YIELD_OWNER, + $epoch_ago); + } + } diff --git a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php --- a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php +++ b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php @@ -141,6 +141,7 @@ $worker = null; try { $worker = $this->getWorkerInstance(); + $worker->setCurrentWorkerTask($this); $maximum_failures = $worker->getMaximumRetryCount(); if ($maximum_failures !== null) { @@ -175,6 +176,8 @@ } catch (PhabricatorWorkerYieldException $ex) { $this->setExecutionException($ex); + $this->setLeaseOwner(PhabricatorWorker::YIELD_OWNER); + $retry = $ex->getDuration(); $retry = max($retry, 5);