1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-10 08:52:39 +01:00

Move cancel/retry/free task queue actions to bin/worker

Summary:
Fixes T6702. Ref T3554. Currently, tasks can be cancelled, retried and freed from the web UI by any logged in user.

This isn't appreciably dangerous (I can't come up with a way that a user could do anything security-affecting), but I think I probably intended this to be admin-only, but these actions should move to the CLI anyway.

Move them to the CLI. Lay some groundwork for some future `bin/worker cancel --class SomeTaskClass`, but don't implement that yet.

Test Plan: Used `cancel`, `retry` and `free` from the CLI. Hit all the error/success states.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T3554, T6702

Differential Revision: https://secure.phabricator.com/D10939
This commit is contained in:
epriestley 2014-12-06 09:14:16 -08:00
parent a0907819cd
commit 9a7383121d
8 changed files with 221 additions and 173 deletions

View file

@ -2518,13 +2518,15 @@ phutil_register_library_map(array(
'PhabricatorWorkerArchiveTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerArchiveTask.php',
'PhabricatorWorkerDAO' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerDAO.php',
'PhabricatorWorkerLeaseQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php',
'PhabricatorWorkerManagementCancelWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementCancelWorkflow.php',
'PhabricatorWorkerManagementFloodWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementFloodWorkflow.php',
'PhabricatorWorkerManagementFreeWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementFreeWorkflow.php',
'PhabricatorWorkerManagementRetryWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementRetryWorkflow.php',
'PhabricatorWorkerManagementWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php',
'PhabricatorWorkerPermanentFailureException' => 'infrastructure/daemon/workers/exception/PhabricatorWorkerPermanentFailureException.php',
'PhabricatorWorkerTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTask.php',
'PhabricatorWorkerTaskData' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTaskData.php',
'PhabricatorWorkerTaskDetailController' => 'applications/daemon/controller/PhabricatorWorkerTaskDetailController.php',
'PhabricatorWorkerTaskUpdateController' => 'applications/daemon/controller/PhabricatorWorkerTaskUpdateController.php',
'PhabricatorWorkerTestCase' => 'infrastructure/daemon/workers/__tests__/PhabricatorWorkerTestCase.php',
'PhabricatorWorkerYieldException' => 'infrastructure/daemon/workers/exception/PhabricatorWorkerYieldException.php',
'PhabricatorWorkingCopyDiscoveryTestCase' => 'applications/repository/engine/__tests__/PhabricatorWorkingCopyDiscoveryTestCase.php',
@ -5702,13 +5704,15 @@ phutil_register_library_map(array(
'PhabricatorWorkerArchiveTask' => 'PhabricatorWorkerTask',
'PhabricatorWorkerDAO' => 'PhabricatorLiskDAO',
'PhabricatorWorkerLeaseQuery' => 'PhabricatorQuery',
'PhabricatorWorkerManagementCancelWorkflow' => 'PhabricatorWorkerManagementWorkflow',
'PhabricatorWorkerManagementFloodWorkflow' => 'PhabricatorWorkerManagementWorkflow',
'PhabricatorWorkerManagementFreeWorkflow' => 'PhabricatorWorkerManagementWorkflow',
'PhabricatorWorkerManagementRetryWorkflow' => 'PhabricatorWorkerManagementWorkflow',
'PhabricatorWorkerManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorWorkerPermanentFailureException' => 'Exception',
'PhabricatorWorkerTask' => 'PhabricatorWorkerDAO',
'PhabricatorWorkerTaskData' => 'PhabricatorWorkerDAO',
'PhabricatorWorkerTaskDetailController' => 'PhabricatorDaemonController',
'PhabricatorWorkerTaskUpdateController' => 'PhabricatorDaemonController',
'PhabricatorWorkerTestCase' => 'PhabricatorTestCase',
'PhabricatorWorkerYieldException' => 'Exception',
'PhabricatorWorkingCopyDiscoveryTestCase' => 'PhabricatorWorkingCopyTestCase',

View file

@ -41,8 +41,6 @@ final class PhabricatorDaemonsApplication extends PhabricatorApplication {
'/daemon/' => array(
'' => 'PhabricatorDaemonConsoleController',
'task/(?P<id>[1-9]\d*)/' => 'PhabricatorWorkerTaskDetailController',
'task/(?P<id>[1-9]\d*)/(?P<action>[^/]+)/'
=> 'PhabricatorWorkerTaskUpdateController',
'log/' => array(
'' => 'PhabricatorDaemonLogListController',
'(?P<id>[1-9]\d*)/' => 'PhabricatorDaemonLogViewController',

View file

@ -38,8 +38,7 @@ final class PhabricatorWorkerTaskDetailController
$task->getID(),
$task->getTaskClass()));
$actions = $this->buildActionListView($task);
$properties = $this->buildPropertyListView($task, $actions);
$properties = $this->buildPropertyListView($task);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
@ -74,57 +73,12 @@ final class PhabricatorWorkerTaskDetailController
));
}
private function buildActionListView(PhabricatorWorkerTask $task) {
$request = $this->getRequest();
$user = $request->getUser();
$id = $task->getID();
$view = id(new PhabricatorActionListView())
->setUser($user)
->setObjectURI($request->getRequestURI());
if ($task->isArchived()) {
$result_success = PhabricatorWorkerArchiveTask::RESULT_SUCCESS;
$can_retry = ($task->getResult() != $result_success);
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Retry Task'))
->setHref($this->getApplicationURI('/task/'.$id.'/retry/'))
->setIcon('fa-refresh')
->setWorkflow(true)
->setDisabled(!$can_retry));
} else {
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Cancel Task'))
->setHref($this->getApplicationURI('/task/'.$id.'/cancel/'))
->setIcon('fa-times')
->setWorkflow(true));
}
$can_release = (!$task->isArchived()) &&
($task->getLeaseOwner());
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Free Lease'))
->setHref($this->getApplicationURI('/task/'.$id.'/release/'))
->setIcon('fa-unlock')
->setWorkflow(true)
->setDisabled(!$can_release));
return $view;
}
private function buildPropertyListView(
PhabricatorWorkerTask $task,
PhabricatorActionListView $actions) {
PhabricatorWorkerTask $task) {
$viewer = $this->getRequest()->getUser();
$view = new PHUIPropertyListView();
$view->setActionList($actions);
if ($task->isArchived()) {
switch ($task->getResult()) {

View file

@ -1,120 +0,0 @@
<?php
final class PhabricatorWorkerTaskUpdateController
extends PhabricatorDaemonController {
private $id;
private $action;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
$this->action = $data['action'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$task = id(new PhabricatorWorkerActiveTask())->load($this->id);
if (!$task) {
$task = id(new PhabricatorWorkerArchiveTask())->load($this->id);
}
if (!$task) {
return new Aphront404Response();
}
$result_success = PhabricatorWorkerArchiveTask::RESULT_SUCCESS;
$can_retry = ($task->isArchived()) &&
($task->getResult() != $result_success);
$can_cancel = !$task->isArchived();
$can_release = (!$task->isArchived()) &&
($task->getLeaseOwner());
$next_uri = $this->getApplicationURI('/task/'.$task->getID().'/');
if ($request->isFormPost()) {
switch ($this->action) {
case 'retry':
if ($can_retry) {
$task->unarchiveTask();
}
break;
case 'cancel':
if ($can_cancel) {
// Forcibly break the lease if one exists, so we can archive the
// task.
$task->setLeaseOwner(null);
$task->setLeaseExpires(time());
$task->archiveTask(
PhabricatorWorkerArchiveTask::RESULT_CANCELLED,
0);
}
break;
case 'release':
if ($can_release) {
$task->setLeaseOwner(null);
$task->setLeaseExpires(time());
$task->save();
}
break;
}
return id(new AphrontRedirectResponse())
->setURI($next_uri);
}
$dialog = new AphrontDialogView();
$dialog->setUser($user);
switch ($this->action) {
case 'retry':
if ($can_retry) {
$dialog->setTitle(pht('Really retry task?'));
$dialog->appendChild(phutil_tag('p', array(), pht(
'The task will be put back in the queue and executed again.')));
$dialog->addSubmitButton('Retry Task');
} else {
$dialog->setTitle(pht('Can Not Retry'));
$dialog->appendChild(phutil_tag('p', array(), pht(
'Only archived, unsuccessful tasks can be retried.')));
}
break;
case 'cancel':
if ($can_cancel) {
$dialog->setTitle(pht('Really cancel task?'));
$dialog->appendChild(phutil_tag('p', array(), pht(
'The work this task represents will never be performed if you '.
'cancel it. Are you sure you want to cancel it?')));
$dialog->addSubmitButton(pht('Cancel Task'));
} else {
$dialog->setTitle(pht('Cannot Cancel'));
$dialog->appendChild(phutil_tag('p', array(), pht(
'Only active tasks can be cancelled.')));
}
break;
case 'release':
if ($can_release) {
$dialog->setTitle(pht('Really free task lease?'));
$dialog->appendChild(phutil_tag('p', array(), pht(
'If the process which owns the task lease is still doing work '.
'on it, the work may be performed twice. Are you sure you '.
'want to free the lease?')));
$dialog->addSubmitButton(pht('Free Lease'));
} else {
$dialog->setTitle(pht('Cannot Free Lease'));
$dialog->appendChild(phutil_tag('p', array(), pht(
'Only active, leased tasks may have their leases freed.')));
}
break;
default:
return new Aphront404Response();
}
$dialog->addCancelButton($next_uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}

View file

@ -0,0 +1,52 @@
<?php
final class PhabricatorWorkerManagementCancelWorkflow
extends PhabricatorWorkerManagementWorkflow {
public function didConstruct() {
$this
->setName('cancel')
->setExamples('**cancel** --id __id__')
->setSynopsis(
pht(
'Cancel selected tasks. The work these tasks represent will never '.
'be performed.'))
->setArguments($this->getTaskSelectionArguments());
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$tasks = $this->loadTasks($args);
foreach ($tasks as $task) {
$can_cancel = !$task->isArchived();
if (!$can_cancel) {
$console->writeOut(
"**<bg:yellow> %s </bg>** %s\n",
pht('ARCHIVED'),
pht(
'%s is already archived, and can not be cancelled.',
$this->describeTask($task)));
continue;
}
// Forcibly break the lease if one exists, so we can archive the
// task.
$task->setLeaseOwner(null);
$task->setLeaseExpires(PhabricatorTime::getNow());
$task->archiveTask(
PhabricatorWorkerArchiveTask::RESULT_CANCELLED,
0);
$console->writeOut(
"**<bg:green> %s </bg>** %s\n",
pht('CANCELLED'),
pht(
'%s was cancelled.',
$this->describeTask($task)));
}
return 0;
}
}

View file

@ -0,0 +1,58 @@
<?php
final class PhabricatorWorkerManagementFreeWorkflow
extends PhabricatorWorkerManagementWorkflow {
public function didConstruct() {
$this
->setName('free')
->setExamples('**free** --id __id__')
->setSynopsis(
pht(
'Free leases on selected tasks. If the daemon holding the lease is '.
'still working on the task, this may cause the task to execute '.
'twice.'))
->setArguments($this->getTaskSelectionArguments());
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$tasks = $this->loadTasks($args);
foreach ($tasks as $task) {
if ($task->isArchived()) {
$console->writeOut(
"**<bg:yellow> %s </bg>** %s\n",
pht('ARCHIVED'),
pht(
'%s is archived; archived tasks do not have leases.',
$this->describeTask($task)));
continue;
}
if ($task->getLeaseOwner() === null) {
$console->writeOut(
"**<bg:yellow> %s </bg>** %s\n",
pht('FREE'),
pht(
'%s has no active lease.',
$this->describeTask($task)));
continue;
}
$task->setLeaseOwner(null);
$task->setLeaseExpires(PhabricatorTime::getNow());
$task->save();
$console->writeOut(
"**<bg:green> %s </bg>** %s\n",
pht('LEASE FREED'),
pht(
'%s was freed from its lease.',
$this->describeTask($task)));
}
return 0;
}
}

View file

@ -0,0 +1,57 @@
<?php
final class PhabricatorWorkerManagementRetryWorkflow
extends PhabricatorWorkerManagementWorkflow {
public function didConstruct() {
$this
->setName('retry')
->setExamples('**retry** --id __id__')
->setSynopsis(
pht(
'Retry selected tasks which previously failed permanently or '.
'were cancelled. Only archived, unsuccessful tasks can be '.
'retried.'))
->setArguments($this->getTaskSelectionArguments());
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$tasks = $this->loadTasks($args);
foreach ($tasks as $task) {
if (!$task->isArchived()) {
$console->writeOut(
"**<bg:yellow> %s </bg>** %s\n",
pht('ACTIVE'),
pht(
'%s is already in the active task queue.',
$this->describeTask($task)));
continue;
}
$result_success = PhabricatorWorkerArchiveTask::RESULT_SUCCESS;
if ($task->getResult() == $result_success) {
$console->writeOut(
"**<bg:yellow> %s </bg>** %s\n",
pht('SUCCEEDED'),
pht(
'%s has already succeeded, and can not be retried.',
$this->describeTask($task)));
continue;
}
$task->unarchiveTask();
$console->writeOut(
"**<bg:green> %s </bg>** %s\n",
pht('QUEUED'),
pht(
'%s was queued for retry.',
$this->describeTask($task)));
}
return 0;
}
}

View file

@ -1,4 +1,49 @@
<?php
abstract class PhabricatorWorkerManagementWorkflow
extends PhabricatorManagementWorkflow {}
extends PhabricatorManagementWorkflow {
protected function getTaskSelectionArguments() {
return array(
array(
'name' => 'id',
'param' => 'id',
'repeat' => true,
'help' => pht('Select one or more tasks by ID.'),
),
);
}
protected function loadTasks(PhutilArgumentParser $args) {
$ids = $args->getArg('id');
if (!$ids) {
throw new PhutilArgumentUsageException(
pht('Use --id to select tasks by ID.'));
}
$active_tasks = id(new PhabricatorWorkerActiveTask())->loadAllWhere(
'id IN (%Ls)',
$ids);
$archive_tasks = id(new PhabricatorWorkerArchiveTask())->loadAllWhere(
'id IN (%Ls)',
$ids);
$tasks =
mpull($active_tasks, null, 'getID') +
mpull($archive_tasks, null, 'getID');
foreach ($ids as $id) {
if (empty($tasks[$id])) {
throw new PhutilArgumentUsageException(
pht('No task exists with id "%s"!', $id));
}
}
return $tasks;
}
protected function describeTask(PhabricatorWorkerTask $task) {
return pht('Task %d (%s)', $task->getID(), $task->getTaskClass());
}
}