Problem/Motivation

When a node is trashed, its menu_link_content entities are left behind. The TrashEntityTypeManager prevents the trashed node from being loaded, which causes ParamNotConvertedException in AccessManager::checkNamedRoute(). This returns AccessResult::forbidden()->setCacheMaxAge(0).

This max-age=0 bubbles up through the render tree and makes the entire menu block uncacheable. On every page load, Drupal re-runs access checks on all menu links (nearly 1000 in our case), causing 3–4 second page load times.

A single trashed node with a menu link is enough to break the render cache for the entire menu block.

Steps to reproduce

  1. Enable the Trash module with node as a trashable entity type.
  2. Create a node and add a menu link for it in the main navigation.
  3. Trash the node.
  4. Clear caches (drush cr).
  5. Load any page that displays the main menu block.
  6. Check the render cache: the menu block is not cached (max-age=0).
  7. Reload the page: it is still slow because the block is re-rendered every time.

Root cause

The call chain is:

  1. SystemMenuBlock::build() loads the menu tree and applies the checkAccess manipulator.
  2. DefaultMenuLinkTreeManipulators::checkAccess() calls menuLinkCheckAccess() per link.
  3. menuLinkCheckAccess() calls AccessManager::checkNamedRoute().
  4. checkNamedRoute() calls ParamConverterManager::convert() which tries to load the node.
  5. TrashEntityTypeManager::getStorage() returns a storage that filters out trashed entities.
  6. EntityConverter cannot load the trashed node and throws ParamNotConvertedException.
  7. checkNamedRoute() catches the exception and returns AccessResult::forbidden()->setCacheMaxAge(0).
  8. This max-age=0 bubbles up to the menu block render array, making it uncacheable.

Impact

On a production site with ~960 menu links in the main navigation and 12 trashed nodes with menu links, the page load time went from ~100ms (cached) to 3–4 seconds on every request. The issue started as soon as the first node with a menu link was trashed.

Proposed resolution

When a node is trashed, the Trash module should disable or delete the associated menu_link_content entities. The existing MenuLinkContentTrashHandler handles trashing of menu link entities themselves, but there is no handler for the reverse case: a node being trashed while menu links reference it.

Possible approaches:

  • In the node trash handler, find and disable all menu_link_content entities that reference the trashed node (and re-enable them on restore).
  • Alternatively, make the EntityConverter aware of trashed entities so that the access result is cacheable (e.g., return forbidden with appropriate cache tags instead of max-age=0).

Related issue

#3398777: Custom menu links are not updated when a node is soft-deleted (closed as "works as designed", but the render cache performance impact was not discussed).

CommentFileSizeAuthor
#4 3583106-3.x.patch6.54 KBamateescu

Issue fork trash-3583106

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

mably created an issue. See original summary.

amateescu’s picture

Version: 3.0.26 » 3.1.x-dev

amateescu’s picture

Status: Active » Needs review
StatusFileSize
new6.54 KB

Since we just provided Trash support for menu links generally (it used to be tied to Workspaces/wse_menu) in #3575319: Support menu links without workspaces, we can now introduce the same behavior that we have for path aliases: soft-delete and restore them along with their content.

Opened a MR for 3.1.x (which will be backported properly on commit), but for easier testing on 3.x, attaching a patch as well.

I would appreciate it if you can test it on your site. Note that you'll need the dev version of Trash because #3575319: Support menu links without workspaces is not part of 3.0.26, and you also need to enable Trash support for menu links.

mably’s picture

Will give it a try. Thanks for your quick answer.

mably’s picture

Status: Needs review » Reviewed & tested by the community

Tested the MR on a production site with 604 enabled node menu links in the main menu, 12 of which pointed to trashed nodes.

Before the patch: the menu block rendered with max-age=0 on every request because checkAccess() triggered ParamNotConvertedException on the trashed node links. Dynamic Page Cache showed HIT but the menu block placeholder was rebuilt every time (~4s per anonymous page load).

After the patch: restored the 12 trashed nodes, then re-trashed them. The patch correctly deleted their associated menu links during the trash operation. The menu block now renders with max-age=3600, Dynamic Page Cache serves the full page in ~0.2s.

Note: the patch only fixes newly trashed content. Pre-existing broken menu links (trashed before the patch was applied) need a one-time restore + re-trash cycle to trigger the cleanup.

  • amateescu committed bba7181a on 3.1.x
    fix: #3583106 Trashed nodes with menu links poison the menu block render...

  • amateescu committed 012771fb on 3.x
    fix: #3583106 Trashed nodes with menu links poison the menu block render...
amateescu’s picture

Status: Reviewed & tested by the community » Fixed

Thanks for testing! I also added another change to enable custom menu link support by default when Trash is installed on new sites, and suggest it on the settings form for existing ones.

Note: the patch only fixes newly trashed content. Pre-existing broken menu links (trashed before the patch was applied) need a one-time restore + re-trash cycle to trigger the cleanup.

Yes, there's no safe way to do this after the fact. But hopefully people that might bump into this problem will find this issue and the easy fix :)

Merged into 3.1.x and cherry-picked to 3.x, thanks again!

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

mably’s picture

You're welcome :)

When can we expect a first 3.1.x release?

amateescu’s picture

The first 3.1.x release will be out when #3376216: Translation support is done, but this change will be in 3.0.27 as well, and that one is waiting for feedback on #3583020: All aliases are deleted when deleting a term.