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
|
||||
*/
|
||||
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)";
|
||||
}
|
||||
|
||||
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->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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user