@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<?php
2
3/**
4 * @task update Updating Resources
5 * @task command Processing Commands
6 * @task activate Activating Resources
7 * @task release Releasing Resources
8 * @task break Breaking Resources
9 * @task destroy Destroying Resources
10 */
11final class DrydockResourceUpdateWorker extends DrydockWorker {
12
13 protected function doWork() {
14 $resource_phid = $this->getTaskDataValue('resourcePHID');
15
16 $hash = PhabricatorHash::digestForIndex($resource_phid);
17 $lock_key = 'drydock.resource:'.$hash;
18
19 $lock = PhabricatorGlobalLock::newLock($lock_key)
20 ->lock(1);
21
22 try {
23 $resource = $this->loadResource($resource_phid);
24 $this->handleUpdate($resource);
25 } catch (Exception $ex) {
26 $lock->unlock();
27 $this->flushDrydockTaskQueue();
28 throw $ex;
29 }
30
31 $lock->unlock();
32 }
33
34
35/* -( Updating Resources )------------------------------------------------- */
36
37
38 /**
39 * Update a resource, handling exceptions thrown during the update.
40 *
41 * @param DrydockResource $resource Resource to update.
42 * @return void
43 * @task update
44 */
45 private function handleUpdate(DrydockResource $resource) {
46 try {
47 $this->updateResource($resource);
48 } catch (Exception $ex) {
49 if ($this->isTemporaryException($ex)) {
50 $this->yieldResource($resource, $ex);
51 } else {
52 $this->breakResource($resource, $ex);
53 }
54 }
55 }
56
57
58 /**
59 * Update a resource.
60 *
61 * @param DrydockResource $resource Resource to update.
62 * @return void
63 * @task update
64 */
65 private function updateResource(DrydockResource $resource) {
66 $this->processResourceCommands($resource);
67
68 $resource_status = $resource->getStatus();
69 switch ($resource_status) {
70 case DrydockResourceStatus::STATUS_PENDING:
71 $this->activateResource($resource);
72 break;
73 case DrydockResourceStatus::STATUS_ACTIVE:
74 case DrydockResourceStatus::STATUS_DESTROYED:
75 // Nothing to do.
76 break;
77 case DrydockResourceStatus::STATUS_RELEASED:
78 case DrydockResourceStatus::STATUS_BROKEN:
79 $this->destroyResource($resource);
80 break;
81 break;
82 }
83
84 $this->yieldIfExpiringResource($resource);
85 }
86
87
88 /**
89 * Convert a temporary exception into a yield.
90 *
91 * @param DrydockResource $resource Resource to yield.
92 * @param Exception $ex Temporary exception worker encountered.
93 * @task update
94 */
95 private function yieldResource(DrydockResource $resource, Exception $ex) {
96 $duration = $this->getYieldDurationFromException($ex);
97
98 $resource->logEvent(
99 DrydockResourceActivationYieldLogType::LOGCONST,
100 array(
101 'duration' => $duration,
102 ));
103
104 throw new PhabricatorWorkerYieldException($duration);
105 }
106
107
108/* -( Processing Commands )------------------------------------------------ */
109
110
111 /**
112 * @task command
113 */
114 private function processResourceCommands(DrydockResource $resource) {
115 if (!$resource->canReceiveCommands()) {
116 return;
117 }
118
119 $this->checkResourceExpiration($resource);
120
121 $commands = $this->loadCommands($resource->getPHID());
122 foreach ($commands as $command) {
123 if (!$resource->canReceiveCommands()) {
124 break;
125 }
126
127 $this->processResourceCommand($resource, $command);
128
129 $command
130 ->setIsConsumed(true)
131 ->save();
132 }
133 }
134
135
136 /**
137 * @task command
138 */
139 private function processResourceCommand(
140 DrydockResource $resource,
141 DrydockCommand $command) {
142
143 switch ($command->getCommand()) {
144 case DrydockCommand::COMMAND_RELEASE:
145 $this->releaseResource($resource, null);
146 break;
147 case DrydockCommand::COMMAND_RECLAIM:
148 $reclaimer_phid = $command->getAuthorPHID();
149 $this->releaseResource($resource, $reclaimer_phid);
150 break;
151 }
152
153 // If the command specifies that other worker tasks should be awakened
154 // after it executes, awaken them now.
155 $awaken_ids = $command->getProperty('awakenTaskIDs');
156 if (is_array($awaken_ids) && $awaken_ids) {
157 PhabricatorWorker::awakenTaskIDs($awaken_ids);
158 }
159 }
160
161
162/* -( Activating Resources )----------------------------------------------- */
163
164
165 /**
166 * @task activate
167 */
168 private function activateResource(DrydockResource $resource) {
169 $blueprint = $resource->getBlueprint();
170 $blueprint->activateResource($resource);
171 $this->validateActivatedResource($blueprint, $resource);
172
173 $awaken_ids = $this->getTaskDataValue('awakenOnActivation');
174 if (is_array($awaken_ids) && $awaken_ids) {
175 PhabricatorWorker::awakenTaskIDs($awaken_ids);
176 }
177 }
178
179
180 /**
181 * @task activate
182 */
183 private function validateActivatedResource(
184 DrydockBlueprint $blueprint,
185 DrydockResource $resource) {
186
187 if (!$resource->isActivatedResource()) {
188 throw new Exception(
189 pht(
190 'Blueprint "%s" (of type "%s") is not properly implemented: %s '.
191 'must actually allocate the resource it returns.',
192 $blueprint->getBlueprintName(),
193 $blueprint->getClassName(),
194 'allocateResource()'));
195 }
196
197 }
198
199
200/* -( Releasing Resources )------------------------------------------------ */
201
202
203 /**
204 * @task release
205 */
206 private function releaseResource(
207 DrydockResource $resource,
208 $reclaimer_phid) {
209
210 if ($reclaimer_phid) {
211 if (!$this->canReclaimResource($resource)) {
212 return;
213 }
214
215 $resource->logEvent(
216 DrydockResourceReclaimLogType::LOGCONST,
217 array(
218 'reclaimerPHID' => $reclaimer_phid,
219 ));
220 }
221
222 $viewer = $this->getViewer();
223 $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
224
225 $resource
226 ->setStatus(DrydockResourceStatus::STATUS_RELEASED)
227 ->save();
228
229 $statuses = array(
230 DrydockLeaseStatus::STATUS_PENDING,
231 DrydockLeaseStatus::STATUS_ACQUIRED,
232 DrydockLeaseStatus::STATUS_ACTIVE,
233 );
234
235 $leases = id(new DrydockLeaseQuery())
236 ->setViewer($viewer)
237 ->withResourcePHIDs(array($resource->getPHID()))
238 ->withStatuses($statuses)
239 ->execute();
240
241 foreach ($leases as $lease) {
242 $command = DrydockCommand::initializeNewCommand($viewer)
243 ->setTargetPHID($lease->getPHID())
244 ->setAuthorPHID($drydock_phid)
245 ->setCommand(DrydockCommand::COMMAND_RELEASE)
246 ->save();
247
248 $lease->scheduleUpdate();
249 }
250
251 $this->destroyResource($resource);
252 }
253
254
255/* -( Breaking Resources )------------------------------------------------- */
256
257
258 /**
259 * @task break
260 */
261 private function breakResource(DrydockResource $resource, Exception $ex) {
262 switch ($resource->getStatus()) {
263 case DrydockResourceStatus::STATUS_BROKEN:
264 case DrydockResourceStatus::STATUS_RELEASED:
265 case DrydockResourceStatus::STATUS_DESTROYED:
266 // If the resource was already broken, just throw a normal exception.
267 // This will retry the task eventually.
268 throw new Exception(
269 pht(
270 'Unexpected failure while destroying resource ("%s").',
271 $resource->getPHID()),
272 0,
273 $ex);
274 }
275
276 $resource
277 ->setStatus(DrydockResourceStatus::STATUS_BROKEN)
278 ->save();
279
280 $resource->scheduleUpdate();
281
282 $resource->logEvent(
283 DrydockResourceActivationFailureLogType::LOGCONST,
284 array(
285 'class' => get_class($ex),
286 'message' => $ex->getMessage(),
287 ));
288
289 throw new PhabricatorWorkerPermanentFailureException(
290 pht(
291 'Permanent failure while activating resource ("%s"): %s',
292 $resource->getPHID(),
293 $ex->getMessage()));
294 }
295
296
297/* -( Destroying Resources )----------------------------------------------- */
298
299
300 /**
301 * @task destroy
302 */
303 private function destroyResource(DrydockResource $resource) {
304 $blueprint = $resource->getBlueprint();
305 $blueprint->destroyResource($resource);
306
307 DrydockSlotLock::releaseLocks($resource->getPHID());
308
309 $resource
310 ->setStatus(DrydockResourceStatus::STATUS_DESTROYED)
311 ->save();
312 }
313}