Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/drydock/worker/DrydockWorker.php
12256 views
1
<?php
2
3
abstract 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 instanceof PhutilProxyException) {
155
return $this->isTemporaryException($ex->getPreviousException());
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
}
286
287