@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 more selectors to existing "bin/worker" commands

Summary:
Ref T13591. Add more selector flags to let "bin/worker" commands operate on tasks by container PHID, object PHID, priority, etc.

This anticipates adding "bin/worker reprioritize" and "bin/worker delay" workflows, to provide more tools for handling repository imports.

Test Plan:
- Ran `bin/worker execute`, `cancel`, `retry`, and `free` with various sets of selector flags.
- Used `--min-priority`, `--max-priority`, `--object`, `--container`, `--archived`, `--max-failure-count` to select tasks.
- Specified invalid, duplicate, aliased objects with "--object".
- Specified invalid range priority selectors.

Maniphest Tasks: T13591

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

+352 -82
+23 -11
src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementCancelWorkflow.php
··· 6 6 protected function didConstruct() { 7 7 $this 8 8 ->setName('cancel') 9 - ->setExamples('**cancel** --id __id__') 9 + ->setExamples('**cancel** __selectors__') 10 10 ->setSynopsis( 11 11 pht( 12 12 'Cancel selected tasks. The work these tasks represent will never '. ··· 15 15 } 16 16 17 17 public function execute(PhutilArgumentParser $args) { 18 - $console = PhutilConsole::getConsole(); 19 18 $tasks = $this->loadTasks($args); 20 19 20 + if (!$tasks) { 21 + $this->logWarn( 22 + pht('NO TASKS'), 23 + pht('No tasks selected to cancel.')); 24 + 25 + return 0; 26 + } 27 + 28 + $cancel_count = 0; 21 29 foreach ($tasks as $task) { 22 30 $can_cancel = !$task->isArchived(); 23 31 if (!$can_cancel) { 24 - $console->writeOut( 25 - "**<bg:yellow> %s </bg>** %s\n", 32 + $this->logWarn( 26 33 pht('ARCHIVED'), 27 34 pht( 28 35 '%s is already archived, and can not be cancelled.', ··· 32 39 33 40 // Forcibly break the lease if one exists, so we can archive the 34 41 // task. 35 - $task->setLeaseOwner(null); 36 - $task->setLeaseExpires(PhabricatorTime::getNow()); 37 - $task->archiveTask( 38 - PhabricatorWorkerArchiveTask::RESULT_CANCELLED, 39 - 0); 42 + $task 43 + ->setLeaseOwner(null) 44 + ->setLeaseExpires(PhabricatorTime::getNow()); 45 + 46 + $task->archiveTask(PhabricatorWorkerArchiveTask::RESULT_CANCELLED, 0); 40 47 41 - $console->writeOut( 42 - "**<bg:green> %s </bg>** %s\n", 48 + $this->logInfo( 43 49 pht('CANCELLED'), 44 50 pht( 45 51 '%s was cancelled.', 46 52 $this->describeTask($task))); 53 + 54 + $cancel_count++; 47 55 } 56 + 57 + $this->logOkay( 58 + pht('DONE'), 59 + pht('Cancelled %s task(s).', new PhutilNumber($cancel_count))); 48 60 49 61 return 0; 50 62 }
+30 -20
src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementExecuteWorkflow.php
··· 6 6 protected function didConstruct() { 7 7 $this 8 8 ->setName('execute') 9 - ->setExamples('**execute** --id __id__') 9 + ->setExamples('**execute** __selectors__') 10 10 ->setSynopsis( 11 11 pht( 12 12 'Execute a task explicitly. This command ignores leases, is '. ··· 27 27 } 28 28 29 29 public function execute(PhutilArgumentParser $args) { 30 - $console = PhutilConsole::getConsole(); 30 + $is_retry = $args->getArg('retry'); 31 + $is_repeat = $args->getArg('repeat'); 32 + 31 33 $tasks = $this->loadTasks($args); 34 + if (!$tasks) { 35 + $this->logWarn( 36 + pht('NO TASKS'), 37 + pht('No tasks selected to execute.')); 32 38 33 - $is_retry = $args->getArg('retry'); 34 - $is_repeat = $args->getArg('repeat'); 39 + return 0; 40 + } 35 41 42 + $execute_count = 0; 36 43 foreach ($tasks as $task) { 37 44 $can_execute = !$task->isArchived(); 38 45 if (!$can_execute) { 39 46 if (!$is_retry) { 40 - $console->writeOut( 41 - "**<bg:yellow> %s </bg>** %s\n", 47 + $this->logWarn( 42 48 pht('ARCHIVED'), 43 49 pht( 44 50 '%s is already archived, and will not be executed. '. ··· 50 56 $result_success = PhabricatorWorkerArchiveTask::RESULT_SUCCESS; 51 57 if ($task->getResult() == $result_success) { 52 58 if (!$is_repeat) { 53 - $console->writeOut( 54 - "**<bg:yellow> %s </bg>** %s\n", 59 + $this->logWarn( 55 60 pht('SUCCEEDED'), 56 61 pht( 57 62 '%s has already succeeded, and will not be retried. '. ··· 61 66 } 62 67 } 63 68 64 - echo tsprintf( 65 - "**<bg:yellow> %s </bg>** %s\n", 66 - pht('ARCHIVED'), 69 + $this->logInfo( 70 + pht('UNARCHIVING'), 67 71 pht( 68 72 'Unarchiving %s.', 69 73 $this->describeTask($task))); ··· 74 78 // NOTE: This ignores leases, maybe it should respect them without 75 79 // a parameter like --force? 76 80 77 - $task->setLeaseOwner(null); 78 - $task->setLeaseExpires(PhabricatorTime::getNow()); 79 - $task->save(); 81 + $task 82 + ->setLeaseOwner(null) 83 + ->setLeaseExpires(PhabricatorTime::getNow()) 84 + ->save(); 80 85 81 86 $task_data = id(new PhabricatorWorkerTaskData())->loadOneWhere( 82 87 'id = %d', 83 88 $task->getDataID()); 84 89 $task->setData($task_data->getData()); 85 90 86 - echo tsprintf( 87 - "%s\n", 91 + $this->logInfo( 92 + pht('EXECUTE'), 88 93 pht( 89 - 'Executing task %d (%s)...', 90 - $task->getID(), 91 - $task->getTaskClass())); 94 + 'Executing %s...', 95 + $this->describeTask($task))); 92 96 93 97 $task = $task->executeTask(); 98 + 94 99 $ex = $task->getExecutionException(); 95 - 96 100 if ($ex) { 97 101 throw $ex; 98 102 } 103 + 104 + $execute_count++; 99 105 } 106 + 107 + $this->logOkay( 108 + pht('DONE'), 109 + pht('Executed %s task(s).', new PhutilNumber($execute_count))); 100 110 101 111 return 0; 102 112 }
+23 -11
src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementFreeWorkflow.php
··· 6 6 protected function didConstruct() { 7 7 $this 8 8 ->setName('free') 9 - ->setExamples('**free** --id __id__') 9 + ->setExamples('**free** __selectors__') 10 10 ->setSynopsis( 11 11 pht( 12 12 'Free leases on selected tasks. If the daemon holding the lease is '. ··· 16 16 } 17 17 18 18 public function execute(PhutilArgumentParser $args) { 19 - $console = PhutilConsole::getConsole(); 20 19 $tasks = $this->loadTasks($args); 21 20 21 + if (!$tasks) { 22 + $this->logWarn( 23 + pht('NO TASKS'), 24 + pht('No tasks selected to free leases on.')); 25 + 26 + return 0; 27 + } 28 + 29 + $free_count = 0; 22 30 foreach ($tasks as $task) { 23 31 if ($task->isArchived()) { 24 - $console->writeOut( 25 - "**<bg:yellow> %s </bg>** %s\n", 32 + $this->logWarn( 26 33 pht('ARCHIVED'), 27 34 pht( 28 35 '%s is archived; archived tasks do not have leases.', ··· 31 38 } 32 39 33 40 if ($task->getLeaseOwner() === null) { 34 - $console->writeOut( 35 - "**<bg:yellow> %s </bg>** %s\n", 41 + $this->logWarn( 36 42 pht('FREE'), 37 43 pht( 38 44 '%s has no active lease.', ··· 40 46 continue; 41 47 } 42 48 43 - $task->setLeaseOwner(null); 44 - $task->setLeaseExpires(PhabricatorTime::getNow()); 45 - $task->save(); 49 + $task 50 + ->setLeaseOwner(null) 51 + ->setLeaseExpires(PhabricatorTime::getNow()) 52 + ->save(); 46 53 47 - $console->writeOut( 48 - "**<bg:green> %s </bg>** %s\n", 54 + $this->logInfo( 49 55 pht('LEASE FREED'), 50 56 pht( 51 57 '%s was freed from its lease.', 52 58 $this->describeTask($task))); 59 + 60 + $free_count++; 53 61 } 62 + 63 + $this->logOkay( 64 + pht('DONE'), 65 + pht('Freed %s task lease(s).', new PhutilNumber($free_count))); 54 66 55 67 return 0; 56 68 }
+20 -9
src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementRetryWorkflow.php
··· 6 6 protected function didConstruct() { 7 7 $this 8 8 ->setName('retry') 9 - ->setExamples('**retry** --id __id__') 9 + ->setExamples('**retry** __selectors__') 10 10 ->setSynopsis( 11 11 pht( 12 12 'Retry selected tasks which previously failed permanently or '. ··· 24 24 } 25 25 26 26 public function execute(PhutilArgumentParser $args) { 27 - $console = PhutilConsole::getConsole(); 27 + $is_repeat = $args->getArg('repeat'); 28 + 28 29 $tasks = $this->loadTasks($args); 30 + if (!$tasks) { 31 + $this->logWarn( 32 + pht('NO TASKS'), 33 + pht('No tasks selected to retry.')); 29 34 30 - $is_repeat = $args->getArg('repeat'); 35 + return 0; 36 + } 37 + 38 + $retry_count = 0; 31 39 foreach ($tasks as $task) { 32 40 if (!$task->isArchived()) { 33 - $console->writeOut( 34 - "**<bg:yellow> %s </bg>** %s\n", 41 + $this->logWarn( 35 42 pht('ACTIVE'), 36 43 pht( 37 44 '%s is already in the active task queue.', ··· 42 49 $result_success = PhabricatorWorkerArchiveTask::RESULT_SUCCESS; 43 50 if ($task->getResult() == $result_success) { 44 51 if (!$is_repeat) { 45 - $console->writeOut( 46 - "**<bg:yellow> %s </bg>** %s\n", 52 + $this->logWarn( 47 53 pht('SUCCEEDED'), 48 54 pht( 49 55 '%s has already succeeded, and will not be repeated. '. ··· 55 61 56 62 $task->unarchiveTask(); 57 63 58 - $console->writeOut( 59 - "**<bg:green> %s </bg>** %s\n", 64 + $this->logInfo( 60 65 pht('QUEUED'), 61 66 pht( 62 67 '%s was queued for retry.', 63 68 $this->describeTask($task))); 69 + 70 + $retry_count++; 64 71 } 72 + 73 + $this->logOkay( 74 + pht('DONE'), 75 + pht('Queued %s task(s) for retry.', new PhutilNumber($retry_count))); 65 76 66 77 return 0; 67 78 }
+221 -31
src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php
··· 14 14 array( 15 15 'name' => 'class', 16 16 'param' => 'name', 17 - 'help' => pht('Select all tasks of a given class.'), 17 + 'help' => pht('Select tasks of a given class.'), 18 18 ), 19 19 array( 20 20 'name' => 'min-failure-count', 21 21 'param' => 'int', 22 - 'help' => pht('Limit to tasks with at least this many failures.'), 22 + 'help' => pht('Select tasks with a minimum failure count.'), 23 + ), 24 + array( 25 + 'name' => 'max-failure-count', 26 + 'param' => 'int', 27 + 'help' => pht('Select tasks with a maximum failure count.'), 23 28 ), 24 29 array( 25 30 'name' => 'active', 26 - 'help' => pht('Select all active tasks.'), 31 + 'help' => pht('Select active tasks.'), 32 + ), 33 + array( 34 + 'name' => 'archived', 35 + 'help' => pht('Select archived tasks.'), 36 + ), 37 + array( 38 + 'name' => 'container', 39 + 'param' => 'name', 40 + 'help' => pht( 41 + 'Select tasks with the given container or containers.'), 42 + 'repeat' => true, 43 + ), 44 + array( 45 + 'name' => 'object', 46 + 'param' => 'name', 47 + 'repeat' => true, 48 + 'help' => pht( 49 + 'Select tasks affecting the given object or objects.'), 50 + ), 51 + array( 52 + 'name' => 'min-priority', 53 + 'param' => 'int', 54 + 'help' => pht('Select tasks with a minimum priority.'), 55 + ), 56 + array( 57 + 'name' => 'max-priority', 58 + 'param' => 'int', 59 + 'help' => pht('Select tasks with a maximum priority.'), 60 + ), 61 + array( 62 + 'name' => 'limit', 63 + 'param' => 'int', 64 + 'help' => pht('Limit selection to a maximum number of tasks.'), 27 65 ), 28 66 ); 29 67 } ··· 31 69 protected function loadTasks(PhutilArgumentParser $args) { 32 70 $ids = $args->getArg('id'); 33 71 $class = $args->getArg('class'); 72 + $active = $args->getArg('active'); 73 + $archived = $args->getArg('archived'); 74 + 75 + $container_names = $args->getArg('container'); 76 + $object_names = $args->getArg('object'); 77 + 34 78 $min_failures = $args->getArg('min-failure-count'); 35 - $active = $args->getArg('active'); 79 + $max_failures = $args->getArg('max-failure-count'); 80 + 81 + $min_priority = $args->getArg('min-priority'); 82 + $max_priority = $args->getArg('max-priority'); 83 + 84 + $limit = $args->getArg('limit'); 85 + 86 + $any_constraints = false; 87 + if ($ids) { 88 + $any_constraints = true; 89 + } 36 90 37 - if (!$ids && !$class && !$min_failures && !$active) { 91 + if ($class) { 92 + $any_constraints = true; 93 + } 94 + 95 + if ($active || $archived) { 96 + $any_constraints = true; 97 + if ($active && $archived) { 98 + throw new PhutilArgumentUsageException( 99 + pht( 100 + 'You can not specify both "--active" and "--archived" tasks: '. 101 + 'no tasks can match both constraints.')); 102 + } 103 + } 104 + 105 + if ($container_names) { 106 + $any_constraints = true; 107 + $container_phids = $this->loadObjectPHIDsFromArguments($container_names); 108 + } else { 109 + $container_phids = array(); 110 + } 111 + 112 + if ($object_names) { 113 + $any_constraints = true; 114 + $object_phids = $this->loadObjectPHIDsFromArguments($object_names); 115 + } else { 116 + $object_phids = array(); 117 + } 118 + 119 + if (($min_failures !== null) || ($max_failures !== null)) { 120 + $any_constraints = true; 121 + if (($min_failures !== null) && ($max_failures !== null)) { 122 + if ($min_failures > $max_failures) { 123 + throw new PhutilArgumentUsageException( 124 + pht( 125 + 'Specified "--min-failures" must not be larger than '. 126 + 'specified "--max-failures".')); 127 + } 128 + } 129 + } 130 + 131 + if (($min_priority !== null) || ($max_priority !== null)) { 132 + $any_constraints = true; 133 + if (($min_priority !== null) && ($max_priority !== null)) { 134 + if ($min_priority > $max_priority) { 135 + throw new PhutilArgumentUsageException( 136 + pht( 137 + 'Specified "--min-priority" may not be larger than '. 138 + 'specified "--max-priority".')); 139 + } 140 + } 141 + } 142 + 143 + if (!$any_constraints) { 38 144 throw new PhutilArgumentUsageException( 39 145 pht( 40 - 'Use "--id", "--class", "--active", and/or "--min-failure-count" '. 41 - 'to select tasks.')); 146 + 'Use constraint flags (like "--id" or "--class") to select which '. 147 + 'tasks to affect. Use "--help" for a list of supported constraint '. 148 + 'flags.')); 149 + } 150 + 151 + if ($limit !== null) { 152 + $limit = (int)$limit; 153 + if ($limit <= 0) { 154 + throw new PhutilArgumentUsageException( 155 + pht( 156 + 'Specified "--limit" must be a positive integer.')); 157 + } 42 158 } 43 159 44 160 $active_query = new PhabricatorWorkerActiveTaskQuery(); ··· 56 172 } 57 173 58 174 if ($min_failures) { 59 - $active_query = $active_query->withFailureCountBetween( 60 - $min_failures, null); 61 - $archive_query = $archive_query->withFailureCountBetween( 62 - $min_failures, null); 175 + $active_query->withFailureCountBetween($min_failures, $max_failures); 176 + $archive_query->withFailureCountBetween($min_failures, $max_failures); 63 177 } 64 178 65 - $active_tasks = $active_query->execute(); 179 + if ($container_phids) { 180 + $active_query->withContainerPHIDs($container_phids); 181 + $archive_query->withContainerPHIDs($container_phids); 182 + } 183 + 184 + if ($object_phids) { 185 + $active_query->withObjectPHIDs($object_phids); 186 + $archive_query->withObjectPHIDs($object_phids); 187 + } 188 + 189 + if ($min_priority || $max_priority) { 190 + $active_query->withPriorityBetween($min_priority, $max_priority); 191 + $archive_query->withPriorityBetween($min_priority, $max_priority); 192 + } 193 + 194 + if ($limit) { 195 + $active_query->setLimit($limit); 196 + $archive_query->setLimit($limit); 197 + } 198 + 199 + if ($archived) { 200 + $active_tasks = array(); 201 + } else { 202 + $active_tasks = $active_query->execute(); 203 + } 66 204 67 205 if ($active) { 68 206 $archive_tasks = array(); ··· 74 212 mpull($active_tasks, null, 'getID') + 75 213 mpull($archive_tasks, null, 'getID'); 76 214 215 + if ($limit) { 216 + $tasks = array_slice($tasks, 0, $limit, $preserve_keys = true); 217 + } 218 + 219 + 77 220 if ($ids) { 78 221 foreach ($ids as $id) { 79 222 if (empty($tasks[$id])) { 80 223 throw new PhutilArgumentUsageException( 81 - pht('No task exists with id "%s"!', $id)); 224 + pht('No task with ID "%s" matches the constraints!', $id)); 82 225 } 83 226 } 84 227 } 85 - if ($class && $min_failures) { 86 - if (!$tasks) { 87 - throw new PhutilArgumentUsageException( 88 - pht('No task exists with class "%s" and at least %d failures!', 89 - $class, 90 - $min_failures)); 91 - } 92 - } else if ($class) { 93 - if (!$tasks) { 94 - throw new PhutilArgumentUsageException( 95 - pht('No task exists with class "%s"!', $class)); 96 - } 97 - } else if ($min_failures) { 98 - if (!$tasks) { 99 - throw new PhutilArgumentUsageException( 100 - pht('No tasks exist with at least %d failures!', $min_failures)); 101 - } 102 - } 228 + 229 + // We check that IDs are valid, but for all other constraints it is 230 + // acceptable to select no tasks to act upon. 103 231 104 232 // When we lock tasks properly, this gets populated as a side effect. Just 105 233 // fake it when doing manual CLI stuff. This makes sure CLI yields have ··· 110 238 } 111 239 } 112 240 241 + // If the user specified one or more "--id" flags, process the tasks in 242 + // the given order. Otherwise, process them in FIFO order so the sequence 243 + // is somewhat consistent with natural execution order. 244 + 245 + // NOTE: When "--limit" is used, we end up selecting the newest tasks 246 + // first. At time of writing, there's no way to order the queries 247 + // correctly, so just accept it as reasonable behavior. 248 + 249 + if ($ids) { 250 + $tasks = array_select_keys($tasks, $ids); 251 + } else { 252 + $tasks = msort($tasks, 'getID'); 253 + } 254 + 113 255 return $tasks; 114 256 } 115 257 116 258 protected function describeTask(PhabricatorWorkerTask $task) { 117 259 return pht('Task %d (%s)', $task->getID(), $task->getTaskClass()); 260 + } 261 + 262 + private function loadObjectPHIDsFromArguments(array $names) { 263 + $viewer = $this->getViewer(); 264 + 265 + $seen_names = array(); 266 + foreach ($names as $name) { 267 + if (isset($seen_names[$name])) { 268 + throw new PhutilArgumentUsageException( 269 + pht( 270 + 'Object "%s" is specified more than once. Specify only unique '. 271 + 'objects.', 272 + $name)); 273 + } 274 + $seen_names[$name] = true; 275 + } 276 + 277 + $object_query = id(new PhabricatorObjectQuery()) 278 + ->setViewer($viewer) 279 + ->withNames($names); 280 + 281 + $object_query->execute(); 282 + 283 + $name_map = $object_query->getNamedResults(); 284 + $phid_map = array(); 285 + foreach ($names as $name) { 286 + if (!isset($name_map[$name])) { 287 + throw new PhutilArgumentUsageException( 288 + pht( 289 + 'No object with name "%s" could be loaded.', 290 + $name)); 291 + } 292 + 293 + $phid = $name_map[$name]->getPHID(); 294 + 295 + if (isset($phid_map[$phid])) { 296 + throw new PhutilArgumentUsageException( 297 + pht( 298 + 'Names "%s" and "%s" identify the same object. Specify only '. 299 + 'unique objects.', 300 + $name, 301 + $phid_map[$phid])); 302 + } 303 + 304 + $phid_map[$phid] = $name; 305 + } 306 + 307 + return array_keys($phid_map); 118 308 } 119 309 120 310 }
+35
src/infrastructure/daemon/workers/query/PhabricatorWorkerTaskQuery.php
··· 7 7 private $dateModifiedSince; 8 8 private $dateCreatedBefore; 9 9 private $objectPHIDs; 10 + private $containerPHIDs; 10 11 private $classNames; 11 12 private $limit; 12 13 private $minFailureCount; 13 14 private $maxFailureCount; 15 + private $minPriority; 16 + private $maxPriority; 14 17 15 18 public function withIDs(array $ids) { 16 19 $this->ids = $ids; ··· 32 35 return $this; 33 36 } 34 37 38 + public function withContainerPHIDs(array $phids) { 39 + $this->containerPHIDs = $phids; 40 + return $this; 41 + } 42 + 35 43 public function withClassNames(array $names) { 36 44 $this->classNames = $names; 37 45 return $this; ··· 43 51 return $this; 44 52 } 45 53 54 + public function withPriorityBetween($min, $max) { 55 + $this->minPriority = $min; 56 + $this->maxPriority = $max; 57 + return $this; 58 + } 59 + 46 60 public function setLimit($limit) { 47 61 $this->limit = $limit; 48 62 return $this; ··· 65 79 $this->objectPHIDs); 66 80 } 67 81 82 + if ($this->containerPHIDs !== null) { 83 + $where[] = qsprintf( 84 + $conn, 85 + 'containerPHID IN (%Ls)', 86 + $this->containerPHIDs); 87 + } 88 + 68 89 if ($this->dateModifiedSince !== null) { 69 90 $where[] = qsprintf( 70 91 $conn, ··· 98 119 $conn, 99 120 'failureCount <= %d', 100 121 $this->maxFailureCount); 122 + } 123 + 124 + if ($this->minPriority !== null) { 125 + $where[] = qsprintf( 126 + $conn, 127 + 'priority >= %d', 128 + $this->minPriority); 129 + } 130 + 131 + if ($this->maxPriority !== null) { 132 + $where[] = qsprintf( 133 + $conn, 134 + 'priority <= %d', 135 + $this->maxPriority); 101 136 } 102 137 103 138 return $this->formatWhereClause($conn, $where);