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
- Enable the Trash module with node as a trashable entity type.
- Create a node and add a menu link for it in the main navigation.
- Trash the node.
- Clear caches (
drush cr). - Load any page that displays the main menu block.
- Check the render cache: the menu block is not cached (
max-age=0). - Reload the page: it is still slow because the block is re-rendered every time.
Root cause
The call chain is:
SystemMenuBlock::build()loads the menu tree and applies thecheckAccessmanipulator.DefaultMenuLinkTreeManipulators::checkAccess()callsmenuLinkCheckAccess()per link.menuLinkCheckAccess()callsAccessManager::checkNamedRoute().checkNamedRoute()callsParamConverterManager::convert()which tries to load the node.TrashEntityTypeManager::getStorage()returns a storage that filters out trashed entities.EntityConvertercannot load the trashed node and throwsParamNotConvertedException.checkNamedRoute()catches the exception and returnsAccessResult::forbidden()->setCacheMaxAge(0).- This
max-age=0bubbles 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_contententities that reference the trashed node (and re-enable them on restore). - Alternatively, make the
EntityConverteraware of trashed entities so that the access result is cacheable (e.g., returnforbiddenwith appropriate cache tags instead ofmax-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).
| Comment | File | Size | Author |
|---|---|---|---|
| #4 | 3583106-3.x.patch | 6.54 KB | amateescu |
Issue fork trash-3583106
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
Comment #2
amateescu commentedComment #4
amateescu commentedSince 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.
Comment #5
mably commentedWill give it a try. Thanks for your quick answer.
Comment #6
mably commentedTested 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.
Comment #9
amateescu commentedThanks 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.
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!
Comment #11
mably commentedYou're welcome :)
When can we expect a first 3.1.x release?
Comment #12
amateescu commentedThe 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.