Skip to content

Commit

Permalink
Issue #1011614 by catch, pillarsdotnet: Fixed Theme registry can grow…
Browse files Browse the repository at this point in the history
… too large for MySQL max_allowed_packet() and memcache default slab size.
  • Loading branch information
webchick committed Oct 29, 2011
1 parent 4060a32 commit e56afba
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 27 deletions.
166 changes: 145 additions & 21 deletions includes/theme.inc
Original file line number Diff line number Diff line change
Expand Up @@ -237,18 +237,33 @@ function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callb
/**
* Get the theme registry.
*
* @param $complete
* Optional boolean to indicate whether to return the complete theme registry
* array or an instance of the ThemeRegistry class. If TRUE, the complete
* theme registry array will be returned. This is useful if you want to
* foreach over the whole registry, use array_* functions or inspect it in a
* debugger. If FALSE, an instance of the ThemeRegistry class will be
* returned, this provides an ArrayObject which allows it to be accessed
* with array syntax and isset(), and should be more lightweight
* than the full registry. Defaults to TRUE.
*
* @return
* The theme registry array if it has been stored in memory, NULL otherwise.
* The complete theme registry array, or an instance of the ThemeRegistry
* class.
*/
function theme_get_registry() {
static $theme_registry = NULL;
function theme_get_registry($complete = TRUE) {
static $theme_registry = array();
$key = (int) $complete;

if (!isset($theme_registry)) {
if (!isset($theme_registry[$key])) {
list($callback, $arguments) = _theme_registry_callback();
$theme_registry = call_user_func_array($callback, $arguments);
if (!$complete) {
$arguments[] = FALSE;
}
$theme_registry[$key] = call_user_func_array($callback, $arguments);
}

return $theme_registry;
return $theme_registry[$key];
}

/**
Expand All @@ -268,7 +283,7 @@ function _theme_registry_callback($callback = NULL, array $arguments = array())
}

/**
* Get the theme_registry cache from the database; if it doesn't exist, build it.
* Get the theme_registry cache; if it doesn't exist, build it.
*
* @param $theme
* The loaded $theme object as returned by list_themes().
Expand All @@ -277,23 +292,34 @@ function _theme_registry_callback($callback = NULL, array $arguments = array())
* oldest first order.
* @param $theme_engine
* The name of the theme engine.
* @param $complete
* Whether to load the complete theme registry or an instance of the
* ThemeRegistry class.
*
* @return
* The theme registry array, or an instance of the ThemeRegistry class.
*/
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;
function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL, $complete = TRUE) {
if ($complete) {
// Check the theme registry cache; if it exists, use it.
$cached = cache_get("theme_registry:$theme->name");
if (isset($cached->data)) {
$registry = $cached->data;
}
else {
// If not, build one and cache it.
$registry = _theme_build_registry($theme, $base_theme, $theme_engine);
// Only persist this registry if all modules are loaded. This assures a
// complete set of theme hooks.
if (module_load_all(NULL)) {
_theme_save_registry($theme, $registry);
}
}
return $registry;
}
else {
// If not, build one and cache it.
$registry = _theme_build_registry($theme, $base_theme, $theme_engine);
// Only persist this registry if all modules are loaded. This assures a
// complete set of theme hooks.
if (module_load_all(NULL)) {
_theme_save_registry($theme, $registry);
}
return new ThemeRegistry('theme_registry:runtime:' . $theme->name, 'cache');
}
return $registry;
}

/**
Expand All @@ -312,6 +338,104 @@ function drupal_theme_rebuild() {
cache_clear_all('theme_registry', 'cache', TRUE);
}

/**
* Builds the run-time theme registry.
*
* Extends DrupalCacheArray 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 loaded and used to update the run-time cache.
*/
class ThemeRegistry Extends DrupalCacheArray {

/**
* Whether the partial registry can be persisted to the cache.
*
* This is only allowed if all modules and the request method is GET. theme()
* should be very rarely called on POST requests and this avoids polluting
* the runtime cache.
*/
protected $persistable;

/**
* The complete theme registry array.
*/
protected $completeRegistry;

function __construct($cid, $bin) {
$this->cid = $cid;
$this->bin = $bin;
$this->persistable = module_load_all(NULL) && $_SERVER['REQUEST_METHOD'] == 'GET';

$data = array();
if ($this->persistable && $cached = cache_get($this->cid, $this->bin)) {
$data = $cached->data;
}
else {
$complete_registry = theme_get_registry();
if ($this->persistable) {
// If there is no runtime cache stored, fetch the full theme registry,
// but then initialize each value to NULL. This allows
// offsetExists() to function correctly on non-registered theme hooks
// without triggering a call to resolveCacheMiss().
$data = array_fill_keys(array_keys($complete_registry), NULL);
$this->set($this->cid, $data, $this->bin);
$this->completeRegistry = $complete_registry;
}
else {
$data = $complete_registry;
}
}
$this->storage = $data;
}

public function offsetExists($offset) {
// Since the theme registry allows for theme hooks to be requested that
// are not registered, just check the existence of the key in the registry.
// Use array_key_exists() here since a NULL value indicates that the theme
// hook exists but has not yet been requested.
return array_key_exists($offset, $this->storage);
}

public function offsetGet($offset) {
// If the offset is set but empty, it is a registered theme hook that has
// not yet been requested. Offsets that do not exist at all were not
// registered in hook_theme().
if (isset($this->storage[$offset])) {
return $this->storage[$offset];
}
elseif (array_key_exists($offset, $this->storage)) {
return $this->resolveCacheMiss($offset);
}
}

public function resolveCacheMiss($offset) {
if (!isset($this->completeRegistry)) {
$this->completeRegistry = theme_get_registry();
}
$this->storage[$offset] = $this->completeRegistry[$offset];
if ($this->persistable) {
$this->persist($offset);
}
return $this->storage[$offset];
}

public function set($cid, $data, $bin, $lock = TRUE) {
$lock_name = $cid . ':' . $bin;
if (!$lock || lock_acquire($lock_name)) {
if ($cached = cache_get($cid, $this->bin)) {
// Use array merge instead of union so that filled in values in $data
// overwrite empty values in the current cache.
$data = array_merge($cached->data, $data);
}
cache_set($cid, $data, $bin);
if ($lock) {
lock_release($lock_name);
}
}
}
}

/**
* Process a single implementation of hook_theme().
*
Expand Down Expand Up @@ -771,7 +895,7 @@ function theme($hook, $variables = array()) {

if (!isset($hooks)) {
drupal_theme_initialize();
$hooks = theme_get_registry();
$hooks = theme_get_registry(FALSE);
}

// If an array of hook candidates were passed, use the first one that has an
Expand Down
6 changes: 1 addition & 5 deletions modules/contextual/contextual.module
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,12 @@ function contextual_element_info() {
* @see contextual_pre_render_links()
*/
function contextual_preprocess(&$variables, $hook) {
static $hooks;

// Nothing to do here if the user is not permitted to access contextual links.
if (!user_access('access contextual links')) {
return;
}

if (!isset($hooks)) {
$hooks = theme_get_registry();
}
$hooks = theme_get_registry(FALSE);

// Determine the primary theme function argument.
if (!empty($hooks[$hook]['variables'])) {
Expand Down
2 changes: 1 addition & 1 deletion modules/simpletest/tests/theme.test
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ class ThemeItemListUnitTest extends DrupalWebTestCase {
/**
* Unit tests for theme_links().
*/
class ThemeLinksUnitTest extends DrupalUnitTestCase {
class ThemeLinksTest extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Links',
Expand Down

0 comments on commit e56afba

Please sign in to comment.