public function execute() { if (!$this->limit) { throw new Exception('You must setLimit() when leasing tasks.'); } $task_table = new PhabricatorWorkerActiveTask(); $taskdata_table = new PhabricatorWorkerTaskData(); $lease_ownership_name = $this->getLeaseOwnershipName(); $conn_w = $task_table->establishConnection('w'); // Try to satisfy the request from new, unleased tasks first. If we don't // find enough tasks, try tasks with expired leases (i.e., tasks which have // previously failed). $phases = array(self::PHASE_UNLEASED, self::PHASE_EXPIRED); $limit = $this->limit; $leased = 0; foreach ($phases as $phase) { // NOTE: If we issue `UPDATE ... WHERE ... ORDER BY id ASC`, the query // goes very, very slowly. The `ORDER BY` triggers this, although we get // the same apparent results without it. Without the ORDER BY, binary // read slaves complain that the query isn't repeatable. To avoid both // problems, do a SELECT and then an UPDATE. $rows = queryfx_all($conn_w, 'SELECT id, leaseOwner FROM %T %Q %Q %Q', $task_table->getTableName(), $this->buildWhereClause($conn_w, $phase), $this->buildOrderClause($conn_w, $phase), $this->buildLimitClause($conn_w, $limit - $leased)); // NOTE: Sometimes, we'll race with another worker and they'll grab // this task before we do. We could reduce how often this happens by // selecting more tasks than we need, then shuffling them and trying // to lock only the number we're actually after. However, the amount // of time workers spend here should be very small relative to their // total runtime, so keep it simple for the moment. if ($rows) { queryfx($conn_w, 'UPDATE %T task SET leaseOwner = %s, leaseExpires = UNIX_TIMESTAMP() + %d %Q', $task_table->getTableName(), $lease_ownership_name, self::getDefaultLeaseDuration(), $this->buildUpdateWhereClause($conn_w, $phase, $rows)); $leased += $conn_w->getAffectedRows(); if ($leased == $limit) { break; } } } if (!$leased) { return array(); } $data = queryfx_all($conn_w, 'SELECT task.*, taskdata.data _taskData, UNIX_TIMESTAMP() _serverTime FROM %T task LEFT JOIN %T taskdata ON taskdata.id = task.dataID WHERE leaseOwner = %s AND leaseExpires > UNIX_TIMESTAMP() %Q %Q', $task_table->getTableName(), $taskdata_table->getTableName(), $lease_ownership_name, $this->buildOrderClause($conn_w, $phase), $this->buildLimitClause($conn_w, $limit)); $tasks = $task_table->loadAllFromArray($data); $tasks = mpull($tasks, null, 'getID'); foreach ($data as $row) { $tasks[$row['id']]->setServerTime($row['_serverTime']); if ($row['_taskData']) { $task_data = json_decode($row['_taskData'], true); } else { $task_data = null; } $tasks[$row['id']]->setData($task_data); } return $tasks; }
public function delete() { $this->openTransaction(); if ($this->getDataID()) { $conn_w = $this->establishConnection('w'); $data_table = new PhabricatorWorkerTaskData(); queryfx($conn_w, 'DELETE FROM %T WHERE id = %d', $data_table->getTableName(), $this->getDataID()); } $result = parent::delete(); $this->saveTransaction(); return $result; }
public function forceSaveWithoutLease() { $is_new = !$this->getID(); if ($is_new) { $this->failureCount = 0; } if ($is_new && $this->getData() !== null) { $data = new PhabricatorWorkerTaskData(); $data->setData($this->getData()); $data->save(); $this->setDataID($data->getID()); } return parent::save(); }
protected function collectGarbage() { $table = new PhabricatorWorkerArchiveTask(); $data_table = new PhabricatorWorkerTaskData(); $conn_w = $table->establishConnection('w'); $tasks = id(new PhabricatorWorkerArchiveTaskQuery())->withDateCreatedBefore($this->getGarbageEpoch())->setLimit(100)->execute(); if (!$tasks) { return false; } $data_ids = array_filter(mpull($tasks, 'getDataID')); $task_ids = mpull($tasks, 'getID'); $table->openTransaction(); if ($data_ids) { queryfx($conn_w, 'DELETE FROM %T WHERE id IN (%Ld)', $data_table->getTableName(), $data_ids); } queryfx($conn_w, 'DELETE FROM %T WHERE id IN (%Ld)', $table->getTableName(), $task_ids); $table->saveTransaction(); return count($task_ids) == 100; }
public function save() { if ($this->leaseOwner) { $current_server_time = $this->serverTime + (time() - $this->localTime); if ($current_server_time >= $this->leaseExpires) { throw new Exception("Trying to update task after lease expiration!"); } } $is_new = !$this->getID(); if ($is_new) { $this->failureCount = 0; } if ($is_new && $this->data) { $data = new PhabricatorWorkerTaskData(); $data->setData($this->data); $data->save(); $this->setDataID($data->getID()); } return parent::save(); }
public function collectGarbage() { $key = 'gcdaemon.ttl.task-archive'; $ttl = PhabricatorEnv::getEnvConfig($key); if ($ttl <= 0) { return false; } $table = new PhabricatorWorkerArchiveTask(); $data_table = new PhabricatorWorkerTaskData(); $conn_w = $table->establishConnection('w'); $rows = queryfx_all($conn_w, 'SELECT id, dataID FROM %T WHERE dateCreated < %d LIMIT 100', $table->getTableName(), time() - $ttl); if (!$rows) { return false; } $data_ids = array_filter(ipull($rows, 'dataID')); $task_ids = ipull($rows, 'id'); $table->openTransaction(); if ($data_ids) { queryfx($conn_w, 'DELETE FROM %T WHERE id IN (%Ld)', $data_table->getTableName(), $data_ids); } queryfx($conn_w, 'DELETE FROM %T WHERE id IN (%Ld)', $table->getTableName(), $task_ids); $table->saveTransaction(); return count($task_ids) == 100; }
public function collectGarbage() { $key = 'gcdaemon.ttl.task-archive'; $ttl = PhabricatorEnv::getEnvConfig($key); if ($ttl <= 0) { return false; } $table = new PhabricatorWorkerArchiveTask(); $data_table = new PhabricatorWorkerTaskData(); $conn_w = $table->establishConnection('w'); $tasks = id(new PhabricatorWorkerArchiveTaskQuery())->withDateCreatedBefore(time() - $ttl)->execute(); if (!$tasks) { return false; } $data_ids = array_filter(mpull($tasks, 'getDataID')); $task_ids = mpull($tasks, 'getID'); $table->openTransaction(); if ($data_ids) { queryfx($conn_w, 'DELETE FROM %T WHERE id IN (%Ld)', $data_table->getTableName(), $data_ids); } queryfx($conn_w, 'DELETE FROM %T WHERE id IN (%Ld)', $table->getTableName(), $task_ids); $table->saveTransaction(); return count($task_ids) == 100; }
public function run() { $lease_ownership_name = $this->getLeaseOwnershipName(); $task_table = new PhabricatorWorkerTask(); $taskdata_table = new PhabricatorWorkerTaskData(); $sleep = 0; do { $conn_w = $task_table->establishConnection('w'); queryfx($conn_w, 'UPDATE %T SET leaseOwner = %s, leaseExpires = UNIX_TIMESTAMP() + 15 WHERE leaseOwner IS NULL LIMIT 1', $task_table->getTableName(), $lease_ownership_name); $rows = $conn_w->getAffectedRows(); if (!$rows) { $rows = queryfx($conn_w, 'UPDATE %T SET leaseOwner = %s, leaseExpires = UNIX_TIMESTAMP() + 15 WHERE leaseExpires < UNIX_TIMESTAMP() LIMIT 1', $task_table->getTableName(), $lease_ownership_name); $rows = $conn_w->getAffectedRows(); } if ($rows) { $data = queryfx_all($conn_w, 'SELECT task.*, taskdata.data _taskData, UNIX_TIMESTAMP() _serverTime FROM %T task LEFT JOIN %T taskdata ON taskdata.id = task.dataID WHERE leaseOwner = %s AND leaseExpires > UNIX_TIMESTAMP() LIMIT 1', $task_table->getTableName(), $taskdata_table->getTableName(), $lease_ownership_name); $tasks = $task_table->loadAllFromArray($data); $tasks = mpull($tasks, null, 'getID'); $task_data = array(); foreach ($data as $row) { $tasks[$row['id']]->setServerTime($row['_serverTime']); if ($row['_taskData']) { $task_data[$row['id']] = json_decode($row['_taskData'], true); } else { $task_data[$row['id']] = null; } } foreach ($tasks as $task) { // TODO: We should detect if we acquired a task with an expired lease // and log about it / bump up failure count. // TODO: We should detect if we acquired a task with an excessive // failure count and fail it permanently. $data = idx($task_data, $task->getID()); $class = $task->getTaskClass(); try { PhutilSymbolLoader::loadClass($class); if (!is_subclass_of($class, 'PhabricatorWorker')) { throw new Exception("Task class '{$class}' does not extend PhabricatorWorker."); } $worker = newv($class, array($data)); $lease = $worker->getRequiredLeaseTime(); if ($lease !== null) { $task->setLeaseDuration($lease); } $worker->executeTask(); $task->delete(); if ($data !== null) { queryfx($conn_w, 'DELETE FROM %T WHERE id = %d', $taskdata_table->getTableName(), $task->getDataID()); } } catch (Exception $ex) { $task->setFailureCount($task->getFailureCount() + 1); $task->save(); throw $ex; } } $sleep = 0; } else { $sleep = min($sleep + 1, 30); } $this->sleep($sleep); } while (true); }