Skip to content

Commit 65a8924

Browse files
committed
Unattended Upgrades: Rebuild routing caches after background migrations to fix unroutable document URLs (#22269)
* Prevent HybridCache from caching null content entries. * Revert change and use approach of ensuring null cached values are tagged.
1 parent 6f152ea commit 65a8924

1 file changed

Lines changed: 22 additions & 6 deletions

File tree

src/Umbraco.PublishedCache.HybridCache/Services/DocumentCacheService.cs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,9 @@ await _hybridCache.SetAsync(
148148
// If we can resolve the content cache node, we still need to check if the ancestor path is published.
149149
// This does cost some performance, but it's necessary to ensure that the content is actually published.
150150
// When unpublishing a node, a payload with RefreshBranch is published, so we don't have to worry about this.
151-
// Similarly, when a branch is published, next time the content is requested, the parent will be published,
152-
// this works because we don't cache null values.
151+
// Similarly, when a branch is published, next time the content is requested, the parent will be published.
152+
// Null values are cached here are tagged and cleared by ClearMemoryCacheAsync, so the next request after a
153+
// cache clear will re-query the database.
153154
if (preview is false && contentCacheNode is not null && _publishStatusQueryService.HasPublishedAncestorPath(contentCacheNode.Key) is false)
154155
{
155156
// Careful not to early return here. We need to complete the scope even if returning null.
@@ -333,10 +334,25 @@ public async Task RefreshContentAsync(IContent content)
333334

334335
private static string GetCacheKey(Guid key, bool preview) => preview ? $"{key}+draft" : $"{key}";
335336

336-
// Generates the cache tags for a given CacheNode
337-
// We use the tags to be able to clear all cache entries that are related to a given content item.
338-
// Tags for now are only content/media, but can be expanded with draft/published later.
339-
private static HashSet<string> GenerateTags(ContentCacheNode? cacheNode) => cacheNode is null ? [] : [Constants.Cache.Tags.Content, ContentTypeIdTag(cacheNode.ContentTypeId)];
337+
/// <summary>
338+
/// Generates the cache tags for a given <see cref="ContentCacheNode"/>.
339+
/// </summary>
340+
/// <param name="cacheNode">The cache node to generate tags for, or <c>null</c> for a negative-cache entry.</param>
341+
/// <returns>
342+
/// A set of tags that always includes <see cref="Constants.Cache.Tags.Content"/>.
343+
/// When <paramref name="cacheNode"/> is non-null, the content type ID tag is also included.
344+
/// </returns>
345+
/// <remarks>
346+
/// Tags are used to clear all cache entries related to a given content item or type.
347+
/// The <see cref="Constants.Cache.Tags.Content"/> tag is always included — even for null entries — so
348+
/// that <see cref="ClearMemoryCacheAsync"/> (which clears by this tag) can evict negative-cache entries.
349+
/// Without this, null entries survive tag-based cache clears and become permanently stale.
350+
/// Tags currently cover content/media distinctions but can be expanded with draft/published later.
351+
/// </remarks>
352+
private static HashSet<string> GenerateTags(ContentCacheNode? cacheNode) =>
353+
cacheNode is null
354+
? [Constants.Cache.Tags.Content]
355+
: [Constants.Cache.Tags.Content, ContentTypeIdTag(cacheNode.ContentTypeId)];
340356

341357
public async Task DeleteItemAsync(IContentBase content)
342358
{

0 commit comments

Comments
 (0)