1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-01 11:12:42 +01:00
phorge-phorge/src/applications/drydock/storage/DrydockSlotLock.php

108 lines
2.6 KiB
PHP
Raw Normal View History

Implement optimistic "slot locks" in Drydock Summary: See discussion in D10304. There's a lot of context there, but the general idea is: - Blueprints should manage locks in a granular way during the actual allocation/acquisition phase. - Optimistic "slot locks" might a pretty good primitive to make that easy to implement and reason about in most cases. The way these locks work is that you just pick some name for the lock (like the PHID of a resource) and say that it needs to be acquired for the allocation/acquisition to work: ``` ... ->needSlotLock("mylock(PHID-XYZQ-...)") ... ``` When you fire off the acquisition or allocation, it fails unless it could acquire the slot with that name. This is really simple (no explicit lock management) and a pretty good fit for most of the locking that blueprints and leases need to do. If you need to do limit-based locks (e.g., maximum of 3 locks) you could acquire a lock like this: ``` mylock(whatever).slot(2) ``` Blueprints generally only contend with themselves, so it's normally OK for them to pick whatever strategy works best for them in naming locks. This may not work as well if you have a huge number of slots (e.g., 100TB you want to give out in 1MB chunks), or other complex needs for locks (like you have to synchronize access to some external resource), but slot locks don't need to be the only mechanism that blueprints use. If they run into a problem that slot locks aren't a good fit for, they can use something else instead. For now, slot locks seem like a good fit for the problems we currently face and most of the problems I anticipate facing. (The release workflows have other race issues which I'm not addressing here. They work fine if nothing races, but aren't race-safe.) Test Plan: To create a race where the same binding is allocated as a resource twice: - Add `sleep(10)` near the beginning of `allocateResource()`, after the free bindings are loaded but before resources are allocated. - (Comment out slot lock acquisition if you have this patch.) - Run `bin/drydock lease ...` in two windows, within 10 seconds of one another. This will reliably double-allocate the binding because both blueprints see a view of the world where the binding is free. To verify the lock works, un-comment it (or apply this patch) and run the same test again. Now, the lock fails in one process and only one resource is allocated. Reviewers: hach-que, chad Reviewed By: hach-que, chad Differential Revision: https://secure.phabricator.com/D14118
2015-09-21 13:45:25 +02:00
<?php
/**
* Simple optimistic locks for Drydock resources and leases.
*
* Most blueprints only need very simple locks: for example, a host blueprint
* might not want to create multiple resources representing the same physical
* machine. These optimistic "slot locks" provide a flexible way to do this
* sort of simple locking.
*
* @task lock Acquiring and Releasing Locks
*/
final class DrydockSlotLock extends DrydockDAO {
protected $ownerPHID;
protected $lockIndex;
protected $lockKey;
protected function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array(
'lockIndex' => 'bytes12',
'lockKey' => 'text',
),
self::CONFIG_KEY_SCHEMA => array(
'key_lock' => array(
'columns' => array('lockIndex'),
'unique' => true,
),
'key_owner' => array(
'columns' => array('ownerPHID'),
),
),
) + parent::getConfiguration();
}
public static function loadLocks($owner_phid) {
return id(new DrydockSlotLock())->loadAllWhere(
'ownerPHID = %s',
$owner_phid);
}
/* -( Acquiring and Releasing Locks )-------------------------------------- */
/**
* Acquire a set of slot locks.
*
* This method either acquires all the locks or throws an exception (usually
* because one or more locks are held).
*
* @param phid Lock owner PHID.
* @param list<string> List of locks to acquire.
* @return void
* @task locks
*/
public static function acquireLocks($owner_phid, array $locks) {
if (!$locks) {
return;
}
$table = new DrydockSlotLock();
$conn_w = $table->establishConnection('w');
$sql = array();
foreach ($locks as $lock) {
$sql[] = qsprintf(
$conn_w,
'(%s, %s, %s)',
$owner_phid,
PhabricatorHash::digestForIndex($lock),
$lock);
}
// TODO: These exceptions are pretty tricky to read. It would be good to
// figure out which locks could not be acquired and try to improve the
// exception to make debugging easier.
queryfx(
$conn_w,
'INSERT INTO %T (ownerPHID, lockIndex, lockKey) VALUES %Q',
$table->getTableName(),
implode(', ', $sql));
}
/**
* Release all locks held by an owner.
*
* @param phid Lock owner PHID.
* @return void
* @task locks
*/
public static function releaseLocks($owner_phid) {
$table = new DrydockSlotLock();
$conn_w = $table->establishConnection('w');
queryfx(
$conn_w,
'DELETE FROM %T WHERE ownerPHID = %s',
$table->getTableName(),
$owner_phid);
}
}