Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Per record locking #46

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 39 additions & 13 deletions lib/Doctrine/Locking/Manager/Pessimistic.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public function __construct(Doctrine_Connection $conn)
* Obtains a lock on a {@link Doctrine_Record}
*
* @param Doctrine_Record $record The record that has to be locked
* @param mixed $userIdent A unique identifier of the locking user
* @param mixed $userIdent A unique identifier for the lock.
* @return boolean TRUE if the locking was successful, FALSE if another user
* holds a lock on this record
* @throws Doctrine_Locking_Exception If the locking failed due to database errors
Expand All @@ -106,9 +106,16 @@ public function getLock(Doctrine_Record $record, $userIdent)
$gotLock = false;
$time = time();

$objectKey = [];
if (is_array($key)) {
// Composite key
foreach ($key as $keyName) {
$objectKey[] = $record->get($keyName);
}
$key = implode('|', $key);
$objectKey = implode('|', $objectKey);
} else {
$objectKey = $record->get($key);
}

try {
Expand All @@ -120,7 +127,7 @@ public function getLock(Doctrine_Record $record, $userIdent)
. ' VALUES (:object_type, :object_key, :user_ident, :ts_obtained)');

$stmt->bindParam(':object_type', $objectType);
$stmt->bindParam(':object_key', $key);
$stmt->bindParam(':object_key', $objectKey);
$stmt->bindParam(':user_ident', $userIdent);
$stmt->bindParam(':ts_obtained', $time);

Expand All @@ -134,8 +141,8 @@ public function getLock(Doctrine_Record $record, $userIdent)
}

if ( ! $gotLock) {
$lockingUserIdent = $this->_getLockingUserIdent($objectType, $key);
if ($lockingUserIdent !== null && $lockingUserIdent == $userIdent) {
$lockingKey = $this->_getUserIdent($objectType, $key);
if ($lockingKey !== null && $lockingKey == $userIdent) {
$gotLock = true; // The requesting user already has a lock
// Update timestamp
$stmt = $dbh->prepare('UPDATE ' . $this->_lockTable
Expand All @@ -145,8 +152,8 @@ public function getLock(Doctrine_Record $record, $userIdent)
. ' user_ident = :user_ident');
$stmt->bindParam(':ts', $time);
$stmt->bindParam(':object_type', $objectType);
$stmt->bindParam(':object_key', $key);
$stmt->bindParam(':user_ident', $lockingUserIdent);
$stmt->bindParam(':object_key', $objectKey);
$stmt->bindParam(':user_ident', $lockingKey);
$stmt->execute();
}
}
Expand All @@ -163,7 +170,7 @@ public function getLock(Doctrine_Record $record, $userIdent)
* Releases a lock on a {@link Doctrine_Record}
*
* @param Doctrine_Record $record The record for which the lock has to be released
* @param mixed $userIdent The unique identifier of the locking user
* @param mixed $userIdent. The unique identifier for the lock
* @return boolean TRUE if a lock was released, FALSE if no lock was released
* @throws Doctrine_Locking_Exception If the release procedure failed due to database errors
*/
Expand All @@ -172,9 +179,16 @@ public function releaseLock(Doctrine_Record $record, $userIdent)
$objectType = $record->getTable()->getComponentName();
$key = $record->getTable()->getIdentifier();

$objectKey = [];
if (is_array($key)) {
// Composite key
foreach ($key as $keyName) {
$objectKey[] = $record->get($keyName);
}
$key = implode('|', $key);
$objectKey = implode('|', $objectKey);
} else {
$objectKey = $record->get($key);
}

try {
Expand All @@ -184,7 +198,7 @@ public function releaseLock(Doctrine_Record $record, $userIdent)
object_key = :object_key AND
user_ident = :user_ident");
$stmt->bindParam(':object_type', $objectType);
$stmt->bindParam(':object_key', $key);
$stmt->bindParam(':object_key', $objectKey);
$stmt->bindParam(':user_ident', $userIdent);
$stmt->execute();

Expand All @@ -197,14 +211,14 @@ public function releaseLock(Doctrine_Record $record, $userIdent)
}

/**
* Gets the unique user identifier of a lock
* Gets the unique identifier of a lock
*
* @param string $objectType The type of the object (component name)
* @param mixed $key The unique key of the object. Can be string or array
* @return string The unique user identifier for the specified lock
* @return string The unique identifier for the specified lock
* @throws Doctrine_Locking_Exception If the query failed due to database errors
*/
private function _getLockingUserIdent($objectType, $key)
private function _getUserIdent($objectType, $key)
{
if (is_array($key)) {
// Composite key
Expand Down Expand Up @@ -242,7 +256,19 @@ public function getLockOwner($lockedRecord)
{
$objectType = $lockedRecord->getTable()->getComponentName();
$key = $lockedRecord->getTable()->getIdentifier();
return $this->_getLockingUserIdent($objectType, $key);

$objectKey = [];
if (is_array($key)) {
// Composite key
foreach ($key as $keyName) {
$objectKey[] = $lockedRecord->get($keyName);
}
$objectKey = implode('|', $objectKey);
} else {
$objectKey = $lockedRecord->get($key);
}

return $this->_getUserIdent($objectType, $objectKey);
}

/**
Expand All @@ -252,7 +278,7 @@ public function getLockOwner($lockedRecord)
*
* @param integer $age The maximum valid age of locks in seconds
* @param string $objectType The type of the object (component name)
* @param mixed $userIdent The unique identifier of the locking user
* @param mixed $userIdent The unique identifier of the lock
* @return integer The number of locks that have been released
* @throws Doctrine_Locking_Exception If the release process failed due to database errors
*/
Expand Down
22 changes: 20 additions & 2 deletions tests/PessimisticLockingTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,29 @@ class Doctrine_PessimisticLocking_TestCase extends Doctrine_UnitTestCase
*
* Creates a locking manager and a test record to work with.
*/
public function testInitData()
public function setup()
{
parent::setup();

$this->lockingManager = new Doctrine_Locking_Manager_Pessimistic($this->connection);

// Create sample data to test on
$entry1 = new Forum_Entry();
$entry1->author = 'Bart Simpson';
$entry1->topic = 'I love donuts!';
$entry1->save();

$entry2 = new Forum_Entry();
$entry2->author = 'Bart Simpson';
$entry2->topic = 'I play saxophone.';
$entry2->save();
}

public function tearDown()
{
parent::tearDown();
$this->lockingManager->releaseAgedLocks(-1); // age -1 seconds => release all in prep for next test
$this->connection->query("DELETE FROM Forum_Entry WHERE Forum_Entry.author = 'Bart Simpson'");
}

public function prepareTables()
Expand All @@ -73,6 +87,10 @@ public function testLock()
$gotLock = $this->lockingManager->getLock($entries[0], 'konstav');
$this->assertFalse($gotLock);

// Test successfully getting a lock on the second forum entry
$gotLock = $this->lockingManager->getLock($entries[1], 'michael');
$this->assertTrue($gotLock);

// Test release lock
$released = $this->lockingManager->releaseLock($entries[0], 'romanb');
$this->assertTrue($released);
Expand Down