Repository navigation
Include a hash of the template content in the CMS Twig cache key - #1571
austinderrick wants to merge 5 commits into
Conversation
Twig names the compiled template class after the cache key and skips its freshness check once the class is loaded. The key was the file path alone, so a long-running process that had rendered an object kept rendering the old version after the object was edited.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
LukeTowers
left a comment
There was a problem hiding this comment.
That test doesn't look right. I don't see what's persisting the change and it's explicitly disabling the cache.
@LukeTowers : Test updated! :) |
|
@austinderrick sorry, but I'm not a fan of this change. This change would mean a file read every time the system queries a template's cache key. I'm presuming when multiple templates are being read on a page, that issue would be multiplied. That would undoubtedly slow performance. |
Hey @bennothommo : I gave this another look and I'm pretty sure there isn't an extra file read. getTwigContent returns the object's markup (or content), which Halcyon has already loaded into memory by the time Twig asks for the key, and getSourceContext() already calls the same method. Am I missing something? For context, this only matters for runtimes that keep PHP processes alive between requests, such as Octane, FrankenPHP worker mode or RoadRunner. Once a worker has loaded a template's compiled class, Twig never checks freshness again, so edits made in the CMS editor don't show up on that worker until it restarts. PHP-FPM never hits this because each request gets a new process. The cost is one xxh128 hash over the in-memory markup per template load. I benchmarked the real Cms\Twig\Loader on PHP 8.4 (Linux, OPcache on) running each version in its own process and alternating the order:
I'm happy to share the benchmark scripts. If you'd still rather not change the default for everyone, I could put the content hash behind a config option that persistent-worker setups trigger. |
|
@austinderrick I had assumed Your change makes sense, however, I would rather it be made in the |
Summary
Cms\Twig\Loader::getCacheKey()returned the object's file path. Twig names the compiled template class after the cache key, and once that class is loaded it skips the freshness check (Twig\Environment::loadTemplate()only checksisTemplateFresh()when the class does not exist yet). A process that has rendered a page, layout, partial or content file therefore keeps rendering the old version after the object is edited, until the process ends.Under PHP-FPM the process ends with the request, so nobody sees this. Under a persistent worker such as Laravel Octane, an edit made in the CMS editor never reaches a worker that already rendered the object.
The loader now appends a hash of the object's Twig content to the key, so an edit produces a new class name and Twig compiles the new version. The hash is used instead of the modification time because the modification time only changes once per second, so a second save in the same second would still render stale. Building the key in the loader also covers component partials and any
cmsObject.getTwigCacheKeylistener that returns its own key.One side effect: when the Twig cache is on, each edit writes a new compiled file instead of overwriting the old one. Old files remain until the cache is cleared, as other cache files do.
Testing
testEditedObjectRendersItsNewContentInTheSameProcessinmodules/cms/tests/classes/CmsObjectTest.phprenders a theme file through a Twig environment with a filesystem cache andauto_reload, asCms\ServiceProviderconfigures it. It then saves an edit to disk, reloads the object and renders it again in the same process, within the same second. It fails without the change. The CMS (220 tests) and System (348 tests) module suites pass.