1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-23 22:10:55 +01:00

Fix daemon task queue to respect task priority

Summary:
Fixes an issue with T5336 / D9871. We did 99% of the work here but didn't actually turn on the priority sorting. The unit test passed by default, which didn't catch this.

  - Fix the unit test (it failed).
  - Fix the query (test now passes).
  - Add a "Next in Queue" element to the UI to make this kind of thing easier to spot/understand.

Test Plan: Ran unit test. Viewed "Next in Queue". Queued some tasks, flushed the queue. Web UI tracked the state sensibly.

Reviewers: joshuaspence, btrahan

Reviewed By: btrahan

Subscribers: cburroughs, epriestley

Differential Revision: https://secure.phabricator.com/D10766
This commit is contained in:
epriestley 2014-10-31 09:27:04 -07:00
parent 915d9a52f0
commit 914b8bb32c
3 changed files with 121 additions and 66 deletions

View file

@ -126,58 +126,21 @@ final class PhabricatorDaemonConsoleController
$daemon_table->setUser($user); $daemon_table->setUser($user);
$daemon_table->setDaemonLogs($logs); $daemon_table->setDaemonLogs($logs);
$tasks = id(new PhabricatorWorkerActiveTask())->loadAllWhere(
'leaseOwner IS NOT NULL');
$rows = array();
foreach ($tasks as $task) {
$rows[] = array(
$task->getID(),
$task->getTaskClass(),
$task->getLeaseOwner(),
$task->getLeaseExpires() - time(),
$task->getPriority(),
$task->getFailureCount(),
phutil_tag(
'a',
array(
'href' => '/daemon/task/'.$task->getID().'/',
'class' => 'button small grey',
),
pht('View Task')),
);
}
$daemon_panel = new PHUIObjectBoxView(); $daemon_panel = new PHUIObjectBoxView();
$daemon_panel->setHeaderText(pht('Active Daemons')); $daemon_panel->setHeaderText(pht('Active Daemons'));
$daemon_panel->appendChild($daemon_table); $daemon_panel->appendChild($daemon_table);
$leased_table = new AphrontTableView($rows);
$leased_table->setHeaders(
array(
pht('ID'),
pht('Class'),
pht('Owner'),
pht('Expires'),
pht('Priority'),
pht('Failures'),
'',
));
$leased_table->setColumnClasses(
array(
'n',
'wide',
'',
'',
'n',
'n',
'action',
));
$leased_table->setNoDataString(pht('No tasks are leased by workers.'));
$leased_panel = new PHUIObjectBoxView(); $tasks = id(new PhabricatorWorkerActiveTask())->loadAllWhere(
$leased_panel->setHeaderText(pht('Leased Tasks')); 'leaseOwner IS NOT NULL');
$leased_panel->appendChild($leased_table);
$tasks_table = $this->renderTasksTable(
$tasks,
pht('No tasks are leased by workers.'));
$leased_panel = id(new PHUIObjectBoxView())
->setHeaderText(pht('Leased Tasks'))
->appendChild($tasks_table);
$task_table = new PhabricatorWorkerActiveTask(); $task_table = new PhabricatorWorkerActiveTask();
$queued = queryfx_all( $queued = queryfx_all(
@ -211,6 +174,16 @@ final class PhabricatorDaemonConsoleController
$queued_panel->setHeaderText(pht('Queued Tasks')); $queued_panel->setHeaderText(pht('Queued Tasks'));
$queued_panel->appendChild($queued_table); $queued_panel->appendChild($queued_table);
$upcoming = id(new PhabricatorWorkerLeaseQuery())
->setLimit(10)
->setSkipLease(true)
->execute();
$upcoming_panel = id(new PHUIObjectBoxView())
->setHeaderText(pht('Next In Queue'))
->appendChild(
$this->renderTasksTable($upcoming, pht('Task queue is empty.')));
$crumbs = $this->buildApplicationCrumbs(); $crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Console')); $crumbs->addTextCrumb(pht('Console'));
@ -223,6 +196,7 @@ final class PhabricatorDaemonConsoleController
$daemon_panel, $daemon_panel,
$queued_panel, $queued_panel,
$leased_panel, $leased_panel,
$upcoming_panel,
)); ));
return $this->buildApplicationPage( return $this->buildApplicationPage(
@ -233,4 +207,52 @@ final class PhabricatorDaemonConsoleController
)); ));
} }
private function renderTasksTable(array $tasks, $nodata) {
$rows = array();
foreach ($tasks as $task) {
$rows[] = array(
$task->getID(),
$task->getTaskClass(),
$task->getLeaseOwner(),
$task->getLeaseExpires()
? phutil_format_relative_time($task->getLeaseExpires() - time())
: '-',
$task->getPriority(),
$task->getFailureCount(),
phutil_tag(
'a',
array(
'href' => '/daemon/task/'.$task->getID().'/',
'class' => 'button small grey',
),
pht('View Task')),
);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
pht('ID'),
pht('Class'),
pht('Owner'),
pht('Expires'),
pht('Priority'),
pht('Failures'),
'',
));
$table->setColumnClasses(
array(
'n',
'wide',
'',
'',
'n',
'n',
'action',
));
$table->setNoDataString($nodata);
return $table;
}
} }

View file

@ -161,17 +161,17 @@ final class PhabricatorWorkerTestCase extends PhabricatorTestCase {
} }
public function testLeasedIsHighestPriority() { public function testLeasedIsHighestPriority() {
$task1 = $this->scheduleTask(array(), 2); $task1 = $this->scheduleTask(array(), 1);
$task2 = $this->scheduleTask(array(), 1); $task2 = $this->scheduleTask(array(), 1);
$task3 = $this->scheduleTask(array(), 1); $task3 = $this->scheduleTask(array(), 2);
$this->expectNextLease( $this->expectNextLease(
$task1, $task3,
'Tasks with a higher priority should be scheduled first.'); 'Tasks with a higher priority should be scheduled first.');
$this->expectNextLease( $this->expectNextLease(
$task2, $task1,
'Tasks with the same priority should be FIFO.'); 'Tasks with the same priority should be FIFO.');
$this->expectNextLease($task3); $this->expectNextLease($task2);
} }
private function expectNextLease($task, $message = null) { private function expectNextLease($task, $message = null) {

View file

@ -10,6 +10,7 @@ final class PhabricatorWorkerLeaseQuery extends PhabricatorQuery {
private $ids; private $ids;
private $limit; private $limit;
private $skipLease;
public static function getDefaultWaitBeforeRetry() { public static function getDefaultWaitBeforeRetry() {
return phutil_units('5 minutes in seconds'); return phutil_units('5 minutes in seconds');
@ -19,6 +20,20 @@ final class PhabricatorWorkerLeaseQuery extends PhabricatorQuery {
return phutil_units('2 hours in seconds'); return phutil_units('2 hours in seconds');
} }
/**
* Set this flag to select tasks from the top of the queue without leasing
* them.
*
* This can be used to show which tasks are coming up next without altering
* the queue's behavior.
*
* @param bool True to skip the lease acquisition step.
*/
public function setSkipLease($skip) {
$this->skipLease = $skip;
return $this;
}
public function withIDs(array $ids) { public function withIDs(array $ids) {
$this->ids = $ids; $this->ids = $ids;
return $this; return $this;
@ -51,6 +66,7 @@ final class PhabricatorWorkerLeaseQuery extends PhabricatorQuery {
$limit = $this->limit; $limit = $this->limit;
$leased = 0; $leased = 0;
$task_ids = array();
foreach ($phases as $phase) { foreach ($phases as $phase) {
// NOTE: If we issue `UPDATE ... WHERE ... ORDER BY id ASC`, the query // NOTE: If we issue `UPDATE ... WHERE ... ORDER BY id ASC`, the query
// goes very, very slowly. The `ORDER BY` triggers this, although we get // goes very, very slowly. The `ORDER BY` triggers this, although we get
@ -74,17 +90,23 @@ final class PhabricatorWorkerLeaseQuery extends PhabricatorQuery {
// total runtime, so keep it simple for the moment. // total runtime, so keep it simple for the moment.
if ($rows) { if ($rows) {
queryfx( if ($this->skipLease) {
$conn_w, $leased += count($rows);
'UPDATE %T task $task_ids += array_fuse(ipull($rows, 'id'));
SET leaseOwner = %s, leaseExpires = UNIX_TIMESTAMP() + %d } else {
%Q', queryfx(
$task_table->getTableName(), $conn_w,
$lease_ownership_name, 'UPDATE %T task
self::getDefaultLeaseDuration(), SET leaseOwner = %s, leaseExpires = UNIX_TIMESTAMP() + %d
$this->buildUpdateWhereClause($conn_w, $phase, $rows)); %Q',
$task_table->getTableName(),
$lease_ownership_name,
self::getDefaultLeaseDuration(),
$this->buildUpdateWhereClause($conn_w, $phase, $rows));
$leased += $conn_w->getAffectedRows();
}
$leased += $conn_w->getAffectedRows();
if ($leased == $limit) { if ($leased == $limit) {
break; break;
} }
@ -95,16 +117,27 @@ final class PhabricatorWorkerLeaseQuery extends PhabricatorQuery {
return array(); return array();
} }
if ($this->skipLease) {
$selection_condition = qsprintf(
$conn_w,
'task.id IN (%Ld)',
$task_ids);
} else {
$selection_condition = qsprintf(
$conn_w,
'task.leaseOwner = %s AND leaseExpires > UNIX_TIMESTAMP()',
$lease_ownership_name);
}
$data = queryfx_all( $data = queryfx_all(
$conn_w, $conn_w,
'SELECT task.*, taskdata.data _taskData, UNIX_TIMESTAMP() _serverTime 'SELECT task.*, taskdata.data _taskData, UNIX_TIMESTAMP() _serverTime
FROM %T task LEFT JOIN %T taskdata FROM %T task LEFT JOIN %T taskdata
ON taskdata.id = task.dataID ON taskdata.id = task.dataID
WHERE leaseOwner = %s AND leaseExpires > UNIX_TIMESTAMP() WHERE %Q %Q %Q',
%Q %Q',
$task_table->getTableName(), $task_table->getTableName(),
$taskdata_table->getTableName(), $taskdata_table->getTableName(),
$lease_ownership_name, $selection_condition,
$this->buildOrderClause($conn_w, $phase), $this->buildOrderClause($conn_w, $phase),
$this->buildLimitClause($conn_w, $limit)); $this->buildLimitClause($conn_w, $limit));
@ -183,7 +216,7 @@ final class PhabricatorWorkerLeaseQuery extends PhabricatorQuery {
case self::PHASE_UNLEASED: case self::PHASE_UNLEASED:
// When selecting new tasks, we want to consume them in order of // When selecting new tasks, we want to consume them in order of
// decreasing priority (and then FIFO). // decreasing priority (and then FIFO).
return qsprintf($conn_w, 'ORDER BY id ASC'); return qsprintf($conn_w, 'ORDER BY priority DESC, id ASC');
case self::PHASE_EXPIRED: case self::PHASE_EXPIRED:
// When selecting failed tasks, we want to consume them in roughly // When selecting failed tasks, we want to consume them in roughly
// FIFO order of their failures, which is not necessarily their original // FIFO order of their failures, which is not necessarily their original