diff --git a/resources/sql/autopatches/20190215.daemons.01.dropdataid.php b/resources/sql/autopatches/20190215.daemons.01.dropdataid.php new file mode 100644 index 0000000000..05cc4adfee --- /dev/null +++ b/resources/sql/autopatches/20190215.daemons.01.dropdataid.php @@ -0,0 +1,21 @@ +<?php + +// See T6615. We're about to change the nullability on the "dataID" column, +// but it may have a UNIQUE KEY on it. Make sure we get rid of this key first +// so we don't run into trouble. + +// There's no "IF EXISTS" modifier for "ALTER TABLE" so run this as a PHP patch +// instead of an SQL patch. + +$table = new PhabricatorWorkerActiveTask(); +$conn = $table->establishConnection('w'); + +try { + queryfx( + $conn, + 'ALTER TABLE %R DROP KEY %T', + $table, + 'dataID'); +} catch (AphrontQueryException $ex) { + // Ignore. +} diff --git a/resources/sql/autopatches/20190215.daemons.02.nulldataid.sql b/resources/sql/autopatches/20190215.daemons.02.nulldataid.sql new file mode 100644 index 0000000000..19be602efe --- /dev/null +++ b/resources/sql/autopatches/20190215.daemons.02.nulldataid.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_worker.worker_activetask + CHANGE dataID dataID INT UNSIGNED NOT NULL; diff --git a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php index ed1eeaea63..b6bd462df7 100644 --- a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php +++ b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php @@ -1,228 +1,214 @@ <?php final class PhabricatorWorkerActiveTask extends PhabricatorWorkerTask { protected $failureTime; private $serverTime; private $localTime; protected function getConfiguration() { $parent = parent::getConfiguration(); $config = array( self::CONFIG_IDS => self::IDS_COUNTER, self::CONFIG_TIMESTAMPS => false, self::CONFIG_KEY_SCHEMA => array( - 'dataID' => array( - 'columns' => array('dataID'), - 'unique' => true, - ), 'taskClass' => array( 'columns' => array('taskClass'), ), 'leaseExpires' => array( 'columns' => array('leaseExpires'), ), - 'leaseOwner' => array( - 'columns' => array('leaseOwner(16)'), - ), 'key_failuretime' => array( 'columns' => array('failureTime'), ), - 'leaseOwner_2' => array( + 'key_owner' => array( 'columns' => array('leaseOwner', 'priority', 'id'), ), ) + $parent[self::CONFIG_KEY_SCHEMA], ); - $config[self::CONFIG_COLUMN_SCHEMA] = array( - // T6203/NULLABILITY - // This isn't nullable in the archive table, so at a minimum these - // should be the same. - 'dataID' => 'uint32?', - ) + $parent[self::CONFIG_COLUMN_SCHEMA]; - return $config + $parent; } public function setServerTime($server_time) { $this->serverTime = $server_time; $this->localTime = time(); return $this; } public function setLeaseDuration($lease_duration) { $this->checkLease(); $server_lease_expires = $this->serverTime + $lease_duration; $this->setLeaseExpires($server_lease_expires); // NOTE: This is primarily to allow unit tests to set negative lease // durations so they don't have to wait around for leases to expire. We // check that the lease is valid above. return $this->forceSaveWithoutLease(); } public function save() { $this->checkLease(); return $this->forceSaveWithoutLease(); } public function forceSaveWithoutLease() { $is_new = !$this->getID(); if ($is_new) { $this->failureCount = 0; } - if ($is_new && ($this->getData() !== null)) { + if ($is_new) { $data = new PhabricatorWorkerTaskData(); $data->setData($this->getData()); $data->save(); $this->setDataID($data->getID()); } return parent::save(); } protected function checkLease() { $owner = $this->leaseOwner; if (!$owner) { return; } if ($owner == PhabricatorWorker::YIELD_OWNER) { return; } $current_server_time = $this->serverTime + (time() - $this->localTime); if ($current_server_time >= $this->leaseExpires) { throw new Exception( pht( 'Trying to update Task %d (%s) after lease expiration!', $this->getID(), $this->getTaskClass())); } } public function delete() { throw new Exception( pht( 'Active tasks can not be deleted directly. '. 'Use %s to move tasks to the archive.', 'archiveTask()')); } public function archiveTask($result, $duration) { if ($this->getID() === null) { throw new Exception( pht("Attempting to archive a task which hasn't been saved!")); } $this->checkLease(); $archive = id(new PhabricatorWorkerArchiveTask()) ->setID($this->getID()) ->setTaskClass($this->getTaskClass()) ->setLeaseOwner($this->getLeaseOwner()) ->setLeaseExpires($this->getLeaseExpires()) ->setFailureCount($this->getFailureCount()) ->setDataID($this->getDataID()) ->setPriority($this->getPriority()) ->setObjectPHID($this->getObjectPHID()) ->setResult($result) ->setDuration($duration); // NOTE: This deletes the active task (this object)! $archive->save(); return $archive; } public function executeTask() { // We do this outside of the try .. catch because we don't have permission // to release the lease otherwise. $this->checkLease(); $did_succeed = false; $worker = null; try { $worker = $this->getWorkerInstance(); $worker->setCurrentWorkerTask($this); $maximum_failures = $worker->getMaximumRetryCount(); if ($maximum_failures !== null) { if ($this->getFailureCount() > $maximum_failures) { throw new PhabricatorWorkerPermanentFailureException( pht( 'Task %d has exceeded the maximum number of failures (%d).', $this->getID(), $maximum_failures)); } } $lease = $worker->getRequiredLeaseTime(); if ($lease !== null) { $this->setLeaseDuration($lease); } $t_start = microtime(true); $worker->executeTask(); $duration = phutil_microseconds_since($t_start); $result = $this->archiveTask( PhabricatorWorkerArchiveTask::RESULT_SUCCESS, $duration); $did_succeed = true; } catch (PhabricatorWorkerPermanentFailureException $ex) { $result = $this->archiveTask( PhabricatorWorkerArchiveTask::RESULT_FAILURE, 0); $result->setExecutionException($ex); } catch (PhabricatorWorkerYieldException $ex) { $this->setExecutionException($ex); $this->setLeaseOwner(PhabricatorWorker::YIELD_OWNER); $retry = $ex->getDuration(); $retry = max($retry, 5); // NOTE: As a side effect, this saves the object. $this->setLeaseDuration($retry); $result = $this; } catch (Exception $ex) { $this->setExecutionException($ex); $this->setFailureCount($this->getFailureCount() + 1); $this->setFailureTime(time()); $retry = null; if ($worker) { $retry = $worker->getWaitBeforeRetry($this); } $retry = coalesce( $retry, PhabricatorWorkerLeaseQuery::getDefaultWaitBeforeRetry()); // NOTE: As a side effect, this saves the object. $this->setLeaseDuration($retry); $result = $this; } // NOTE: If this throws, we don't want it to cause the task to fail again, // so execute it out here and just let the exception escape. if ($did_succeed) { // Default the new task priority to our own priority. $defaults = array( 'priority' => (int)$this->getPriority(), ); $worker->flushTaskQueue($defaults); } return $result; } } diff --git a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerArchiveTask.php b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerArchiveTask.php index fe1164e532..0062d07a84 100644 --- a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerArchiveTask.php +++ b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerArchiveTask.php @@ -1,100 +1,97 @@ <?php final class PhabricatorWorkerArchiveTask extends PhabricatorWorkerTask { const RESULT_SUCCESS = 0; const RESULT_FAILURE = 1; const RESULT_CANCELLED = 2; protected $duration; protected $result; protected function getConfiguration() { $parent = parent::getConfiguration(); $config = array( // We manage the IDs in this table; they are allocated in the ActiveTask // table and moved here without alteration. self::CONFIG_IDS => self::IDS_MANUAL, ) + $parent; $config[self::CONFIG_COLUMN_SCHEMA] = array( 'result' => 'uint32', 'duration' => 'uint64', ) + $config[self::CONFIG_COLUMN_SCHEMA]; $config[self::CONFIG_KEY_SCHEMA] = array( 'dateCreated' => array( 'columns' => array('dateCreated'), ), - 'leaseOwner' => array( - 'columns' => array('leaseOwner', 'priority', 'id'), - ), 'key_modified' => array( 'columns' => array('dateModified'), ), ) + $parent[self::CONFIG_KEY_SCHEMA]; return $config; } public function save() { if ($this->getID() === null) { throw new Exception(pht('Trying to archive a task with no ID.')); } $other = new PhabricatorWorkerActiveTask(); $conn_w = $this->establishConnection('w'); $this->openTransaction(); queryfx( $conn_w, 'DELETE FROM %T WHERE id = %d', $other->getTableName(), $this->getID()); $result = parent::insert(); $this->saveTransaction(); return $result; } 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 unarchiveTask() { $this->openTransaction(); $active = id(new PhabricatorWorkerActiveTask()) ->setID($this->getID()) ->setTaskClass($this->getTaskClass()) ->setLeaseOwner(null) ->setLeaseExpires(0) ->setFailureCount(0) ->setDataID($this->getDataID()) ->setPriority($this->getPriority()) ->setObjectPHID($this->getObjectPHID()) ->insert(); $this->setDataID(null); $this->delete(); $this->saveTransaction(); return $active; } }