mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
ENHANCEMENT Added Database->getLock() and Database->releaseLock() for application-level advisory locks
This commit is contained in:
parent
67568b08a3
commit
8302af1ea8
@ -828,6 +828,59 @@ abstract class SS_Database {
|
|||||||
* Commit everything inside this transaction so far
|
* Commit everything inside this transaction so far
|
||||||
*/
|
*/
|
||||||
abstract function transactionEnd();
|
abstract function transactionEnd();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the used database supports application-level locks,
|
||||||
|
* which is different from table- or row-level locking.
|
||||||
|
* See {@link getLock()} for details.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
function supportsLocks() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if the lock is available.
|
||||||
|
* See {@link supportsLocks()} to check if locking is generally supported.
|
||||||
|
*
|
||||||
|
* @return Boolean
|
||||||
|
*/
|
||||||
|
function canLock($name) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an application-level lock so that no two processes can run at the same time,
|
||||||
|
* also called a "cooperative advisory lock".
|
||||||
|
*
|
||||||
|
* Return FALSE if acquiring the lock fails; otherwise return TRUE, if lock was acquired successfully.
|
||||||
|
* Lock is automatically released if connection to the database is broken (either normally or abnormally),
|
||||||
|
* making it less prone to deadlocks than session- or file-based locks.
|
||||||
|
* Should be accompanied by a {@link releaseLock()} call after the logic requiring the lock has completed.
|
||||||
|
* Can be called multiple times, in which case locks "stack" (PostgreSQL, SQL Server),
|
||||||
|
* or auto-releases the previous lock (MySQL).
|
||||||
|
*
|
||||||
|
* Note that this might trigger the database to wait for the lock to be released, delaying further execution.
|
||||||
|
*
|
||||||
|
* @param String
|
||||||
|
* @param Int Timeout in seconds
|
||||||
|
* @return Boolean
|
||||||
|
*/
|
||||||
|
function getLock($name, $timeout = 5) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an application-level lock file to allow another process to run
|
||||||
|
* (if the execution aborts (e.g. due to an error) all locks are automatically released).
|
||||||
|
*
|
||||||
|
* @param String
|
||||||
|
* @return Boolean
|
||||||
|
*/
|
||||||
|
function releaseLock($name) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1039,6 +1039,34 @@ class MySQLDatabase extends SS_Database {
|
|||||||
|
|
||||||
return "UNIX_TIMESTAMP($date1) - UNIX_TIMESTAMP($date2)";
|
return "UNIX_TIMESTAMP($date1) - UNIX_TIMESTAMP($date2)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function supportsLocks() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function canLock($name) {
|
||||||
|
$id = $this->getLockIdentifier($name);
|
||||||
|
return (bool)DB::query(sprintf("SELECT IS_FREE_LOCK('%s')", $id))->value();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLock($name, $timeout = 5) {
|
||||||
|
$id = $this->getLockIdentifier($name);
|
||||||
|
|
||||||
|
// MySQL auto-releases existing locks on subsequent GET_LOCK() calls,
|
||||||
|
// in contrast to PostgreSQL and SQL Server who stack the locks.
|
||||||
|
|
||||||
|
return (bool)DB::query(sprintf("SELECT GET_LOCK('%s', %d)", $id, $timeout))->value();
|
||||||
|
}
|
||||||
|
|
||||||
|
function releaseLock($name) {
|
||||||
|
$id = $this->getLockIdentifier($name);
|
||||||
|
return (bool)DB::query(sprintf("SELECT RELEASE_LOCK('%s')", $id))->value();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getLockIdentifier($name) {
|
||||||
|
// Prefix with database name
|
||||||
|
return Convert::raw2sql($this->database . '_' . Convert::raw2sql($name));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -82,7 +82,47 @@ class DatabaseTest extends SapphireTest {
|
|||||||
$this->assertTrue(DB::getConn()->hasTable('DatabaseTest_MyObject'));
|
$this->assertTrue(DB::getConn()->hasTable('DatabaseTest_MyObject'));
|
||||||
$this->assertFalse(DB::getConn()->hasTable('asdfasdfasdf'));
|
$this->assertFalse(DB::getConn()->hasTable('asdfasdfasdf'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testGetAndReleaseLock() {
|
||||||
|
$db = DB::getConn();
|
||||||
|
|
||||||
|
if(!$db->supportsLocks()) {
|
||||||
|
return $this->markTestSkipped('Tested database doesn\'t support application locks');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertTrue($db->getLock('DatabaseTest'), 'Can aquire lock');
|
||||||
|
// $this->assertFalse($db->getLock('DatabaseTest'), 'Can\'t repeatedly aquire the same lock');
|
||||||
|
$this->assertTrue($db->getLock('DatabaseTest'), 'The same lock can be aquired multiple times in the same connection');
|
||||||
|
|
||||||
|
$this->assertTrue($db->getLock('DatabaseTestOtherLock'), 'Can aquire different lock');
|
||||||
|
$db->releaseLock('DatabaseTestOtherLock');
|
||||||
|
|
||||||
|
// Release potentially stacked locks from previous getLock() invocations
|
||||||
|
$db->releaseLock('DatabaseTest');
|
||||||
|
$db->releaseLock('DatabaseTest');
|
||||||
|
|
||||||
|
$this->assertTrue($db->getLock('DatabaseTest'), 'Can aquire lock after releasing it');
|
||||||
|
$db->releaseLock('DatabaseTest');
|
||||||
|
}
|
||||||
|
|
||||||
|
function testCanLock() {
|
||||||
|
$db = DB::getConn();
|
||||||
|
|
||||||
|
if(!$db->supportsLocks()) {
|
||||||
|
return $this->markTestSkipped('Database doesn\'t support locks');
|
||||||
|
}
|
||||||
|
|
||||||
|
if($db instanceof MSSQLDatabase) {
|
||||||
|
return $this->markTestSkipped('MSSQLDatabase doesn\'t support inspecting locks');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertTrue($db->canLock('DatabaseTest'), 'Can lock before first aquiring one');
|
||||||
|
$db->getLock('DatabaseTest');
|
||||||
|
$this->assertFalse($db->canLock('DatabaseTest'), 'Can\'t lock after aquiring one');
|
||||||
|
$db->releaseLock('DatabaseTest');
|
||||||
|
$this->assertTrue($db->canLock('DatabaseTest'), 'Can lock again after releasing it');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DatabaseTest_MyObject extends DataObject implements TestOnly {
|
class DatabaseTest_MyObject extends DataObject implements TestOnly {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user