Skip to content

Content Types: Fix stale published cache when property variance changes without content type variance changing - #23445

Merged
AndyButland merged 4 commits into
v17/devfrom
v17/bugfix/property-variance-stale-cache
Jul 22, 2026
Merged

AndyButland merged 4 commits into
v17/devfrom
v17/bugfix/property-variance-stale-cache

Conversation

@lauraneto

@lauraneto lauraneto commented Jul 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

ContentTypeBase only overrode the parameterless ResetDirtyProperties(), not the ResetDirtyProperties(bool rememberDirty) overload the repository cache actually calls on every cache round-trip. That overload fell back to the non-cascading base implementation, so property types never had their dirty flags reset — permanently defeating WasPropertyTypeVariationChanged()'s new-property guard. The result: if only a property's variance changed (not the content type's own), the change was misclassified as non-structural and the published cache could keep serving the stale value.

Fix: add the missing ResetDirtyProperties(bool rememberDirty) override, cascading into PropertyGroups/PropertyTypes the same way the parameterless one did. Drop the now-redundant parameterless override, since BeingDirtyBase.ResetDirtyProperties() already delegates to ResetDirtyProperties(true).

Caveat found during review: the new tests only catch this because the integration harness runs with AppCaches.NoCache by default. With real caching (AppCaches using DeepCloneAppCache, matching any actual deployment), the tests pass even without this fix — DeepCloneAppCache deep-clones and resets dirty state on every cache write, independently masking the missing cascade. We could not reproduce the staleness manually against a running instance either. So this is a real gap in the dirty-tracking contract and a correct fix, but its real-world reachability under normal caching is unconfirmed — flagging for you to judge whether that changes scope or priority.

Test plan

DocumentHybridCacheVarianceTests.cs has 4 tests covering property-variance changes with the content type's own variance held constant. Run them against v17/dev (before this fix) — 2 of the 4 fail. Run them again with this branch — all 4 pass.

…content type's doesn't

ContentTypeBase never cascaded ResetDirtyProperties(bool) into its property groups/types, so a
property's dirty snapshot survived every cache round-trip and permanently defeated
WasPropertyTypeVariationChanged's new-property guard for any reload-then-edit flow, silently
misclassifying the change as non-structural and leaving the published cache stale.
@lauraneto
lauraneto force-pushed the v17/bugfix/property-variance-stale-cache branch from ad09eeb to 18fe8c4 Compare July 21, 2026 09:04
@lauraneto
lauraneto marked this pull request as ready for review July 21, 2026 11:21
Copilot AI review requested due to automatic review settings July 21, 2026 11:21
@claude

claude Bot commented Jul 21, 2026 •

Copy link
Copy Markdown

Claude finished @lauraneto's task in 7m 19s —— View job


PR Review

Target: origin/v17/dev · Based on commit: 5a162c44

Fixes a missing ResetDirtyProperties(bool rememberDirty) override in ContentTypeBase that prevented property types from having their dirty state "remembered" on cache round-trips, causing WasPropertyTypeVariationChanged() to always return false when only a property's variance changed (not the content type's own). The fix adds the cascade to the bool overload and drops the now-redundant parameterless override (which the base already delegates to via ResetDirtyProperties(true)).

  • Modified public API: ContentTypeBase.ResetDirtyProperties(bool) — now has an explicit override; parameterless override removed (base already delegates to the bool overload, behaviour is identical)
  • Breaking changes: None — the removed parameterless override fell through to the base which called ResetDirtyProperties(true), same as the new path. No signature change.

Important

  • DocumentHybridCacheVarianceTests.cs:30: CustomTestSetup does not register a real AppCaches. The integration-test CLAUDE.md explicitly documents that cache-related tests must override this (otherwise NoCache is in effect and stale-cache scenarios can't be reproduced). The PR description acknowledges that with a DeepCloneAppCache (matching production), the tests pass even without the fix — so the tests only exercise the dirty-tracking contract in no-cache mode. A code comment explaining this intentional omission would help future readers.

Suggestions

  • DocumentHybridCacheVarianceTests.cs:44: Test naming deviates from the Can_/Cannot_ convention used consistently across all sibling test classes (DocumentHybridCacheTests, DocumentHybridCacheVariantsTests, etc.). Consider renaming, e.g. Can_Published_Output_Reflect_Property_Variance_Change_Invariant_To_Variant_When_Type_Variance_Changes.

  • DocumentHybridCacheVarianceTests.cs:232: CreateAsync result is silently discarded. CreateAdditionalLanguage in the same class asserts the operation status — consider doing the same here so a setup failure surfaces clearly rather than as a cryptic assertion failure later in the test body.


Approved with Suggestions for improvement

The production fix is correct and well-targeted. The tests do validate the dirty-tracking contract and demonstrate 2 of 4 failing before the fix. The suggestions above are polish items that don't block merging.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a dirty-tracking contract gap in ContentTypeBase where the repository cache path calls ResetDirtyProperties(bool rememberDirty), but the type previously only cascaded resets via the parameterless overload. As a result, nested PropertyTypes could remain permanently dirty after round-trips, leading to property-variance changes being misclassified and the published cache potentially serving stale values.

Changes:

  • Add ContentTypeBase.ResetDirtyProperties(bool rememberDirty) override that cascades dirty-reset into PropertyGroups and PropertyTypes (and remove reliance on the parameterless override).
  • Add integration tests covering published output behavior when property variance changes without republishing.
  • Wire required distributed-cache notification handlers + local server messenger for the new integration tests.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
src/Umbraco.Core/Models/ContentTypeBase.cs Fixes dirty-reset cascading for the overload actually used by repository cache round-trips.
tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCacheVarianceTests.cs Adds regression coverage for published output after property-variance changes (no republish).

@claude claude Bot added the area/backend label Jul 21, 2026
lauraneto and others added 2 commits July 21, 2026 15:26
Force a real repository round-trip before mutating variance so the dirty-tracking
contract is actually exercised, assert CreateAsync/GetAsync results instead of
discarding or null-forgiving them, rename tests to the Can_/Cannot_ convention used
elsewhere in the file, and parameterize the fixture over NoCache and a real
DeepCloneAppCache so the regression is proven under both, since a previous version
of this bug was only reproducible with caching disabled.

@AndyButland AndyButland left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me @lauraneto. Even if it's not reproducible in production today, I read this as a genuine oversight and a correct fix. ContentBase/Content already override that overload and cascade correctly, so it's good to align ContentTypeBase. And if the cache implementation ever changes, this would stops the bug from surfacing.

It looked like we could verify cheaply with a plain unit test on the model itself, so I've added a few cases to ContentTypeTests and checked them in. They assert the reset cascades into every property type (both grouped and un-grouped) and that the rememberDirty flag is threaded through correctly. I confirmed they fail on v17/dev before the fix and pass with it.

Worth keeping your integration tests too though as they cover a broader end-to-end path that the unit test doesn't.

@sonarqubecloud

Copy link
Copy Markdown

@AndyButland
AndyButland merged commit 604b631 into v17/dev Jul 22, 2026
30 of 31 checks passed
@AndyButland
AndyButland deleted the v17/bugfix/property-variance-stale-cache branch July 22, 2026 13:47
@KevinJump KevinJump mentioned this pull request Sep 7, 2026
13 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants