Path: blob/master/src/applications/drydock/worker/DrydockWorker.php
12256 views
<?php12abstract class DrydockWorker extends PhabricatorWorker {34protected function getViewer() {5return PhabricatorUser::getOmnipotentUser();6}78protected function loadLease($lease_phid) {9$viewer = $this->getViewer();1011$lease = id(new DrydockLeaseQuery())12->setViewer($viewer)13->withPHIDs(array($lease_phid))14->executeOne();15if (!$lease) {16throw new PhabricatorWorkerPermanentFailureException(17pht('No such lease "%s"!', $lease_phid));18}1920return $lease;21}2223protected function loadResource($resource_phid) {24$viewer = $this->getViewer();2526$resource = id(new DrydockResourceQuery())27->setViewer($viewer)28->withPHIDs(array($resource_phid))29->executeOne();30if (!$resource) {31throw new PhabricatorWorkerPermanentFailureException(32pht('No such resource "%s"!', $resource_phid));33}3435return $resource;36}3738protected function loadOperation($operation_phid) {39$viewer = $this->getViewer();4041$operation = id(new DrydockRepositoryOperationQuery())42->setViewer($viewer)43->withPHIDs(array($operation_phid))44->executeOne();45if (!$operation) {46throw new PhabricatorWorkerPermanentFailureException(47pht('No such operation "%s"!', $operation_phid));48}4950return $operation;51}5253protected function loadCommands($target_phid) {54$viewer = $this->getViewer();5556$commands = id(new DrydockCommandQuery())57->setViewer($viewer)58->withTargetPHIDs(array($target_phid))59->withConsumed(false)60->execute();6162$commands = msort($commands, 'getID');6364return $commands;65}6667protected function checkLeaseExpiration(DrydockLease $lease) {68$this->checkObjectExpiration($lease);69}7071protected function checkResourceExpiration(DrydockResource $resource) {72$this->checkObjectExpiration($resource);73}7475private function checkObjectExpiration($object) {76// Check if the resource or lease has expired. If it has, we're going to77// send it a release command.7879// This command is sent from within the update worker so it is handled80// immediately, but doing this generates a log and improves consistency.8182$expires = $object->getUntil();83if (!$expires) {84return;85}8687$now = PhabricatorTime::getNow();88if ($expires > $now) {89return;90}9192$viewer = $this->getViewer();93$drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();9495$command = DrydockCommand::initializeNewCommand($viewer)96->setTargetPHID($object->getPHID())97->setAuthorPHID($drydock_phid)98->setCommand(DrydockCommand::COMMAND_RELEASE)99->save();100}101102protected function yieldIfExpiringLease(DrydockLease $lease) {103if (!$lease->canReceiveCommands()) {104return;105}106107$this->yieldIfExpiring($lease->getUntil());108}109110protected function yieldIfExpiringResource(DrydockResource $resource) {111if (!$resource->canReceiveCommands()) {112return;113}114115$this->yieldIfExpiring($resource->getUntil());116}117118private function yieldIfExpiring($expires) {119if (!$expires) {120return;121}122123if (!$this->getTaskDataValue('isExpireTask')) {124return;125}126127$now = PhabricatorTime::getNow();128throw new PhabricatorWorkerYieldException($expires - $now);129}130131protected function isTemporaryException(Exception $ex) {132if ($ex instanceof PhabricatorWorkerYieldException) {133return true;134}135136if ($ex instanceof DrydockSlotLockException) {137return true;138}139140if ($ex instanceof PhutilAggregateException) {141$any_temporary = false;142foreach ($ex->getExceptions() as $sub) {143if ($this->isTemporaryException($sub)) {144$any_temporary = true;145break;146}147}148if ($any_temporary) {149return true;150}151}152153if ($ex instanceof PhutilProxyException) {154return $this->isTemporaryException($ex->getPreviousException());155}156157return false;158}159160protected function getYieldDurationFromException(Exception $ex) {161if ($ex instanceof PhabricatorWorkerYieldException) {162return $ex->getDuration();163}164165if ($ex instanceof DrydockSlotLockException) {166return 5;167}168169return 15;170}171172protected function flushDrydockTaskQueue() {173// NOTE: By default, queued tasks are not scheduled if the current task174// fails. This is a good, safe default behavior. For example, it can175// protect us from executing side effect tasks too many times, like176// sending extra email.177178// However, it is not the behavior we want in Drydock, because we queue179// followup tasks after lease and resource failures and want them to180// execute in order to clean things up.181182// At least for now, we just explicitly flush the queue before exiting183// with a failure to make sure tasks get queued up properly.184try {185$this->flushTaskQueue();186} catch (Exception $ex) {187// If this fails, we want to swallow the exception so the caller throws188// the original error, since we're more likely to be able to understand189// and fix the problem if we have the original error than if we replace190// it with this one.191phlog($ex);192}193194return $this;195}196197protected function canReclaimResource(DrydockResource $resource) {198$viewer = $this->getViewer();199200// Don't reclaim a resource if it has been updated recently. If two201// leases are fighting, we don't want them to keep reclaiming resources202// from one another forever without making progress, so make resources203// immune to reclamation for a little while after they activate or update.204205$now = PhabricatorTime::getNow();206$max_epoch = ($now - phutil_units('3 minutes in seconds'));207208// TODO: It would be nice to use a more narrow time here, like "last209// activation or lease release", but we don't currently store that210// anywhere.211212$updated = $resource->getDateModified();213if ($updated > $max_epoch) {214return false;215}216217$statuses = array(218DrydockLeaseStatus::STATUS_PENDING,219DrydockLeaseStatus::STATUS_ACQUIRED,220DrydockLeaseStatus::STATUS_ACTIVE,221DrydockLeaseStatus::STATUS_RELEASED,222DrydockLeaseStatus::STATUS_BROKEN,223);224225// Don't reclaim resources that have any active leases.226$leases = id(new DrydockLeaseQuery())227->setViewer($viewer)228->withResourcePHIDs(array($resource->getPHID()))229->withStatuses($statuses)230->setLimit(1)231->execute();232if ($leases) {233return false;234}235236// See T13676. Don't reclaim a resource if a lease recently released.237$leases = id(new DrydockLeaseQuery())238->setViewer($viewer)239->withResourcePHIDs(array($resource->getPHID()))240->withStatuses(241array(242DrydockLeaseStatus::STATUS_DESTROYED,243))244->withDateModifiedBetween($max_epoch, null)245->setLimit(1)246->execute();247if ($leases) {248return false;249}250251return true;252}253254protected function reclaimResource(255DrydockResource $resource,256DrydockLease $lease) {257$viewer = $this->getViewer();258259// Mark the lease as reclaiming this resource. It won't be allowed to start260// another reclaim as long as this resource is still in the process of261// being reclaimed.262$lease->addReclaimedResourcePHIDs(array($resource->getPHID()));263264// When the resource releases, we we want to reawaken this task since it265// should (usually) be able to start building a new resource right away.266$worker_task_id = $this->getCurrentWorkerTaskID();267268$command = DrydockCommand::initializeNewCommand($viewer)269->setTargetPHID($resource->getPHID())270->setAuthorPHID($lease->getPHID())271->setCommand(DrydockCommand::COMMAND_RECLAIM)272->setProperty('awakenTaskIDs', array($worker_task_id));273274$lease->openTransaction();275$lease->save();276$command->save();277$lease->saveTransaction();278279$resource->scheduleUpdate();280281return $this;282}283284}285286287