Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

ENHANCEMENT Added Database->getLock() and Database->releaseLock() for…

… application-level advisory locks
  • Loading branch information...
commit 8302af1ea8e31e5435f5543c1d21d151f3638e8e 1 parent 67568b0
@chillu chillu authored
View
53 model/Database.php
@@ -828,6 +828,59 @@ public function sqlQueryToString(SQLQuery $sqlQuery) {
* 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;
+ }
}
/**
View
28 model/MySQLDatabase.php
@@ -1039,6 +1039,34 @@ function datetimeDifferenceClause($date1, $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));
+ }
}
/**
View
40 tests/model/DatabaseTest.php
@@ -82,7 +82,47 @@ function testHasTable() {
$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 {
Please sign in to comment.
Something went wrong with that request. Please try again.