From ff8562b85efe8f145ae8f64ababbdf8faa8f6298 Mon Sep 17 00:00:00 2001
From: Steven Merrill <steven.merrill@gmail.com>
Date: Thu, 1 May 2014 23:04:02 -0400
Subject:  Steven Merrill: Generated with Drush iq

---
 core/core.services.yml                             |   7 +
 core/includes/bootstrap.inc                        |  10 +-
 core/includes/common.inc                           |   2 +-
 core/lib/Drupal/Core/Cache/ApcBackend.php          | 320 +++++++++++++++++++++
 core/lib/Drupal/Core/Cache/ApcBackendFactory.php   |  30 ++
 core/lib/Drupal/Core/Cache/DatabaseBackend.php     |  15 +-
 core/lib/Drupal/Core/Cache/InconsistentBackend.php | 260 +++++++++++++++++
 .../Core/Cache/InconsistentBackendFactory.php      |  46 +++
 .../Cache/GenericCacheBackendUnitTestBase.php      |   8 +-
 .../Tests/Core/Cache/InconsistentBackendTest.php   |  69 +++++
 10 files changed, 752 insertions(+), 15 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Cache/ApcBackend.php
 create mode 100644 core/lib/Drupal/Core/Cache/ApcBackendFactory.php
 create mode 100644 core/lib/Drupal/Core/Cache/InconsistentBackend.php
 create mode 100644 core/lib/Drupal/Core/Cache/InconsistentBackendFactory.php
 create mode 100644 core/tests/Drupal/Tests/Core/Cache/InconsistentBackendTest.php

diff --git a/core/core.services.yml b/core/core.services.yml
index 19b0b72..3a7604b 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -22,6 +22,13 @@ services:
     arguments: ['@request', '@theme.negotiator']
     tags:
       - { name: cache.context}
+  cache_inconsistent_factory:
+    class: Drupal\Core\Cache\InconsistentBackendFactory
+    arguments: ['@settings']
+    calls:
+      - [setContainer, ['@service_container']]
+  cache.backend.apc:
+    class: Drupal\Core\Cache\ApcBackendFactory
   cache.backend.database:
     class: Drupal\Core\Cache\DatabaseBackendFactory
     arguments: ['@database']
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index fe34fc5..a8390d5 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -901,8 +901,12 @@ function drupal_serve_page_from_cache(stdClass $cache, Response $response, Reque
   }
   $response->headers->set('Cache-Control', 'public, max-age=' . $max_age);
 
+  // HTTP's caching mechanism works with timestamps with precision up to a
+  // second, so drop any fractions of a second.
+  $created = (int) $cache->created;
+
   // Entity tag should change if the output changes.
-  $response->setEtag($cache->created);
+  $response->setEtag($created);
 
   // See if the client has provided the required HTTP headers.
   $if_modified_since = $request->server->has('HTTP_IF_MODIFIED_SINCE') ? strtotime($request->server->get('HTTP_IF_MODIFIED_SINCE')) : FALSE;
@@ -910,7 +914,7 @@ function drupal_serve_page_from_cache(stdClass $cache, Response $response, Reque
 
   if ($if_modified_since && $if_none_match
       && $if_none_match == $response->headers->get('etag') // etag must match
-      && $if_modified_since == $cache->created) {  // if-modified-since must match
+      && $if_modified_since == $created) {  // if-modified-since must match
     $response->setStatusCode(304);
     return;
   }
@@ -923,7 +927,7 @@ function drupal_serve_page_from_cache(stdClass $cache, Response $response, Reque
     drupal_add_http_header($name, $value);
   }
 
-  $response->setLastModified(\DateTime::createFromFormat('U', $cache->created));
+  $response->setLastModified(\DateTime::createFromFormat('U', $created));
 
   // HTTP/1.0 proxies does not support the Vary header, so prevent any caching
   // by sending an Expires date in the past. HTTP/1.1 clients ignores the
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 79b2224..cd3eb3b 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -2897,7 +2897,7 @@ function drupal_page_set_cache(Response $response, Request $request) {
       ),
       'tags' => HtmlViewSubscriber::convertHeaderToCacheTags($response->headers->get('X-Drupal-Cache-Tags')),
       'expire' => Cache::PERMANENT,
-      'created' => REQUEST_TIME,
+      'created' => microtime(TRUE),
     );
 
     $cache->data['headers'] = $response->headers->all();
diff --git a/core/lib/Drupal/Core/Cache/ApcBackend.php b/core/lib/Drupal/Core/Cache/ApcBackend.php
new file mode 100644
index 0000000..397dbf7
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/ApcBackend.php
@@ -0,0 +1,320 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Cache\ApcBackend.
+ */
+
+namespace Drupal\Core\Cache;
+
+/**
+ * Defines an APC user cache implementation.
+ *
+ * @ingroup cache
+ */
+class ApcBackend implements CacheBackendInterface {
+
+  /**
+   * @var string
+   */
+  protected $tagPrefix;
+
+  /**
+   * @var string
+   */
+  protected $bin;
+
+  /**
+   * Constructs an ApcBackend object.
+   *
+   * @param string $bin
+   *   The cache bin for which the object is created.
+   */
+  public function __construct($bin) {
+    $bin = 'cache_' . $bin;
+    $this->bin = $bin;
+    $this->tagPrefix = 'tags-' . $this->bin . '_';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get($cid, $allow_invalid = FALSE) {
+    $cids = array($cid);
+    $cache = $this->getMultiple($cids, $allow_invalid);
+    return reset($cache);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setMultiple(array $items) {
+    $prefixed_items = array();
+    foreach ($items as $cid => $item) {
+      $prefixed_items[$this->bin . '-' . $cid] = $item;
+    }
+    apc_store($prefixed_items);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMultiple(&$cids, $allow_invalid = FALSE) {
+    $prefixed_cids = array();
+    foreach ($cids as $cid) {
+      $prefixed_cids[$this->bin . '-' . $cid] = $cid;
+    }
+    $result = apc_fetch(array_keys($prefixed_cids));
+    $cache = array();
+    foreach ($result as $item) {
+      if ($item = $this->prepareItem($item, $allow_invalid)) {
+        $cache[$item->cid] = $item;
+      }
+    }
+    $cids = array_diff($cids, array_keys($cache));
+    return $cache;
+  }
+
+  /**
+   * Prepares a cached item.
+   *
+   * Checks that items are either permanent or did not expire, and unserializes
+   * data as appropriate.
+   *
+   * @param object $cache
+   *   An item loaded from cache_get() or cache_get_multiple().
+   * @param bool $allow_invalid
+   *   If FALSE, the method returns FALSE if the cache item is not valid.
+   *
+   * @return mixed|false
+   *   The item with data unserialized as appropriate and a property indicating
+   *   whether the item is valid, or FALSE if there is no valid item to load.
+   */
+  protected function prepareItem($cache, $allow_invalid) {
+    if (!isset($cache->data)) {
+      return FALSE;
+    }
+
+    $cache->tags = $cache->tags ? explode(' ', $cache->tags) : array();
+
+    $checksum = $this->checksumTags($cache->tags);
+
+    // Check if deleteTags() has been called with any of the entry's tags.
+    if ($cache->checksum_deletions != $checksum['deletions']) {
+      return FALSE;
+    }
+
+    // Check expire time.
+    $cache->valid = $cache->expire == Cache::PERMANENT || $cache->expire >= REQUEST_TIME;
+
+    // Check if invalidateTags() has been called with any of the entry's tags.
+    if ($cache->checksum_invalidations != $checksum['invalidations']) {
+      $cache->valid = FALSE;
+    }
+
+    if (!$allow_invalid && !$cache->valid) {
+      return FALSE;
+    }
+
+    // Unserialize and return the cached data.
+    if ($cache->serialized) {
+      $cache->data = unserialize($cache->data);
+    }
+
+    return $cache;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) {
+    $flat_tags = $this->flattenTags($tags);
+    $deleted_tags = &drupal_static('Drupal\Core\Cache\ApcBackend::deletedTags', array());
+    $invalidated_tags = &drupal_static('Drupal\Core\Cache\ApcBackend::invalidatedTags', array());
+    // Remove tags that were already deleted or invalidated during this request
+    // from the static caches so that another deletion or invalidation can
+    // occur.
+    foreach ($flat_tags as $tag) {
+      if (isset($deleted_tags[$tag])) {
+        unset($deleted_tags[$tag]);
+      }
+      if (isset($invalidated_tags[$tag])) {
+        unset($invalidated_tags[$tag]);
+      }
+    }
+    $checksum = $this->checksumTags($flat_tags);
+    $fields = (object) array(
+      'serialized' => 0,
+      'created' => microtime(TRUE),
+      'expire' => $expire,
+      'tags' => implode(' ', $flat_tags),
+      'checksum_invalidations' => $checksum['invalidations'],
+      'checksum_deletions' => $checksum['deletions'],
+      'cid' => $cid,
+      'data' => $data,
+      'serialized' => 0,
+    );
+    apc_store(array($this->bin . '-' . $cid => $fields));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function delete($cid) {
+    $this->deleteMultiple(array($cid));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteMultiple(array $cids) {
+    $prefixed_cids = array();
+    foreach ($cids as $cid) {
+      $prefixed_cids[$this->bin . '-' . $cid] = $cid;
+    }
+    apc_delete($prefixed_cids);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteTags(array $tags) {
+    $tag_cache = &drupal_static('Drupal\Core\Cache\CacheBackendInterface::tagCache', array());
+    $deleted_tags = &drupal_static('Drupal\Core\Cache\ApcBackend::deletedTags', array());
+    foreach ($this->flattenTags($tags) as $tag) {
+      // Only delete tags once per request unless they are written again.
+      if (isset($deleted_tags[$tag])) {
+        continue;
+      }
+      $deleted_tags[$tag] = TRUE;
+      unset($tag_cache[$tag]);
+      $prefixed_tags = $this->tagPrefix . $tag;
+    }
+    apc_delete($prefixed_tags);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteAll() {
+    apc_delete(new APCIterator('user', '/^' . $this->bin . '-/'));
+    apc_delete(new APCIterator('user', '/^' . $this->tagPrefix . '/'));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function invalidate($cid) {
+    $this->invalidateMultiple(array($cid));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function invalidateMultiple(array $cids) {
+    $this->deleteMultiple($cids);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function invalidateTags(array $tags) {
+    foreach ($tags as $tag) {
+      apc_inc($this->tagPrefix . $tag);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function invalidateAll() {
+    $this->deleteAll();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function garbageCollection() {
+    // No work to be done.
+  }
+
+  /**
+   * 'Flattens' a tags array into an array of strings.
+   *
+   * @param array $tags
+   *   Associative array of tags to flatten.
+   *
+   * @return array
+   *   An indexed array of flattened tag identifiers.
+   */
+  protected function flattenTags(array $tags) {
+    if (isset($tags[0])) {
+      return $tags;
+    }
+
+    $flat_tags = array();
+    foreach ($tags as $namespace => $values) {
+      if (is_array($values)) {
+        foreach ($values as $value) {
+          $flat_tags[] = "$namespace:$value";
+        }
+      }
+      else {
+        $flat_tags[] = "$namespace:$values";
+      }
+    }
+    return $flat_tags;
+  }
+
+  /**
+   * Returns the sum total of validations for a given set of tags.
+   *
+   * @param array $flat_tags
+   *   Array of flat tags.
+   *
+   * @return array
+   *   Array containing the sums of all invalidations and deletions.
+   *
+   * @see \Drupal\Core\Cache\ApcBackend::flattenTags()
+   */
+  protected function checksumTags($flat_tags) {
+    $tag_cache = &drupal_static('Drupal\Core\Cache\CacheBackendInterface::tagCache', array());
+
+    $checksum = array(
+      'invalidations' => 0,
+      'deletions' => 0,
+    );
+
+    $query_tags = array_diff($flat_tags, array_keys($tag_cache));
+    if ($query_tags) {
+      $db_tags = apc_fetch($flat_tags);
+      $tag_cache += $db_tags;
+
+      // Fill static cache with empty objects for tags not found in the APC user
+      // cache.
+      $tag_cache += array_fill_keys(array_diff($query_tags, array_keys($db_tags)), $checksum);
+    }
+
+    foreach ($flat_tags as $tag) {
+      $checksum['invalidations'] += $tag_cache[$tag]['invalidations'];
+      $checksum['deletions'] += $tag_cache[$tag]['deletions'];
+    }
+
+    return $checksum;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isEmpty() {
+    $apc = new ApcIterator('user');
+    return $apc->getTotalCount > 0;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function removeBin() {
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Cache/ApcBackendFactory.php b/core/lib/Drupal/Core/Cache/ApcBackendFactory.php
new file mode 100644
index 0000000..86e9c5f
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/ApcBackendFactory.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Cache\ApcBackendFactory.
+ */
+
+namespace Drupal\Core\Cache;
+
+/**
+ * Defines the APC cache backend factory.
+ */
+use Drupal\Component\Utility\Settings;
+
+class ApcBackendFactory implements CacheFactoryInterface {
+
+  /**
+   * Instantiates a cache backend class for a given cache bin.
+   *
+   * @param string $bin
+   *   The cache bin for which a cache backend object should be returned.
+   *
+   * @return \Drupal\Core\Cache\ApcBackend
+   *   An APC cache backend for the given bin.
+   */
+  public function get($bin) {
+    return new ApcBackend($bin);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackend.php b/core/lib/Drupal/Core/Cache/DatabaseBackend.php
index 3ae9eac..26e1bd0 100644
--- a/core/lib/Drupal/Core/Cache/DatabaseBackend.php
+++ b/core/lib/Drupal/Core/Cache/DatabaseBackend.php
@@ -25,7 +25,6 @@ class DatabaseBackend implements CacheBackendInterface {
    */
   protected $bin;
 
-
   /**
    * The database connection.
    *
@@ -180,7 +179,7 @@ protected function doSet($cid, $data, $expire, $tags) {
     $checksum = $this->checksumTags($flat_tags);
     $fields = array(
       'serialized' => 0,
-      'created' => REQUEST_TIME,
+      'created' => microtime(TRUE),
       'expire' => $expire,
       'tags' => implode(' ', $flat_tags),
       'checksum_invalidations' => $checksum['invalidations'],
@@ -460,15 +459,15 @@ protected function flattenTags(array $tags) {
   /**
    * Returns the sum total of validations for a given set of tags.
    *
-   * @param array $tags
+   * @param array $flat_tags
    *   Array of flat tags.
    *
-   * @return int
-   *   Sum of all invalidations.
+   * @return array
+   *   Array containing the sums of all invalidations and deletions.
    *
    * @see \Drupal\Core\Cache\DatabaseBackend::flattenTags()
    */
-  protected function checksumTags($flat_tags) {
+  protected function checksumTags(array $flat_tags) {
     $tag_cache = &drupal_static('Drupal\Core\Cache\CacheBackendInterface::tagCache', array());
 
     $checksum = array(
@@ -594,8 +593,10 @@ public function schemaDefinition() {
         ),
         'created' => array(
           'description' => 'A Unix timestamp indicating when the cache entry was created.',
-          'type' => 'int',
+          'type' => 'numeric',
           'not null' => TRUE,
+          'precision' => 14,
+          'scale' => 3,
           'default' => 0,
         ),
         'serialized' => array(
diff --git a/core/lib/Drupal/Core/Cache/InconsistentBackend.php b/core/lib/Drupal/Core/Cache/InconsistentBackend.php
new file mode 100644
index 0000000..343c0a5
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/InconsistentBackend.php
@@ -0,0 +1,260 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Cache\InconsistentBackend.
+ */
+
+namespace Drupal\Core\Cache;
+
+use Drupal\Core\State\StateInterface;
+
+/**
+ * Defines a backend with an inconsistent and consistent backend chain.
+ *
+ * We always use the inconsistent (but faster) backend when reading (get())
+ * entries from cache, but check whether they were created before the last write
+ * (set()) to this (chained) cache backend. Those cache entries that were
+ * created before the last write are discarded, but we use their cache IDs to
+ * then read them from the consistent (slower) cache backend instead; at the
+ * same time we update the inconsistent cache backend so that the next read will
+ * hit the (faster) inconsistent backend again. Hence we can guarantee that the
+ * cache entries we return are all up-to-date, and maximally exploit the faster
+ * cache backend.
+ * This cache backend uses and maintains a "last write timestamp" to determine
+ * which cache entries should be discarded.
+ *
+ * @ingroup cache
+ */
+class InconsistentBackend implements CacheBackendInterface {
+
+  /**
+   * State entry key prefix, for the bin-specific entry to track the last write.
+   */
+  const LAST_WRITE_TIMESTAMP_PREFIX = 'cache_last_write_timestamp_';
+
+  /**
+   * @var string
+   */
+  protected $bin;
+
+  /**
+   * The consistent cache backend.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $consistentBackend;
+
+  /**
+   * The inconsistent cache backend.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $inconsistentBackend;
+
+  /**
+   * The state instance to get and set the last write timestamp.
+   *
+   * @var \Drupal\Core\State\StateInterface
+   */
+  protected $state;
+
+  /**
+   * The time at which the last write to this cache bin happened.
+   *
+   * @var int
+   */
+  protected $lastWriteTimestamp;
+
+  /**
+   * Constructs a InconsistentBackend object.
+   *
+   * @param \Drupal\Core\Cache\CacheBackendInterface
+   *   The consistent cache backend.
+   * @param \Drupal\Core\Cache\CacheBackendInterface
+   *   The inconsistent cache backend.
+   * @param string $bin
+   *   The cache bin for which the object is created.
+   * @param \Drupal\Core\State\StateInterface $state
+   *   The state instance to get and set the last write timestamp.
+   */
+  public function __construct(CacheBackendInterface $consistent_backend, CacheBackendInterface $inconsistent_backend, $bin, StateInterface $state) {
+    $this->consistentBackend = $consistent_backend;
+    $this->inconsistentBackend = $inconsistent_backend;
+    $this->bin = 'cache_' . $bin;
+    $this->state = $state;
+    $this->lastWriteTimestamp = NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get($cid, $allow_invalid = FALSE) {
+    $cids = array($cid);
+    $cache = $this->getMultiple($cids, $allow_invalid);
+    return reset($cache);
+  }
+
+  /**
+   * Initialize the last write timestamp using the state instance.
+   */
+  public function lastWriteTimestamp() {
+    if ($this->lastWriteTimestamp === NULL) {
+      $this->lastWriteTimestamp = $this->state->get(self::LAST_WRITE_TIMESTAMP_PREFIX . $this->bin);
+    }
+    return $this->lastWriteTimestamp;
+  }
+
+  protected function setLastWriteTimestamp($timestamp) {
+    $this->lastWriteTimestamp = $timestamp;
+  }
+
+  /**
+   * Mark the inconsistent cache bin as outdated because of a write.
+   */
+  protected function markAsOutdated() {
+    $now = microtime(TRUE);
+    if ($now > $this->lastWriteTimestamp()) {
+      $this->setLastWriteTimestamp($now);
+      $this->state->set(self::LAST_WRITE_TIMESTAMP_PREFIX . $this->bin, $this->lastWriteTimestamp);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setMultiple(array $items) {
+    $this->markAsOutdated();
+    $this->consistentBackend->setMultiple($items);
+    $this->inconsistentBackend->setMultiple($items);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMultiple(&$cids, $allow_invalid = FALSE) {
+    // Retrieve as many cache entries as possible from the (faster) inconsistent
+    // backend. (Some cache entries may have been created before the last write
+    // to this cache bin and therefore be stale/wrong/inconsistent.)
+    $cache = array();
+    $last_write_timestamp = $this->lastWriteTimestamp();
+    if ($last_write_timestamp) {
+      foreach ($this->inconsistentBackend->getMultiple($cids, $allow_invalid) as $item) {
+        if ($item->created < $last_write_timestamp) {
+          $cids[] = $item->cid;
+        }
+        else {
+          $cache[$item->cid] = $item;
+        }
+      }
+    }
+
+    // If there were any cache entries that weren't available in the
+    // inconsistent backend, retrieve them from the consistent backend and store
+    // them in the inconsistent one.
+    if ($cids) {
+      foreach ($this->consistentBackend->getMultiple($cids, $allow_invalid) as $item) {
+        $cache[$item->cid] = $item;
+        $this->inconsistentBackend->set($item->cid, $item->data);
+      }
+    }
+
+    return $cache;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) {
+    $this->markAsOutdated();
+    $this->consistentBackend->set($cid, $data, $expire, $tags);
+    $this->inconsistentBackend->set($cid, $data, $expire, $tags);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function delete($cid) {
+    $this->markAsOutdated();
+    $this->consistentBackend->deleteMultiple(array($cid));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteMultiple(array $cids) {
+    $this->markAsOutdated();
+    $this->consistentBackend->deleteMultiple($cids);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteTags(array $tags) {
+    $this->markAsOutdated();
+    $this->consistentBackend->deleteTags($tags);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteAll() {
+    $this->markAsOutdated();
+    $this->consistentBackend->deleteAll();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function invalidate($cid) {
+    $this->invalidateMultiple(array($cid));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function invalidateMultiple(array $cids) {
+    $this->markAsOutdated();
+    $this->consistentBackend->invalidateMultiple($cids);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function invalidateTags(array $tags) {
+    $this->markAsOutdated();
+    $this->consistentBackend->invalidateTags($tags);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function invalidateAll() {
+    $this->markAsOutdated();
+    $this->consistentBackend->invalidateAll();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function garbageCollection() {
+    $this->consistentBackend->garbageCollection();
+    $this->inconsistentBackend->garbageCollection();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isEmpty() {
+    return $this->consistentBackend->isEmpty();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function removeBin() {
+    $this->consistentBackend->removeBin();
+    $this->inconsistentBackend->removeBin();
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Cache/InconsistentBackendFactory.php b/core/lib/Drupal/Core/Cache/InconsistentBackendFactory.php
new file mode 100644
index 0000000..2671166
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/InconsistentBackendFactory.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Cache\InconsistentBackendFactory.
+ */
+
+namespace Drupal\Core\Cache;
+
+/**
+ * Defines the chained inconsistent cache backend factory.
+ */
+class InconsistentBackendFactory extends CacheFactory {
+
+  /**
+   * Instantiates an inconsistent cache backend class for a given cache bin.
+   *
+   * @param string $bin
+   *   The cache bin for which a cache backend object should be returned.
+   *
+   * @return \Drupal\Core\Cache\CacheBackendInterface
+   *   The cache backend object associated with the specified bin.
+   */
+  public function get($bin) {
+    $consistent_service = 'cache.backend.database';
+    $inconsistent_service = 'cache.backend.apc';
+
+    $cache_settings = $this->settings->get('cache');
+    if (isset($cache_settings['inconsistent_cache']) && is_array($cache_settings['inconsistent_cache'])) {
+      if (!empty($cache_settings['inconsistent_cache']['consistent'])) {
+        $consistent_service = $cache_settings['inconsistent_cache']['consistent'];
+      }
+      if (!empty($cache_settings['inconsistent_cache']['inconsistent'])) {
+        $inconsistent_service = $cache_settings['inconsistent_cache']['inconsistent'];
+      }
+    }
+
+    return new InconsistentBackend(
+      $this->container->get($consistent_service)->get($bin),
+      $this->container->get($inconsistent_service)->get($bin),
+      $bin,
+      $this->container->get('state')
+    );
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Cache/GenericCacheBackendUnitTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Cache/GenericCacheBackendUnitTestBase.php
index b390429..675bd2f 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Cache/GenericCacheBackendUnitTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Cache/GenericCacheBackendUnitTestBase.php
@@ -142,7 +142,7 @@ public function testSetGet() {
     $this->assert(is_object($cached), "Backend returned an object for cache id test1.");
     $this->assertIdentical($with_backslash, $cached->data);
     $this->assertTrue($cached->valid, 'Item is marked as valid.');
-    $this->assertEqual($cached->created, REQUEST_TIME, 'Created time is correct.');
+    $this->assertTrue($cached->created >= REQUEST_TIME && $cached->created < microtime(TRUE), 'Created time is correct.');
     $this->assertEqual($cached->expire, Cache::PERMANENT, 'Expire time is correct.');
 
     $this->assertIdentical(FALSE, $backend->get('test2'), "Backend does not contain data for cache id test2.");
@@ -151,7 +151,7 @@ public function testSetGet() {
     $this->assert(is_object($cached), "Backend returned an object for cache id test2.");
     $this->assertIdentical(array('value' => 3), $cached->data);
     $this->assertTrue($cached->valid, 'Item is marked as valid.');
-    $this->assertEqual($cached->created, REQUEST_TIME, 'Created time is correct.');
+    $this->assertTrue($cached->created >= REQUEST_TIME && $cached->created < microtime(TRUE), 'Created time is correct.');
     $this->assertEqual($cached->expire, REQUEST_TIME + 3, 'Expire time is correct.');
 
     $backend->set('test3', 'foobar', REQUEST_TIME - 3);
@@ -159,7 +159,7 @@ public function testSetGet() {
     $cached = $backend->get('test3', TRUE);
     $this->assert(is_object($cached), 'Backend returned an object for cache id test3.');
     $this->assertFalse($cached->valid, 'Item is marked as valid.');
-    $this->assertEqual($cached->created, REQUEST_TIME, 'Created time is correct.');
+    $this->assertTrue($cached->created >= REQUEST_TIME && $cached->created < microtime(TRUE), 'Created time is correct.');
     $this->assertEqual($cached->expire, REQUEST_TIME - 3, 'Expire time is correct.');
   }
 
@@ -248,7 +248,7 @@ public function testGetMultiple() {
     $this->assert(isset($ret['test7']), "Existing cache id test7 is set.");
     // Test return - ensure that objects has expected properties.
     $this->assertTrue($ret['test2']->valid, 'Item is marked as valid.');
-    $this->assertEqual($ret['test2']->created, REQUEST_TIME, 'Created time is correct.');
+    $this->assertTrue($ret['test2']->created >= REQUEST_TIME && $ret['test2']->created < microtime(TRUE), 'Created time is correct.');
     $this->assertEqual($ret['test2']->expire, Cache::PERMANENT, 'Expire time is correct.');
     // Test return - ensure it does not contain nonexistent cache ids.
     $this->assertFalse(isset($ret['test19']), "Nonexistent cache id test19 is not set.");
diff --git a/core/tests/Drupal/Tests/Core/Cache/InconsistentBackendTest.php b/core/tests/Drupal/Tests/Core/Cache/InconsistentBackendTest.php
new file mode 100644
index 0000000..f17d80b
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Cache/InconsistentBackendTest.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Drupal\Tests\Core\Cache;
+
+use Drupal\Core\Cache\MemoryBackend;
+use Drupal\Core\Cache\InconsistentBackend;
+use Drupal\Core\State\StateInterface;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @group Cache
+ */
+class InconsistentBackendTest extends UnitTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => '',
+      'description' => '',
+      'group' => 'Cache'
+    );
+  }
+
+  public function setUp() {
+    $this->consistent_cache = new MemoryBackend("foo");
+    $this->inconsistent_cache = new MemoryBackend("foo");
+    $this->bin = "somebin";
+  }
+
+  public function testGetFromInconsistentCache() {
+    $state = $this->getMock("Drupal\Core\State\StateInterface");
+    $state->expects($this->once())
+      ->method("get")
+      ->will($this->returnValue(1234));
+
+    $inconsistent_backend = new InconsistentBackend(
+      $this->consistent_cache,
+      $this->inconsistent_cache,
+      $this->bin,
+      $state
+    );
+
+    $this->consistent_cache->set("foo", "baz");
+    $inconsistent_backend->set("foo", "bar");
+    $this->assertEquals("bar", $inconsistent_backend->get("foo")->data);
+  }
+
+  public function testFallThroughToConsistentCache() {
+    $state = $this->getMock("Drupal\Core\State\StateInterface");
+    $state->expects($this->once())
+      ->method("get")
+      ->will($this->returnValue(time() + 99999));
+
+    $inconsistent_cache = $this->getMock("Drupal\Core\Cache\MemoryBackend", array("set"), array("foo"));
+    $inconsistent_cache->expects($this->exactly(2))
+      ->method("set");
+
+    $inconsistent_backend = new InconsistentBackend(
+      $this->consistent_cache,
+      $inconsistent_cache,
+      $this->bin,
+      $state
+    );
+
+    $inconsistent_cache->set("foo", "bar");
+    $this->consistent_cache->set("foo", "baz");
+    $this->assertEquals("baz", $inconsistent_backend->get("foo")->data);
+  }
+
+}
-- 
1.9.2

