Skip to content

Commit f0fdcf1

Browse files
author
epriestley
committedNov 1, 2012
Undumb the Drydock resource allocator pipeline
Summary: This was the major goal of D3859/D3855, and to a lesser degree D3854/D3852. As Drydock is allocating a resource, it may need to allocate other resources first. For example, if it's allocating a working copy, it may need to allocate a host first. Currently, we have the process basically queue up the allocation (insert a task into the queue) and sleep() until it finishes. This is problematic for a bunch of reasons, but the major one is that if allocation takes more resources (host, port, machine, DNS) than you have daemons, they could all end up sleeping and waiting for some other daemon to do their work. This is really stupid. Even if you only take up some of them, you're spending slots sleeping when you could be doing useful work. To partially get around this and make the CLI experience less dumb, there's this goofy `synchronous` flag that gets passed around everywhere and pushes the workflow through a pile of special cases. Basically the `synchronous` flag causes us to do everything in-process. But this is dumb too because we'd rather do things in parallel if we can, and we have to have a lot of special case code to make it work at all. Get rid of all of this. Instead of sleep()ing, try to work on the tasks that need to be worked on. If another daemon grabbed them already that's fine, but in the worst case we just gracefully degrade and do everything in process. So we get the best of both worlds: if we have parallelizable tasks and free daemons, things will execute in parallel. If we have nonparallelizable tasks or no free daemons, things will execute in process. Test Plan: Ran `drydock_control.php --trace` and saw it perform cascading allocations without sleeping or special casing. Reviewers: btrahan Reviewed By: btrahan CC: aran Maniphest Tasks: T2015 Differential Revision: https://secure.phabricator.com/D3861
1 parent 84ee4cd commit f0fdcf1

File tree

9 files changed

+102
-48
lines changed

9 files changed

+102
-48
lines changed
 
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE {$NAMESPACE}_drydock.drydock_lease ADD taskID INT UNSIGNED;

‎scripts/drydock/drydock_control.php

+5-4
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@
2020
$root = dirname(dirname(dirname(__FILE__)));
2121
require_once $root.'/scripts/__init_script__.php';
2222

23-
PhutilServiceProfiler::installEchoListener();
23+
$args = new PhutilArgumentParser($argv);
24+
$args->parseStandardArguments();
25+
$args->parse(array());
2426

2527
$allocator = new DrydockAllocator();
26-
$allocator->makeSynchronous();
2728
$allocator->setResourceType('webroot');
2829
$lease = $allocator->allocate();
2930

@@ -46,5 +47,5 @@
4647
$lease->release();
4748

4849

49-
//$i_http = $lease->getInterface('httpd');
50-
//echo $i_http->getURI('/index.html')."\n";
50+
// $i_http = $lease->getInterface('httpd');
51+
// echo $i_http->getURI('/index.html')."\n";

‎src/applications/drydock/allocator/DrydockAllocator.php

+3-12
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ final class DrydockAllocator {
2020

2121
private $resourceType;
2222
private $lease;
23-
private $synchronous;
2423

2524
public function setResourceType($resource_type) {
2625
$this->resourceType = $resource_type;
@@ -31,10 +30,6 @@ public function getResourceType() {
3130
return $this->resourceType;
3231
}
3332

34-
public function makeSynchronous() {
35-
$this->synchronous = true;
36-
}
37-
3833
public function getPendingLease() {
3934
if (!$this->lease) {
4035
$lease = new DrydockLease();
@@ -54,13 +49,9 @@ public function allocate() {
5449
'lease' => $lease->getID(),
5550
);
5651

57-
if ($this->synchronous) {
58-
$data['synchronous'] = true;
59-
$worker = new DrydockAllocatorWorker($data);
60-
$worker->executeTask();
61-
} else {
62-
PhabricatorWorker::scheduleTask('DrydockAllocatorWorker', $data);
63-
}
52+
$task = PhabricatorWorker::scheduleTask('DrydockAllocatorWorker', $data);
53+
54+
$lease->setTaskID($task->getID());
6455

6556
return $lease;
6657
}

‎src/applications/drydock/allocator/DrydockAllocatorWorker.php

-5
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,6 @@ protected function doWork() {
6464
shuffle($blueprints);
6565

6666
$blueprint = head($blueprints);
67-
68-
if (isset($data['synchronous'])) {
69-
$blueprint->makeSynchronous();
70-
}
71-
7267
$resource = $blueprint->allocateResource();
7368
}
7469

‎src/applications/drydock/blueprint/DrydockBlueprint.php

-8
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,6 @@ abstract class DrydockBlueprint {
2020

2121
private $activeLease;
2222
private $activeResource;
23-
private $synchronous;
24-
25-
final public function makeSynchronous() {
26-
$this->synchronous = true;
27-
}
2823

2924
abstract public function getType();
3025
abstract public function getInterface(
@@ -40,9 +35,6 @@ protected function executeAcquireLease(
4035

4136
protected function getAllocator($type) {
4237
$allocator = new DrydockAllocator();
43-
if ($this->synchronous) {
44-
$allocator->makeSynchronous();
45-
}
4638
$allocator->setResourceType($type);
4739

4840
return $allocator;

‎src/applications/drydock/storage/DrydockLease.php

+33-14
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ final class DrydockLease extends DrydockDAO {
2424
protected $ownerPHID;
2525
protected $attributes = array();
2626
protected $status;
27+
protected $taskID;
2728

2829
private $resource;
2930

@@ -95,25 +96,43 @@ private function assertActive() {
9596
}
9697
}
9798

98-
public function waitUntilActive() {
99-
$this->reload();
99+
public static function waitForLeases(array $leases) {
100+
assert_instances_of($leases, 'DrydockLease');
101+
102+
$task_ids = array_filter(mpull($leases, 'getTaskID'));
103+
PhabricatorWorker::waitForTasks($task_ids);
104+
105+
$unresolved = $leases;
100106
while (true) {
101-
switch ($this->status) {
102-
case DrydockLeaseStatus::STATUS_ACTIVE:
103-
break 2;
104-
case DrydockLeaseStatus::STATUS_RELEASED:
105-
case DrydockLeaseStatus::STATUS_EXPIRED:
106-
case DrydockLeaseStatus::STATUS_BROKEN:
107-
throw new Exception("Lease will never become active!");
108-
case DrydockLeaseStatus::STATUS_PENDING:
109-
break;
107+
foreach ($unresolved as $key => $lease) {
108+
$lease->reload();
109+
switch ($lease->getStatus()) {
110+
case DrydockLeaseStatus::STATUS_ACTIVE:
111+
unset($unresolved[$key]);
112+
break;
113+
case DrydockLeaseStatus::STATUS_RELEASED:
114+
case DrydockLeaseStatus::STATUS_EXPIRED:
115+
case DrydockLeaseStatus::STATUS_BROKEN:
116+
throw new Exception("Lease will never become active!");
117+
case DrydockLeaseStatus::STATUS_PENDING:
118+
break;
119+
}
120+
}
121+
122+
if ($unresolved) {
123+
sleep(1);
124+
} else {
125+
break;
110126
}
111-
sleep(2);
112-
$this->reload();
113127
}
114128

115-
$this->attachResource($this->loadResource());
129+
foreach ($leases as $lease) {
130+
$lease->attachResource($lease->loadResource());
131+
}
132+
}
116133

134+
public function waitUntilActive() {
135+
self::waitForLeases(array($this));
117136
return $this;
118137
}
119138

‎src/infrastructure/daemon/workers/PhabricatorWorker.php

+43
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,47 @@ final public static function scheduleTask($task_class, $data) {
107107
->save();
108108
}
109109

110+
111+
/**
112+
* Wait for tasks to complete. If tasks are not leased by other workers, they
113+
* will be executed in this process while waiting.
114+
*
115+
* @param list<int> List of queued task IDs to wait for.
116+
* @return void
117+
*/
118+
final public static function waitForTasks(array $task_ids) {
119+
$task_table = new PhabricatorWorkerActiveTask();
120+
121+
$waiting = array_combine($task_ids, $task_ids);
122+
while ($waiting) {
123+
$conn_w = $task_table->establishConnection('w');
124+
125+
// Check if any of the tasks we're waiting on are still queued. If they
126+
// are not, we're done waiting.
127+
$row = queryfx_one(
128+
$conn_w,
129+
'SELECT COUNT(*) N FROM %T WHERE id IN (%Ld)',
130+
$task_table->getTableName(),
131+
$waiting);
132+
if (!$row['N']) {
133+
// Nothing is queued anymore. Stop waiting.
134+
break;
135+
}
136+
137+
$tasks = id(new PhabricatorWorkerLeaseQuery())
138+
->withIDs($waiting)
139+
->setLimit(1)
140+
->execute();
141+
142+
if (!$tasks) {
143+
// We were not successful in leasing anything. Sleep for a bit and
144+
// see if we have better luck later.
145+
sleep(1);
146+
continue;
147+
}
148+
149+
$task = head($tasks)->executeTask();
150+
}
151+
}
152+
110153
}

‎src/infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php

+13-5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ final class PhabricatorWorkerLeaseQuery extends PhabricatorQuery {
2525

2626
const PHASE_UNLEASED = 'unleased';
2727
const PHASE_EXPIRED = 'expired';
28+
const PHASE_SELECT = 'select';
2829

2930
const DEFAULT_LEASE_DURATION = 60; // Seconds
3031

@@ -66,7 +67,8 @@ public function execute() {
6667
foreach ($phases as $phase) {
6768
queryfx(
6869
$conn_w,
69-
'UPDATE %T SET leaseOwner = %s, leaseExpires = UNIX_TIMESTAMP() + %d
70+
'UPDATE %T task
71+
SET leaseOwner = %s, leaseExpires = UNIX_TIMESTAMP() + %d
7072
%Q %Q %Q',
7173
$task_table->getTableName(),
7274
$lease_ownership_name,
@@ -90,11 +92,10 @@ public function execute() {
9092
'SELECT task.*, taskdata.data _taskData, UNIX_TIMESTAMP() _serverTime
9193
FROM %T task LEFT JOIN %T taskdata
9294
ON taskdata.id = task.dataID
93-
WHERE leaseOwner = %s AND leaseExpires > UNIX_TIMESTAMP()
94-
%Q %Q',
95+
%Q %Q %Q',
9596
$task_table->getTableName(),
9697
$taskdata_table->getTableName(),
97-
$lease_ownership_name,
98+
$this->buildWhereClause($conn_w, self::PHASE_SELECT),
9899
$this->buildOrderClause($conn_w),
99100
$this->buildLimitClause($conn_w, $limit));
100101

@@ -124,14 +125,21 @@ private function buildWhereClause(AphrontDatabaseConnection $conn_w, $phase) {
124125
case self::PHASE_EXPIRED:
125126
$where[] = 'leaseExpires < UNIX_TIMESTAMP()';
126127
break;
128+
case self::PHASE_SELECT:
129+
$where[] = qsprintf(
130+
$conn_w,
131+
'leaseOwner = %s',
132+
$this->getLeaseOwnershipName());
133+
$where[] = 'leaseExpires > UNIX_TIMESTAMP()';
134+
break;
127135
default:
128136
throw new Exception("Unknown phase '{$phase}'!");
129137
}
130138

131139
if ($this->ids) {
132140
$where[] = qsprintf(
133141
$conn_w,
134-
'id IN (%Ld)',
142+
'task.id IN (%Ld)',
135143
$this->ids);
136144
}
137145

‎src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php

+4
Original file line numberDiff line numberDiff line change
@@ -1020,6 +1020,10 @@ public function getPatches() {
10201020
'type' => 'sql',
10211021
'name' => $this->getPatchPath('daemontaskarchive.sql'),
10221022
),
1023+
'drydocktaskid.sql' => array(
1024+
'type' => 'sql',
1025+
'name' => $this->getPatchPath('drydocktaskid.sql'),
1026+
),
10231027
);
10241028
}
10251029

0 commit comments

Comments
 (0)
Failed to load comments.