Quantcast
Channel: Issues for Drupal core
Viewing all articles
Browse latest Browse all 291687

Auto-placeholdering does not always happen if #lazy_builder did not specify cache keys

$
0
0

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.

N/A

Remaining tasks

Fix

User interface changes

N/A

API changes

N/A

Data model changes

N/A

Release notes snippet

N/A


Viewing all articles
Browse latest Browse all 291687

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>