Problem/Motivation

The toolbar takes ~15 ms and ~17,000 function calls to render. On every page load. That's ridiculously slow. See #21.

The Toolbar module has optimized the rendering of the menu tree, by aggressively caching it. But… the rendering of the "main" toolbar (the black bar at the top of the page, with the white trays underneath it if you click any of the black items) is not yet cached.

Proposed resolution

We have cache context bubbling (#2429257: Bubble cache contexts). Use that: let every item returned by hook_toolbar() specify cache contexts if it depends on contextual information.

See #22 for detailed performance numbers. But here's the gist of it:

Remaining tasks

Review.

User interface changes

None.

API changes

There's a case of "API refinement":

  • Roughly speaking: no API changes.
  • Strictly speaking: we now require hook_toolbar() implementations to associate cache contexts if they are dynamic. Since that would be an API break in Drupal 8.x.0, marking this is as critical.

Original report by Wim Leers

Problem/Motivation

Given a relatively optimized setup (PHP 5.5 with OpCache enabled, no xdebug/xhprof), super simple pages like /contact take 190–200 ms on my computer to generate. That's ridiculously slow.

So I did some profiling and easily found a piece of tasty low-hanging performance fruit: toolbar_pre_render().

The Toolbar module has optimized the rendering of the menu tree, by aggressively caching it. But… the rendering of the "main" toolbar (the black bar at the top of the page, with the white trays underneath it if you click any of the black items) is not yet cached.
Render caching the "main" toolbar seems easy enough, but the user tray is personalized for each user and the shortcuts tray is not by default, but might be. The easiest solution for that is to cache the toolbar per user, but that easily becomes very expensive.

Proposed resolution

The only mechanism that we have to cache per role but still be able to personalize per user is #post_render_cache. If we apply that, we get the following improvements (measured using the excellent https://drupal.org/project/webprofiler), using PHP 5.5 with OpCache enabled:

/
  Before After Δ
DB queries 183 150 -33
Response time (ms) Variance too high.
Memory (MB) 13.8 13.2 -0.6
Cache gets 74 66 -8
Config gets 50 46 -4
/node/1
  Before After Δ
DB queries 173 140 -33
Response time (ms) Variance too high.
Memory (MB) 13.8 13.2 -0.6
Cache gets 73 65 -8
Config gets 54 50 -4
/contact
  Before After Δ
DB queries 136 103 -33
Response time (ms) ~190 ~170 -~20
Memory (MB) 11 10.2 -0.8
Cache gets 46 38 -8
Config gets 41 37 -4

Remaining tasks

Review.

User interface changes

None.

API changes

None.

Postponed until

#2099131: Use #pre_render pattern for entity render caching

CommentFileSizeAuthor
#86 2254865.86.patch13.56 KBalexpott
#86 83-86-interdiff.txt3.38 KBalexpott
#83 interdiff.txt3.73 KBlauriii
#83 toolbar_pre_render-2254865-83.patch13.12 KBlauriii
#73 interdiff.txt5.02 KBlauriii
#73 toolbar_pre_render-2254865-73.patch12.92 KBlauriii
#67 interdiff-2254865-62-65.txt811 bytesJeroenT
#65 toolbar_pre_render-2254865-65.patch11.2 KBJeroenT
#65 interdiff-2254865-62-65.patch811 bytesJeroenT
#62 toolbar_pre_render-2254865-62.patch11.22 KBborisson_
#62 interdiff.txt1.92 KBborisson_
#58 toolbar_pre_render-2254865-58.patch11.04 KBborisson_
#58 interdiff.txt1.48 KBborisson_
#56 interdiff.txt6.43 KBlauriii
#56 toolbar_pre_render-2254865-56.patch11 KBlauriii
#42 toolbar_pre_render-2254865-43.patch11.72 KBlauriii
#42 toolbar_pre_render-2254865-test-only-43.patch5.74 KBlauriii
#40 interdiff.txt5.13 KBlauriii
#40 toolbar_pre_render-2254865-40.patch11.99 KBlauriii
#40 toolbar_pre_render-2254865-test-only-40.patch6.01 KBlauriii
#28 interdiff.txt1008 bytesWim Leers
#28 toolbar_prerender-2254865-28.patch6.36 KBWim Leers
#27 interdiff.txt707 bytesWim Leers
#27 toolbar_prerender-2254865-26.patch5.7 KBWim Leers
#22 xhprof runs.zip289.23 KBWim Leers
#22 histogram_interleaved.png8.35 KBWim Leers
#22 histogram_facet.png7.13 KBWim Leers
#22 ab runs.zip7.42 KBWim Leers
#22 toolbar_prerender-2254865-22.patch5.48 KBWim Leers
#17 toolbar_pre_render-2254865-17.patch9.1 KBWim Leers
#16 interdiff.txt6.61 KBWim Leers
#16 toolbar_pre_render-2254865-16.patch17.3 KBWim Leers
#11 toolbar_pre_render-2254865-11.patch12.6 KBWim Leers
#1 toolbar_pre_render-2254865-1.patch15.17 KBWim Leers
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Wim Leers’s picture

Status: Active » Needs review
FileSize
15.17 KB

In the process, I've discovered a new set of problems previously undiscovered:

  1. in theory, we should have a single drupal_render() call to render the entire page. However, in practice, DefaultHtmlFragmentRenderer renders the special page_top and page_bottom regions first, and only then renders everything else. This causes #post_render_cache callbacks inside those two special regions to be executed again when rendering everything else.
  2. drupal_render_collect_post_render_cache() was running too early
  3. drupal_render_children() by definition is a recursive call, but it was pretending to be the root call every time it rendered a child, therefore executing #post_render_cache callbacks far too early (toolbar.module uses drupal_render_children(), hence I discovered this)

Status: Needs review » Needs work

The last submitted patch, 1: toolbar_pre_render-2254865-1.patch, failed testing.

jessebeach’s picture

Issue summary: View changes
Status: Needs work » Postponed

Given the significant changes coming down the pipe in #2099131: Use #pre_render pattern for entity render caching and the nature of the failures in #1, perhaps we should postpone this work for the moment. We've seen errors like this before in our #pre_render issue work.

Wim Leers’s picture

Agreed.

Wim Leers’s picture

Working on #2241235: Shortcut/ShortcutSet entity types should use cache tags in the mean time, that will allow this patch to make an even bigger impact, because it'll allow us to render cache the shortcuts as well!

Wim Leers’s picture

Title: toolbar_pre_render() runs on every page and is responsible for ~20ms/~30 DB queries » [PP-1] toolbar_pre_render() runs on every page and is responsible for ~20ms/~30 DB queries
jessebeach’s picture

Status: Postponed » Active

This is unpostponed.

sun’s picture

Wait, why are we (ab)using the render cache for everything now?

The issue summary mainly talks about caching data?

Also, it refers to per-role caching, but the toolbar actually needs to be cached per-session. (?)

Wim Leers’s picture

#7: I had left it marked as postponed as per #6 ;)

#8:

  1. The toolbar is a rendered piece of HTML. Rendering it is pretty expensive, because of the top-level menu that needs to be looked up, the shortcuts that need to be looked up, and so on. Replacing that with a single render cache get is much cheaper. What's abusive about that?
  2. What is session-specific about the toolbar? It's the same on a per-role basis, with user-specific things in there.
Wim Leers’s picture

Title: [PP-1] toolbar_pre_render() runs on every page and is responsible for ~20ms/~30 DB queries » toolbar_pre_render() runs on every page and is responsible for ~20ms/~30 DB queries
Wim Leers’s picture

Title: toolbar_pre_render() runs on every page and is responsible for ~20ms/~30 DB queries » toolbar_pre_render() runs on every page and is responsible for ~20ms/33 DB queries/0.8 MB memory/
Issue summary: View changes
Status: Active » Needs review
Related issues: +#2068471: Normalize Controller/View-listener behavior with a Page object
FileSize
12.6 KB

Rerolled, now that both #2099131: Use #pre_render pattern for entity render caching and #2241235: Shortcut/ShortcutSet entity types should use cache tags have landed. No interdiff; too many changes, and this is the first patch up for review anyway.

Now, instead of re-rendering the shortcut links on every page load, they're also render cached.

So, overall, in a Standard profile install with this patch:

  • toolbar is render cached per role, theme and language, and contains two placeholders
  • the first placeholder is for the contents of the Shortcut module tray, a #post_render_cache callback replaces this with the shortcut tray contents, which are render cached themselves as well (to prevent all shortcut entities from being loaded on every page load)
  • the second placeholder is for the contents of the User module tray, a #post_render_cache callback replaces this with the user tray contents, which are not cheap to render but are user-specific

The difference has become even slightly more stark now (D8 @ a0961ff65b273483f1a1d2f92318b1d21bfaceae): at /contact without this patch, 136 DB queries are performed (down from 139 when this issue was opened), and with this patch, 103 DB queries are performed (down from 108 when this issue was opened)… so a difference of 33 DB queries!.


There's only one special hunk in this patch, which is the one for DefaultHtmlFragmentRenderer::render(). Instead of the crazy hacks I applied in #1, I propose a much saner approach here: don't let DefaultHtmlFragmentRenderer::render() abuse the renderable page array and drupal_render() in unintended ways.
DefaultHtmlFragmentRenderer::render() must create a HtmlPage, and HtmlPage expects both "top-content" (setBodyTop()) and "bottom-content" (setBodyBottom()) to be separate things, and both of those most already be rendered HTML. But page_bottom and page_top are part of the renderable array like all other regions, even though they should be rendered separately. So the fix is simple: we pull them out of the renderable page array and treat them separately, just like HtmlPage expects us to do.
Without this change, any #post_render_cache callback that was applied for $page->setBodyTop(drupal_render($page_array['page_top'])); would be re-applied for $page->setContent(drupal_render($page_array));, because $page_array is a superset of $page_array['page_top'], hence drupal_render() re-applies #post_render_cache callbacks.

(Yes, we're still cleaning up the half-baked mess that was introduced in [#2170551]…)

jessebeach’s picture

Look at some performance numbers for the patch in #11. This is my standard testing scenario: a single, simple article with body, image and tags. I compared 8.x warm cache to PATCH warm cache, running 6 page loads for each and picking the fastest run to compare.

Run #537518dac850a Run #537519f63f4ab Diff Diff%
Number of Function Calls 63,865 54,218 -9,647 -15.1%
Incl. Wall Time (microsec) 438,697 374,189 -64,508 -14.7%
Incl. CPU (microsecs) 417,160 357,420 -59,740 -14.3%
Incl. MemUse (bytes) 13,329,400 12,649,272 -680,128 -5.1%
Incl. PeakMemUse (bytes) 13,396,008 12,692,208 -703,800 -5.3%

I get a 64ms(!) drop in page load time for a single article. Wowzers, that's awesome!

jessebeach’s picture

  1. +++ b/core/includes/common.inc
    @@ -3506,7 +3506,7 @@ function drupal_render_children(&$element, $children_keys = NULL) {
       $output = '';
       foreach ($children_keys as $key) {
         if (!empty($element[$key])) {
    -      $output .= drupal_render($element[$key]);
    +      $output .= drupal_render($element[$key], TRUE);
         }
       }
    

    It seems like this was outright missing from the #pre_render work we did, no?

  2. +++ b/core/lib/Drupal/Core/Page/DefaultHtmlFragmentRenderer.php
    @@ -51,15 +52,30 @@ public function render(HtmlFragment $fragment, $status_code = 200) {
    +    if (isset($page_array['page_top'])) {
    +      $page_top = $page_array['page_top'];
    +      unset($page_array['page_top']);
    +      $page->setBodyTop(drupal_render($page_top));
    +    }
    

    Just curious. Why the unsetting? Wouldn't this renderable just be marked as 'printed' after rendering?

  3. +++ b/core/lib/Drupal/Core/Page/DefaultHtmlFragmentRenderer.php
    @@ -51,15 +52,30 @@ public function render(HtmlFragment $fragment, $status_code = 200) {
    +    $page_cache_tags = NestedArray::mergeDeep(
    +      isset($page_top) ? $page_top['#cache']['tags'] : array(),
    +      isset($page_bottom) ? $page_bottom['#cache']['tags'] : array(),
    +      $page_array['#cache']['tags'],
    +      // Enforce the generic "content" cache tag on all pages.
    +      // @todo Remove the "content" cache tag. @see https://drupal.org/node/2124957
    +      array('content' => TRUE)
    +    );
    

    Oh, you unset the page top and bottom above so that the cache tags won't get mingled together with the content of the page, right?

Wim Leers’s picture

  1. Yes :)
  2. drupal_render() has some things that descend down subtrees that have been marked as 'printed'. This sidesteps that problem. Plus, it's conceptually more honest: we have three parts of the page that must be rendered independently, so then let's do it truly independently.
  3. Kind of. The exact reason is explained in #11, below the horizontal rule.

Status: Needs review » Needs work

The last submitted patch, 11: toolbar_pre_render-2254865-11.patch, failed testing.

Wim Leers’s picture

Title: toolbar_pre_render() runs on every page and is responsible for ~20ms/33 DB queries/0.8 MB memory/ » [PP-1] toolbar_pre_render() runs on every page and is responsible for ~20ms/33 DB queries/0.8 MB memory/
Status: Needs work » Postponed
FileSize
17.3 KB
6.61 KB

Sadly, the promises of this patch were too good to be true.

I made a HUGE mistake: I failed to consider the "menu" tray (called toolbar_administration in code)… which is of course rendered menu tree. I was caching this with the rest of the toolbar, so render cached per role, theme and language, instead of creating another post-render cache placeholder for it (hence the test failures). If I fix that, and render that tray in another #post_render_cache callback, the deltas look like this:

  • -3 DB queries
  • +1.5 MB memory
  • +2 cache gets
  • -4 config gets

That's of course not great.


The only sane solution is to finally fix #1805054: Cache localized, access filtered, URL resolved, and rendered menu trees. So I'm going to work on that now.

What this patch does overall is still valid, because the different parts of the toolbar have different personalization aspects (for menu trees, see #1805054: Cache localized, access filtered, URL resolved, and rendered menu trees; for the shortcuts tray: each user may have a different shortcut set, but many have the same; for the user tray: this is obviously per-user, but is cheap to render).

So, this is now postponed on #1805054, once that is done, we can reroll this patch and it should still be a net win. But the huge win lies in #1805054.

Wim Leers’s picture

Title: [PP-1] toolbar_pre_render() runs on every page and is responsible for ~20ms/33 DB queries/0.8 MB memory/ » [PP-1] toolbar_pre_render() runs on every page and is responsible for ~8ms/5000 function calls
FileSize
9.1 KB

Lots of blockers for #1805054: Cache localized, access filtered, URL resolved, and rendered menu trees went in, I'm currently rerolling #1805054: Cache localized, access filtered, URL resolved, and rendered menu trees. I rerolled this patch as well to get a sense of the performance improvements of this in *today*'s HEAD with #1805054's patch (WIP) applied.

This can save an additional 6 DB queries per page, ~8 ms, and 5000 function calls.

I might merge this into #1805054, we'll see how that evolves. In the mean time, uploading an updated patch. The majority of the changes in toolbar.module from the above patches has already been committed to Drupal core, hence this patch is about half as big.

jessebeach’s picture

In my queue to review.

Wim Leers’s picture

#1805054: Cache localized, access filtered, URL resolved, and rendered menu trees is moving forward again, and once that lands, we'll be able to do this too.

Wim Leers’s picture

Title: [PP-1] toolbar_pre_render() runs on every page and is responsible for ~8ms/5000 function calls » toolbar_pre_render() runs on every page and is responsible for ~8ms/5000 function calls
Status: Postponed » Needs work
Wim Leers’s picture

Title: toolbar_pre_render() runs on every page and is responsible for ~8ms/5000 function calls » toolbar_pre_render() runs on every page and is responsible for ~15ms/17000 function calls
Parent issue: » #1744302: [meta] Resolve known performance regressions in Drupal 8
Related issues: +#1805054: Cache localized, access filtered, URL resolved, and rendered menu trees

To decide whether this is still relevant, I did some profiling and benchmarking. The answer is a resounding "YES", this is still worth doing.

Testing as UID 1, the root user. The costs for other authenticated users would be even higher.

Aspect HEAD Early return in Toolbar::preRenderToolbar() Delta
Function calls 63,109 46,263 -17K, or -27%
Median of 200 ab requests 105 ms 89 ms -16 ms, or -15%

If the solution for this turns out to be impossible to do in 8.1, this will therefore be bumped to critical as per the guidelines in #1744302: [meta] Resolve known performance regressions in Drupal 8

Wim Leers’s picture

Issue summary: View changes
Priority: Major » Critical
Status: Needs work » Needs review
Related issues: +#2429257: Bubble cache contexts
FileSize
5.48 KB
7.42 KB
7.13 KB
8.35 KB
289.23 KB

The patch to solve this is completely different than previous patches. It is much simpler. Because we now have #2429257: Bubble cache contexts, so we let the system take care of it instead of making hook_toolbar() complicating implementations.

The performance difference is very stark.


This is profiled as UID 1 on the frontpage, using the Standard install profile. Fresh install. UID 1 because that is *cheaper* in terms of access checking (lots of things return early for UID 1) and because that user can access the toolbar.

In other words: the numbers below present the minimum expected improvement, for more complex set-ups and for non-admin users, the improvement will be much bigger.

Profiling (XHProf)

Before
Function calls: 63,109
Mem: 20.53 MB
Peak mem: 20.75 MB
After
Function calls: 48,284 (-15K, -23.5%)
Mem: 19.56 MB (-1 MB, -5%)
Peak mem: 19.74 MB (-1 MB, -5%)

Benchmarking (ab -n 1000 -c 1)

Before
Requests per second:    9.58 [#/sec] (mean)
Time per request:       104.428 [ms] (mean)
Time per request:       104.428 [ms] (mean, across all concurrent requests)
Transfer rate:          169.78 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       1
Processing:   100  104   1.3    104     112
Waiting:       90   94   1.3     93     102
Total:        100  104   1.3    104     113
After
Requests per second:    11.07 [#/sec] (mean)
Time per request:       90.358 [ms] (mean)
Time per request:       90.358 [ms] (mean, across all concurrent requests)
Transfer rate:          189.48 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       0
Processing:    87   90   1.2     90      96
Waiting:       77   80   1.2     80      86
Total:         87   90   1.2     90      96

Before vs. after


IS updated. Also marking this as critical, because AFAICT this would be considered an API break in Drupal 8.x.0, and it exceeds the ">=10 ms faster for warm caches" requirement.

Wim Leers’s picture

Issue summary: View changes
Wim Leers’s picture

Oh, and as a bonus, this should also reduce the number of failures in #2429617: Make D8 2x as fast: Dynamic Page Cache: context-dependent page caching (for *all* users!).

Status: Needs review » Needs work

The last submitted patch, 22: toolbar_prerender-2254865-22.patch, failed testing.

dawehner’s picture

  1. +++ b/core/modules/contextual/contextual.module
    @@ -14,12 +14,20 @@
    +    '#cache' => [
    +      'contexts' => [
    +        'user.permissions',
    +      ],
    +    ],
    

    Why do we have to explicit care about those cache contexts here. Won't things bubble up automatically? OH I see there is a call to permissions in here.

  2. +++ b/core/modules/toolbar/toolbar.module
    @@ -59,6 +59,10 @@ function toolbar_page_top(array &$page_top) {
         '#access' => \Drupal::currentUser()->hasPermission('access toolbar'),
    +    '#cache' => [
    +      'keys' => ['toolbar'],
    +      'contexts' => ['user.permissions'],
    +    ],
    

    So ideally #access should be able to contain this cacheablity metadata, right?

  3. +++ b/core/modules/tour/tour.module
    --- a/core/modules/user/user.module
    +++ b/core/modules/user/user.module
    
    +++ b/core/modules/user/user.module
    +++ b/core/modules/user/user.module
    @@ -1358,10 +1358,23 @@ function user_toolbar() {
    

    The entry edit profile is also per user ...

  4. +++ b/core/modules/user/user.module
    @@ -1358,10 +1358,23 @@ function user_toolbar() {
    +        'contexts' => [
    +          // Cacheable per user, because the current user's name is shown.
    +          'user',
    

    Will this be automatically done via a placeholder in the future?

Wim Leers’s picture

Status: Needs work » Needs review
Issue tags: +Needs tests
FileSize
5.7 KB
707 bytes

Easy fix, that points to another bug in the cacheability metadata.

Obviously, all of this still needs test coverage. The toolbar module has test coverage, but not for this area.

Wim Leers’s picture

FileSize
6.36 KB
1008 bytes

#26 Thanks for the review!

  1. Indeed :)
  2. Yep! Then this could be much more elegant.
  3. Good call. Fixed.
  4. Yes.
catch’s picture

+++ b/core/modules/shortcut/shortcut.module
@@ -364,10 +364,22 @@ function shortcut_preprocess_page(&$variables) {
+  $items['shortcuts'] = [
+    '#cache' => [
+      'contexts' => [
+        // Cacheable per user, because each user can have his own shortcut set,
+        // even if he cannot create or select a shortcut set, because an
+        // administrator may have assigned a non-default shortcut set.
+        'user',
+      ],
+    ],
+  ];
+

Is there any value to creating a 'current users's shortcut set' cache context? Then if a single shortcut set is used across the site, we'd not need to vary per-user.

Would have to lookup the shortcut set each time in the cache context implementation - bit of overhead there but given a lot of sites just have one shortcut set is probably a good trade off?

Wim Leers’s picture

Is there any value to creating a 'current users's shortcut set' cache context? Then if a single shortcut set is used across the site, we'd not need to vary per-user.

Hahaha! Glad you asked! This is exactly what crossed my mind as well :) I figured to not bring it up to not distract the discussion :)

Reasons to not do that here:

  1. the point you already made: such a cache context would still cause shortcut set entity storage to be initialized; right now we avoid that altogether
  2. user_toolbar() is already per-user anyway.
  3. it'd only actually benefit sites that either have: A) many administrators, B) granted a non-admin role permission to use the toolbar. In other words: it'd benefit relatively few sites. Most sites will have few users using the toolbar, in which case per-user caching is fine. Any further improvements in this area can IMHO therefore be deferred to 8.1.0.
Wim Leers’s picture

Issue tags: +Needs change record
Wim Leers’s picture

Also, fun fact: see #1805054-144: Cache localized, access filtered, URL resolved, and rendered menu trees, which contains the perf numbers for the issue that was blocking this one. It is that issue that brought down the UID1 front page load time down to 105 ms. This one is bringing that 105 ms down to 90 ms. They're adding up quite nicely!

Wim Leers’s picture

Assigned: Wim Leers » Unassigned

Unassigning. I'm working on other performance patches now, hopefully somebody else can write the test coverage.

Fabianx’s picture

Status: Needs review » Needs work

CNW for test coverage, code looks great!

One question:

+++ b/core/modules/user/user.module
@@ -1317,6 +1317,7 @@ function user_toolbar() {
+  $links_cache_contexts = [];

@@ -1338,6 +1339,8 @@ function user_toolbar() {
+    // The "Edit user account" link is per-user.
+    $links_cache_contexts[] = 'user';

@@ -1358,15 +1361,31 @@ function user_toolbar() {
+        '#cache' => [
+          'contexts' => $links_cache_contexts,
+        ],

Similar to dawehner I wonder why this needs to be set for the whole thing. Can't it be set on the "Edit user account" render array entry and bubble up?

Wim Leers’s picture

#34: no, that's impossible, because what you're seeing is NOT a render array, but the value for the #links property.

Fabianx’s picture

Would the "Edit user account" link be anytime be differently cacheable than per user? So could the link itself have this? (not to block this - the solution is fine, just very curious).

Wim Leers’s picture

#36: Well, it's the route parameter that is being set to the current user:

      'account_edit' => array(
        'title' => t('Edit profile'),
        'url' => Url::fromRoute('entity.user.edit_form', ['user' => $user->id()]),
        'attributes' => array(
          'title' => t('Edit user account'),
        ),
      ),

So, no, the link itself could not have this, because this is not a MenuLinkInterface object. It's just a simple Url object.

Fabianx’s picture

Opened #2495779: Make #theme => links take cacheability metadata as an argument to clean this up. Thanks for the explanation!

lauriii’s picture

Assigned: Unassigned » lauriii

Writing tests for this

lauriii’s picture

Assigned: lauriii » Unassigned
Status: Needs work » Needs review
FileSize
6.01 KB
11.99 KB
5.13 KB

The last submitted patch, 40: toolbar_pre_render-2254865-test-only-40.patch, failed testing.

lauriii’s picture

The last submitted patch, 40: toolbar_pre_render-2254865-40.patch, failed testing.

The last submitted patch, 42: toolbar_pre_render-2254865-test-only-43.patch, failed testing.

lauriii’s picture

Issue tags: -Needs tests
googletorp’s picture

Status: Needs review » Reviewed & tested by the community

Looks good.

lauriii’s picture

+++ b/core/modules/toolbar/tests/modules/toolbar_disable_user_toolbar/toolbar_disable_user_toolbar.info.yml
@@ -0,0 +1,6 @@
+name: 'Disbale user toolbar'

Minor typo s/Disbale/Disable. Can be fixed during commit:)

webchick’s picture

Assigned: Unassigned » catch

Methinks this is a catch-patch.

alexpott’s picture

Assigned: catch » Unassigned
Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/modules/shortcut/shortcut.module
    @@ -364,10 +364,22 @@ function shortcut_preprocess_page(&$variables) {
    +        // Cacheable per user, because each user can have his own shortcut set,
    +        // even if he cannot create or select a shortcut set, because an
    

    his => their, he => they

  2. +++ b/core/modules/user/user.module
    @@ -1358,15 +1361,31 @@ function user_toolbar() {
    +        '#cache' => [
    +          'contexts' => [
    +            // Cacheable per "authenticated or not", because the links to
    +            // display depend on that.
    +            'user.roles:authenticated',
    +          ],
    +        ],
             '#theme' => 'links__toolbar_user',
             '#links' => $links,
             '#attributes' => array(
               'class' => array('toolbar-menu'),
             ),
    +        '#cache' => [
    +          'contexts' => $links_cache_contexts,
    +        ],
    

    Duplicate #cache keys - which one is right - atm only the last is being used.

catch’s picture

Also this was bumped to critical due to an API change, but we're missing a change record.

I didn't spot anything that wasn't in #47/#49 otherwise.

Wim Leers’s picture

  1. +++ b/core/modules/toolbar/src/Tests/ToolbarCacheContextsTest.php
    @@ -0,0 +1,151 @@
    +      'languages:language_interface',
    +      'theme',
    

    We repeat this everywhere. Let's put that in a $required_cache_contexts variable and merge that with the other arrays. That'll allow us to remove repetition, and will hence make it clearer what the actual expectations for each specific thing are.

    In fact… why not create something like:

    $expectations = [
      [['toolbar, contextual'], ['user.permissions']]
      [['toolbar', 'shortcut'], …]
      …
    ];
    

    So that we can iterate over the various toolbar + other module combinations and define the expectations?

  2. +++ b/core/modules/toolbar/src/Tests/ToolbarCacheContextsTest.php
    @@ -0,0 +1,151 @@
    +    // Test without user menu. User module is required so we have to
    

    s/menu/toolbar tab+tray/

  3. +++ b/core/modules/toolbar/src/Tests/ToolbarCacheContextsTest.php
    @@ -0,0 +1,151 @@
    +    // manually remove the user toolbar.
    

    s/toolbar/toolbar tab+tray/

  4. +++ b/core/modules/toolbar/src/Tests/ToolbarCacheContextsTest.php
    @@ -0,0 +1,151 @@
    +  /**
    +   * Tests toolbar cache contexts with contextual toolbar.
    +   *
    +   * @param array $anonymous_cache_contexts
    +   *   Expected cache contexts for anonymous user.
    +   * @param array $authenticated_cache_contexts
    +   *   Expected cache contexts for authenticated user.
    +   */
    +  private function testToolbarCacheContexts(array $anonymous_cache_contexts, array $authenticated_cache_contexts) {
    

    This is not testing the right thing yet. Anonymous users never get to see the toolbar, which is why they have the same expected cache contexts across all situations!

    What this really needs to be testing is that if one authenticated user has the "access contextual links" permissions and another does not, that for both of those users the user.permissions cache context is present.

Wim Leers’s picture

We could clean up the $links_cache_context stuff if we land #2495779: Make #theme => links take cacheability metadata as an argument. That has a patch now.

lauriii’s picture

#51.4 Isn't those cache contexts attached even if the user don't have the permissions? I guess my test cases proves that they are attached even without permissions because of the user in my test is never given the permissions because the cache contexts are always there even without permissions.

Wim Leers’s picture

Yes, they are, but the anonymous user doesn't see the toolbar at all, so those tests as the anonymous user don't really test anything :)

lauriii’s picture

Assigned: Unassigned » lauriii

Working on fixing comments from #51

lauriii’s picture

Assigned: lauriii » Unassigned
Status: Needs work » Needs review
FileSize
11 KB
6.43 KB

Fixed the nits on the tests.. Might be working on this later on. Unassigning so others can take a look if they are willing to do so

Wim Leers’s picture

This looks vastly better! IMO this is RTBC-worthy. What did you plan to do later on? (It sounds like you still wanted to make some changes, but I'm not sure what still needs to be improved?)

  1. +++ b/core/modules/toolbar/src/Tests/ToolbarCacheContextsTest.php
    @@ -27,125 +27,98 @@
    +    $this->adminUser2 = $this->drupalCreateUser(array_merge($this->perms, ['access contextual links']));
    ...
    +    $this->adminUser2 = $this->drupalCreateUser(array_merge($this->perms, ['access tour']));
    ...
    +    $this->adminUser2 = $this->drupalCreateUser(array_merge($this->perms, ['access shortcuts', 'administer shortcuts']));
    

    Granting additional permissions would be slightly better, but wouldn't actually matter here.

  2. +++ b/core/modules/toolbar/src/Tests/ToolbarCacheContextsTest.php
    @@ -27,125 +27,98 @@
    +   * @param array $cache_contexts
    

    Nit: string[].

  3. +++ b/core/modules/toolbar/src/Tests/ToolbarCacheContextsTest.php
    @@ -27,125 +27,98 @@
    +    // Caches has to be rebuilt since modules might have added some routes.
    

    s/has/have/

  4. +++ b/core/modules/toolbar/src/Tests/ToolbarCacheContextsTest.php
    @@ -27,125 +27,98 @@
    +    $cache_contexts = array_merge($default_cache_contexts, $cache_contexts);
    

    Cache::mergeContexts()

borisson_’s picture

Fixed nitpicks 2, 3 and 4 of #57.

Fabianx’s picture

Status: Needs review » Needs work

#49 was not yet addressed.

Still needs a change record for the API changes.

Wim Leers’s picture

To address #49, use Cache::mergeContexts().

Wim Leers’s picture

Issue tags: -Needs change record

CR created. This reminds me: we must also update the documentation for hook_toolbar().

borisson_’s picture

Status: Needs work » Needs review
FileSize
1.92 KB
11.22 KB

Addressed both remarks from #49

Status: Needs review » Needs work

The last submitted patch, 62: toolbar_pre_render-2254865-62.patch, failed testing.

Fabianx’s picture

+++ b/core/modules/user/user.module
@@ -1375,7 +1376,7 @@ function user_toolbar() {
-            'user.roles:authenticated',
+            Cache::mergeContexts('user.roles:authenticated', $links_cache_contexts),

Needs to be an array and not an array of arrays.

Also first parameter needs to be an array, too.

JeroenT’s picture

Status: Needs work » Needs review
FileSize
811 bytes
11.2 KB

Fixed the failing tests as suggested by fabianx in #64.

The last submitted patch, 65: interdiff-2254865-62-65.patch, failed testing.

JeroenT’s picture

FileSize
811 bytes

Added interdiff in txt format instead of patch format.

Fabianx’s picture

Status: Needs review » Reviewed & tested by the community

RTBC, looks great now!

@Core Committers:

Can I haz commit credit, please? (for code reviews)

lauriii’s picture

Double commit credit for Fabianx!

webchick’s picture

Assigned: Unassigned » catch

Back to catch, Fabianx is checked. :)

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/modules/toolbar/src/Tests/ToolbarCacheContextsTest.php
    @@ -0,0 +1,125 @@
    +    $this->testToolbarCacheContexts(['user']);
    ...
    +    $this->testToolbarCacheContexts(['user.permissions']);
    ...
    +    $this->testToolbarCacheContexts(['user.permissions']);
    ...
    +    $this->testToolbarCacheContexts(['user.permissions']);
    ...
    +    $this->testToolbarCacheContexts(['user']);
    

    Calling the same method lots of times with a test method makes it much harder to debug when something goes wrong within the testToolbarCacheContexts method. Personally I think making the method an assert would make it easier to know what has gone wrong. But at the very least outputting some kind of verbose message in the testToolbarCacheContexts to let us know what is being tested would be good.

  2. +++ b/core/modules/toolbar/src/Tests/ToolbarCacheContextsTest.php
    @@ -0,0 +1,125 @@
    +    \Drupal::service('module_installer')->install(['toolbar_disable_user_toolbar']);
    ...
    +    \Drupal::service('module_installer')->install(['contextual']);
    ...
    +    \Drupal::service('module_installer')->install(['tour']);
    ...
    +    \Drupal::service('module_installer')->install(['shortcut']);
    ...
    +    // Caches have to be rebuilt since modules might have added some routes.
    +    drupal_flush_all_caches();
    

    Let's wrap this into a private method to install a module since atm we're doing an unnecessary drupal_flush_all_caches() on the first call to the method. Also I think the drupal_flush_all_caches() should be a $this->rebuildContainer(). So the test's container is properly updated.

  3. +++ b/core/modules/toolbar/tests/modules/toolbar_disable_user_toolbar/toolbar_disable_user_toolbar.info.yml
    @@ -0,0 +1,6 @@
    +name: 'Disbale user toolbar'
    

    This didn't get fixed since it was reported... :(

lauriii’s picture

#71.2: The problem is that $this->rebuildContainer() doesn't rebuilt routes why it didn't work in this case

lauriii’s picture

Status: Needs work » Needs review
FileSize
12.92 KB
5.02 KB
tim.plunkett’s picture

+++ b/core/modules/toolbar/src/Tests/ToolbarCacheContextsTest.php
@@ -0,0 +1,127 @@
+  private function testToolbarCacheContexts(array $cache_contexts, $message = NULL) {

Instead of making this a private function to avoid simpletest from running it (since it starts with the word test), please please make it protected and name it something else.

Wim Leers’s picture

Status: Needs review » Needs work

#71.1: Oops! I should've caught that.

#74: +1, please rename it to assert*, which is the convention. i.e. in this case assertToolbarCacheContexts().

dawehner’s picture

+++ b/core/modules/toolbar/src/Tests/ToolbarCacheContextsTest.php
@@ -0,0 +1,127 @@
+    // Assert contexts for user1 which has only default permissions.
+    $this->drupalLogin($this->adminUser);
+    $this->drupalGet('test-page');
+    $this->assertCacheContexts($cache_contexts, $message);
+    $this->drupalLogout();
+
+    // Assert contexts for user2 which has some additional permissions.
+    $this->drupalLogin($this->adminUser2);
+    $this->drupalGet('test-page');
+    $this->assertCacheContexts($cache_contexts, $message);

Once you use assert() the line number are changed and so its harder to debug that test. Do you mind adding some $this->pass("Some helpful string of what happens next") here?

Wim Leers’s picture

Perhaps "verify"is better, then.

We have a strange test system.

alexpott’s picture

@dawehner really? If it fails on any lines of the private testToolbarCacheContexts() you actually do know which call to it failed - which is silly. It's a custom assertion knowing where it failed in testToolbarCacheContextsCaller is the entire point.

alexpott’s picture

+++ b/core/modules/toolbar/src/Tests/ToolbarCacheContextsTest.php
@@ -0,0 +1,127 @@
+    // Caches have to be rebuilt since modules might have added some routes.
+    drupal_flush_all_caches();

We're still doing an unnecessary drupal_flush_all_caches(). IU think we should still wrap the module installs and this in a method on the class.

dawehner’s picture

@dawehner really? If it fails on any lines of the private testToolbarCacheContexts() you actually do know which call to it failed - which is silly. It's a custom assertion knowing where it failed in testToolbarCacheContextsCaller is the entire point.

Please reread my comment. I haven't argued against using assert*() but rather making it easier inside the assert function to see where you are.

alexpott’s picture

@dawehner sorry - obviously had our real life discussion in my head :(. oops.

dawehner’s picture

Don't feel bad, I feared this came.

lauriii’s picture

Status: Needs work » Needs review
FileSize
13.12 KB
3.73 KB

I think I have addressed all the points since last patch in this patch

dawehner’s picture

+++ b/core/modules/toolbar/src/Tests/ToolbarCacheContextsTest.php
@@ -0,0 +1,138 @@
+
+    // Caches have to be rebuilt since modules might have added some routes.
+    drupal_flush_all_caches();

If this is the only thing which can happen, I think we should be more explicit and rebuild the router: \Drupal::service('router.builder')->rebuild()

Wim Leers’s picture

Status: Needs review » Needs work

NW for #84.

alexpott’s picture

Status: Needs work » Needs review
FileSize
3.38 KB
13.56 KB

assertMethods() should return a boolean... and I've removed passing the message on to assertCacheContexts so we get the helpful verbose output from assertIdentical.

Fixed #84 so that we do the absolute minimal about of work after installing a module. Imo WebTestBase should have a helper method for this - but that is for another issue.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community
+++ b/core/modules/toolbar/src/Tests/ToolbarCacheContextsTest.php
@@ -112,13 +115,21 @@ protected function assertToolbarCacheContexts(array $cache_contexts, $message =
+    $return = $return && $this->assertCacheContexts($cache_contexts);
+
+    if ($return) {
+      $this->pass($message);
+    }
+    else {
+      $this->fail($message);
+    }
+    return $return;

+1 for not create another master assertion, which is hard to handle.

Fabianx’s picture

RTBC + 1

catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed/pushed to 8.0.x, thanks!

  • catch committed 5c8a50e on 8.0.x
    Issue #2254865 by Wim Leers, lauriii, borisson_, JeroenT, alexpott,...
Wim Leers’s picture

Awesome! Thanks :) :)

David_Rothstein’s picture

This seems to have caused a regression with the "Back to site" link.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.