This is a theoretical bug at this stage, I have yet to write a test to confirm it. But from reading the code it sure seems to be the way I think it is.
Problem/Motivation
Assume the following render array:
$render = [
'something_expensive' => [
'#lazy_builder' => 'my_service:somethingExpensive',
'#cache' => [
'contexts' => ['foo'],
'tags' => ['bar'],
],
],
];
Now imagine if my_service:somethingExpensive
generated something that varies by the user cache context.
Normally, if something_expensive
specified cache keys, then Renderer would run this code:
// Cache the processed element if both $pre_bubbling_elements and $elements
// have the metadata necessary to generate a cache ID.
if (isset($pre_bubbling_elements['#cache']['keys']) && isset($elements['#cache']['keys'])) {
if ($pre_bubbling_elements['#cache']['keys'] !== $elements['#cache']['keys']) {
throw new \LogicException('Cache keys may not be changed after initial setup. Use the contexts property instead to bubble additional metadata.');
}
$this->renderCache->set($elements, $pre_bubbling_elements);
// Add debug output to the renderable array on cache miss.
if ($this->rendererConfig['debug'] === TRUE) {
$render_stop = microtime(TRUE);
$elements = $this->addDebugOutput($elements, FALSE, $pre_bubbling_elements, $render_stop - $render_start);
}
// Update the render context; the render cache implementation may update
// the element, and it may have different bubbleable metadata now.
// @see \Drupal\Core\Render\PlaceholderingRenderCache::set()
$context->pop();
$context->push(new BubbleableMetadata());
$context->update($elements);
}
In PlaceholderingRenderCache, it finds that a highly dynamic cache context got added by the lazy builder, placeholders the whole thing after all and makes it so the cache context does not bubble up. But this only happens if there were cache keys! Meaning that in the example above, PlaceholderingRenderCache is never called for something_expensive
and all of this magic never happens.
The end result is that the really expensive thing that varies by user and the user cache context both make it to the parent render array elements, all the way up to the response. Making the whole page uncacheable. (Last bit is not true, HtmlRenderer takes care of that)
Examples that do work:
$render = [
'something_expensive' => [
'#lazy_builder' => 'my_service:somethingExpensive',
'#cache' => [
'contexts' => ['user'], // VARIES BY USER UPFRONT, GETS PLACEHOLDERED IMMEDIATELY
'tags' => ['bar'],
],
],
];
$render = [
'something_expensive' => [
'#lazy_builder' => 'my_service:somethingExpensive', // SETS USER CACHE CONTEXT
'#cache' => [
'keys' => ['my', 'expensive', 'thing'], // TRIGGERS CACHE AND THUS LATE PLACEHOLDERING
'contexts' => ['user'],
'tags' => ['bar'],
],
],
];
Steps to reproduce
Use example above, witness that 'user' bubbles up to the page.
Proposed resolution
Late placeholder even if there are no cache keys set.
Merge request link
N/A
Remaining tasks
Fix
User interface changes
N/A
API changes
N/A
Data model changes
N/A
Release notes snippet
N/A