Caching: Resolves publish and install issues related to stale cached data retrieval (closes #20539 and #20630) (#20640)

* Request cache published content creation with version.

* Reload memory cache after install with package migrations.

* Improve message on install for database cache rebuild.

* Update src/Umbraco.Infrastructure/Install/MigrationPlansExecutedNotificationHandler.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Relocated memory cache refresh after package install from notification handler to unattended upgrader.

* Fix construtor breaking change

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: mole <nikolajlauridsen@protonmail.ch>
This commit is contained in:
Andy Butland
2025-10-28 13:25:13 +01:00
parent 3dc65c48b3
commit 0d2393d866
4 changed files with 42 additions and 8 deletions

View File

@@ -20,6 +20,7 @@ public static partial class UmbracoBuilderExtensions
// Add post migration notification handlers // Add post migration notification handlers
builder.AddNotificationHandler<UmbracoPlanExecutedNotification, ClearCsrfCookieHandler>(); builder.AddNotificationHandler<UmbracoPlanExecutedNotification, ClearCsrfCookieHandler>();
return builder; return builder;
} }
} }

View File

@@ -1,6 +1,8 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Umbraco.Cms.Core; using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.DependencyInjection;
@@ -28,7 +30,10 @@ public class UnattendedUpgrader : INotificationAsyncHandler<RuntimeUnattendedUpg
private readonly IRuntimeState _runtimeState; private readonly IRuntimeState _runtimeState;
private readonly IUmbracoVersion _umbracoVersion; private readonly IUmbracoVersion _umbracoVersion;
private readonly UnattendedSettings _unattendedSettings; private readonly UnattendedSettings _unattendedSettings;
private readonly DistributedCache _distributedCache;
private readonly ILogger<UnattendedUpgrader> _logger;
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 19.")]
public UnattendedUpgrader( public UnattendedUpgrader(
IProfilingLogger profilingLogger, IProfilingLogger profilingLogger,
IUmbracoVersion umbracoVersion, IUmbracoVersion umbracoVersion,
@@ -36,13 +41,36 @@ public class UnattendedUpgrader : INotificationAsyncHandler<RuntimeUnattendedUpg
IRuntimeState runtimeState, IRuntimeState runtimeState,
PackageMigrationRunner packageMigrationRunner, PackageMigrationRunner packageMigrationRunner,
IOptions<UnattendedSettings> unattendedSettings) IOptions<UnattendedSettings> unattendedSettings)
: this(
profilingLogger,
umbracoVersion,
databaseBuilder,
runtimeState,
packageMigrationRunner,
unattendedSettings,
StaticServiceProvider.Instance.GetRequiredService<DistributedCache>(),
StaticServiceProvider.Instance.GetRequiredService<ILogger<UnattendedUpgrader>>())
{ {
_profilingLogger = profilingLogger ?? throw new ArgumentNullException(nameof(profilingLogger)); }
_umbracoVersion = umbracoVersion ?? throw new ArgumentNullException(nameof(umbracoVersion));
_databaseBuilder = databaseBuilder ?? throw new ArgumentNullException(nameof(databaseBuilder)); public UnattendedUpgrader(
_runtimeState = runtimeState ?? throw new ArgumentNullException(nameof(runtimeState)); IProfilingLogger profilingLogger,
IUmbracoVersion umbracoVersion,
DatabaseBuilder databaseBuilder,
IRuntimeState runtimeState,
PackageMigrationRunner packageMigrationRunner,
IOptions<UnattendedSettings> unattendedSettings,
DistributedCache distributedCache,
ILogger<UnattendedUpgrader> logger)
{
_profilingLogger = profilingLogger;
_umbracoVersion = umbracoVersion;
_databaseBuilder = databaseBuilder;
_runtimeState = runtimeState;
_packageMigrationRunner = packageMigrationRunner; _packageMigrationRunner = packageMigrationRunner;
_unattendedSettings = unattendedSettings.Value; _unattendedSettings = unattendedSettings.Value;
_distributedCache = distributedCache;
_logger = logger;
} }
public async Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, CancellationToken cancellationToken) public async Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, CancellationToken cancellationToken)
@@ -109,8 +137,13 @@ public class UnattendedUpgrader : INotificationAsyncHandler<RuntimeUnattendedUpg
try try
{ {
await _packageMigrationRunner.RunPackagePlansAsync(pendingMigrations); await _packageMigrationRunner.RunPackagePlansAsync(pendingMigrations);
notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult.PackageMigrationComplete;
.PackageMigrationComplete;
// Migration plans may have changed published content, so refresh the distributed cache to ensure consistency on first request.
_distributedCache.RefreshAllPublishedSnapshot();
_logger.LogInformation(
"Migration plans run: {Plans}. Triggered refresh of distributed published content cache.",
string.Join(", ", pendingMigrations));
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -108,7 +108,7 @@ internal sealed class DatabaseCacheRebuilder : IDatabaseCacheRebuilder
_logger.LogWarning( _logger.LogWarning(
"Database cache was serialized using {CurrentSerializer}. Currently configured cache serializer {Serializer}. Rebuilding database cache.", "Database cache was serialized using {CurrentSerializer}. Currently configured cache serializer {Serializer}. Rebuilding database cache.",
currentSerializer, currentSerializer == 0 ? "None" : currentSerializer,
serializer); serializer);
using (_profilingLogger.TraceDuration<DatabaseCacheRebuilder>($"Rebuilding database cache with {serializer} serializer")) using (_profilingLogger.TraceDuration<DatabaseCacheRebuilder>($"Rebuilding database cache with {serializer} serializer"))

View File

@@ -38,7 +38,7 @@ internal sealed class PublishedContentFactory : IPublishedContentFactory
/// <inheritdoc/> /// <inheritdoc/>
public IPublishedContent? ToIPublishedContent(ContentCacheNode contentCacheNode, bool preview) public IPublishedContent? ToIPublishedContent(ContentCacheNode contentCacheNode, bool preview)
{ {
var cacheKey = $"{nameof(PublishedContentFactory)}DocumentCache_{contentCacheNode.Id}_{preview}"; var cacheKey = $"{nameof(PublishedContentFactory)}DocumentCache_{contentCacheNode.Id}_{preview}_{contentCacheNode.Data?.VersionDate.Ticks ?? 0}";
IPublishedContent? publishedContent = null; IPublishedContent? publishedContent = null;
if (_appCaches.RequestCache.IsAvailable) if (_appCaches.RequestCache.IsAvailable)
{ {