@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
at upstream/main 285 lines 8.0 kB view raw
1<?php 2 3abstract class DrydockWorker extends PhabricatorWorker { 4 5 protected function getViewer() { 6 return PhabricatorUser::getOmnipotentUser(); 7 } 8 9 protected function loadLease($lease_phid) { 10 $viewer = $this->getViewer(); 11 12 $lease = id(new DrydockLeaseQuery()) 13 ->setViewer($viewer) 14 ->withPHIDs(array($lease_phid)) 15 ->executeOne(); 16 if (!$lease) { 17 throw new PhabricatorWorkerPermanentFailureException( 18 pht('No such lease "%s"!', $lease_phid)); 19 } 20 21 return $lease; 22 } 23 24 protected function loadResource($resource_phid) { 25 $viewer = $this->getViewer(); 26 27 $resource = id(new DrydockResourceQuery()) 28 ->setViewer($viewer) 29 ->withPHIDs(array($resource_phid)) 30 ->executeOne(); 31 if (!$resource) { 32 throw new PhabricatorWorkerPermanentFailureException( 33 pht('No such resource "%s"!', $resource_phid)); 34 } 35 36 return $resource; 37 } 38 39 protected function loadOperation($operation_phid) { 40 $viewer = $this->getViewer(); 41 42 $operation = id(new DrydockRepositoryOperationQuery()) 43 ->setViewer($viewer) 44 ->withPHIDs(array($operation_phid)) 45 ->executeOne(); 46 if (!$operation) { 47 throw new PhabricatorWorkerPermanentFailureException( 48 pht('No such operation "%s"!', $operation_phid)); 49 } 50 51 return $operation; 52 } 53 54 protected function loadCommands($target_phid) { 55 $viewer = $this->getViewer(); 56 57 $commands = id(new DrydockCommandQuery()) 58 ->setViewer($viewer) 59 ->withTargetPHIDs(array($target_phid)) 60 ->withConsumed(false) 61 ->execute(); 62 63 $commands = msort($commands, 'getID'); 64 65 return $commands; 66 } 67 68 protected function checkLeaseExpiration(DrydockLease $lease) { 69 $this->checkObjectExpiration($lease); 70 } 71 72 protected function checkResourceExpiration(DrydockResource $resource) { 73 $this->checkObjectExpiration($resource); 74 } 75 76 private function checkObjectExpiration($object) { 77 // Check if the resource or lease has expired. If it has, we're going to 78 // send it a release command. 79 80 // This command is sent from within the update worker so it is handled 81 // immediately, but doing this generates a log and improves consistency. 82 83 $expires = $object->getUntil(); 84 if (!$expires) { 85 return; 86 } 87 88 $now = PhabricatorTime::getNow(); 89 if ($expires > $now) { 90 return; 91 } 92 93 $viewer = $this->getViewer(); 94 $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); 95 96 $command = DrydockCommand::initializeNewCommand($viewer) 97 ->setTargetPHID($object->getPHID()) 98 ->setAuthorPHID($drydock_phid) 99 ->setCommand(DrydockCommand::COMMAND_RELEASE) 100 ->save(); 101 } 102 103 protected function yieldIfExpiringLease(DrydockLease $lease) { 104 if (!$lease->canReceiveCommands()) { 105 return; 106 } 107 108 $this->yieldIfExpiring($lease->getUntil()); 109 } 110 111 protected function yieldIfExpiringResource(DrydockResource $resource) { 112 if (!$resource->canReceiveCommands()) { 113 return; 114 } 115 116 $this->yieldIfExpiring($resource->getUntil()); 117 } 118 119 private function yieldIfExpiring($expires) { 120 if (!$expires) { 121 return; 122 } 123 124 if (!$this->getTaskDataValue('isExpireTask')) { 125 return; 126 } 127 128 $now = PhabricatorTime::getNow(); 129 throw new PhabricatorWorkerYieldException($expires - $now); 130 } 131 132 protected function isTemporaryException(Exception $ex) { 133 if ($ex instanceof PhabricatorWorkerYieldException) { 134 return true; 135 } 136 137 if ($ex instanceof DrydockSlotLockException) { 138 return true; 139 } 140 141 if ($ex instanceof PhutilAggregateException) { 142 $any_temporary = false; 143 foreach ($ex->getExceptions() as $sub) { 144 if ($this->isTemporaryException($sub)) { 145 $any_temporary = true; 146 break; 147 } 148 } 149 if ($any_temporary) { 150 return true; 151 } 152 } 153 154 if ($ex->getPrevious()) { 155 return $this->isTemporaryException($ex->getPrevious()); 156 } 157 158 return false; 159 } 160 161 protected function getYieldDurationFromException(Exception $ex) { 162 if ($ex instanceof PhabricatorWorkerYieldException) { 163 return $ex->getDuration(); 164 } 165 166 if ($ex instanceof DrydockSlotLockException) { 167 return 5; 168 } 169 170 return 15; 171 } 172 173 protected function flushDrydockTaskQueue() { 174 // NOTE: By default, queued tasks are not scheduled if the current task 175 // fails. This is a good, safe default behavior. For example, it can 176 // protect us from executing side effect tasks too many times, like 177 // sending extra email. 178 179 // However, it is not the behavior we want in Drydock, because we queue 180 // followup tasks after lease and resource failures and want them to 181 // execute in order to clean things up. 182 183 // At least for now, we just explicitly flush the queue before exiting 184 // with a failure to make sure tasks get queued up properly. 185 try { 186 $this->flushTaskQueue(); 187 } catch (Exception $ex) { 188 // If this fails, we want to swallow the exception so the caller throws 189 // the original error, since we're more likely to be able to understand 190 // and fix the problem if we have the original error than if we replace 191 // it with this one. 192 phlog($ex); 193 } 194 195 return $this; 196 } 197 198 protected function canReclaimResource(DrydockResource $resource) { 199 $viewer = $this->getViewer(); 200 201 // Don't reclaim a resource if it has been updated recently. If two 202 // leases are fighting, we don't want them to keep reclaiming resources 203 // from one another forever without making progress, so make resources 204 // immune to reclamation for a little while after they activate or update. 205 206 $now = PhabricatorTime::getNow(); 207 $max_epoch = ($now - phutil_units('3 minutes in seconds')); 208 209 // TODO: It would be nice to use a more narrow time here, like "last 210 // activation or lease release", but we don't currently store that 211 // anywhere. 212 213 $updated = $resource->getDateModified(); 214 if ($updated > $max_epoch) { 215 return false; 216 } 217 218 $statuses = array( 219 DrydockLeaseStatus::STATUS_PENDING, 220 DrydockLeaseStatus::STATUS_ACQUIRED, 221 DrydockLeaseStatus::STATUS_ACTIVE, 222 DrydockLeaseStatus::STATUS_RELEASED, 223 DrydockLeaseStatus::STATUS_BROKEN, 224 ); 225 226 // Don't reclaim resources that have any active leases. 227 $leases = id(new DrydockLeaseQuery()) 228 ->setViewer($viewer) 229 ->withResourcePHIDs(array($resource->getPHID())) 230 ->withStatuses($statuses) 231 ->setLimit(1) 232 ->execute(); 233 if ($leases) { 234 return false; 235 } 236 237 // See T13676. Don't reclaim a resource if a lease recently released. 238 $leases = id(new DrydockLeaseQuery()) 239 ->setViewer($viewer) 240 ->withResourcePHIDs(array($resource->getPHID())) 241 ->withStatuses( 242 array( 243 DrydockLeaseStatus::STATUS_DESTROYED, 244 )) 245 ->withDateModifiedBetween($max_epoch, null) 246 ->setLimit(1) 247 ->execute(); 248 if ($leases) { 249 return false; 250 } 251 252 return true; 253 } 254 255 protected function reclaimResource( 256 DrydockResource $resource, 257 DrydockLease $lease) { 258 $viewer = $this->getViewer(); 259 260 // Mark the lease as reclaiming this resource. It won't be allowed to start 261 // another reclaim as long as this resource is still in the process of 262 // being reclaimed. 263 $lease->addReclaimedResourcePHIDs(array($resource->getPHID())); 264 265 // When the resource releases, we we want to reawaken this task since it 266 // should (usually) be able to start building a new resource right away. 267 $worker_task_id = $this->getCurrentWorkerTaskID(); 268 269 $command = DrydockCommand::initializeNewCommand($viewer) 270 ->setTargetPHID($resource->getPHID()) 271 ->setAuthorPHID($lease->getPHID()) 272 ->setCommand(DrydockCommand::COMMAND_RECLAIM) 273 ->setProperty('awakenTaskIDs', array($worker_task_id)); 274 275 $lease->openTransaction(); 276 $lease->save(); 277 $command->save(); 278 $lease->saveTransaction(); 279 280 $resource->scheduleUpdate(); 281 282 return $this; 283 } 284 285}