@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.

Move task leasing to a dedicated query

Summary: This simplifies the fairly thorny logic of leasing tasks a bit. I'm planning to introduce another callsite shortly for Drydock.

Test Plan: Ran `bin/phd debug taskmaster`, observed sensible queries and correct operation.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T2015

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

+161 -59
+2
src/__phutil_library_map__.php
··· 1143 1143 'PhabricatorWorkerActiveTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php', 1144 1144 'PhabricatorWorkerArchiveTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerArchiveTask.php', 1145 1145 'PhabricatorWorkerDAO' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerDAO.php', 1146 + 'PhabricatorWorkerLeaseQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php', 1146 1147 'PhabricatorWorkerTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTask.php', 1147 1148 'PhabricatorWorkerTaskData' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTaskData.php', 1148 1149 'PhabricatorWorkerTaskDetailController' => 'applications/daemon/controller/PhabricatorWorkerTaskDetailController.php', ··· 2305 2306 'PhabricatorWorkerActiveTask' => 'PhabricatorWorkerTask', 2306 2307 'PhabricatorWorkerArchiveTask' => 'PhabricatorWorkerTask', 2307 2308 'PhabricatorWorkerDAO' => 'PhabricatorLiskDAO', 2309 + 'PhabricatorWorkerLeaseQuery' => 'PhabricatorQuery', 2308 2310 'PhabricatorWorkerTask' => 'PhabricatorWorkerDAO', 2309 2311 'PhabricatorWorkerTaskData' => 'PhabricatorWorkerDAO', 2310 2312 'PhabricatorWorkerTaskDetailController' => 'PhabricatorDaemonController',
+5 -59
src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php
··· 19 19 final class PhabricatorTaskmasterDaemon extends PhabricatorDaemon { 20 20 21 21 public function run() { 22 - $lease_ownership_name = $this->getLeaseOwnershipName(); 23 - 24 - $task_table = new PhabricatorWorkerActiveTask(); 25 - $taskdata_table = new PhabricatorWorkerTaskData(); 26 - 27 22 $sleep = 0; 28 23 do { 29 - $this->log('Dequeuing a task...'); 30 - 31 - $conn_w = $task_table->establishConnection('w'); 32 - queryfx( 33 - $conn_w, 34 - 'UPDATE %T SET leaseOwner = %s, leaseExpires = UNIX_TIMESTAMP() + 15 35 - WHERE leaseOwner IS NULL LIMIT 1', 36 - $task_table->getTableName(), 37 - $lease_ownership_name); 38 - $rows = $conn_w->getAffectedRows(); 39 - 40 - if (!$rows) { 41 - $this->log('No unleased tasks. Dequeuing an expired lease...'); 42 - queryfx( 43 - $conn_w, 44 - 'UPDATE %T SET leaseOwner = %s, leaseExpires = UNIX_TIMESTAMP() + 15 45 - WHERE leaseExpires < UNIX_TIMESTAMP() LIMIT 1', 46 - $task_table->getTableName(), 47 - $lease_ownership_name); 48 - $rows = $conn_w->getAffectedRows(); 49 - } 50 - 51 - if ($rows) { 52 - $data = queryfx_all( 53 - $conn_w, 54 - 'SELECT task.*, taskdata.data _taskData, UNIX_TIMESTAMP() _serverTime 55 - FROM %T task LEFT JOIN %T taskdata 56 - ON taskdata.id = task.dataID 57 - WHERE leaseOwner = %s AND leaseExpires > UNIX_TIMESTAMP() 58 - LIMIT 1', 59 - $task_table->getTableName(), 60 - $taskdata_table->getTableName(), 61 - $lease_ownership_name); 62 - $tasks = $task_table->loadAllFromArray($data); 63 - $tasks = mpull($tasks, null, 'getID'); 64 - 65 - $task_data = array(); 66 - foreach ($data as $row) { 67 - $tasks[$row['id']]->setServerTime($row['_serverTime']); 68 - if ($row['_taskData']) { 69 - $task_data[$row['id']] = json_decode($row['_taskData'], true); 70 - } else { 71 - $task_data[$row['id']] = null; 72 - } 73 - } 24 + $tasks = id(new PhabricatorWorkerLeaseQuery()) 25 + ->setLimit(1) 26 + ->execute(); 74 27 28 + if ($tasks) { 75 29 foreach ($tasks as $task) { 76 30 $id = $task->getID(); 77 31 $class = $task->getTaskClass(); ··· 84 38 // TODO: We should detect if we acquired a task with an excessive 85 39 // failure count and fail it permanently. 86 40 87 - $data = idx($task_data, $task->getID()); 41 + $data = $task->getData(); 88 42 try { 89 43 if (!class_exists($class) || 90 44 !is_subclass_of($class, 'PhabricatorWorker')) { ··· 122 76 123 77 $this->sleep($sleep); 124 78 } while (true); 125 - } 126 - 127 - private function getLeaseOwnershipName() { 128 - static $name = null; 129 - if ($name === null) { 130 - $name = getmypid().':'.time().':'.php_uname('n'); 131 - } 132 - return $name; 133 79 } 134 80 135 81 }
+154
src/infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2012 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + /** 20 + * Select and lease tasks from the worker task queue. 21 + * 22 + * @group worker 23 + */ 24 + final class PhabricatorWorkerLeaseQuery extends PhabricatorQuery { 25 + 26 + const PHASE_UNLEASED = 'unleased'; 27 + const PHASE_EXPIRED = 'expired'; 28 + 29 + private $ids; 30 + private $limit; 31 + 32 + public function withIDs(array $ids) { 33 + $this->ids = $ids; 34 + return $this; 35 + } 36 + 37 + public function setLimit($limit) { 38 + $this->limit = $limit; 39 + return $this; 40 + } 41 + 42 + public function execute() { 43 + if (!$this->limit) { 44 + throw new Exception("You must setLimit() when leasing tasks."); 45 + } 46 + 47 + $task_table = new PhabricatorWorkerActiveTask(); 48 + $taskdata_table = new PhabricatorWorkerTaskData(); 49 + $lease_ownership_name = $this->getLeaseOwnershipName(); 50 + 51 + $conn_w = $task_table->establishConnection('w'); 52 + 53 + // Try to satisfy the request from new, unleased tasks first. If we don't 54 + // find enough tasks, try tasks with expired leases (i.e., tasks which have 55 + // previously failed). 56 + 57 + $phases = array( 58 + self::PHASE_UNLEASED, 59 + self::PHASE_EXPIRED, 60 + ); 61 + $limit = $this->limit; 62 + 63 + $leased = 0; 64 + foreach ($phases as $phase) { 65 + queryfx( 66 + $conn_w, 67 + 'UPDATE %T SET leaseOwner = %s, leaseExpires = UNIX_TIMESTAMP() + 15 68 + %Q %Q %Q', 69 + $task_table->getTableName(), 70 + $lease_ownership_name, 71 + $this->buildWhereClause($conn_w, $phase), 72 + $this->buildOrderClause($conn_w), 73 + $this->buildLimitClause($conn_w, $limit - $leased)); 74 + 75 + $leased += $conn_w->getAffectedRows(); 76 + if ($leased == $limit) { 77 + break; 78 + } 79 + } 80 + 81 + if (!$leased) { 82 + return array(); 83 + } 84 + 85 + $data = queryfx_all( 86 + $conn_w, 87 + 'SELECT task.*, taskdata.data _taskData, UNIX_TIMESTAMP() _serverTime 88 + FROM %T task LEFT JOIN %T taskdata 89 + ON taskdata.id = task.dataID 90 + WHERE leaseOwner = %s AND leaseExpires > UNIX_TIMESTAMP() 91 + %Q %Q', 92 + $task_table->getTableName(), 93 + $taskdata_table->getTableName(), 94 + $lease_ownership_name, 95 + $this->buildOrderClause($conn_w), 96 + $this->buildLimitClause($conn_w, $limit)); 97 + 98 + $tasks = $task_table->loadAllFromArray($data); 99 + $tasks = mpull($tasks, null, 'getID'); 100 + 101 + foreach ($data as $row) { 102 + $tasks[$row['id']]->setServerTime($row['_serverTime']); 103 + if ($row['_taskData']) { 104 + $task_data = json_decode($row['_taskData'], true); 105 + } else { 106 + $task_data = null; 107 + } 108 + $tasks[$row['id']]->setData($task_data); 109 + } 110 + 111 + return $tasks; 112 + } 113 + 114 + private function buildWhereClause(AphrontDatabaseConnection $conn_w, $phase) { 115 + $where = array(); 116 + 117 + switch ($phase) { 118 + case self::PHASE_UNLEASED: 119 + $where[] = 'leaseOwner IS NULL'; 120 + break; 121 + case self::PHASE_Expired: 122 + $where[] = 'leaseExpires < UNIX_TIMESTAMP()'; 123 + break; 124 + default: 125 + throw new Exception("Unknown phase '{$phase}'!"); 126 + } 127 + 128 + if ($this->ids) { 129 + $where[] = qsprintf( 130 + $conn_w, 131 + 'id IN (%Ld)', 132 + $this->ids); 133 + } 134 + 135 + return $this->formatWhereClause($where); 136 + } 137 + 138 + private function buildOrderClause(AphrontDatabaseConnection $conn_w) { 139 + return qsprintf($conn_w, 'ORDER BY id ASC'); 140 + } 141 + 142 + private function buildLimitClause(AphrontDatabaseConnection $conn_w, $limit) { 143 + return qsprintf($conn_w, 'LIMIT %d', $limit); 144 + } 145 + 146 + private function getLeaseOwnershipName() { 147 + static $name = null; 148 + if ($name === null) { 149 + $name = getmypid().':'.time().':'.php_uname('n'); 150 + } 151 + return $name; 152 + } 153 + 154 + }