diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php
index c1abfe5..ed15dd5 100644
--- a/core/lib/Drupal/Core/CoreBundle.php
+++ b/core/lib/Drupal/Core/CoreBundle.php
@@ -55,6 +55,11 @@ public function build(ContainerBuilder $container) {
       ->setFactoryMethod('getConnection')
       ->addArgument('slave');
     $container->register('typed_data', 'Drupal\Core\TypedData\TypedDataManager');
+    // Add the user's storage for temporary, non-cache data.
+    $container->register('lock', 'Drupal\Core\Lock\DatabaseLockBackend');
+    $container->register('user.tempstore', 'Drupal\user\TempStoreFactory')
+      ->addArgument(new Reference('database'))
+      ->addArgument(new Reference('lock'));
 
     $container->register('router.dumper', '\Drupal\Core\Routing\MatcherDumper')
       ->addArgument(new Reference('database'));
diff --git a/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php
index 532f690..c837796 100644
--- a/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php
+++ b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php
@@ -7,15 +7,28 @@
 
 namespace Drupal\Core\KeyValueStore;
 
+use Drupal\Core\Database\Query\Merge;
+
 /**
  * Defines a default key/value store implementation.
  *
  * This is Drupal's default key/value store implementation. It uses the database
  * to store key/value data.
+ *
+ * @todo This class still calls db_* functions directly because it's needed
+ *   very early, pre-Container.  Once the early bootstrap dependencies are
+ *   sorted out, consider using an injected database connection instead.
  */
 class DatabaseStorage extends StorageBase {
 
   /**
+   * The name of the SQL table to use.
+   *
+   * @var string
+   */
+  protected $table;
+
+  /**
    * Overrides Drupal\Core\KeyValueStore\StorageBase::__construct().
    *
    * @param string $collection
@@ -78,6 +91,22 @@ public function set($key, $value) {
   }
 
   /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::setIfNotExists().
+   */
+  public function setIfNotExists($key, $value) {
+    $result = db_merge($this->table)
+      ->insertFields(array(
+        'collection' => $this->collection,
+        'name' => $key,
+        'value' => serialize($value),
+      ))
+      ->condition('collection', $this->collection)
+      ->condition('name', $key)
+      ->execute();
+    return $result == Merge::STATUS_INSERT;
+  }
+
+  /**
    * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::deleteMultiple().
    */
   public function deleteMultiple(array $keys) {
diff --git a/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php
new file mode 100644
index 0000000..27fad30
--- /dev/null
+++ b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\KeyValueStore\DatabaseStorageExpirable.
+ */
+
+namespace Drupal\Core\KeyValueStore;
+
+use Drupal\Core\Database\Query\Merge;
+use Drupal\Core\Database\Database;
+
+/**
+ * Defines a default key/value store implementation for expiring items.
+ *
+ * This key/value store implementation uses the database to store key/value
+ * data with an expire date.
+ */
+class DatabaseStorageExpirable extends DatabaseStorage implements KeyValueStoreExpirableInterface {
+
+  /**
+   * The connection object for this storage.
+   *
+   * @var Drupal\Core\Database\Connection
+   */
+  protected $connection;
+
+  /**
+   * Overrides Drupal\Core\KeyValueStore\StorageBase::__construct().
+   *
+   * @param string $collection
+   *   The name of the collection holding key and value pairs.
+   * @param array $options
+   *   An associative array of options for the key/value storage collection.
+   *   Keys used:
+   *   - connection: (optional) The database connection to use for storing the
+   *     data. Defaults to the current connection.
+   *   - table: (optional) The name of the SQL table to use. Defaults to
+   *     key_value_expire.
+   */
+  public function __construct($collection, array $options = array()) {
+    parent::__construct($collection, $options);
+    $this->connection = isset($options['connection']) ? $options['connection'] : Database::getConnection();
+    $this->table = isset($options['table']) ? $options['table'] : 'key_value_expire';
+  }
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::getMultiple().
+   */
+  public function getMultiple(array $keys) {
+    $values = $this->connection->query(
+      'SELECT name, value FROM {' . $this->connection->escapeTable($this->table) . '} WHERE expire > :now AND name IN (:keys) AND collection = :collection',
+      array(
+        ':now' => REQUEST_TIME,
+        ':keys' => $keys,
+        ':collection' => $this->collection,
+      ))->fetchAllKeyed();
+    return array_map('unserialize', $values);
+  }
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::getAll().
+   */
+  public function getAll() {
+    $values = $this->connection->query(
+      'SELECT name, value FROM {' . $this->connection->escapeTable($this->table) . '} WHERE collection = :collection AND expire > :now',
+      array(
+        ':collection' => $this->collection,
+        ':now' => REQUEST_TIME
+      ))->fetchAllKeyed();
+    return array_map('unserialize', $values);
+  }
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreExpireInterface::setWithExpire().
+   */
+  function setWithExpire($key, $value, $expire) {
+    $this->connection->merge($this->table)
+      ->key(array(
+        'name' => $key,
+        'collection' => $this->collection,
+      ))
+      ->fields(array(
+        'value' => serialize($value),
+        'expire' => REQUEST_TIME + $expire,
+      ))
+      ->execute();
+  }
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface::setWithExpireIfNotExists().
+   */
+  function setWithExpireIfNotExists($key, $value, $expire) {
+    $result = $this->connection->merge($this->table)
+      ->insertFields(array(
+        'collection' => $this->collection,
+        'name' => $key,
+        'value' => serialize($value),
+        'expire' => REQUEST_TIME + $expire,
+      ))
+      ->condition('collection', $this->collection)
+      ->condition('name', $key)
+      ->execute();
+    return $result == Merge::STATUS_INSERT;
+  }
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreExpirablInterface::setMultipleWithExpire().
+   */
+  function setMultipleWithExpire(array $data, $expire) {
+    foreach ($data as $key => $value) {
+      $this->set($key, $value, $expire);
+    }
+  }
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::deleteMultiple().
+   */
+  public function deleteMultiple(array $keys) {
+    $this->garbageCollection();
+    parent::deleteMultiple($keys);
+  }
+
+  /**
+   * Deletes expired items.
+   */
+  public function garbageCollection() {
+    $this->connection->delete($this->table)
+      ->condition('expire', REQUEST_TIME, '<')
+      ->execute();
+  }
+
+}
diff --git a/core/lib/Drupal/Core/KeyValueStore/KeyValueStoreExpirableInterface.php b/core/lib/Drupal/Core/KeyValueStore/KeyValueStoreExpirableInterface.php
new file mode 100644
index 0000000..e39dbe5
--- /dev/null
+++ b/core/lib/Drupal/Core/KeyValueStore/KeyValueStoreExpirableInterface.php
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface.
+ */
+
+namespace Drupal\Core\KeyValueStore;
+
+/**
+ * Defines the interface for expiring data in a key/value store.
+ */
+interface KeyValueStoreExpirableInterface extends KeyValueStoreInterface {
+
+  /**
+   * Saves a value for a given key with a time to live.
+   *
+   * @param string $key
+   *   The key of the data to store.
+   * @param mixed $value
+   *   The data to store.
+   * @param int $expire
+   *   The time to live for items, in seconds.
+   */
+  function setWithExpire($key, $value, $expire);
+
+  /**
+   * Sets a value for a given key with a time to live if it does not yet exist.
+   *
+   * @param string $key
+   *   The key of the data to store.
+   * @param mixed $value
+   *   The data to store.
+   * @param int $expire
+   *   The time to live for items, in seconds.
+   *
+   * @return bool
+   *   TRUE if the data was set, or FALSE if it already existed.
+   */
+  function setWithExpireIfNotExists($key, $value, $expire);
+
+  /**
+   * Saves an array of values with a time to live.
+   *
+   * @param array $data
+   *   An array of data to store.
+   * @param int $expire
+   *   The time to live for items, in seconds.
+   */
+  function setMultipleWithExpire(array $data, $expire);
+
+}
diff --git a/core/lib/Drupal/Core/KeyValueStore/KeyValueStoreInterface.php b/core/lib/Drupal/Core/KeyValueStore/KeyValueStoreInterface.php
index d350472..8f006ce 100644
--- a/core/lib/Drupal/Core/KeyValueStore/KeyValueStoreInterface.php
+++ b/core/lib/Drupal/Core/KeyValueStore/KeyValueStoreInterface.php
@@ -63,6 +63,19 @@ public function getAll();
   public function set($key, $value);
 
   /**
+   * Saves a value for a given key if it does not exist yet.
+   *
+   * @param string $key
+   *   The key of the data to store.
+   * @param mixed $value
+   *   The data to store.
+   *
+   * @return bool
+   *   TRUE if the data was set, FALSE if it already existed.
+   */
+  public function setIfNotExists($key, $value);
+
+  /**
    * Saves key/value pairs.
    *
    * @param array $data
diff --git a/core/lib/Drupal/Core/KeyValueStore/MemoryStorage.php b/core/lib/Drupal/Core/KeyValueStore/MemoryStorage.php
index ef3ef0e..6101507 100644
--- a/core/lib/Drupal/Core/KeyValueStore/MemoryStorage.php
+++ b/core/lib/Drupal/Core/KeyValueStore/MemoryStorage.php
@@ -48,6 +48,17 @@ public function set($key, $value) {
   }
 
   /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::setIfNotExists().
+   */
+  public function setIfNotExists($key, $value) {
+    if (!isset($this->data[$key])) {
+      $this->data[$key] = $value;
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  /**
    * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::setMultiple().
    */
   public function setMultiple(array $data) {
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
index 6370033..7320a74 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
@@ -439,6 +439,35 @@ protected function assertNotIdentical($first, $second, $message = '', $group = '
   }
 
   /**
+   * Checks to see if two objects are identical.
+   *
+   * @param object $object1
+   *   The first object to check.
+   * @param object $object2
+   *   The second object to check.
+   * @param $message
+   *   The message to display along with the assertion.
+   * @param $group
+   *   The type of assertion - examples are "Browser", "PHP".
+   *
+   * @return
+   *   TRUE if the assertion succeeded, FALSE otherwise.
+   */
+  protected function assertIdenticalObject($object1, $object2, $message = '', $group = '') {
+    $message = $message ?: format_string('!object1 is identical to !object2', array(
+      '!object1' => var_export($object1, TRUE),
+      '!object2' => var_export($object2, TRUE),
+    ));
+    $identical = TRUE;
+    foreach ($object1 as $key => $value) {
+      $identical = $identical && isset($object2->$key) && $object2->$key === $value;
+    }
+    return $this->assertTrue($identical, $message);
+  }
+
+
+
+  /**
    * Fire an assertion that is always positive.
    *
    * @param $message
@@ -940,6 +969,26 @@ public static function randomName($length = 8) {
   }
 
   /**
+   * Generates a random PHP object.
+   *
+   * @param int $size
+   *   The number of random keys to add to the object.
+   *
+   * @return \stdClass
+   *   The generated object, with the specified number of random keys. Each key
+   *   has a random string value.
+   */
+  public static function randomObject($size = 4) {
+    $object = new \stdClass();
+    for ($i = 0; $i < $size; $i++) {
+      $random_key = self::randomName();
+      $random_value = self::randomString();
+      $object->{$random_key} = $random_value;
+    }
+    return $object;
+  }
+
+  /**
    * Converts a list of possible parameters into a stack of permutations.
    *
    * Takes a list of parameters containing possible values, and converts all of
diff --git a/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/DatabaseStorageExpirableTest.php b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/DatabaseStorageExpirableTest.php
new file mode 100644
index 0000000..7b592be
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/DatabaseStorageExpirableTest.php
@@ -0,0 +1,168 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\system\Tests\KeyValueStore\DatabaseStorageExpirableTest.
+ */
+
+namespace Drupal\system\Tests\KeyValueStore;
+
+/**
+ * Tests the key-value database storage.
+ */
+class DatabaseStorageExpirableTest extends StorageTestBase {
+
+  /**
+   * The name of the class to test.
+   *
+   * The tests themselves are in StorageTestBase and use this class.
+   */
+  protected $storageClass = 'Drupal\Core\KeyValueStore\DatabaseStorageExpirable';
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Expirable database storage',
+      'description' => 'Tests the expirable key-value database storage.',
+      'group' => 'Key-value store',
+    );
+  }
+
+  protected function setUp() {
+    parent::setUp();
+    module_load_install('system');
+    $schema = system_schema();
+    db_create_table('key_value_expire', $schema['key_value_expire']);
+  }
+
+  protected function tearDown() {
+    db_drop_table('key_value_expire');
+    parent::tearDown();
+  }
+
+  /**
+   * Tests CRUD functionality with expiration.
+   */
+  public function testCRUDWithExpiration() {
+    // Verify that an item can be stored with setWithExpire().
+    $expire = 604800;
+    $this->verbose($expire);
+    $this->store1->setWithExpire('foo', $this->objects[0], $expire);
+    $this->assertIdenticalObject($this->objects[0], $this->store1->get('foo'));
+    // Verify that the other collection is not affected.
+    $this->assertFalse($this->store2->get('foo'));
+
+    // Verify that an item can be updated with setWithExpire().
+    $this->store1->setWithExpire('foo', $this->objects[1], $expire);
+    $this->assertIdenticalObject($this->objects[1], $this->store1->get('foo'));
+    // Verify that the other collection is still not affected.
+    $this->assertFalse($this->store2->get('foo'));
+
+    // Verify that the expirable data key is unique.
+    $this->store2->setWithExpire('foo', $this->objects[2], $expire);
+    $this->assertIdenticalObject($this->objects[1], $this->store1->get('foo'));
+    $this->assertIdenticalObject($this->objects[2], $this->store2->get('foo'));
+
+    // Verify that multiple items can be stored with setMultipleWithExpire().
+    $values = array(
+      'foo' => $this->objects[3],
+      'bar' => $this->objects[4],
+    );
+    $this->store1->setMultipleWithExpire($values, $expire);
+    $result = $this->store1->getMultiple(array('foo', 'bar'));
+    foreach ($values as $j => $value) {
+      $this->assertIdenticalObject($value, $result[$j]);
+    }
+
+    // Verify that the other collection was not affected.
+    $this->assertIdenticalObject($this->store2->get('foo'), $this->objects[2]);
+    $this->assertFalse($this->store2->get('bar'));
+
+    // Verify that all items in a collection can be retrieved.
+    // Ensure that an item with the same name exists in the other collection.
+    $this->store2->set('foo', $this->objects[5]);
+    $result = $this->store1->getAll();
+    // Not using assertIdentical(), since the order is not defined for getAll().
+    $this->assertEqual(count($result), count($values));
+    foreach ($result as $key => $value) {
+      $this->assertEqual($values[$key], $value);
+    }
+    // Verify that all items in the other collection are different.
+    $result = $this->store2->getAll();
+    $this->assertEqual($result, array('foo' => $this->objects[5]));
+
+    // Verify that multiple items can be deleted.
+    $this->store1->deleteMultiple(array_keys($values));
+    $this->assertFalse($this->store1->get('foo'));
+    $this->assertFalse($this->store1->get('bar'));
+    $this->assertFalse($this->store1->getMultiple(array('foo', 'bar')));
+    // Verify that the item in the other collection still exists.
+    $this->assertIdenticalObject($this->objects[5], $this->store2->get('foo'));
+
+    // Test that setWithExpireIfNotExists() succeeds only the first time.
+    $key = $this->randomName();
+    for ($i = 0; $i <= 1; $i++) {
+      // setIfNotExists() should be TRUE the first time (when $i is 0) and
+      // FALSE the second time (when $i is 1).
+      $this->assertEqual(!$i, $this->store1->setWithExpireIfNotExists($key, $this->objects[$i], $expire));
+      $this->assertIdenticalObject($this->objects[0], $this->store1->get($key));
+      // Verify that the other collection is not affected.
+      $this->assertFalse($this->store2->get($key));
+    }
+
+    // Remove the item and try to set it again.
+    $this->store1->delete($key);
+    $this->store1->setIfNotExists($key, $this->objects[1]);
+    // This time it should succeed.
+    $this->assertIdenticalObject($this->objects[1], $this->store1->get($key));
+    // Verify that the other collection is still not affected.
+    $this->assertFalse($this->store2->get($key));
+
+  }
+
+  /**
+   * Tests data expiration and garbage collection.
+   */
+  public function testExpiration() {
+    $day = 604800;
+
+    // Set an item to expire in the past and another without an expiration.
+    $this->store1->setWithExpire('yesterday', 'all my troubles seemed so far away', -1 * $day);
+    $this->store1->set('troubles', 'here to stay');
+
+    // Only the non-expired item should be returned.
+    $this->assertFalse($this->store1->get('yesterday'));
+    $this->assertIdentical($this->store1->get('troubles'), 'here to stay');
+    $this->assertIdentical(count($this->store1->getMultiple(array('yesterday', 'troubles'))), 1);
+
+    // Store items set to expire in the past in various ways.
+    $this->store1->setWithExpire($this->randomName(), $this->objects[0], -7 * $day);
+    $this->store1->setWithExpireIfNotExists($this->randomName(), $this->objects[1], -5 * $day);
+    $this->store1->setMultipleWithExpire(
+      array(
+        $this->randomName() => $this->objects[2],
+        $this->randomName() => $this->objects[3],
+      ),
+      -3 * $day
+    );
+    $this->store1->setWithExpireIfNotExists('yesterday', "you'd forgiven me", -1 * $day);
+    $this->store1->setWithExpire('still', "'til we say we're sorry", 2 * $day);
+
+    // Ensure only non-expired items are retrived.
+    $all = $this->store1->getAll();
+    $this->assertIdentical(count($all), 2);
+    foreach (array('troubles', 'still') as $key) {
+      $this->assertTrue(!empty($all[$key]));
+    }
+
+    // Perform garbage collection and confirm that the expired items are
+    // deleted from the database.
+    $this->store1->garbageCollection();
+    $result = db_query(
+      'SELECT name, value FROM {key_value_expire} WHERE collection = :collection',
+      array(
+        ':collection' => $this->collection1,
+      ))->fetchAll();
+    $this->assertIdentical(sizeof($result), 2);
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/StorageTestBase.php b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/StorageTestBase.php
index 1eec831..41d216c 100644
--- a/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/StorageTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/StorageTestBase.php
@@ -21,6 +21,13 @@
    */
   protected $storageClass;
 
+  /**
+   * An array of random stdClass objects.
+   *
+   * @var array
+   */
+  protected $objects = array();
+
   protected function setUp() {
     parent::setUp();
 
@@ -29,6 +36,11 @@ protected function setUp() {
 
     $this->store1 = new $this->storageClass($this->collection1);
     $this->store2 = new $this->storageClass($this->collection2);
+
+    // Create several objects for testing.
+    for ($i = 0; $i <= 5; $i++) {
+      $this->objects[$i] = $this->randomObject();
+    }
   }
 
   /**
@@ -40,49 +52,51 @@ public function testCRUD() {
     $this->assertIdentical($this->store2->getCollectionName(), $this->collection2);
 
     // Verify that an item can be stored.
-    $this->store1->set('foo', 'bar');
-    $this->assertIdentical('bar', $this->store1->get('foo'));
+    $this->store1->set('foo', $this->objects[0]);
+    $this->assertIdenticalObject($this->objects[0], $this->store1->get('foo'));
     // Verify that the other collection is not affected.
     $this->assertFalse($this->store2->get('foo'));
 
     // Verify that an item can be updated.
-    $this->store1->set('foo', 'baz');
-    $this->assertIdentical('baz', $this->store1->get('foo'));
+    $this->store1->set('foo', $this->objects[1]);
+    $this->assertIdenticalObject($this->objects[1], $this->store1->get('foo'));
     // Verify that the other collection is still not affected.
     $this->assertFalse($this->store2->get('foo'));
 
     // Verify that a collection/name pair is unique.
-    $this->store2->set('foo', 'other');
-    $this->assertIdentical('baz', $this->store1->get('foo'));
-    $this->assertIdentical('other', $this->store2->get('foo'));
+    $this->store2->set('foo', $this->objects[2]);
+    $this->assertIdenticalObject($this->objects[1], $this->store1->get('foo'));
+    $this->assertIdenticalObject($this->objects[2], $this->store2->get('foo'));
 
     // Verify that an item can be deleted.
     $this->store1->delete('foo');
     $this->assertFalse($this->store1->get('foo'));
 
     // Verify that the other collection is not affected.
-    $this->assertIdentical('other', $this->store2->get('foo'));
+    $this->assertIdenticalObject($this->objects[2], $this->store2->get('foo'));
     $this->store2->delete('foo');
     $this->assertFalse($this->store2->get('foo'));
 
     // Verify that multiple items can be stored.
     $values = array(
-      'foo' => 'bar',
-      'baz' => 'qux',
+      'foo' => $this->objects[3],
+      'bar' => $this->objects[4],
     );
     $this->store1->setMultiple($values);
 
     // Verify that multiple items can be retrieved.
-    $result = $this->store1->getMultiple(array('foo', 'baz'));
-    $this->assertIdentical($values, $result);
+    $result = $this->store1->getMultiple(array('foo', 'bar'));
+    foreach ($values as $j => $value) {
+      $this->assertIdenticalObject($value, $result[$j]);
+    }
 
     // Verify that the other collection was not affected.
     $this->assertFalse($this->store2->get('foo'));
-    $this->assertFalse($this->store2->get('baz'));
+    $this->assertFalse($this->store2->get('bar'));
 
     // Verify that all items in a collection can be retrieved.
     // Ensure that an item with the same name exists in the other collection.
-    $this->store2->set('foo', 'other');
+    $this->store2->set('foo', $this->objects[5]);
     $result = $this->store1->getAll();
     // Not using assertIdentical(), since the order is not defined for getAll().
     $this->assertEqual(count($result), count($values));
@@ -91,15 +105,15 @@ public function testCRUD() {
     }
     // Verify that all items in the other collection are different.
     $result = $this->store2->getAll();
-    $this->assertEqual($result, array('foo' => 'other'));
+    $this->assertEqual($result, array('foo' => $this->objects[5]));
 
     // Verify that multiple items can be deleted.
     $this->store1->deleteMultiple(array_keys($values));
     $this->assertFalse($this->store1->get('foo'));
     $this->assertFalse($this->store1->get('bar'));
-    $this->assertFalse($this->store1->getMultiple(array('foo', 'baz')));
+    $this->assertFalse($this->store1->getMultiple(array('foo', 'bar')));
     // Verify that the item in the other collection still exists.
-    $this->assertIdentical('other', $this->store2->get('foo'));
+    $this->assertIdenticalObject($this->objects[5], $this->store2->get('foo'));
   }
 
   /**
@@ -123,4 +137,29 @@ public function testNonExistingKeys() {
     $this->assertFalse(isset($values['foo']), "Key 'foo' not found.");
     $this->assertIdentical($values['bar'], 'baz');
   }
+
+  /**
+   * Tests the setIfNotExists() method.
+   */
+  public function testSetIfNotExists() {
+    $key = $this->randomName();
+    // Test that setIfNotExists() succeeds only the first time.
+    for ($i = 0; $i <= 1; $i++) {
+      // setIfNotExists() should be TRUE the first time (when $i is 0) and
+      // FALSE the second time (when $i is 1).
+      $this->assertEqual(!$i, $this->store1->setIfNotExists($key, $this->objects[$i]));
+      $this->assertIdenticalObject($this->objects[0], $this->store1->get($key));
+      // Verify that the other collection is not affected.
+      $this->assertFalse($this->store2->get($key));
+    }
+
+    // Remove the item and try to set it again.
+    $this->store1->delete($key);
+    $this->store1->setIfNotExists($key, $this->objects[1]);
+    // This time it should succeed.
+    $this->assertIdenticalObject($this->objects[1], $this->store1->get($key));
+    // Verify that the other collection is still not affected.
+    $this->assertFalse($this->store2->get($key));
+  }
+
 }
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 8331d55..28d95da 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -832,6 +832,44 @@ function system_schema() {
     'primary key' => array('collection', 'name'),
   );
 
+  $schema['key_value_expire'] = array(
+    'description' => 'Generic key/value storage table with an expiration.',
+    'fields' => array(
+      'collection' => array(
+        'description' => 'A named collection of key and value pairs.',
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'name' => array(
+        // KEY is an SQL reserved word, so use 'name' as the key's field name.
+        'description' => 'The key of the key/value pair.',
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'value' => array(
+        'description' => 'The value of the key/value pair.',
+        'type' => 'blob',
+        'not null' => TRUE,
+        'size' => 'big',
+        'translatable' => TRUE,
+      ),
+      'expire' => array(
+        'description' => 'The time since Unix epoch in seconds when this item expires. Defaults to the maximum possible time.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 2147483647,
+      ),
+    ),
+    'primary key' => array('collection', 'name'),
+    'indexes' => array(
+      'all' => array('name', 'collection', 'expire'),
+    ),
+  );
+
   $schema['menu_router'] = array(
     'description' => 'Maps paths to various callbacks (access, page and title)',
     'fields' => array(
@@ -2102,6 +2140,51 @@ function system_update_8022() {
 }
 
 /**
+ * Create the 'key_value_expire' table.
+ */
+function system_update_8023() {
+  $table = array(
+    'description' => 'Generic key/value storage table with an expiration.',
+    'fields' => array(
+      'collection' => array(
+        'description' => 'A named collection of key and value pairs.',
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'name' => array(
+        // KEY is an SQL reserved word, so use 'name' as the key's field name.
+        'description' => 'The key of the key/value pair.',
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'value' => array(
+        'description' => 'The value of the key/value pair.',
+        'type' => 'blob',
+        'not null' => TRUE,
+        'size' => 'big',
+        'translatable' => TRUE,
+      ),
+      'expire' => array(
+        'description' => 'The time since Unix epoch in seconds when this item expires. Defaults to the maximum possible time.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 2147483647,
+      ),
+    ),
+    'primary key' => array('collection', 'name'),
+    'indexes' => array(
+      'all' => array('name', 'collection', 'expire'),
+    ),
+  );
+
+  db_create_table('key_value_expire', $table);
+}
+
+/**
  * @} End of "defgroup updates-7.x-to-8.x".
  * The next series of updates should start at 9000.
  */
diff --git a/core/modules/user/lib/Drupal/user/TempStore.php b/core/modules/user/lib/Drupal/user/TempStore.php
new file mode 100644
index 0000000..9ebde36
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/TempStore.php
@@ -0,0 +1,197 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\user\TempStore.
+ */
+
+namespace Drupal\user;
+
+use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface;
+use Drupal\Core\Lock\LockBackendInterface;
+
+/**
+ * Stores and retrieves temporary data for a given owner.
+ *
+ * A TempStore can be used to make temporary, non-cache data available across
+ * requests. The data for the TempStore is stored in one key/value collection.
+ * TempStore data expires automatically after a given timeframe.
+ *
+ * The TempStore is different from a cache, because the data in it is not yet
+ * saved permanently and so it cannot be rebuilt. Typically, the TempStore
+ * might be used to store work in progress that is later saved permanently
+ * elsewhere, e.g. autosave data, multistep forms, or in-progress changes
+ * to complex configuration that are not ready to be saved.
+ *
+ * Each TempStore belongs belongs to a particular owner (e.g. a user, session,
+ * or process). Multiple owners may use the same key/value collection, and the
+ * owner is stored along with the key/value pair.
+ *
+ * Every key is unique within the collection, so the TempStore can check
+ * whether a particular key is already set by a different owner. This is
+ * useful for informing one owner that the data is already in use by another;
+ * for example, to let one user know that another user is in the process of
+ * editing certain data, or even to restrict other users from editing it at
+ * the same time. It is the responsibility of the implementation to decide
+ * when and whether one owner can use or update another owner's data.
+ *
+ * @todo We could add getIfOwner() or setIfOwner() methods to make this more
+ *   explicit.
+ */
+class TempStore {
+
+  /**
+   * The key/value storage object used for this data.
+   *
+   * @var Drupal\Core\KeyValueStore\KeyValueStoreExpireInterface;
+   */
+  protected $storage;
+
+  /**
+   * The lock object used for this data.
+   *
+   * @var Drupal\Core\Lock\LockBackendInterface
+   */
+  protected $lockBackend;
+
+  /**
+   * The owner key to store along with the data (e.g. a user or session ID).
+   *
+   * @var mixed
+   */
+  protected $owner;
+
+  /**
+   * The time to live for items in seconds.
+   *
+   * By default, data is stored for one week (604800 seconds) before expiring.
+   *
+   * @var int
+   *
+   * @todo Currently, this property is not exposed anywhere, and so the only
+   *   way to override it is by extending the class.
+   */
+  protected $expire = 604800;
+
+  /**
+   * Constructs a new object for accessing data from a key/value store.
+   *
+   * @param KeyValueStoreExpireInterface $storage
+   *   The key/value storage object used for this data. Each storage object
+   *   represents a particular collection of data and will contain any number
+   *   of key/value pairs.
+   * @param Drupal\Core\Lock\LockBackendInterface $lockBackend
+   *   The lock object used for this data.
+   * @param mixed $owner
+   *   The owner key to store along with the data (e.g. a user or session ID).
+   */
+  function __construct(KeyValueStoreExpirableInterface $storage, LockBackendInterface $lockBackend, $owner) {
+    $this->storage = $storage;
+    $this->lockBackend = $lockBackend;
+    $this->owner = $owner;
+  }
+
+  /**
+   * Retrieves a value from this TempStore for a given key.
+   *
+   * @param string $key
+   *   The key of the data to retrieve.
+   *
+   * @return mixed
+   *   The data associated with the key, or NULL if the key does not exist.
+   */
+  function get($key) {
+    if ($object = $this->storage->get($key)) {
+      return $object->data;
+    }
+  }
+
+  /**
+   * Stores a particular key/value pair only if the key doesn't already exist.
+   *
+   * @param string $key
+   *   The key of the data to check and store.
+   * @param mixed $value
+   *   The data to store.
+   *
+   * @return bool
+   *   TRUE if the data was set, or FALSE if it already existed.
+   */
+  function setIfNotExists($key, $value) {
+    $value = (object) array(
+      'owner' => $this->owner,
+      'data' => $value,
+      'updated' => REQUEST_TIME,
+    );
+    return $this->storage->setWithExpireIfNotExists($key, $value, $this->expire);
+  }
+
+  /**
+   * Stores a particular key/value pair in this TempStore.
+   *
+   * @param string $key
+   *   The key of the data to store.
+   * @param mixed $value
+   *   The data to store.
+   */
+  function set($key, $value) {
+    if (!$this->lockBackend->acquire($key)) {
+      $this->lockBackend->wait($key);
+      if (!$this->lockBackend->acquire($key)) {
+        throw new TempStoreException(format_string("Couldn't acquire lock to update item %key in %collection temporary storage.", array(
+          '%key' => $key,
+          '%collection' => $this->storage->collection,
+        )));
+      }
+    }
+
+    $value = (object) array(
+      'owner' => $this->owner,
+      'data' => $value,
+      'updated' => REQUEST_TIME,
+    );
+    $this->storage->setWithExpire($key, $value, $this->expire);
+    $this->lockBackend->release($key);
+  }
+
+  /**
+   * Returns the metadata associated with a particular key/value pair.
+   *
+   * @param string $key
+   *   The key of the data to store.
+   *
+   * @return mixed
+   *   An object with the owner and updated time if the key has a value, or
+   *   NULL otherwise.
+   */
+  function getMetadata($key) {
+    // Fetch the key/value pair and its metadata.
+    $object = $this->storage->get($key);
+    if ($object) {
+      // Don't keep the data itself in memory.
+      unset($object->data);
+      return $object;
+    }
+  }
+
+  /**
+   * Deletes data from the store for a given key and releases the lock on it.
+   *
+   * @param string $key
+   *   The key of the data to delete.
+   */
+  function delete($key) {
+    if (!$this->lockBackend->acquire($key)) {
+      $this->lockBackend->wait($key);
+      if (!$this->lockBackend->acquire($key)) {
+        throw new TempStoreException(format_string("Couldn't acquire lock to delete item %key from %collection temporary storage.", array(
+          '%key' => $key,
+          '%collection' => $this->storage->collection,
+        )));
+      }
+    }
+    $this->storage->delete($key);
+    $this->lockBackend->release($key);
+  }
+
+}
diff --git a/core/modules/user/lib/Drupal/user/TempStoreException.php b/core/modules/user/lib/Drupal/user/TempStoreException.php
new file mode 100644
index 0000000..497a2ef
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/TempStoreException.php
@@ -0,0 +1,13 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\user\TempStoreException.
+ */
+
+namespace Drupal\user;
+
+/**
+ * Defines the exception thrown if the TempStore cannot acquire a lock.
+ */
+class TempStoreException extends \Exception {}
diff --git a/core/modules/user/lib/Drupal/user/TempStoreFactory.php b/core/modules/user/lib/Drupal/user/TempStoreFactory.php
new file mode 100644
index 0000000..473d515
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/TempStoreFactory.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\user\TempStoreFactory.
+ */
+
+namespace Drupal\user;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\KeyValueStore\DatabaseStorageExpirable;
+use Drupal\Core\Lock\LockBackendInterface;
+
+/**
+ * Creates a key/value storage object for the current user or anonymous session.
+ */
+class TempStoreFactory {
+
+  /**
+   * The connection object used for this data.
+   *
+   * @var Drupal\Core\Database\Connection $connection
+   */
+  protected $connection;
+
+  /**
+   * The lock object used for this data.
+   *
+   * @var Drupal\Core\Lock\LockBackendInterface $lockBackend
+   */
+  protected $lockBackend;
+
+  /**
+   * Constructs a Drupal\user\TempStoreFactory object.
+   *
+   * @param Drupal\Core\Database\Connection $connection
+   *   The connection object used for this data.
+   * @param Drupal\Core\Lock\LockBackendInterface $lockBackend
+   *   The lock object used for this data.
+   */
+  function __construct(Connection $connection, LockBackendInterface $lockBackend) {
+    $this->connection = $connection;
+    $this->lockBackend = $lockBackend;
+  }
+
+  /**
+   * Creates a TempStore for the current user or anonymous session.
+   *
+   * @param string $collection
+   *   The collection name to use for this key/value store. This is typically
+   *   a shared namespace or module name, e.g. 'views', 'entity', etc.
+   * @param mixed $owner
+   *   (optional) The owner of this TempStore. By default, the TempStore is
+   *   owned by the currently authenticated user, or by the active anonymous
+   *   session if no user is logged in.
+   *
+   * @return Drupal\user\TempStore
+   *   An instance of the the key/value store.
+   */
+  function get($collection, $owner = NULL) {
+    // Use the currently authenticated user ID or the active user ID unless
+    // the owner is overridden.
+    if (!isset($owner)) {
+      $owner = $GLOBALS['user']->uid ?: session_id();
+    }
+
+    // Store the data for this collection in the database.
+    $storage = new DatabaseStorageExpirable($collection, array('connection' => $this->connection));
+    return new TempStore($storage, $this->lockBackend, $owner);
+  }
+
+}
diff --git a/core/modules/user/lib/Drupal/user/Tests/TempStoreDatabaseTest.php b/core/modules/user/lib/Drupal/user/Tests/TempStoreDatabaseTest.php
new file mode 100644
index 0000000..b0ef393
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Tests/TempStoreDatabaseTest.php
@@ -0,0 +1,154 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\user\Tests\TempStoreDatabaseTest.
+ */
+
+namespace Drupal\user\Tests;
+
+use Drupal\simpletest\UnitTestBase;
+use Drupal\user\TempStoreFactory;
+use Drupal\Core\Lock\DatabaseLockBackend;
+use Drupal\Core\Database\Database;
+
+/**
+ * Tests the TempStore namespace.
+ *
+ * @see Drupal\Core\TempStore\TempStore.
+ */
+class TempStoreDatabaseTest extends UnitTestBase {
+
+  /**
+   * A key/value store factory.
+   *
+   * @var Drupal\user\TempStoreFactory
+   */
+  protected $storeFactory;
+
+  /**
+   * The name of the key/value collection to set and retrieve.
+   *
+   * @var string
+   */
+  protected $collection;
+
+  /**
+   * An array of (fake) user IDs.
+   *
+   * @var array
+   */
+  protected $users = array();
+
+  /**
+   * An array of random stdClass objects.
+   *
+   * @var array
+   */
+  protected $objects = array();
+
+  public static function getInfo() {
+    return array(
+      'name' => 'TempStore',
+      'description' => 'Tests the temporary object storage system.',
+      'group' => 'TempStore',
+    );
+  }
+
+  protected function setUp() {
+    parent::setUp();
+
+    // Install system tables to test the key/value storage without installing a
+    // full Drupal environment.
+    module_load_install('system');
+    $schema = system_schema();
+    db_create_table('semaphore', $schema['semaphore']);
+    db_create_table('key_value_expire', $schema['key_value_expire']);
+
+    // Create a key/value collection.
+    $this->storeFactory = new TempStoreFactory(Database::getConnection(), new DatabaseLockBackend());
+    $this->collection = $this->randomName();
+
+    // Create several objects for testing.
+    for ($i = 0; $i <= 3; $i++) {
+      $this->objects[$i] = $this->randomObject();
+    }
+    // Create two mock users for testing.
+    for ($i = 0; $i <= 1; $i++) {
+      $this->users[$i] = mt_rand(500, 5000000);
+      $this->stores[$i] = $this->getStorePerUID($this->users[$i]);
+    }
+
+  }
+
+  protected function tearDown() {
+    db_drop_table('key_value_expire');
+    db_drop_table('semaphore');
+    parent::tearDown();
+  }
+
+  /**
+   * Tests the UserTempStore API.
+   */
+  public function testUserTempStore() {
+    $key = $this->randomName();
+    // Test that setIfNotExists() succeeds only the first time.
+    for ($i = 0; $i <= 1; $i++) {
+      // setIfNotExists() should fail the second time ($i = 1).
+      $this->assertEqual(!$i, $this->stores[0]->setIfNotExists($key, $this->objects[$i]));
+      $metadata = $this->stores[0]->getMetadata($key);
+      $this->assertEqual($this->users[0], $metadata->owner);
+      $this->assertIdenticalObject($this->objects[0], $this->stores[0]->get($key));
+      // Another user should get the same result.
+      $metadata = $this->stores[1]->getMetadata($key);
+      $this->assertEqual($this->users[0], $metadata->owner);
+      $this->assertIdenticalObject($this->objects[0], $this->stores[1]->get($key));
+    }
+
+    // Remove the item and try to set it again.
+    $this->stores[0]->delete($key);
+    $this->stores[0]->setIfNotExists($key, $this->objects[1]);
+    // This time it should succeed.
+    $this->assertIdenticalObject($this->objects[1], $this->stores[0]->get($key));
+
+    // This user can update the object.
+    $this->stores[0]->set($key, $this->objects[2]);
+    $this->assertIdenticalObject($this->objects[2], $this->stores[0]->get($key));
+    // The object is the same when another user loads it.
+    $this->assertIdenticalObject($this->objects[2], $this->stores[1]->get($key));
+    // Another user can update the object and become the owner.
+    $this->stores[1]->set($key, $this->objects[3]);
+    $this->assertIdenticalObject($this->objects[3], $this->stores[0]->get($key));
+    $this->assertIdenticalObject($this->objects[3], $this->stores[1]->get($key));
+    $metadata = $this->stores[1]->getMetadata($key);
+    $this->assertEqual($this->users[1], $metadata->owner);
+
+    // The first user should be informed that the second now owns the data.
+    $metadata = $this->stores[0]->getMetadata($key);
+    $this->assertEqual($this->users[1], $metadata->owner);
+
+    // Now manually expire the item (this is not exposed by the API) and then
+    // assert it is no longer accessible.
+    db_update('key_value_expire')
+      ->fields(array('expire' => REQUEST_TIME - 1))
+      ->condition('collection', $this->collection)
+      ->condition('name', $key)
+      ->execute();
+    $this->assertFalse($this->stores[0]->get($key));
+    $this->assertFalse($this->stores[1]->get($key));
+  }
+
+  /**
+   * Returns a TempStore for this collection belonging to the given user.
+   *
+   * @param int $uid
+   *   A user ID.
+   *
+   * @return Drupal\user\TempStore
+   *   The key/value store object.
+   */
+  protected function getStorePerUID($uid) {
+    return $this->storeFactory->get($this->collection, $uid);
+  }
+
+}
