Skip to content

Commit 9a679bf

Browse files
committed
Allow worker tasks to have priorities
Summary: Fixes T5336. Currently, `PhabricatorWorkerLeaseQuery` is basically FIFO. It makes more sense for the queue to be a priority-queue, and to assign higher priorities to alerts (email and SMS). Test Plan: Created dummy tasks in the queue (with different priorities). Verified that the priority field was set correctly in the DB and that the priority was shown on the `/daemon/` page. Started a `PhabricatorTaskmasterDaemon` and verified that the higher priority tasks were executed before lower priority tasks. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: epriestley, Korvin Maniphest Tasks: T5336 Differential Revision: https://secure.phabricator.com/D9871
1 parent 66a3abe commit 9a679bf

15 files changed

+131
-91
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
ALTER TABLE {$NAMESPACE}_worker.worker_activetask
2+
ADD COLUMN priority int unsigned NOT NULL;
3+
4+
ALTER TABLE {$NAMESPACE}_worker.worker_activetask
5+
ADD KEY (leaseOwner, priority, id);
6+
7+
ALTER TABLE {$NAMESPACE}_worker.worker_archivetask
8+
ADD COLUMN priority int unsigned NOT NULL;
9+
10+
ALTER TABLE {$NAMESPACE}_worker.worker_archivetask
11+
ADD KEY (leaseOwner, priority, id);

scripts/repository/reparse.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,10 @@
267267

268268
if ($all_from_repo && !$force_local) {
269269
foreach ($classes as $class) {
270-
PhabricatorWorker::scheduleTask($class, $spec);
270+
PhabricatorWorker::scheduleTask(
271+
$class,
272+
$spec,
273+
PhabricatorWorker::PRIORITY_IMPORT);
271274

272275
$commit_name = 'r'.$callsign.$commit->getCommitIdentifier();
273276
echo " Queued '{$class}' for commit '{$commit_name}'.\n";

src/applications/daemon/controller/PhabricatorDaemonConsoleController.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ public function processRequest() {
136136
$task->getTaskClass(),
137137
$task->getLeaseOwner(),
138138
$task->getLeaseExpires() - time(),
139+
$task->getPriority(),
139140
$task->getFailureCount(),
140141
phutil_tag(
141142
'a',
@@ -158,6 +159,7 @@ public function processRequest() {
158159
pht('Class'),
159160
pht('Owner'),
160161
pht('Expires'),
162+
pht('Priority'),
161163
pht('Failures'),
162164
'',
163165
));
@@ -168,6 +170,7 @@ public function processRequest() {
168170
'',
169171
'',
170172
'n',
173+
'n',
171174
'action',
172175
));
173176
$leased_table->setNoDataString(pht('No tasks are leased by workers.'));

src/applications/diffusion/engine/DiffusionCommitHookEngine.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,8 @@ public function execute() {
187187
'eventPHID' => $event->getPHID(),
188188
'emailPHIDs' => array_values($this->emailPHIDs),
189189
'info' => $this->loadCommitInfoForWorker($all_updates),
190-
));
190+
),
191+
PhabricatorWorker::PRIORITY_ALERTS);
191192
}
192193

193194
return 0;

src/applications/metamta/management/PhabricatorMailManagementResendWorkflow.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ public function execute(PhutilArgumentParser $args) {
4949

5050
$mailer_task = PhabricatorWorker::scheduleTask(
5151
'PhabricatorMetaMTAWorker',
52-
$message->getID());
52+
$message->getID(),
53+
PhabricatorWorker::PRIORITY_ALERTS);
5354

5455
$console->writeOut(
5556
"Queued message #%d for resend.\n",

src/applications/metamta/storage/PhabricatorMetaMTAMail.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,8 @@ public function save() {
300300
// Queue a task to send this mail.
301301
$mailer_task = PhabricatorWorker::scheduleTask(
302302
'PhabricatorMetaMTAWorker',
303-
$this->getID());
303+
$this->getID(),
304+
PhabricatorWorker::PRIORITY_ALERTS);
304305

305306
$this->saveTransaction();
306307

src/applications/search/index/PhabricatorSearchIndexer.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ public function queueDocumentForIndexing($phid) {
77
'PhabricatorSearchWorker',
88
array(
99
'documentPHID' => $phid,
10-
));
10+
),
11+
PhabricatorWorker::PRIORITY_IMPORT);
1112
}
1213

1314
public function indexDocumentByPHID($phid) {

src/infrastructure/daemon/workers/PhabricatorWorker.php

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ abstract class PhabricatorWorker {
99
private static $runAllTasksInProcess = false;
1010
private $queuedTasks = array();
1111

12+
const PRIORITY_ALERTS = 4000;
13+
const PRIORITY_DEFAULT = 3000;
14+
const PRIORITY_BULK = 2000;
15+
const PRIORITY_IMPORT = 1000;
16+
1217

1318
/* -( Configuring Retries and Failures )----------------------------------- */
1419

@@ -32,13 +37,13 @@ public function getRequiredLeaseTime() {
3237

3338

3439
/**
35-
* Return the maximum number of times this task may be retried before it
36-
* is considered permanently failed. By default, tasks retry indefinitely. You
40+
* Return the maximum number of times this task may be retried before it is
41+
* considered permanently failed. By default, tasks retry indefinitely. You
3742
* can throw a @{class:PhabricatorWorkerPermanentFailureException} to cause an
3843
* immediate permanent failure.
3944
*
40-
* @return int|null Number of times the task will retry before permanent
41-
* failure. Return `null` to retry indefinitely.
45+
* @return int|null Number of times the task will retry before permanent
46+
* failure. Return `null` to retry indefinitely.
4247
*
4348
* @task config
4449
*/
@@ -52,15 +57,15 @@ public function getMaximumRetryCount() {
5257
* retrying. For most tasks you can leave this at `null`, which will give you
5358
* a short default retry period (currently 60 seconds).
5459
*
55-
* @param PhabricatorWorkerTask The task itself. This object is probably
56-
* useful mostly to examine the failure
57-
* count if you want to implement staggered
58-
* retries, or to examine the execution
59-
* exception if you want to react to
60-
* different failures in different ways.
61-
* @return int|null Number of seconds to wait between retries,
62-
* or null for a default retry period
63-
* (currently 60 seconds).
60+
* @param PhabricatorWorkerTask The task itself. This object is probably
61+
* useful mostly to examine the failure count
62+
* if you want to implement staggered retries,
63+
* or to examine the execution exception if
64+
* you want to react to different failures in
65+
* different ways.
66+
* @return int|null Number of seconds to wait between retries,
67+
* or null for a default retry period
68+
* (currently 60 seconds).
6469
*
6570
* @task config
6671
*/
@@ -70,7 +75,6 @@ public function getWaitBeforeRetry(PhabricatorWorkerTask $task) {
7075

7176
abstract protected function doWork();
7277

73-
7478
final public function __construct($data) {
7579
$this->data = $data;
7680
}
@@ -83,10 +87,19 @@ final public function executeTask() {
8387
$this->doWork();
8488
}
8589

86-
final public static function scheduleTask($task_class, $data) {
90+
final public static function scheduleTask(
91+
$task_class,
92+
$data,
93+
$priority = null) {
94+
95+
if ($priority === null) {
96+
$priority = self::PRIORITY_DEFAULT;
97+
}
98+
8799
$task = id(new PhabricatorWorkerActiveTask())
88100
->setTaskClass($task_class)
89-
->setData($data);
101+
->setData($data)
102+
->setPriority($priority);
90103

91104
if (self::$runAllTasksInProcess) {
92105
// Do the work in-process.
@@ -96,8 +109,8 @@ final public static function scheduleTask($task_class, $data) {
96109
try {
97110
$worker->doWork();
98111
foreach ($worker->getQueuedTasks() as $queued_task) {
99-
list($queued_class, $queued_data) = $queued_task;
100-
self::scheduleTask($queued_class, $queued_data);
112+
list($queued_class, $queued_data, $queued_priority) = $queued_task;
113+
self::scheduleTask($queued_class, $queued_data, $queued_priority);
101114
}
102115
break;
103116
} catch (PhabricatorWorkerYieldException $ex) {
@@ -106,7 +119,6 @@ final public static function scheduleTask($task_class, $data) {
106119
'In-process task "%s" yielded for %s seconds, sleeping...',
107120
$task_class,
108121
$ex->getDuration()));
109-
110122
sleep($ex->getDuration());
111123
}
112124
}
@@ -184,8 +196,7 @@ final public static function waitForTasks(array $task_ids) {
184196

185197
foreach ($tasks as $task) {
186198
if ($task->getResult() != PhabricatorWorkerArchiveTask::RESULT_SUCCESS) {
187-
throw new Exception(
188-
pht('Task %d failed!', $task->getID()));
199+
throw new Exception(pht('Task %d failed!', $task->getID()));
189200
}
190201
}
191202
}
@@ -204,7 +215,7 @@ public static function setRunAllTasksInProcess($all) {
204215
self::$runAllTasksInProcess = $all;
205216
}
206217

207-
protected function log($pattern /* $args */) {
218+
final protected function log($pattern /* , ... */) {
208219
$console = PhutilConsole::getConsole();
209220
$argv = func_get_args();
210221
call_user_func_array(array($console, 'writeLog'), $argv);
@@ -217,22 +228,23 @@ protected function log($pattern /* $args */) {
217228
*
218229
* The followup task will be queued only if this task completes cleanly.
219230
*
220-
* @param string Task class to queue.
221-
* @param array Data for the followup task.
231+
* @param string Task class to queue.
232+
* @param array Data for the followup task.
233+
* @param int|null Priority for the followup task.
222234
* @return this
223235
*/
224-
protected function queueTask($class, array $data) {
225-
$this->queuedTasks[] = array($class, $data);
236+
final protected function queueTask($class, array $data, $priority = null) {
237+
$this->queuedTasks[] = array($class, $data, $priority);
226238
return $this;
227239
}
228240

229241

230242
/**
231243
* Get tasks queued as followups by @{method:queueTask}.
232244
*
233-
* @return list<pair<string, wild>> Queued task specifications.
245+
* @return list<tuple<string, wild, int|null>> Queued task specifications.
234246
*/
235-
public function getQueuedTasks() {
247+
final public function getQueuedTasks() {
236248
return $this->queuedTasks;
237249
}
238250

src/infrastructure/daemon/workers/__tests__/PhabricatorTestWorker.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ public function getWaitBeforeRetry(PhabricatorWorkerTask $task) {
2626
protected function doWork() {
2727
switch (idx($this->getTaskData(), 'doWork')) {
2828
case 'fail-temporary':
29-
throw new Exception(
30-
'Temporary failure!');
29+
throw new Exception('Temporary failure!');
3130
case 'fail-permanent':
3231
throw new PhabricatorWorkerPermanentFailureException(
3332
'Permanent failure!');

src/infrastructure/daemon/workers/__tests__/PhabricatorWorkerTestCase.php

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,43 +9,41 @@ protected function getPhabricatorTestCaseConfiguration() {
99
}
1010

1111
public function testLeaseTask() {
12-
// Leasing should work.
13-
1412
$task = $this->scheduleTask();
15-
16-
$this->expectNextLease($task);
13+
$this->expectNextLease($task, 'Leasing should work.');
1714
}
1815

1916
public function testMultipleLease() {
20-
// We should not be able to lease a task multiple times.
21-
2217
$task = $this->scheduleTask();
2318

2419
$this->expectNextLease($task);
25-
$this->expectNextLease(null);
20+
$this->expectNextLease(
21+
null,
22+
'We should not be able to lease a task multiple times.');
2623
}
2724

2825
public function testOldestFirst() {
29-
// Older tasks should lease first, all else being equal.
30-
3126
$task1 = $this->scheduleTask();
3227
$task2 = $this->scheduleTask();
3328

34-
$this->expectNextLease($task1);
29+
$this->expectNextLease(
30+
$task1,
31+
'Older tasks should lease first, all else being equal.');
3532
$this->expectNextLease($task2);
3633
}
3734

3835
public function testNewBeforeLeased() {
39-
// Tasks not previously leased should lease before previously leased tasks.
40-
4136
$task1 = $this->scheduleTask();
4237
$task2 = $this->scheduleTask();
4338

4439
$task1->setLeaseOwner('test');
4540
$task1->setLeaseExpires(time() - 100000);
4641
$task1->forceSaveWithoutLease();
4742

48-
$this->expectNextLease($task2);
43+
$this->expectNextLease(
44+
$task2,
45+
'Tasks not previously leased should lease before previously '.
46+
'leased tasks.');
4947
$this->expectNextLease($task1);
5048
}
5149

@@ -138,15 +136,13 @@ public function testWaitBeforeRetry() {
138136
public function testRequiredLeaseTime() {
139137
$task = $this->scheduleAndExecuteTask(
140138
array(
141-
'getRequiredLeaseTime' => 1000000,
139+
'getRequiredLeaseTime' => 1000000,
142140
));
143141

144142
$this->assertTrue(($task->getLeaseExpires() - time()) > 1000);
145143
}
146144

147145
public function testLeasedIsOldestFirst() {
148-
// Tasks which expired earlier should lease first, all else being equal.
149-
150146
$task1 = $this->scheduleTask();
151147
$task2 = $this->scheduleTask();
152148

@@ -158,36 +154,59 @@ public function testLeasedIsOldestFirst() {
158154
$task2->setLeaseExpires(time() - 200000);
159155
$task2->forceSaveWithoutLease();
160156

161-
$this->expectNextLease($task2);
157+
$this->expectNextLease(
158+
$task2,
159+
'Tasks which expired earlier should lease first, all else being equal.');
162160
$this->expectNextLease($task1);
163161
}
164162

165-
private function expectNextLease($task) {
163+
public function testLeasedIsHighestPriority() {
164+
$task1 = $this->scheduleTask(array(), 2);
165+
$task2 = $this->scheduleTask(array(), 1);
166+
$task3 = $this->scheduleTask(array(), 1);
167+
168+
$this->expectNextLease(
169+
$task1,
170+
'Tasks with a higher priority should be scheduled first.');
171+
$this->expectNextLease(
172+
$task2,
173+
'Tasks with the same priority should be FIFO.');
174+
$this->expectNextLease($task3);
175+
}
176+
177+
private function expectNextLease($task, $message = null) {
166178
$leased = id(new PhabricatorWorkerLeaseQuery())
167179
->setLimit(1)
168180
->execute();
169181

170182
if ($task === null) {
171-
$this->assertEqual(0, count($leased));
183+
$this->assertEqual(0, count($leased), $message);
172184
return null;
173185
} else {
174-
$this->assertEqual(1, count($leased));
186+
$this->assertEqual(1, count($leased), $message);
175187
$this->assertEqual(
176188
(int)head($leased)->getID(),
177-
(int)$task->getID());
189+
(int)$task->getID(),
190+
$message);
178191
return head($leased);
179192
}
180193
}
181194

182-
private function scheduleAndExecuteTask(array $data = array()) {
183-
$task = $this->scheduleTask($data);
195+
private function scheduleAndExecuteTask(
196+
array $data = array(),
197+
$priority = null) {
198+
199+
$task = $this->scheduleTask($data, $priority);
184200
$task = $this->expectNextLease($task);
185201
$task = $task->executeTask();
186202
return $task;
187203
}
188204

189-
private function scheduleTask(array $data = array()) {
190-
return PhabricatorWorker::scheduleTask('PhabricatorTestWorker', $data);
205+
private function scheduleTask(array $data = array(), $priority = null) {
206+
return PhabricatorWorker::scheduleTask(
207+
'PhabricatorTestWorker',
208+
$data,
209+
$priority);
191210
}
192211

193212
}

0 commit comments

Comments
 (0)