diff --git a/core/includes/Drupal/Lock/CouldNotAcquireLockException.php b/core/includes/Drupal/Lock/CouldNotAcquireLockException.php
new file mode 100644
index 0000000..8982280 100644
--- /dev/null
+++ b/core/includes/Drupal/Lock/CouldNotAcquireLockException.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\Lock;
+
+use RuntimeException;
+
+class CouldNotAcquireLockException extends RuntimeException {
+  /**
+   * @var string
+   */
+  protected $lockName;
+
+  public function getLockName() {
+    return $this->lockName;
+  }
+
+  /**
+   * Default constructor.
+   *
+   * @param string $lock_name
+   * @param Exception $previous
+   */
+  public function __construct($lock_name, Exception $previous = null) {
+
+    $this->lockName = $lock_name;
+    $message = "Could not acquire lock '$lock_name'";
+
+    if (isset($previous)) {
+      parent::__construct($message, 0, $previous);
+    }
+    else {
+      parent::__construct($message);
+    }
+  }
+}
diff --git a/core/includes/Drupal/Lock/DatabaseLockBackend.php b/core/includes/Drupal/Lock/DatabaseLockBackend.php
new file mode 100644
index 0000000..e1dfb65 100644
--- /dev/null
+++ b/core/includes/Drupal/Lock/DatabaseLockBackend.php
@@ -0,0 +1,100 @@
+<?php
+
+namespace Drupal\Lock;
+
+use PDOException;
+
+/**
+ * Database lock backend. This is Drupal default.
+ */
+class DatabaseLockBackend extends LockBackendAbstract {
+
+  public function acquire($name, $timeout = 30.0) {
+    // Insure that the timeout is at least 1 ms.
+    $timeout = max($timeout, 0.001);
+    $expire = microtime(TRUE) + $timeout;
+    if (isset($this->locks[$name])) {
+      // Try to extend the expiration of a lock we already acquired.
+      $success = (bool) db_update('semaphore')
+        ->fields(array('expire' => $expire))
+        ->condition('name', $name)
+        ->condition('value', $this->getLockId())
+        ->execute();
+      if (!$success) {
+        // The lock was broken.
+        unset($this->locks[$name]);
+      }
+      return $success;
+    }
+    else {
+      // Optimistically try to acquire the lock, then retry once if it fails.
+      // The first time through the loop cannot be a retry.
+      $retry = FALSE;
+      // We always want to do this code at least once.
+      do {
+        try {
+          db_insert('semaphore')
+            ->fields(array(
+              'name' => $name,
+              'value' => $this->getLockId(),
+              'expire' => $expire,
+            ))
+            ->execute();
+          // We track all acquired locks in the global variable.
+          $this->locks[$name] = TRUE;
+          // We never need to try again.
+          $retry = FALSE;
+        }
+        catch (PDOException $e) {
+          // Suppress the error. If this is our first pass through the loop,
+          // then $retry is FALSE. In this case, the insert must have failed
+          // meaning some other request acquired the lock but did not release it.
+          // We decide whether to retry by checking lock_may_be_available()
+          // Since this will break the lock in case it is expired.
+          $retry = $retry ? FALSE : $this->lockMayBeAvailable($name);
+        }
+        // We only retry in case the first attempt failed, but we then broke
+        // an expired lock.
+      } while ($retry);
+    }
+    return isset($this->locks[$name]);
+  }
+
+  public function lockMayBeAvailable($name) {
+    $lock = db_query('SELECT expire, value FROM {semaphore} WHERE name = :name', array(':name' => $name))->fetchAssoc();
+    if (!$lock) {
+      return TRUE;
+    }
+    $expire = (float) $lock['expire'];
+    $now = microtime(TRUE);
+    if ($now > $expire) {
+      // We check two conditions to prevent a race condition where another
+      // request acquired the lock and set a new expire time. We add a small
+      // number to $expire to avoid errors with float to string conversion.
+      return (bool) db_delete('semaphore')
+        ->condition('name', $name)
+        ->condition('value', $lock['value'])
+        ->condition('expire', 0.0001 + $expire, '<=')
+        ->execute();
+    }
+    return FALSE;
+  }
+
+  public function release($name) {
+    unset($this->locks[$name]);
+    db_delete('semaphore')
+      ->condition('name', $name)
+      ->condition('value', $this->getLockId())
+      ->execute();
+  }
+
+  public function releaseAll($lock_id = NULL) {
+    $this->locks = array();
+    if (empty($lock_id)) {
+      $lock_id = $this->getLockId();
+    }
+    db_delete('semaphore')
+      ->condition('value', $lock_id)
+      ->execute();
+  }
+}
diff --git a/core/includes/Drupal/Lock/LockBackendAbstract.php b/core/includes/Drupal/Lock/LockBackendAbstract.php
new file mode 100644
index 0000000..f2b6395 100644
--- /dev/null
+++ b/core/includes/Drupal/Lock/LockBackendAbstract.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Drupal\Lock;
+
+/**
+ * Non backend related common methods implementation for lock backends.
+ */
+abstract class LockBackendAbstract implements LockBackendInterface {
+  /**
+   * Current page lock token identifier.
+   *
+   * @var string
+   */
+  protected $lockId;
+
+  /**
+   * Existing locks for this page.
+   *
+   * @var array
+   */
+  protected $locks = array();
+
+  public function wait($name, $delay = 30) {
+    // Pause the process for short periods between calling
+    // lock_may_be_available(). This prevents hitting the database with constant
+    // database queries while waiting, which could lead to performance issues.
+    // However, if the wait period is too long, there is the potential for a
+    // large number of processes to be blocked waiting for a lock, especially
+    // if the item being rebuilt is commonly requested. To address both of these
+    // concerns, begin waiting for 25ms, then add 25ms to the wait period each
+    // time until it reaches 500ms. After this point polling will continue every
+    // 500ms until $delay is reached.
+
+    // $delay is passed in seconds, but we will be using usleep(), which takes
+    // microseconds as a parameter. Multiply it by 1 million so that all
+    // further numbers are equivalent.
+    $delay = (int) $delay * 1000000;
+
+    // Begin sleeping at 25ms.
+    $sleep = 25000;
+    while ($delay > 0) {
+      // This function should only be called by a request that failed to get a
+      // lock, so we sleep first to give the parallel request a chance to finish
+      // and release the lock.
+      usleep($sleep);
+      // After each sleep, increase the value of $sleep until it reaches
+      // 500ms, to reduce the potential for a lock stampede.
+      $delay = $delay - $sleep;
+      $sleep = min(500000, $sleep + 25000, $delay);
+      if ($this->lockMayBeAvailable($name)) {
+        // No longer need to wait.
+        return FALSE;
+      }
+    }
+    // The caller must still wait longer to get the lock.
+    return TRUE;
+  }
+
+  public function getLockId() {
+    if (!isset($this->lockId)) {
+      $this->lockId = uniqid(mt_rand(), TRUE);
+    }
+    return $this->lockId;
+  }
+}
diff --git a/core/includes/Drupal/Lock/LockBackendInterface.php b/core/includes/Drupal/Lock/LockBackendInterface.php
new file mode 100644
index 0000000..7c4f022 100644
--- /dev/null
+++ b/core/includes/Drupal/Lock/LockBackendInterface.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Drupal\Lock;
+
+/**
+ * Lock backend interface.
+ */
+interface LockBackendInterface {
+  /**
+   * Acquire lock.
+   *
+   * @param string $name
+   *   Lock name.
+   * @param float $timeout = 30.0
+   *   (optional) Lock lifetime in seconds.
+   *
+   * @return bool
+   */
+  public function acquire($name, $timeout = 30.0);
+
+  /**
+   * Check if lock is available for acquire.
+   *
+   * @param string $name
+   *   Lock to acquire.
+   *
+   * @return bool
+   */
+  public function lockMayBeAvailable($name);
+
+  /**
+   * Wait a short amount of time before a second lock acquire attempt.
+   *
+   * While this method is subject to have a generic implementation in abstract
+   * backend implementation, some backends may provide non blocking or less I/O
+   * intensive wait mecanism: this is why this method remains on the backend
+   * interface.
+   *
+   * @param string $name
+   *   Lock name currently being locked.
+   * @param int $delay = 30
+   *   Miliseconds to wait for.
+   *
+   * @return bool
+   *   TRUE if the wait operation was successful and lock may be available. You
+   *   still need to acquire the lock manually and it may fail again.
+   */
+  public function wait($name, $delay = 30);
+
+  /**
+   * Release given lock.
+   *
+   * @param string $name
+   */
+  public function release($name);
+
+  /**
+   * Release all locks for the given lock token identifier.
+   *
+   * @param string $lockId = NULL
+   *   (optional) If none given, remove all lock from the current page.
+   */
+  public function releaseAll($lock_id = NULL);
+
+  /**
+   * Get the unique page token for locks. Locks will be wipeout at each end of
+   * page request on a token basis.
+   *
+   * @return string
+   */
+  public function getLockId();
+}
diff --git a/core/includes/Drupal/Lock/LockFactory.php b/core/includes/Drupal/Lock/LockFactory.php
new file mode 100644
index 0000000..897c7ec 100644
--- /dev/null
+++ b/core/includes/Drupal/Lock/LockFactory.php
@@ -0,0 +1,114 @@
+<?php
+
+namespace Drupal\Lock;
+
+use Exception;
+use InvalidArgumentException;
+
+/**
+ * Encapsulate additional complexity of locking outside of backends itself.
+ */
+class LockFactory {
+  /**
+   * @var LockBackendInterface
+   */
+  protected $backend;
+
+  /**
+   * Get lock backend.
+   *
+   * @return LockBackendInterface
+   */
+  public function getBackend() {
+    return $this->backend;
+  }
+
+  /**
+   * Acquire the given lock.
+   *
+   * @param string $name
+   * @param float $timeout = 30.0
+   *
+   * @return LockToken
+   *
+   * @throws CouldNotAcquireLockException
+   *   If the lock could not be acquired at first attempt.
+   */
+  public function acquire($name, $timeout = 30.0) {
+    if ($this->backend->acquire($name, $timeout = 30.0)) {
+      return new LockToken($this->backend, $name);
+    }
+    else {
+      throw new CouldNotAcquireLockException($name);
+    }
+  }
+
+  /**
+   * Acquire the given lock then run the given callback.
+   *
+   * If the lock could not be acquired first time, successive retries will be
+   * done until it is acquired.
+   *
+   * Sample usage with menu handling:
+   * @code
+   * if (variable_get('menu_rebuild_needed', FALSE)) {
+   *   lock()->doWhenAcquired('menu_rebuild', '_menu_rebuild');
+   * }
+   * @endcode
+   *
+   * Another sample business logic usage:
+   * @code
+   * if ($some_condition) {
+   *   lock()->doWhenAcquired('my_lock', function () {
+   *     do_something();
+   *   });
+   * }
+   * @endcode
+   *
+   * @param string $name
+   * @param callback $callback
+   * @param array $args
+   *   Optional parameters to pass to the callback.
+   *
+   * @throws CouldNotAcquireLockException
+   *   If lock could not be acquired after multiple wait iterations.
+   */
+  public function doWhenAcquired($name, $callback, array $args = NULL) {
+
+    if (!is_callable($callback)) {
+      throw new InvalidArgumentException("Given callback is not callable.");
+    }
+
+    if (!$this->backend->acquire($name)) {
+      if (!$this->backend->wait($name) || !$this->backend->acquire($name)) {
+        throw new CouldNotAcquireLockException($name);
+      }
+    }
+
+    try {
+      if (isset($args)) {
+        call_user_func_array($callback, $args);
+      }
+      else {
+        $callback();
+      }
+    }
+    catch (Exception $e) {
+      // Last chance release, we cannot let any lock stall.
+      $this->backend->release($name);
+      throw $e;
+    }
+
+    $this->backend->release($name);
+  }
+
+  /**
+   * Default constructor.
+   */
+  public function __construct(LockBackendInterface $backend) {
+    $this->backend = $backend;
+
+    // Ensure all locks will be released at Drupal shutdown time.
+    drupal_register_shutdown_function(array($this->backend, 'releaseAll'));
+  }
+}
diff --git a/core/includes/Drupal/Lock/LockToken.php b/core/includes/Drupal/Lock/LockToken.php
new file mode 100644
index 0000000..30d72c3 100644
--- /dev/null
+++ b/core/includes/Drupal/Lock/LockToken.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace Drupal\Lock;
+
+/**
+ * Lock token. This object is created when acquire a lock through the factory.
+ * This object acts as the lock holder, and will release automatically the lock
+ * upon destruction, thus acting as both a code shortcut and a safeguard that
+ * avoid lock stalling.
+ */
+class LockToken {
+  /**
+   * @var LockBackendInterface
+   */
+  protected $backend;
+
+  /**
+   * @var string
+   */
+  protected $name;
+
+  /**
+   * @var bool
+   */
+  protected $autoReleaseEnabled = TRUE;
+
+  /**
+   * Disable auto lock release when token goes out of scope.
+   */
+  public function disableAutoRelease() {
+    $this->autoReleaseEnabled = FALSE;
+  }
+
+  /**
+   * Enable auto lock release when token goes out of scope.
+   */
+  public function enableAutoRelease() {
+    $this->autoReleaseEnabled = TRUE;
+  }
+
+  /**
+   * Release this lock.
+   */
+  public function release() {
+    $this->backend->release($this->name);
+  }
+
+  /**
+   * Wait for the given amount of time.
+   *
+   * @param int $delay
+   *   Time to wait, in seconds.
+   */
+  public function wait($delay = 30) {
+    $this->backend->wait($this->name, $delay);
+  }
+
+  /**
+   * Default destructor.
+   */
+  public function __destruct() {
+    if ($this->autoReleaseEnabled) {
+      $this->release();
+    }
+  }
+
+  /**
+   * Default constructor.
+   *
+   * @param LockBackendInterface $backend
+   * @param string $name
+   * @param bool $enableAutoRelease = TRUE
+   */
+  public function __construct(LockBackendInterface $backend, $name, $enableAutoRelease = TRUE) {
+    $this->backend = $backend;
+    $this->name = $name;
+    $this->autoReleaseEnabled = $enableAutoRelease;
+  }
+}
diff --git a/core/includes/Drupal/Lock/NullLockBackend.php b/core/includes/Drupal/Lock/NullLockBackend.php
new file mode 100644
index 0000000..0ab3de6 100644
--- /dev/null
+++ b/core/includes/Drupal/Lock/NullLockBackend.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Drupal\Lock;
+
+/**
+ * Null lock backend implementation: this won't lock anything, and always
+ * return success on lock attempt.
+ */
+class NullLockBackend implements LockBackendInterface {
+
+  protected $lockId;
+
+  public function acquire($name, $timeout = 30.0) {
+    return TRUE;
+  }
+
+  public function lockMayBeAvailable($name) {
+    return TRUE;
+  }
+
+  public function wait($name, $delay = 30) {}
+
+  public function release($name) {}
+
+  public function releaseAll($lock_id = NULL) {}
+
+  public function getLockId() {
+    if (!isset($this->lockId)) {
+      $this->lockId = uniqid(mt_rand(), TRUE);
+    }
+    return $this->lockId;
+  }
+}
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 111e89a..2c5017c 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -2438,8 +2438,7 @@ function _drupal_bootstrap_variables() {
   global $conf;
 
   // Initialize the lock system.
-  require_once DRUPAL_ROOT . '/' . variable_get('lock_inc', 'core/includes/lock.inc');
-  lock_initialize();
+  require_once DRUPAL_ROOT . '/core/includes/lock.inc';
 
   // Load variables from the database, but do not overwrite variables set in settings.php.
   $conf = variable_initialize(isset($conf) ? $conf : array());
diff --git a/core/includes/lock.inc b/core/includes/lock.inc
index 7dd8db3..61c8e7f 100644
--- a/core/includes/lock.inc
+++ b/core/includes/lock.inc
@@ -2,7 +2,7 @@
 
 /**
  * @file
- * A database-mediated implementation of a locking mechanism.
+ * Drupal lock framework procedural proxy implementation.
  */
 
 /**
@@ -53,39 +53,26 @@
  *
  * lock_acquire() and lock_wait() will automatically break (delete) a lock
  * whose duration has exceeded the timeout specified when it was acquired.
- *
- * Alternative implementations of this API (such as APC) may be substituted
- * by setting the 'lock_inc' variable to an alternate include filepath. Since
- * this is an API intended to support alternative implementations, code using
- * this API should never rely upon specific implementation details (for example
- * no code should look for or directly modify a lock in the {semaphore} table).
- */
-
-/**
- * Initialize the locking system.
  */
-function lock_initialize() {
-  global $locks;
 
-  $locks = array();
-}
+use Drupal\Lock\CouldNotAcquireLockException;
+use Drupal\Lock\DatabaseLockBackend;
+use Drupal\Lock\LockBackendInterface;
+use Drupal\Lock\LockFactory;
+use Drupal\Lock\LockToken;
 
 /**
- * Helper function to get this request's unique id.
+ * Get locking layer instance.
+ *
+ * @return Drupal\Lock\LockFactory
  */
-function _lock_id() {
-  // Do not use drupal_static(). This identifier refers to the current
-  // client request, and must not be changed under any circumstances
-  // else the shutdown handler may fail to release our locks.
-  static $lock_id;
-
-  if (!isset($lock_id)) {
-    // Assign a unique id.
-    $lock_id = uniqid(mt_rand(), TRUE);
-    // We only register a shutdown function if a lock is used.
-    drupal_register_shutdown_function('lock_release_all', $lock_id);
+function lock() {
+  static $factory;
+  if (!isset($factory)) {
+    $className = variable_get('default_lock_backend', 'Drupal\\Lock\\DatabaseLockBackend');
+    $factory = new LockFactory(new $className());
   }
-  return $lock_id;
+  return $factory;
 }
 
 /**
@@ -98,89 +85,11 @@ function _lock_id() {
  *
  * @return
  *   TRUE if the lock was acquired, FALSE if it failed.
- */
-function lock_acquire($name, $timeout = 30.0) {
-  global $locks;
-
-  // Insure that the timeout is at least 1 ms.
-  $timeout = max($timeout, 0.001);
-  $expire = microtime(TRUE) + $timeout;
-  if (isset($locks[$name])) {
-    // Try to extend the expiration of a lock we already acquired.
-    $success = (bool) db_update('semaphore')
-      ->fields(array('expire' => $expire))
-      ->condition('name', $name)
-      ->condition('value', _lock_id())
-      ->execute();
-    if (!$success) {
-      // The lock was broken.
-      unset($locks[$name]);
-    }
-    return $success;
-  }
-  else {
-    // Optimistically try to acquire the lock, then retry once if it fails.
-    // The first time through the loop cannot be a retry.
-    $retry = FALSE;
-    // We always want to do this code at least once.
-    do {
-      try {
-        db_insert('semaphore')
-          ->fields(array(
-            'name' => $name,
-            'value' => _lock_id(),
-            'expire' => $expire,
-          ))
-          ->execute();
-        // We track all acquired locks in the global variable.
-        $locks[$name] = TRUE;
-        // We never need to try again.
-        $retry = FALSE;
-      }
-      catch (PDOException $e) {
-        // Suppress the error. If this is our first pass through the loop,
-        // then $retry is FALSE. In this case, the insert must have failed
-        // meaning some other request acquired the lock but did not release it.
-        // We decide whether to retry by checking lock_may_be_available()
-        // Since this will break the lock in case it is expired.
-        $retry = $retry ? FALSE : lock_may_be_available($name);
-      }
-      // We only retry in case the first attempt failed, but we then broke
-      // an expired lock.
-    } while ($retry);
-  }
-  return isset($locks[$name]);
-}
-
-/**
- * Check if lock acquired by a different process may be available.
- *
- * If an existing lock has expired, it is removed.
  *
- * @param $name
- *   The name of the lock.
- *
- * @return
- *   TRUE if there is no lock or it was removed, FALSE otherwise.
+ * @deprecated
  */
-function lock_may_be_available($name) {
-  $lock = db_query('SELECT expire, value FROM {semaphore} WHERE name = :name', array(':name' => $name))->fetchAssoc();
-  if (!$lock) {
-    return TRUE;
-  }
-  $expire = (float) $lock['expire'];
-  $now = microtime(TRUE);
-  if ($now > $expire) {
-    // We check two conditions to prevent a race condition where another
-    // request acquired the lock and set a new expire time. We add a small
-    // number to $expire to avoid errors with float to string conversion.
-    return (bool) db_delete('semaphore')
-      ->condition('name', $name)
-      ->condition('value', $lock['value'])
-      ->condition('expire', 0.0001 + $expire, '<=')
-      ->execute();
-  }
-  return FALSE;
+function lock_acquire($name, $timeout = 30.0) {
+  return lock()->getBackend()->acquire($name, $timeout);
 }
 
 /**
@@ -199,41 +108,11 @@ function lock_may_be_available($name) {
  *
  * @return
  *   TRUE if the lock holds, FALSE if it is available.
+ *
+ * @deprecated
  */
 function lock_wait($name, $delay = 30) {
-  // Pause the process for short periods between calling
-  // lock_may_be_available(). This prevents hitting the database with constant
-  // database queries while waiting, which could lead to performance issues.
-  // However, if the wait period is too long, there is the potential for a
-  // large number of processes to be blocked waiting for a lock, especially
-  // if the item being rebuilt is commonly requested. To address both of these
-  // concerns, begin waiting for 25ms, then add 25ms to the wait period each
-  // time until it reaches 500ms. After this point polling will continue every
-  // 500ms until $delay is reached.
-
-  // $delay is passed in seconds, but we will be using usleep(), which takes
-  // microseconds as a parameter. Multiply it by 1 million so that all
-  // further numbers are equivalent.
-  $delay = (int) $delay * 1000000;
-
-  // Begin sleeping at 25ms.
-  $sleep = 25000;
-  while ($delay > 0) {
-    // This function should only be called by a request that failed to get a
-    // lock, so we sleep first to give the parallel request a chance to finish
-    // and release the lock.
-    usleep($sleep);
-    // After each sleep, increase the value of $sleep until it reaches
-    // 500ms, to reduce the potential for a lock stampede.
-    $delay = $delay - $sleep;
-    $sleep = min(500000, $sleep + 25000, $delay);
-    if (lock_may_be_available($name)) {
-      // No longer need to wait.
-      return FALSE;
-    }
-  }
-  // The caller must still wait longer to get the lock.
-  return TRUE;
+  return lock()->getBackend()->wait($name, $delay);
 }
 
 /**
@@ -243,30 +122,20 @@ function lock_wait($name, $delay = 30) {
  *
  * @param $name
  *   The name of the lock.
+ *
+ * @deprecated
  */
 function lock_release($name) {
-  global $locks;
-
-  unset($locks[$name]);
-  db_delete('semaphore')
-    ->condition('name', $name)
-    ->condition('value', _lock_id())
-    ->execute();
+  lock()->getBackend()->release($name);
 }
 
 /**
  * Release all previously acquired locks.
+ *
+ * @deprecated
  */
 function lock_release_all($lock_id = NULL) {
-  global $locks;
-
-  $locks = array();
-  if (empty($lock_id)) {
-    $lock_id = _lock_id();
-  }
-  db_delete('semaphore')
-    ->condition('value', $lock_id)
-    ->execute();
+  lock()->getBackend()->releaseAll($lock_id);
 }
 
 /**
diff --git a/core/modules/simpletest/tests/lock.test b/core/modules/simpletest/tests/lock.test
index 0b423ff..a82b54f 100644
--- a/core/modules/simpletest/tests/lock.test
+++ b/core/modules/simpletest/tests/lock.test
@@ -1,5 +1,8 @@
 <?php
 
+use Drupal\Lock\CouldNotAcquireLockException;
+use Drupal\Lock\LockToken;
+
 /**
  * Tests for the lock system.
  */
@@ -18,6 +21,127 @@ class LockFunctionalTest extends DrupalWebTestCase {
   }
 
   /**
+   * Test backend release functionality.
+   */
+  function testBackendLockRelease() {
+    $backend = lock()->getBackend();
+
+    $success = $backend->acquire('lock_a');
+    $this->assertTrue($success, "Could acquire first lock");
+
+    // This function is not part of the backend, but the default database
+    // backend implement it, we can here use it safely.
+    $is_free = $backend->lockMayBeAvailable('lock_a');
+    $this->assertFalse($is_free, "First lock is unavailable");
+
+    $backend->release('lock_a');
+    $is_free = $backend->lockMayBeAvailable('lock_a');
+    $this->assertTrue($is_free, "First lock has been released");
+
+    $success = $backend->acquire('lock_b');
+    $this->assertTrue($success, "Could acquire second lock");
+
+    $success = $backend->acquire('lock_b');
+    $this->assertTrue($success, "Could acquire second lock a second time within the same request");
+
+    $backend->release('lock_b');
+  }
+
+  /**
+   * Test backend release functionality.
+   */
+  function testBackendLockReleaseAll() {
+    $backend = lock()->getBackend();
+
+    $success = $backend->acquire('lock_a');
+    $this->assertTrue($success, "Could acquire first lock");
+
+    $success = $backend->acquire('lock_b');
+    $this->assertTrue($success, "Could acquire second lock");
+
+    $backend->releaseAll();
+
+    $is_free = $backend->lockMayBeAvailable('lock_a');
+    $this->assertTrue($is_free, "First lock has been released");
+
+    $is_free = $backend->lockMayBeAvailable('lock_b');
+    $this->assertTrue($is_free, "Second lock has been released");
+  }
+
+  /**
+   * Test end user API acquire method.
+   */
+  function testFrontendAcquire() {
+    $backend = lock()->getBackend();
+
+    $is_free = $backend->lockMayBeAvailable('lock_a');
+    $this->assertTrue($is_free, "First lock is available");
+
+    try {
+      $token = lock()->acquire('lock_a');
+      $this->assertTrue($token instanceof LockToken, "Got the token as expected for the first lock");
+
+      $is_free = $backend->lockMayBeAvailable('lock_a');
+      $this->assertFalse($is_free, "First lock is unavailable");
+    }
+    catch (CouldNotAcquireLockException $e) {
+      $this->fail("Caught an exception while acquiring first lock");
+    }
+
+    // Token went out of scope and lock should be free. Let's ensure that
+    // anyway.
+    unset($token);
+
+    $is_free = $backend->lockMayBeAvailable('lock_a');
+    $this->assertTrue($is_free, "First lock is available again, token has auto released it");
+
+    try {
+      $token = lock()->acquire('lock_b');
+      $this->assertTrue($token instanceof LockToken, "Got the token as expected for the second lock");
+
+      $token->disableAutoRelease();
+    }
+    catch (CouldNotAcquireLockException $e) {
+      $this->fail("Caught an exception while acquiring second lock");
+    }
+
+    // Force token destructor to be called.
+    $token->__destruct();
+
+    $is_free = $backend->lockMayBeAvailable('lock_b');
+    $this->assertFalse($is_free, "Second lock is still unavailable after we explicitely disabled auto release");
+
+    $token->release();
+
+    $is_free = $backend->lockMayBeAvailable('lock_b');
+    $this->assertTrue($is_free, "Second lock is has been freed by manual LockToken::release() call");
+  }
+
+  /**
+   * Test end user API do when acquired helper.
+   */
+  function testFrontendDoWhenAcquired() {
+    $backend = lock()->getBackend();
+
+    $was_free = NULL;
+
+    lock()->doWhenAcquired('lock_a', function () use ($backend, &$was_free) {
+      $was_free = $backend->lockMayBeAvailable('lock_a');
+    });
+
+    $this->assertNotNull($was_free, "doWhenAcquired() callback has been run");
+    $this->assertFalse($was_free, "First lock was not free in doWhenAcquired() callback");
+
+    $is_free = $backend->lockMayBeAvailable('lock_a');
+    $this->assertTrue($is_free, "First lock has been released after doWhenAcquired() call");
+
+    $int = 12;
+    lock()->doWhenAcquired('lock_a', new LockFunctionalTestCallable(), array(&$int));
+
+    $this->assertEqual(13, $int, "Callable object has been called.");
+  }
+
+  /**
    * Confirm that we can acquire and release locks in two parallel requests.
    */
   function testLockAcquire() {
@@ -55,3 +179,9 @@ class LockFunctionalTest extends DrupalWebTestCase {
   }
 }
 
+class LockFunctionalTestCallable {
+
+  public function __invoke(&$int_value) {
+    $int_value++;
+  }
+}
