1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-02-23 20:19:03 +01:00

(stable) Promote 2021 Week 6

This commit is contained in:
epriestley 2021-02-07 10:35:53 -08:00
commit 3e42f8be12
39 changed files with 1013 additions and 218 deletions

View file

@ -1,5 +1,7 @@
<?php
// @phase worker
PhabricatorRebuildIndexesWorker::rebuildObjectsWithQuery(
'PhabricatorDashboardQuery');

View file

@ -1,3 +1,5 @@
<?php
// @phase worker
PhabricatorRebuildIndexesWorker::rebuildObjectsWithQuery('HeraldRuleQuery');

View file

@ -1,3 +1,5 @@
<?php
// @phase worker
PhabricatorRebuildIndexesWorker::rebuildObjectsWithQuery('HeraldRuleQuery');

View file

@ -1,4 +1,6 @@
<?php
// @phase worker
PhabricatorRebuildIndexesWorker::rebuildObjectsWithQuery(
'PhabricatorRepositoryQuery');

View file

@ -0,0 +1,5 @@
ALTER TABLE {$NAMESPACE}_worker.worker_activetask
ADD containerPHID VARBINARY(64);
ALTER TABLE {$NAMESPACE}_worker.worker_archivetask
ADD containerPHID VARBINARY(64);

View file

@ -5145,9 +5145,11 @@ phutil_register_library_map(array(
'PhabricatorWorkerDestructionEngineExtension' => 'infrastructure/daemon/workers/engineextension/PhabricatorWorkerDestructionEngineExtension.php',
'PhabricatorWorkerLeaseQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php',
'PhabricatorWorkerManagementCancelWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementCancelWorkflow.php',
'PhabricatorWorkerManagementDelayWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementDelayWorkflow.php',
'PhabricatorWorkerManagementExecuteWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementExecuteWorkflow.php',
'PhabricatorWorkerManagementFloodWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementFloodWorkflow.php',
'PhabricatorWorkerManagementFreeWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementFreeWorkflow.php',
'PhabricatorWorkerManagementPriorityWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementPriorityWorkflow.php',
'PhabricatorWorkerManagementRetryWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementRetryWorkflow.php',
'PhabricatorWorkerManagementWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php',
'PhabricatorWorkerPermanentFailureException' => 'infrastructure/daemon/workers/exception/PhabricatorWorkerPermanentFailureException.php',
@ -6113,7 +6115,6 @@ phutil_register_library_map(array(
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorProjectInterface',
'AlmanacPropertyInterface',
'PhabricatorDestructibleInterface',
'PhabricatorNgramsInterface',
'PhabricatorConduitResultInterface',
@ -11973,9 +11974,11 @@ phutil_register_library_map(array(
'PhabricatorWorkerDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
'PhabricatorWorkerLeaseQuery' => 'PhabricatorQuery',
'PhabricatorWorkerManagementCancelWorkflow' => 'PhabricatorWorkerManagementWorkflow',
'PhabricatorWorkerManagementDelayWorkflow' => 'PhabricatorWorkerManagementWorkflow',
'PhabricatorWorkerManagementExecuteWorkflow' => 'PhabricatorWorkerManagementWorkflow',
'PhabricatorWorkerManagementFloodWorkflow' => 'PhabricatorWorkerManagementWorkflow',
'PhabricatorWorkerManagementFreeWorkflow' => 'PhabricatorWorkerManagementWorkflow',
'PhabricatorWorkerManagementPriorityWorkflow' => 'PhabricatorWorkerManagementWorkflow',
'PhabricatorWorkerManagementRetryWorkflow' => 'PhabricatorWorkerManagementWorkflow',
'PhabricatorWorkerManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorWorkerPermanentFailureException' => 'Exception',

View file

@ -6,7 +6,6 @@ final class AlmanacNamespace
PhabricatorPolicyInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorProjectInterface,
AlmanacPropertyInterface,
PhabricatorDestructibleInterface,
PhabricatorNgramsInterface,
PhabricatorConduitResultInterface {
@ -17,13 +16,10 @@ final class AlmanacNamespace
protected $viewPolicy;
protected $editPolicy;
private $almanacProperties = self::ATTACHABLE;
public static function initializeNewNamespace() {
return id(new self())
->setViewPolicy(PhabricatorPolicies::POLICY_USER)
->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN)
->attachAlmanacProperties(array());
->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN);
}
protected function getConfiguration() {
@ -113,53 +109,6 @@ final class AlmanacNamespace
}
/* -( AlmanacPropertyInterface )------------------------------------------- */
public function attachAlmanacProperties(array $properties) {
assert_instances_of($properties, 'AlmanacProperty');
$this->almanacProperties = mpull($properties, null, 'getFieldName');
return $this;
}
public function getAlmanacProperties() {
return $this->assertAttached($this->almanacProperties);
}
public function hasAlmanacProperty($key) {
$this->assertAttached($this->almanacProperties);
return isset($this->almanacProperties[$key]);
}
public function getAlmanacProperty($key) {
return $this->assertAttachedKey($this->almanacProperties, $key);
}
public function getAlmanacPropertyValue($key, $default = null) {
if ($this->hasAlmanacProperty($key)) {
return $this->getAlmanacProperty($key)->getFieldValue();
} else {
return $default;
}
}
public function getAlmanacPropertyFieldSpecifications() {
return array();
}
public function newAlmanacPropertyEditEngine() {
throw new PhutilMethodNotImplementedException();
}
public function getAlmanacPropertySetTransactionType() {
throw new PhutilMethodNotImplementedException();
}
public function getAlmanacPropertyDeleteTransactionType() {
throw new PhutilMethodNotImplementedException();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View file

@ -490,7 +490,7 @@ abstract class PhabricatorApplication
return array();
}
final private function getCustomPolicySetting($capability) {
private function getCustomPolicySetting($capability) {
if (!$this->isCapabilityEditable($capability)) {
return null;
}
@ -516,7 +516,7 @@ abstract class PhabricatorApplication
}
final private function getCustomCapabilitySpecification($capability) {
private function getCustomCapabilitySpecification($capability) {
$custom = $this->getCustomCapabilities();
if (!isset($custom[$capability])) {
throw new Exception(pht("Unknown capability '%s'!", $capability));

View file

@ -203,7 +203,7 @@ final class DifferentialHunk
return implode('', $this->makeContent($include));
}
final private function makeContent($include) {
private function makeContent($include) {
$lines = $this->getSplitLines();
$results = array();

View file

@ -125,7 +125,7 @@ abstract class DiffusionRequest extends Phobject {
*
* @task new
*/
final private function __construct() {
private function __construct() {
// <private>
}
@ -138,7 +138,7 @@ abstract class DiffusionRequest extends Phobject {
* @return DiffusionRequest New request object.
* @task new
*/
final private static function newFromIdentifier(
private static function newFromIdentifier(
$identifier,
PhabricatorUser $viewer,
$need_edit = false) {
@ -174,7 +174,7 @@ abstract class DiffusionRequest extends Phobject {
* @return DiffusionRequest New request object.
* @task new
*/
final private static function newFromRepository(
private static function newFromRepository(
PhabricatorRepository $repository) {
$map = array(
@ -205,7 +205,7 @@ abstract class DiffusionRequest extends Phobject {
* @return void
* @task new
*/
final private function initializeFromDictionary(array $data) {
private function initializeFromDictionary(array $data) {
$blob = idx($data, 'blob');
if (strlen($blob)) {
$blob = self::parseRequestBlob($blob, $this->supportsBranches());

View file

@ -8,9 +8,16 @@ final class PhabricatorPolicyManagementUnlockWorkflow
->setName('unlock')
->setSynopsis(
pht(
'Unlock an object which has policies that prevent it from being '.
'viewed or edited.'))
->setExamples('**unlock** --view __user__ __object__')
'Unlock one or more objects by changing their view policies, edit '.
'policies, or owners.'))
->setHelp(
pht(
'Identify each __object__ by passing an object name '.
'(like "T123") or a PHID (like "PHID-ABCD-1234...").'.
"\n\n".
'Not every type of object has an editable view policy, edit '.
'policy, or owner, so not all modes will work with all objects. '))
->setExamples('**unlock** --view __user__ __object__ ...')
->setArguments(
array(
array(

View file

@ -682,7 +682,6 @@ final class PhabricatorRepositoryDiscoveryEngine
$this->queueCommitImportTask(
$repository,
$commit->getID(),
$commit->getPHID(),
$task_priority,
$via = 'discovery');

View file

@ -85,10 +85,9 @@ abstract class PhabricatorRepositoryEngine extends Phobject {
final protected function queueCommitImportTask(
PhabricatorRepository $repository,
$commit_id,
$commit_phid,
$task_priority,
$via = null) {
$via) {
$vcs = $repository->getVersionControlSystem();
switch ($vcs) {
@ -109,7 +108,7 @@ abstract class PhabricatorRepositoryEngine extends Phobject {
}
$data = array(
'commitID' => $commit_id,
'commitPHID' => $commit_phid,
);
if ($via !== null) {
@ -119,6 +118,7 @@ abstract class PhabricatorRepositoryEngine extends Phobject {
$options = array(
'priority' => $task_priority,
'objectPHID' => $commit_phid,
'containerPHID' => $repository->getPHID(),
);
PhabricatorWorker::scheduleTask($class, $data, $options);

View file

@ -597,7 +597,6 @@ final class PhabricatorRepositoryRefEngine
$this->queueCommitImportTask(
$repository,
$row['id'],
$row['phid'],
$task_priority,
$via = 'ref');

View file

@ -247,8 +247,9 @@ final class PhabricatorRepositoryManagementReparseWorkflow
// all the requested steps explicitly.
$spec = array(
'commitID' => $commit->getID(),
'commitPHID' => $commit->getPHID(),
'only' => !$importing,
'via' => 'reparse',
);
foreach ($classes as $class) {
@ -258,6 +259,8 @@ final class PhabricatorRepositoryManagementReparseWorkflow
$spec,
array(
'priority' => PhabricatorWorker::PRIORITY_IMPORT,
'objectPHID' => $commit->getPHID(),
'containerPHID' => $repository->getPHID(),
));
} catch (PhabricatorWorkerPermanentFailureException $ex) {
// See T13315. We expect some reparse steps to occasionally raise

View file

@ -11,30 +11,51 @@ abstract class PhabricatorRepositoryCommitParserWorker
return $this->commit;
}
$commit_id = idx($this->getTaskData(), 'commitID');
if (!$commit_id) {
throw new PhabricatorWorkerPermanentFailureException(
pht('No "%s" in task data.', 'commitID'));
$viewer = $this->getViewer();
$task_data = $this->getTaskData();
$commit_query = id(new DiffusionCommitQuery())
->setViewer($viewer);
$commit_phid = idx($task_data, 'commitPHID');
// TODO: See T13591. This supports execution of legacy tasks and can
// eventually be removed. Newer tasks use "commitPHID" instead of
// "commitID".
if (!$commit_phid) {
$commit_id = idx($task_data, 'commitID');
if ($commit_id) {
$legacy_commit = id(clone $commit_query)
->withIDs(array($commit_id))
->executeOne();
if ($legacy_commit) {
$commit_phid = $legacy_commit->getPHID();
}
}
}
$commit = id(new DiffusionCommitQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withIDs(array($commit_id))
if (!$commit_phid) {
throw new PhabricatorWorkerPermanentFailureException(
pht('Task data has no "commitPHID".'));
}
$commit = id(clone $commit_query)
->withPHIDs(array($commit_phid))
->executeOne();
if (!$commit) {
throw new PhabricatorWorkerPermanentFailureException(
pht('Commit "%s" does not exist.', $commit_id));
pht('Commit "%s" does not exist.', $commit_phid));
}
if ($commit->isUnreachable()) {
throw new PhabricatorWorkerPermanentFailureException(
pht(
'Commit "%s" (with internal ID "%s") is no longer reachable from '.
'any branch, tag, or ref in this repository, so it will not be '.
'Commit "%s" (with PHID "%s") is no longer reachable from any '.
'branch, tag, or ref in this repository, so it will not be '.
'imported. This usually means that the branch the commit was on '.
'was deleted or overwritten.',
$commit->getMonogram(),
$commit_id));
$commit_phid));
}
$this->commit = $commit;
@ -51,10 +72,42 @@ abstract class PhabricatorRepositoryCommitParserWorker
$this->parseCommit($repository, $this->commit);
}
final protected function shouldQueueFollowupTasks() {
private function shouldQueueFollowupTasks() {
return !idx($this->getTaskData(), 'only');
}
final protected function queueCommitTask($task_class) {
if (!$this->shouldQueueFollowupTasks()) {
return;
}
$commit = $this->loadCommit();
$repository = $commit->getRepository();
$data = array(
'commitPHID' => $commit->getPHID(),
);
$task_data = $this->getTaskData();
if (isset($task_data['via'])) {
$data['via'] = $task_data['via'];
}
$options = array(
// We queue followup tasks at default priority so that the queue finishes
// work it has started before starting more work. If followups are queued
// at the same priority level, we do all message parses first, then all
// change parses, etc. This makes progress uneven. See T11677 for
// discussion.
'priority' => parent::PRIORITY_DEFAULT,
'objectPHID' => $commit->getPHID(),
'containerPHID' => $repository->getPHID(),
);
$this->queueTask($task_class, $data, $options);
}
protected function getImportStepFlag() {
return null;
}
@ -112,7 +165,7 @@ abstract class PhabricatorRepositoryCommitParserWorker
$commit = id(new DiffusionCommitQuery())
->setViewer($viewer)
->withIDs(array(idx($this->getTaskData(), 'commitID')))
->withPHIDs(array(idx($this->getTaskData(), 'commitPHID')))
->executeOne();
if (!$commit) {
return $suffix;

View file

@ -396,6 +396,8 @@ final class PhabricatorChangeParserTestCase
}
public function testSubversionParser() {
$this->requireBinaryForTest('svn');
$repository = $this->buildDiscoveredRepository('CHC');
$viewer = PhabricatorUser::getOmnipotentUser();
@ -955,6 +957,8 @@ final class PhabricatorChangeParserTestCase
}
public function testSubversionPartialParser() {
$this->requireBinaryForTest('svn');
$repository = $this->buildBareRepository('CHD');
$repository->setDetail('svn-subpath', 'trunk/');
@ -1059,6 +1063,8 @@ final class PhabricatorChangeParserTestCase
}
public function testSubversionValidRootParser() {
$this->requireBinaryForTest('svn');
// First, automatically configure the root correctly.
$repository = $this->buildBareRepository('CHD');
id(new PhabricatorRepositoryPullEngine())
@ -1104,6 +1110,8 @@ final class PhabricatorChangeParserTestCase
}
public function testSubversionForeignStubsParser() {
$this->requireBinaryForTest('svn');
$repository = $this->buildBareRepository('CHE');
$repository->setDetail('svn-subpath', 'branch/');

View file

@ -99,14 +99,7 @@ abstract class PhabricatorRepositoryCommitChangeParserWorker
}
protected function finishParse() {
$commit = $this->commit;
if ($this->shouldQueueFollowupTasks()) {
$this->queueTask(
'PhabricatorRepositoryCommitPublishWorker',
array(
'commitID' => $commit->getID(),
));
}
$this->queueCommitTask('PhabricatorRepositoryCommitPublishWorker');
}
private function writeCommitChanges(

View file

@ -24,21 +24,7 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker
$this->updateCommitData($commit, $data);
}
if ($this->shouldQueueFollowupTasks()) {
$this->queueTask(
$this->getFollowupTaskClass(),
array(
'commitID' => $commit->getID(),
),
array(
// We queue followup tasks at default priority so that the queue
// finishes work it has started before starting more work. If
// followups are queued at the same priority level, we do all
// message parses first, then all change parses, etc. This makes
// progress uneven. See T11677 for discussion.
'priority' => PhabricatorWorker::PRIORITY_DEFAULT,
));
}
$this->queueCommitTask($this->getFollowupTaskClass());
}
final protected function updateCommitData(

View file

@ -1389,7 +1389,7 @@ abstract class PhabricatorApplicationTransactionEditor
return $xactions;
}
final private function queuePublishing() {
private function queuePublishing() {
$object = $this->publishableObject;
$xactions = $this->publishableTransactions;
@ -4207,7 +4207,7 @@ abstract class PhabricatorApplicationTransactionEditor
* @return dict<string, wild> Serializable editor state.
* @task workers
*/
final private function getWorkerState() {
private function getWorkerState() {
$state = array();
foreach ($this->getAutomaticStateProperties() as $property) {
$state[$property] = $this->$property;
@ -4336,7 +4336,7 @@ abstract class PhabricatorApplicationTransactionEditor
* @return map<string, wild> Map of encoded values.
* @task workers
*/
final private function encodeStateForStorage(
private function encodeStateForStorage(
array $state,
array $encodings) {
@ -4382,7 +4382,7 @@ abstract class PhabricatorApplicationTransactionEditor
* @return map<string, wild> Map of decoded values.
* @task workers
*/
final private function decodeStateFromStorage(
private function decodeStateFromStorage(
array $state,
array $encodings) {

View file

@ -0,0 +1,83 @@
@title Managing the Worker Queue
@group fieldmanual
Advanced guide to managing the background worker task queue.
Overview
========
Phabricator uses daemonized worker processes to execute some tasks (like
importing repositories and sending mail) in the background.
In most cases, this queue will automatically execute tasks in an appropriate
order. However, in some cases you may want to exercise greater control over
which tasks execute, when, and at what priority.
Reference: Priority Levels
==========================
Tasks queued by Phabricator use these default priority levels:
| Priority | Name | Tasks |
|---|---|---|
| 1000 | `ALERTS` | Time-sensitive notifications and email. |
| 2000 | `DEFAULT` | Normal publishing and processing. |
| 2500 | `COMMIT` | Import of commits in existing repositories. |
| 3000 | `BULK` | Edits applied via "Bulk Edit" interface. |
| 3500 | `INDEX` | Search engine index updates. |
| 4000 | `IMPORT` | Import of commits in new repositories. |
Tasks with smaller priority numbers execute before tasks with larger priority
numbers (for example, a task with priority 1000 will execute before a task
with priority 2000).
Any positive integer is a valid priority level, and if you adjust the priority
of tasks with `bin/worker priority` you may select any level even if
Phabricator would never naturally queue tasks at that level. For example, you
may adjust tasks to priority `5678`, which will make them execute after all
other types of natural tasks.
Although tasks usually execute in priority order, task execution order is not
strictly a function of priority, and task priority does not guarantee execution
order.
Large Repository Imports
========================
The most common case where you may want to make an adjustment to the default
behavior of the worker queue is when importing a very large repository like
the Linux kernel.
Although Phabricator will automatically process imports of new repositories at
a lower priority level than all other non-import tasks, you may still run into
issues like these:
- You may also want to import one or more //other// new repositories, and
would prefer they import at a higher priority.
- You may find overall repository performance is impacted by the large
repository import.
You can manually change the priority of tasks with `bin/worker priority`. For
example, if your copy of the Linux repository is `R123` and you'd like it to
import at a lower priority than all other tasks (including other imports of
new repositories), you can run a command like this:
```
phabricator/ $ ./bin/worker priority --priority 5000 --container R123
```
This means: set all tasks associated with container `R123` (in this example,
the Linux repository) to priority 5000 (which is lower than any natural
priority).
You can delay tasks until later with `bin/worker delay`, which allows you to
schedule tasks to execute at night or over the weekend. For example, to
pause an import for 6 hours, run a command like this:
```
phabricator/ $ ./bin/worker delay --until "6 hours" --container R123
```
The selected tasks will not execute until 6 hours from the time this command
is issued. You can also provide an explicit date, or "now" to let tasks begin
execution immediately.

View file

@ -134,6 +134,7 @@ abstract class PhabricatorWorker extends Phobject {
array(
'priority' => 'optional int|null',
'objectPHID' => 'optional string|null',
'containerPHID' => 'optional string|null',
'delayUntil' => 'optional int|null',
));
@ -142,12 +143,14 @@ abstract class PhabricatorWorker extends Phobject {
$priority = self::PRIORITY_DEFAULT;
}
$object_phid = idx($options, 'objectPHID');
$container_phid = idx($options, 'containerPHID');
$task = id(new PhabricatorWorkerActiveTask())
->setTaskClass($task_class)
->setData($data)
->setPriority($priority)
->setObjectPHID($object_phid);
->setObjectPHID($object_phid)
->setContainerPHID($container_phid);
$delay = idx($options, 'delayUntil');
if ($delay) {

View file

@ -6,7 +6,7 @@ final class PhabricatorWorkerManagementCancelWorkflow
protected function didConstruct() {
$this
->setName('cancel')
->setExamples('**cancel** --id __id__')
->setExamples('**cancel** __selectors__')
->setSynopsis(
pht(
'Cancel selected tasks. The work these tasks represent will never '.
@ -15,14 +15,21 @@ final class PhabricatorWorkerManagementCancelWorkflow
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$tasks = $this->loadTasks($args);
if (!$tasks) {
$this->logWarn(
pht('NO TASKS'),
pht('No tasks selected to cancel.'));
return 0;
}
$cancel_count = 0;
foreach ($tasks as $task) {
$can_cancel = !$task->isArchived();
if (!$can_cancel) {
$console->writeOut(
"**<bg:yellow> %s </bg>** %s\n",
$this->logWarn(
pht('ARCHIVED'),
pht(
'%s is already archived, and can not be cancelled.',
@ -32,20 +39,25 @@ final class PhabricatorWorkerManagementCancelWorkflow
// 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);
$task
->setLeaseOwner(null)
->setLeaseExpires(PhabricatorTime::getNow());
$console->writeOut(
"**<bg:green> %s </bg>** %s\n",
$task->archiveTask(PhabricatorWorkerArchiveTask::RESULT_CANCELLED, 0);
$this->logInfo(
pht('CANCELLED'),
pht(
'%s was cancelled.',
$this->describeTask($task)));
$cancel_count++;
}
$this->logOkay(
pht('DONE'),
pht('Cancelled %s task(s).', new PhutilNumber($cancel_count)));
return 0;
}

View file

@ -0,0 +1,97 @@
<?php
final class PhabricatorWorkerManagementDelayWorkflow
extends PhabricatorWorkerManagementWorkflow {
protected function didConstruct() {
$this
->setName('delay')
->setExamples(
implode(
"\n",
array(
'**delay** __selectors__ --until __date__',
'**delay** __selectors__ --until __YYYY-MM-DD__',
'**delay** __selectors__ --until "6 hours"',
'**delay** __selectors__ --until now',
)))
->setSynopsis(
pht(
'Delay execution of selected tasks until the specified time.'))
->setArguments(
array_merge(
array(
array(
'name' => 'until',
'param' => 'date',
'help' => pht(
'Select the date or time to delay the selected tasks until.'),
),
),
$this->getTaskSelectionArguments()));
}
public function execute(PhutilArgumentParser $args) {
$viewer = $this->getViewer();
$until = $args->getArg('until');
$until = $this->parseTimeArgument($until);
if ($until === null) {
throw new PhutilArgumentUsageException(
pht(
'Specify how long to delay tasks for with "--until".'));
}
$tasks = $this->loadTasks($args);
if (!$tasks) {
$this->logWarn(
pht('NO TASKS'),
pht('No tasks selected to delay.'));
return 0;
}
$delay_count = 0;
foreach ($tasks as $task) {
if ($task->isArchived()) {
$this->logWarn(
pht('ARCHIVED'),
pht(
'%s is already archived, and can not be delayed.',
$this->describeTask($task)));
continue;
}
if ($task->getLeaseOwner()) {
$this->logWarn(
pht('LEASED'),
pht(
'% is already leased, and can not be delayed.',
$this->describeTask($task)));
continue;
}
$task
->setLeaseExpires($until)
->save();
$this->logInfo(
pht('DELAY'),
pht(
'%s was delayed until "%s".',
$this->describeTask($task),
phabricator_datetime($until, $viewer)));
$delay_count++;
}
$this->logOkay(
pht('DONE'),
pht('Delayed %s task(s).', new PhutilNumber($delay_count)));
return 0;
}
}

View file

@ -6,7 +6,7 @@ final class PhabricatorWorkerManagementExecuteWorkflow
protected function didConstruct() {
$this
->setName('execute')
->setExamples('**execute** --id __id__')
->setExamples('**execute** __selectors__')
->setSynopsis(
pht(
'Execute a task explicitly. This command ignores leases, is '.
@ -27,18 +27,24 @@ final class PhabricatorWorkerManagementExecuteWorkflow
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$tasks = $this->loadTasks($args);
$is_retry = $args->getArg('retry');
$is_repeat = $args->getArg('repeat');
$tasks = $this->loadTasks($args);
if (!$tasks) {
$this->logWarn(
pht('NO TASKS'),
pht('No tasks selected to execute.'));
return 0;
}
$execute_count = 0;
foreach ($tasks as $task) {
$can_execute = !$task->isArchived();
if (!$can_execute) {
if (!$is_retry) {
$console->writeOut(
"**<bg:yellow> %s </bg>** %s\n",
$this->logWarn(
pht('ARCHIVED'),
pht(
'%s is already archived, and will not be executed. '.
@ -50,8 +56,7 @@ final class PhabricatorWorkerManagementExecuteWorkflow
$result_success = PhabricatorWorkerArchiveTask::RESULT_SUCCESS;
if ($task->getResult() == $result_success) {
if (!$is_repeat) {
$console->writeOut(
"**<bg:yellow> %s </bg>** %s\n",
$this->logWarn(
pht('SUCCEEDED'),
pht(
'%s has already succeeded, and will not be retried. '.
@ -61,9 +66,8 @@ final class PhabricatorWorkerManagementExecuteWorkflow
}
}
echo tsprintf(
"**<bg:yellow> %s </bg>** %s\n",
pht('ARCHIVED'),
$this->logInfo(
pht('UNARCHIVING'),
pht(
'Unarchiving %s.',
$this->describeTask($task)));
@ -74,30 +78,36 @@ final class PhabricatorWorkerManagementExecuteWorkflow
// NOTE: This ignores leases, maybe it should respect them without
// a parameter like --force?
$task->setLeaseOwner(null);
$task->setLeaseExpires(PhabricatorTime::getNow());
$task->save();
$task
->setLeaseOwner(null)
->setLeaseExpires(PhabricatorTime::getNow())
->save();
$task_data = id(new PhabricatorWorkerTaskData())->loadOneWhere(
'id = %d',
$task->getDataID());
$task->setData($task_data->getData());
echo tsprintf(
"%s\n",
$this->logInfo(
pht('EXECUTE'),
pht(
'Executing task %d (%s)...',
$task->getID(),
$task->getTaskClass()));
'Executing %s...',
$this->describeTask($task)));
$task = $task->executeTask();
$ex = $task->getExecutionException();
$ex = $task->getExecutionException();
if ($ex) {
throw $ex;
}
$execute_count++;
}
$this->logOkay(
pht('DONE'),
pht('Executed %s task(s).', new PhutilNumber($execute_count)));
return 0;
}

View file

@ -6,7 +6,7 @@ final class PhabricatorWorkerManagementFreeWorkflow
protected function didConstruct() {
$this
->setName('free')
->setExamples('**free** --id __id__')
->setExamples('**free** __selectors__')
->setSynopsis(
pht(
'Free leases on selected tasks. If the daemon holding the lease is '.
@ -16,13 +16,20 @@ final class PhabricatorWorkerManagementFreeWorkflow
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$tasks = $this->loadTasks($args);
if (!$tasks) {
$this->logWarn(
pht('NO TASKS'),
pht('No tasks selected to free leases on.'));
return 0;
}
$free_count = 0;
foreach ($tasks as $task) {
if ($task->isArchived()) {
$console->writeOut(
"**<bg:yellow> %s </bg>** %s\n",
$this->logWarn(
pht('ARCHIVED'),
pht(
'%s is archived; archived tasks do not have leases.',
@ -31,8 +38,7 @@ final class PhabricatorWorkerManagementFreeWorkflow
}
if ($task->getLeaseOwner() === null) {
$console->writeOut(
"**<bg:yellow> %s </bg>** %s\n",
$this->logWarn(
pht('FREE'),
pht(
'%s has no active lease.',
@ -40,18 +46,24 @@ final class PhabricatorWorkerManagementFreeWorkflow
continue;
}
$task->setLeaseOwner(null);
$task->setLeaseExpires(PhabricatorTime::getNow());
$task->save();
$task
->setLeaseOwner(null)
->setLeaseExpires(PhabricatorTime::getNow())
->save();
$console->writeOut(
"**<bg:green> %s </bg>** %s\n",
$this->logInfo(
pht('LEASE FREED'),
pht(
'%s was freed from its lease.',
$this->describeTask($task)));
$free_count++;
}
$this->logOkay(
pht('DONE'),
pht('Freed %s task lease(s).', new PhutilNumber($free_count)));
return 0;
}

View file

@ -0,0 +1,102 @@
<?php
final class PhabricatorWorkerManagementPriorityWorkflow
extends PhabricatorWorkerManagementWorkflow {
protected function didConstruct() {
$this
->setName('priority')
->setExamples('**priority** __selectors__ --priority __value__')
->setSynopsis(
pht(
'Change the priority of selected tasks, causing them to execute '.
'before or after other tasks.'))
->setArguments(
array_merge(
array(
array(
'name' => 'priority',
'param' => 'int',
'help' => pht(
'Set tasks to this priority. Tasks with a smaller priority '.
'value execute before tasks with a larger priority value.'),
),
),
$this->getTaskSelectionArguments()));
}
public function execute(PhutilArgumentParser $args) {
$new_priority = $args->getArg('priority');
if ($new_priority === null) {
throw new PhutilArgumentUsageException(
pht(
'Select a new priority for selected tasks with "--priority".'));
}
$new_priority = (int)$new_priority;
if ($new_priority <= 0) {
throw new PhutilArgumentUsageException(
pht(
'Priority must be a positive integer.'));
}
$tasks = $this->loadTasks($args);
if (!$tasks) {
$this->logWarn(
pht('NO TASKS'),
pht('No tasks selected to reprioritize.'));
return 0;
}
$priority_count = 0;
foreach ($tasks as $task) {
$can_reprioritize = !$task->isArchived();
if (!$can_reprioritize) {
$this->logWarn(
pht('ARCHIVED'),
pht(
'%s is already archived, and can not be reprioritized.',
$this->describeTask($task)));
continue;
}
$old_priority = (int)$task->getPriority();
if ($old_priority === $new_priority) {
$this->logWarn(
pht('UNCHANGED'),
pht(
'%s already has priority "%s".',
$this->describeTask($task),
$new_priority));
continue;
}
$task
->setPriority($new_priority)
->save();
$this->logInfo(
pht('PRIORITY'),
pht(
'%s was reprioritized (from "%d" to "%d").',
$this->describeTask($task),
$old_priority,
$new_priority));
$priority_count++;
}
$this->logOkay(
pht('DONE'),
pht('Reprioritized %s task(s).', new PhutilNumber($priority_count)));
return 0;
}
}

View file

@ -6,7 +6,7 @@ final class PhabricatorWorkerManagementRetryWorkflow
protected function didConstruct() {
$this
->setName('retry')
->setExamples('**retry** --id __id__')
->setExamples('**retry** __selectors__')
->setSynopsis(
pht(
'Retry selected tasks which previously failed permanently or '.
@ -24,14 +24,21 @@ final class PhabricatorWorkerManagementRetryWorkflow
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$tasks = $this->loadTasks($args);
$is_repeat = $args->getArg('repeat');
$tasks = $this->loadTasks($args);
if (!$tasks) {
$this->logWarn(
pht('NO TASKS'),
pht('No tasks selected to retry.'));
return 0;
}
$retry_count = 0;
foreach ($tasks as $task) {
if (!$task->isArchived()) {
$console->writeOut(
"**<bg:yellow> %s </bg>** %s\n",
$this->logWarn(
pht('ACTIVE'),
pht(
'%s is already in the active task queue.',
@ -42,8 +49,7 @@ final class PhabricatorWorkerManagementRetryWorkflow
$result_success = PhabricatorWorkerArchiveTask::RESULT_SUCCESS;
if ($task->getResult() == $result_success) {
if (!$is_repeat) {
$console->writeOut(
"**<bg:yellow> %s </bg>** %s\n",
$this->logWarn(
pht('SUCCEEDED'),
pht(
'%s has already succeeded, and will not be repeated. '.
@ -55,14 +61,19 @@ final class PhabricatorWorkerManagementRetryWorkflow
$task->unarchiveTask();
$console->writeOut(
"**<bg:green> %s </bg>** %s\n",
$this->logInfo(
pht('QUEUED'),
pht(
'%s was queued for retry.',
$this->describeTask($task)));
$retry_count++;
}
$this->logOkay(
pht('DONE'),
pht('Queued %s task(s) for retry.', new PhutilNumber($retry_count)));
return 0;
}

View file

@ -14,16 +14,54 @@ abstract class PhabricatorWorkerManagementWorkflow
array(
'name' => 'class',
'param' => 'name',
'help' => pht('Select all tasks of a given class.'),
'help' => pht('Select tasks of a given class.'),
),
array(
'name' => 'min-failure-count',
'param' => 'int',
'help' => pht('Limit to tasks with at least this many failures.'),
'help' => pht('Select tasks with a minimum failure count.'),
),
array(
'name' => 'max-failure-count',
'param' => 'int',
'help' => pht('Select tasks with a maximum failure count.'),
),
array(
'name' => 'active',
'help' => pht('Select all active tasks.'),
'help' => pht('Select active tasks.'),
),
array(
'name' => 'archived',
'help' => pht('Select archived tasks.'),
),
array(
'name' => 'container',
'param' => 'name',
'help' => pht(
'Select tasks with the given container or containers.'),
'repeat' => true,
),
array(
'name' => 'object',
'param' => 'name',
'repeat' => true,
'help' => pht(
'Select tasks affecting the given object or objects.'),
),
array(
'name' => 'min-priority',
'param' => 'int',
'help' => pht('Select tasks with a minimum priority.'),
),
array(
'name' => 'max-priority',
'param' => 'int',
'help' => pht('Select tasks with a maximum priority.'),
),
array(
'name' => 'limit',
'param' => 'int',
'help' => pht('Limit selection to a maximum number of tasks.'),
),
);
}
@ -31,14 +69,92 @@ abstract class PhabricatorWorkerManagementWorkflow
protected function loadTasks(PhutilArgumentParser $args) {
$ids = $args->getArg('id');
$class = $args->getArg('class');
$min_failures = $args->getArg('min-failure-count');
$active = $args->getArg('active');
$archived = $args->getArg('archived');
if (!$ids && !$class && !$min_failures && !$active) {
$container_names = $args->getArg('container');
$object_names = $args->getArg('object');
$min_failures = $args->getArg('min-failure-count');
$max_failures = $args->getArg('max-failure-count');
$min_priority = $args->getArg('min-priority');
$max_priority = $args->getArg('max-priority');
$limit = $args->getArg('limit');
$any_constraints = false;
if ($ids) {
$any_constraints = true;
}
if ($class) {
$any_constraints = true;
}
if ($active || $archived) {
$any_constraints = true;
if ($active && $archived) {
throw new PhutilArgumentUsageException(
pht(
'You can not specify both "--active" and "--archived" tasks: '.
'no tasks can match both constraints.'));
}
}
if ($container_names) {
$any_constraints = true;
$container_phids = $this->loadObjectPHIDsFromArguments($container_names);
} else {
$container_phids = array();
}
if ($object_names) {
$any_constraints = true;
$object_phids = $this->loadObjectPHIDsFromArguments($object_names);
} else {
$object_phids = array();
}
if (($min_failures !== null) || ($max_failures !== null)) {
$any_constraints = true;
if (($min_failures !== null) && ($max_failures !== null)) {
if ($min_failures > $max_failures) {
throw new PhutilArgumentUsageException(
pht(
'Specified "--min-failures" must not be larger than '.
'specified "--max-failures".'));
}
}
}
if (($min_priority !== null) || ($max_priority !== null)) {
$any_constraints = true;
if (($min_priority !== null) && ($max_priority !== null)) {
if ($min_priority > $max_priority) {
throw new PhutilArgumentUsageException(
pht(
'Specified "--min-priority" may not be larger than '.
'specified "--max-priority".'));
}
}
}
if (!$any_constraints) {
throw new PhutilArgumentUsageException(
pht(
'Use "--id", "--class", "--active", and/or "--min-failure-count" '.
'to select tasks.'));
'Use constraint flags (like "--id" or "--class") to select which '.
'tasks to affect. Use "--help" for a list of supported constraint '.
'flags.'));
}
if ($limit !== null) {
$limit = (int)$limit;
if ($limit <= 0) {
throw new PhutilArgumentUsageException(
pht(
'Specified "--limit" must be a positive integer.'));
}
}
$active_query = new PhabricatorWorkerActiveTaskQuery();
@ -56,13 +172,35 @@ abstract class PhabricatorWorkerManagementWorkflow
}
if ($min_failures) {
$active_query = $active_query->withFailureCountBetween(
$min_failures, null);
$archive_query = $archive_query->withFailureCountBetween(
$min_failures, null);
$active_query->withFailureCountBetween($min_failures, $max_failures);
$archive_query->withFailureCountBetween($min_failures, $max_failures);
}
$active_tasks = $active_query->execute();
if ($container_phids) {
$active_query->withContainerPHIDs($container_phids);
$archive_query->withContainerPHIDs($container_phids);
}
if ($object_phids) {
$active_query->withObjectPHIDs($object_phids);
$archive_query->withObjectPHIDs($object_phids);
}
if ($min_priority || $max_priority) {
$active_query->withPriorityBetween($min_priority, $max_priority);
$archive_query->withPriorityBetween($min_priority, $max_priority);
}
if ($limit) {
$active_query->setLimit($limit);
$archive_query->setLimit($limit);
}
if ($archived) {
$active_tasks = array();
} else {
$active_tasks = $active_query->execute();
}
if ($active) {
$archive_tasks = array();
@ -74,32 +212,22 @@ abstract class PhabricatorWorkerManagementWorkflow
mpull($active_tasks, null, 'getID') +
mpull($archive_tasks, null, 'getID');
if ($limit) {
$tasks = array_slice($tasks, 0, $limit, $preserve_keys = true);
}
if ($ids) {
foreach ($ids as $id) {
if (empty($tasks[$id])) {
throw new PhutilArgumentUsageException(
pht('No task exists with id "%s"!', $id));
pht('No task with ID "%s" matches the constraints!', $id));
}
}
}
if ($class && $min_failures) {
if (!$tasks) {
throw new PhutilArgumentUsageException(
pht('No task exists with class "%s" and at least %d failures!',
$class,
$min_failures));
}
} else if ($class) {
if (!$tasks) {
throw new PhutilArgumentUsageException(
pht('No task exists with class "%s"!', $class));
}
} else if ($min_failures) {
if (!$tasks) {
throw new PhutilArgumentUsageException(
pht('No tasks exist with at least %d failures!', $min_failures));
}
}
// We check that IDs are valid, but for all other constraints it is
// acceptable to select no tasks to act upon.
// When we lock tasks properly, this gets populated as a side effect. Just
// fake it when doing manual CLI stuff. This makes sure CLI yields have
@ -110,6 +238,20 @@ abstract class PhabricatorWorkerManagementWorkflow
}
}
// If the user specified one or more "--id" flags, process the tasks in
// the given order. Otherwise, process them in FIFO order so the sequence
// is somewhat consistent with natural execution order.
// NOTE: When "--limit" is used, we end up selecting the newest tasks
// first. At time of writing, there's no way to order the queries
// correctly, so just accept it as reasonable behavior.
if ($ids) {
$tasks = array_select_keys($tasks, $ids);
} else {
$tasks = msort($tasks, 'getID');
}
return $tasks;
}
@ -117,4 +259,52 @@ abstract class PhabricatorWorkerManagementWorkflow
return pht('Task %d (%s)', $task->getID(), $task->getTaskClass());
}
private function loadObjectPHIDsFromArguments(array $names) {
$viewer = $this->getViewer();
$seen_names = array();
foreach ($names as $name) {
if (isset($seen_names[$name])) {
throw new PhutilArgumentUsageException(
pht(
'Object "%s" is specified more than once. Specify only unique '.
'objects.',
$name));
}
$seen_names[$name] = true;
}
$object_query = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withNames($names);
$object_query->execute();
$name_map = $object_query->getNamedResults();
$phid_map = array();
foreach ($names as $name) {
if (!isset($name_map[$name])) {
throw new PhutilArgumentUsageException(
pht(
'No object with name "%s" could be loaded.',
$name));
}
$phid = $name_map[$name]->getPHID();
if (isset($phid_map[$phid])) {
throw new PhutilArgumentUsageException(
pht(
'Names "%s" and "%s" identify the same object. Specify only '.
'unique objects.',
$name,
$phid_map[$phid]));
}
$phid_map[$phid] = $name;
}
return array_keys($phid_map);
}
}

View file

@ -7,10 +7,13 @@ abstract class PhabricatorWorkerTaskQuery
private $dateModifiedSince;
private $dateCreatedBefore;
private $objectPHIDs;
private $containerPHIDs;
private $classNames;
private $limit;
private $minFailureCount;
private $maxFailureCount;
private $minPriority;
private $maxPriority;
public function withIDs(array $ids) {
$this->ids = $ids;
@ -32,6 +35,11 @@ abstract class PhabricatorWorkerTaskQuery
return $this;
}
public function withContainerPHIDs(array $phids) {
$this->containerPHIDs = $phids;
return $this;
}
public function withClassNames(array $names) {
$this->classNames = $names;
return $this;
@ -43,6 +51,12 @@ abstract class PhabricatorWorkerTaskQuery
return $this;
}
public function withPriorityBetween($min, $max) {
$this->minPriority = $min;
$this->maxPriority = $max;
return $this;
}
public function setLimit($limit) {
$this->limit = $limit;
return $this;
@ -65,6 +79,13 @@ abstract class PhabricatorWorkerTaskQuery
$this->objectPHIDs);
}
if ($this->containerPHIDs !== null) {
$where[] = qsprintf(
$conn,
'containerPHID IN (%Ls)',
$this->containerPHIDs);
}
if ($this->dateModifiedSince !== null) {
$where[] = qsprintf(
$conn,
@ -100,6 +121,20 @@ abstract class PhabricatorWorkerTaskQuery
$this->maxFailureCount);
}
if ($this->minPriority !== null) {
$where[] = qsprintf(
$conn,
'priority >= %d',
$this->minPriority);
}
if ($this->maxPriority !== null) {
$where[] = qsprintf(
$conn,
'priority <= %d',
$this->maxPriority);
}
return $this->formatWhereClause($conn, $where);
}

View file

@ -116,6 +116,7 @@ final class PhabricatorWorkerActiveTask extends PhabricatorWorkerTask {
->setDataID($this->getDataID())
->setPriority($this->getPriority())
->setObjectPHID($this->getObjectPHID())
->setContainerPHID($this->getContainerPHID())
->setResult($result)
->setDuration($duration)
->setDateCreated($this->getDateCreated())

View file

@ -87,6 +87,7 @@ final class PhabricatorWorkerArchiveTask extends PhabricatorWorkerTask {
->setDataID($this->getDataID())
->setPriority($this->getPriority())
->setObjectPHID($this->getObjectPHID())
->setContainerPHID($this->getContainerPHID())
->setDateCreated($this->getDateCreated())
->insert();

View file

@ -11,6 +11,7 @@ abstract class PhabricatorWorkerTask extends PhabricatorWorkerDAO {
protected $dataID;
protected $priority;
protected $objectPHID;
protected $containerPHID;
private $data;
private $executionException;
@ -25,11 +26,15 @@ abstract class PhabricatorWorkerTask extends PhabricatorWorkerDAO {
'failureTime' => 'epoch?',
'priority' => 'uint32',
'objectPHID' => 'phid?',
'containerPHID' => 'phid?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_object' => array(
'columns' => array('objectPHID'),
),
'key_container' => array(
'columns' => array('containerPHID'),
),
),
) + parent::getConfiguration();
}

View file

@ -1759,6 +1759,36 @@ final class PhabricatorUSEnglishTranslation
'These inline comments will be saved and published.',
),
'Delayed %s task(s).' => array(
'Delayed 1 task.',
'Delayed %s tasks.',
),
'Freed %s task lease(s).' => array(
'Freed 1 task lease.',
'Freed %s task leases.',
),
'Cancelled %s task(s).' => array(
'Cancelled 1 task.',
'Cancelled %s tasks.',
),
'Queued %s task(s) for retry.' => array(
'Queued 1 task for retry.',
'Queued %s tasks for retry.',
),
'Reprioritized %s task(s).' => array(
'Reprioritized one task.',
'Reprioritized %s tasks.',
),
'Executed %s task(s).' => array(
'Executed 1 task.',
'Executed %s tasks.',
),
);
}

View file

@ -135,7 +135,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
);
}
final private function getExternalCursorStringForResult($object) {
private function getExternalCursorStringForResult($object) {
$cursor = $this->newExternalCursorStringForResult($object);
if (!is_string($cursor)) {
@ -154,7 +154,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
return $this->externalCursorString;
}
final private function setExternalCursorString($external_cursor) {
private function setExternalCursorString($external_cursor) {
$this->externalCursorString = $external_cursor;
return $this;
}
@ -168,17 +168,17 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
return $this;
}
final private function getInternalCursorObject() {
private function getInternalCursorObject() {
return $this->internalCursorObject;
}
final private function setInternalCursorObject(
private function setInternalCursorObject(
PhabricatorQueryCursor $cursor) {
$this->internalCursorObject = $cursor;
return $this;
}
final private function getInternalCursorFromExternalCursor(
private function getInternalCursorFromExternalCursor(
$cursor_string) {
$cursor_object = $this->newInternalCursorFromExternalCursor($cursor_string);
@ -196,7 +196,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
return $cursor_object;
}
final private function getPagingMapFromCursorObject(
private function getPagingMapFromCursorObject(
PhabricatorQueryCursor $cursor,
array $keys) {

View file

@ -9,6 +9,10 @@ final class PhabricatorStoragePatch extends Phobject {
private $after;
private $legacy;
private $dead;
private $phase;
const PHASE_DEFAULT = 'default';
const PHASE_WORKER = 'worker';
public function __construct(array $dict) {
$this->key = $dict['key'];
@ -18,6 +22,7 @@ final class PhabricatorStoragePatch extends Phobject {
$this->name = $dict['name'];
$this->after = $dict['after'];
$this->dead = $dict['dead'];
$this->phase = $dict['phase'];
}
public function getLegacy() {
@ -44,6 +49,10 @@ final class PhabricatorStoragePatch extends Phobject {
return $this->key;
}
public function getPhase() {
return $this->phase;
}
public function isDead() {
return $this->dead;
}
@ -52,4 +61,31 @@ final class PhabricatorStoragePatch extends Phobject {
return ($this->getType() == 'php');
}
public static function getPhaseList() {
return array_keys(self::getPhaseMap());
}
public static function getDefaultPhase() {
return self::PHASE_DEFAULT;
}
private static function getPhaseMap() {
return array(
self::PHASE_DEFAULT => array(
'order' => 0,
),
self::PHASE_WORKER => array(
'order' => 1,
),
);
}
public function newSortVector() {
$map = self::getPhaseMap();
$phase = $this->getPhase();
return id(new PhutilSortVector())
->addInt($map[$phase]['order']);
}
}

View file

@ -33,6 +33,7 @@ final class PhabricatorStorageManagementStatusWorkflow
$table = id(new PhutilConsoleTable())
->setShowHeader(false)
->addColumn('id', array('title' => pht('ID')))
->addColumn('phase', array('title' => pht('Phase')))
->addColumn('host', array('title' => pht('Host')))
->addColumn('status', array('title' => pht('Status')))
->addColumn('duration', array('title' => pht('Duration')))
@ -49,16 +50,22 @@ final class PhabricatorStorageManagementStatusWorkflow
$duration = pht('%s us', new PhutilNumber($duration));
}
$table->addRow(array(
'id' => $patch->getFullKey(),
'host' => $ref->getRefKey(),
'status' => in_array($patch->getFullKey(), $applied)
? pht('Applied')
: pht('Not Applied'),
'duration' => $duration,
'type' => $patch->getType(),
'name' => $patch->getName(),
));
if (in_array($patch->getFullKey(), $applied)) {
$status = pht('Applied');
} else {
$status = pht('Not Applied');
}
$table->addRow(
array(
'id' => $patch->getFullKey(),
'phase' => $patch->getPhase(),
'host' => $ref->getRefKey(),
'status' => $status,
'duration' => $duration,
'type' => $patch->getType(),
'name' => $patch->getName(),
));
}
$table->draw();

View file

@ -156,7 +156,7 @@ abstract class PhabricatorStorageManagementWorkflow
return $err;
}
final private function doAdjustSchemata(
private function doAdjustSchemata(
PhabricatorStorageManagementAPI $api,
$unsafe) {
@ -913,7 +913,7 @@ abstract class PhabricatorStorageManagementWorkflow
}
}
final private function doUpgradeSchemata(
private function doUpgradeSchemata(
array $apis,
$apply_only,
$no_quickstart,
@ -922,6 +922,10 @@ abstract class PhabricatorStorageManagementWorkflow
$patches = $this->patches;
$is_dryrun = $this->dryRun;
// We expect that patches should already be sorted properly. However,
// phase behavior will be wrong if they aren't, so make sure.
$patches = msortv($patches, 'newSortVector');
$api_map = array();
foreach ($apis as $api) {
$api_map[$api->getRef()->getRefKey()] = $api;

View file

@ -27,10 +27,20 @@ abstract class PhabricatorSQLPatchList extends Phobject {
$directory));
}
$patch_type = $matches[1];
$patch_full_path = rtrim($directory, '/').'/'.$patch;
$attributes = array();
if ($patch_type === 'php') {
$attributes = $this->getPHPPatchAttributes(
$patch,
$patch_full_path);
}
$patches[$patch] = array(
'type' => $matches[1],
'name' => rtrim($directory, '/').'/'.$patch,
);
'type' => $patch_type,
'name' => $patch_full_path,
) + $attributes;
}
return $patches;
@ -45,8 +55,16 @@ abstract class PhabricatorSQLPatchList extends Phobject {
$specs = array();
$seen_namespaces = array();
$phases = PhabricatorStoragePatch::getPhaseList();
$phases = array_fuse($phases);
$default_phase = PhabricatorStoragePatch::getDefaultPhase();
foreach ($patch_lists as $patch_list) {
$last_key = null;
$last_keys = array_fill_keys(
array_keys($phases),
null);
foreach ($patch_list->getPatches() as $key => $patch) {
if (!is_array($patch)) {
throw new Exception(
@ -63,6 +81,7 @@ abstract class PhabricatorSQLPatchList extends Phobject {
'after' => true,
'legacy' => true,
'dead' => true,
'phase' => true,
);
foreach ($patch as $pkey => $pval) {
@ -128,8 +147,26 @@ abstract class PhabricatorSQLPatchList extends Phobject {
$patch['legacy'] = false;
}
if (!array_key_exists('phase', $patch)) {
$patch['phase'] = $default_phase;
}
$patch_phase = $patch['phase'];
if (!isset($phases[$patch_phase])) {
throw new Exception(
pht(
'Storage patch "%s" specifies it should apply in phase "%s", '.
'but this phase is unrecognized. Valid phases are: %s.',
$full_key,
$patch_phase,
implode(', ', array_keys($phases))));
}
$last_key = $last_keys[$patch_phase];
if (!array_key_exists('after', $patch)) {
if ($last_key === null) {
if ($last_key === null && $patch_phase === $default_phase) {
throw new Exception(
pht(
"Patch '%s' is missing key 'after', and is the first patch ".
@ -139,10 +176,14 @@ abstract class PhabricatorSQLPatchList extends Phobject {
$full_key,
get_class($patch_list)));
} else {
$patch['after'] = array($last_key);
if ($last_key === null) {
$patch['after'] = array();
} else {
$patch['after'] = array($last_key);
}
}
}
$last_key = $full_key;
$last_keys[$patch_phase] = $full_key;
foreach ($patch['after'] as $after_key => $after) {
if (strpos($after, ':') === false) {
@ -186,6 +227,21 @@ abstract class PhabricatorSQLPatchList extends Phobject {
$key,
$after));
}
$patch_phase = $patch['phase'];
$after_phase = $specs[$after]['phase'];
if ($patch_phase !== $after_phase) {
throw new Exception(
pht(
'Storage patch "%s" executes in phase "%s", but depends on '.
'patch "%s" which is in a different phase ("%s"). Patches '.
'may not have dependencies across phases.',
$key,
$patch_phase,
$after,
$after_phase));
}
}
}
@ -196,7 +252,94 @@ abstract class PhabricatorSQLPatchList extends Phobject {
// TODO: Detect cycles?
$patches = msortv($patches, 'newSortVector');
return $patches;
}
private function getPHPPatchAttributes($patch_name, $full_path) {
$data = Filesystem::readFile($full_path);
$phase_list = PhabricatorStoragePatch::getPhaseList();
$phase_map = array_fuse($phase_list);
$attributes = array();
$lines = phutil_split_lines($data, false);
foreach ($lines as $line) {
// Skip over the "PHP" line.
if (preg_match('(^<\?)', $line)) {
continue;
}
// Skip over blank lines.
if (!strlen(trim($line))) {
continue;
}
// If this is a "//" comment...
if (preg_match('(^\s*//)', $line)) {
$matches = null;
if (preg_match('(^\s*//\s*@(\S+)(?:\s+(.*))?\z)', $line, $matches)) {
$attr_key = $matches[1];
$attr_value = trim(idx($matches, 2));
switch ($attr_key) {
case 'phase':
$phase_name = $attr_value;
if (!strlen($phase_name)) {
throw new Exception(
pht(
'Storage patch "%s" specifies a "@phase" attribute with '.
'no phase value. Phase attributes must specify a value, '.
'like "@phase default".',
$patch_name));
}
if (!isset($phase_map[$phase_name])) {
throw new Exception(
pht(
'Storage patch "%s" specifies a "@phase" value ("%s"), '.
'but this is not a recognized phase. Valid phases '.
'are: %s.',
$patch_name,
$phase_name,
implode(', ', $phase_list)));
}
if (isset($attributes['phase'])) {
throw new Exception(
pht(
'Storage patch "%s" specifies a "@phase" value ("%s"), '.
'but it already has a specified phase ("%s"). Patches '.
'may not specify multiple phases.',
$patch_name,
$phase_name,
$attributes['phase']));
}
$attributes[$attr_key] = $phase_name;
break;
default:
throw new Exception(
pht(
'Storage patch "%s" specifies attribute "%s", but this '.
'attribute is unknown.',
$patch_name,
$attr_key));
}
}
continue;
}
// If this is anything else, we're all done. Attributes must be marked
// in the header of the file.
break;
}
return $attributes;
}
}