Index: includes/bootstrap.inc
===================================================================
--- includes/bootstrap.inc	(revision 5912)
+++ includes/bootstrap.inc	(working copy)
@@ -1909,3 +1909,153 @@
   // @see http://bugs.php.net/bug.php?id=32330
   session_set_save_handler('sess_open', 'sess_close', 'sess_read', 'sess_write', 'sess_destroy_sid', 'sess_gc');
 }
+
+/**
+ * Extends ArrayObject to enable it to be used as a caching wrapper.
+ *
+ * This class should be extended by systems that need to cache large amounts
+ * of data and have it represented as an array to calling functions. These
+ * arrays can become very large, so ArrayObject is used to allow different
+ * strategies to be used for caching internally (lazy loading, building caches
+ * over time etc.). This can dramatically reduce the amount of data that needs
+ * to be loaded from cache backends on each request, and memory usage from
+ * static caches of that same data.
+ *
+ * Note that array_* functions do not work with ArrayObject.
+ *
+ * By default, the class accounts for caches where calling functions might
+ * request keys in the array that won't exist even after a cache rebuild. This
+ * prevents situations where a cache rebuild would be triggered over and over
+ * due to a 'missing' item. These cases are stored internally as a value of
+ * NULL. This means that the offsetGet() and offsetExists() methods
+ * must be overridden if caching an array where the top level values can
+ * legitimately be NULL, and where $object->offsetExists() needs to correctly
+ * return (equivalent to array_key_exists() vs. isset()). This should not
+ * be necessary in the majority of cases.
+ *
+ * Classes extending this class will usually need to override at least the
+ * resolveCacheMiss() method to have a working implementation.
+ *
+ * @see ThemeRegistry
+ */
+class CacheArrayObject extends ArrayObject {
+
+  /**
+   * A cid to pass to cache_set() and cache_get().
+   */
+  private $cid;
+
+  /**
+   * A bin to pass to cache_set() and cache_get().
+   */
+  private $bin;
+
+  /**
+   * An array of keys to add to the cache at the end of the request.
+   */
+  protected $add_keys = array();
+
+  /**
+   * Constructor.
+   *
+   * @param $cid
+   *   The cid for the array being cached.
+   * @param $bin
+   *   The bin to cache the array.a
+   */
+  function __construct($cid, $bin) {
+    $this->cid = $cid;
+    $this->bin = $bin;
+
+    if ($cached = cache_get($this->cid, $this->bin)) {
+      parent::__construct($cached->data);
+    }
+    else {
+      parent::__construct(array());
+    }
+  }
+
+  public function offsetExists($offset) {
+    if (!parent::offsetExists($offset)) {
+     $this->resolveCacheMiss($offset);
+    }
+    return parent::offsetGet($offset) !== NULL;
+  }
+
+  public function offsetGet($offset) {
+    if (!parent::offsetExists($offset)) {
+      $this->resolveCacheMiss($offset);
+    }
+    return parent::offsetGet($offset);
+  }
+
+  /**
+   * Add an offset and value to the ArrayObject cache.
+   *
+   * @param $offset
+   *   The array offset that was request.
+   */
+  public function persist($offset) {
+    $this->add_keys[] = $offset;
+  }
+
+  /**
+   * Resolve a cache miss.
+   *
+   * When an offset is not found in the object,  this is treated as a cache
+   * miss. This method allows classes implementing the interface to look up
+   * the actual value and allow it to be cached.
+   *
+   * @param $offset
+   *   The offset that was requested.
+   */
+  public function resolveCacheMiss($offset) {
+    // Setting the offset to NULL causes offsetGet() and offsetSet()
+    // to act as if the item does not exist in the array. Most implementations
+    // will want to override this method to consult the structure being cached
+    // and set offset to the value if it exists there.
+    $this->persist($offset, NULL);
+  }
+
+  /**
+   * Write to the persistent cache.
+   *
+   * @param $cid
+   *   The cache ID.
+   * @param $bin
+   *   The cache bin.
+   * @param $data
+   *   The data to write to the persistent cache.
+   * @param $lock
+   *   Whether to acquire a lock before writing to cache.
+   */
+  public function set($cid, $data, $bin, $lock = TRUE) {
+    $lock_name = $cid . ':' . $bin;
+    if (!$lock || lock_acquire($lock_name)) {
+      if ($cached = cache_get($cid, $bin)) {
+        $data = array_merge($cached->data, $data);
+      }
+      cache_set($cid, $data, $bin);
+      if ($lock) {
+        lock_release($lock_name);
+      }
+    }
+  }
+
+  public function __destruct() {
+    if (!empty($this->add_keys)) {
+      // Since this method merges with the existing cache entry if it exists,
+      // ensure that only one process can update the cache item at any one time.
+      // This ensures that different requests can't overwrite each others'
+      // partial version of the cache and should help to avoid stampedes.
+      // When a lock cannot be acquired, the cache will not be written by
+      // that request. To implement locking for cache misses, override
+      // __construct().
+      $data = array();
+      foreach ($this->add_keys as $key) {
+        $data[$key] = parent::offsetGet($key);
+      }
+      $this->set($this->cid, $data, $this->bin);
+    }
+  }
+}
Index: includes/theme.inc
===================================================================
--- includes/theme.inc	(revision 5912)
+++ includes/theme.inc	(working copy)
@@ -215,6 +215,80 @@
 }
 
 /**
+ * Builds the run-time theme registry.
+ *
+ * This class extends the CacheArrayObject class to allow the theme registry to be
+ * accessed as a complete registry, while internally caching only the parts of
+ * the registry that are actually in use on the site. On cache misses the
+ * complete theme registry is loade and used to update the run-time cache.
+ */
+class ThemeRegistry Extends CacheArrayObject {
+  private $theme;
+  private $base_theme;
+  private $theme_engine;
+  private $unregistered_cid;
+  private $persistable;
+
+  function __construct($theme, $base_theme = NULL, $theme_engine = NULL) {
+    // Make the arguments available to the rest of the class.
+    $this->theme = $theme;
+    $this->base_theme = $base_theme;
+    $this->theme_engine = $theme_engine;
+    // Maintain a global cache item for registered theme hooks.
+    $this->cid = 'theme_registry:runtime:' . $theme->name;
+    $this->bin = 'cache';
+    // @todo: detect full bootstrap.
+    $this->persistable = TRUE;
+    // Maintain a per-page cache item for hooks that aren't registered. This
+    // will usually be un-implemented theme suggestions. Suggestions can be
+    // passed into theme() based on any value, so the per-page cache prevents
+    // any one cache item from growing to large.
+    $this->unregistered_cid = 'theme_registry:unregistered:' . $theme->name . ':' . hash('sha256', $_GET['q']);
+
+    $data = array();
+    if ($cached = cache_get($this->cid, $this->bin)) {
+      $data = $cached->data;
+    }
+    if ($cached = cache_get($this->unregistered_cid, $this->bin)) {
+      $data = array_merge($data, $cached->data);
+    }
+    ArrayObject::__construct($data);
+  }
+
+  public function resolveCacheMiss($offset) {
+    $complete_registry = _theme_load_registry_complete($this->theme, $this->base_theme, $this->theme_engine);
+    $value = isset($complete_registry[$offset]) ? $complete_registry[$offset] :  NULL;
+    parent::offsetSet($offset, $value);
+    if ($this->persistable) {
+      $this->persist($offset);
+    }
+  }
+
+  public function __destruct() {
+    if (!empty($this->add_keys)) {
+      $registered = array();
+      $unregistered = array();
+      foreach ($this->add_keys as $offset) {
+        $value = $this->offsetGet($offset);
+        if ($value === NULL) {
+          $unregistered[$offset] = $value;
+        }
+        else {
+          $registered[$offset] = $value;
+        }
+      }
+      if (!empty($registered)) {
+        $this->set($this->cid, $registered, $this->bin);
+      }
+      if (!empty($unregistered)) {
+        // Don't bother locking for the per-page cache.
+        $this->set($this->unregistered_cid, $unregistered, $this->bin, FALSE);
+      }
+    }
+  }
+}
+
+/**
  * Get the theme_registry cache from the database; if it doesn't exist, build
  * it.
  *
@@ -227,20 +301,31 @@
  *   The name of the theme engine.
  */
 function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL) {
-  // Check the theme registry cache; if it exists, use it.
-  $cache = cache_get("theme_registry:$theme->name", 'cache');
-  if (isset($cache->data)) {
-    $registry = $cache->data;
-  }
-  else {
-    // If not, build one and cache it.
-    $registry = _theme_build_registry($theme, $base_theme, $theme_engine);
-    _theme_save_registry($theme, $registry);
-  }
+  $registry = new ThemeRegistry($theme, $base_theme, $theme_engine);
   _theme_set_registry($registry);
 }
 
 /**
+ * Load the full theme registry from cache, or rebuild it.
+ */
+function _theme_load_registry_complete($theme, $base_theme = NULL, $theme_engine = NULL) {
+  static $registry;
+  if (!isset($registry)) {
+    // Check the theme registry cache; if it exists, use it.
+    $cache = cache_get("theme_registry:$theme->name", 'cache');
+    if (isset($cache->data)) {
+      $registry = $cache->data;
+    }
+    else {
+      // If not, build one and cache it.
+      $registry = _theme_build_registry($theme, $base_theme, $theme_engine);
+      _theme_save_registry($theme, $registry);
+    }
+  }
+  return $registry;
+}
+
+/**
  * Write the theme_registry cache into the database.
  */
 function _theme_save_registry($theme, $registry) {
