Move cache instructions pruning to background job (#19598)
* Remove pruning logic from `CacheInstructionService.ProcessInstructions()` * Add and register `CacheInstructionsPruningJob` background job * Add unit tests * Remove breaking change in ICacheInstructionService * Adjust some obsoletion messages to mention v17 * Added missing scope * Update tests * Fix obsoletion messages version * Update ProcessInstructions methods summary
This commit is contained in:
@@ -20,8 +20,10 @@ internal sealed class CacheInstructionServiceTests : UmbracoIntegrationTest
|
||||
private const string LocalIdentity = "localIdentity";
|
||||
private const string AlternateIdentity = "alternateIdentity";
|
||||
|
||||
private CancellationToken CancellationToken => new();
|
||||
private CancellationToken CancellationToken => CancellationToken.None;
|
||||
|
||||
private CacheRefresherCollection CacheRefreshers => GetRequiredService<CacheRefresherCollection>();
|
||||
|
||||
private IServerRoleAccessor ServerRoleAccessor => GetRequiredService<IServerRoleAccessor>();
|
||||
|
||||
[Test]
|
||||
@@ -150,33 +152,16 @@ internal sealed class CacheInstructionServiceTests : UmbracoIntegrationTest
|
||||
// Create three instruction records, each with two instructions. First two records are for a different identity.
|
||||
CreateAndDeliveryMultipleInstructions(sut);
|
||||
|
||||
var result = sut.ProcessInstructions(CacheRefreshers, ServerRoleAccessor.CurrentServerRole, CancellationToken,
|
||||
LocalIdentity, DateTime.UtcNow.AddSeconds(-1), -1);
|
||||
var result = sut.ProcessInstructions(CacheRefreshers, CancellationToken, LocalIdentity, -1);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(3, result.LastId); // 3 records found.
|
||||
Assert.AreEqual(2,
|
||||
result.NumberOfInstructionsProcessed); // 2 records processed (as one is for the same identity).
|
||||
Assert.IsFalse(result.InstructionsWerePruned);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Process_And_Purge_Instructions()
|
||||
{
|
||||
// Purging of instructions only occurs on single or master servers, so we need to ensure this is set before running the test.
|
||||
EnsureServerRegistered();
|
||||
var sut = (CacheInstructionService)GetRequiredService<ICacheInstructionService>();
|
||||
|
||||
CreateAndDeliveryMultipleInstructions(sut);
|
||||
|
||||
var result = sut.ProcessInstructions(CacheRefreshers, ServerRoleAccessor.CurrentServerRole, CancellationToken,
|
||||
LocalIdentity, DateTime.UtcNow.AddHours(-1), -1);
|
||||
|
||||
Assert.IsTrue(result.InstructionsWerePruned);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Processes_No_Instructions_When_CancellationToken_is_Cancelled()
|
||||
{
|
||||
@@ -187,14 +172,12 @@ internal sealed class CacheInstructionServiceTests : UmbracoIntegrationTest
|
||||
var cancellationTokenSource = new CancellationTokenSource();
|
||||
cancellationTokenSource.Cancel();
|
||||
|
||||
var result = sut.ProcessInstructions(CacheRefreshers, ServerRoleAccessor.CurrentServerRole,
|
||||
cancellationTokenSource.Token, LocalIdentity, DateTime.UtcNow.AddSeconds(-1), -1);
|
||||
var result = sut.ProcessInstructions(CacheRefreshers, cancellationTokenSource.Token, LocalIdentity, -1);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(0, result.LastId);
|
||||
Assert.AreEqual(0, result.NumberOfInstructionsProcessed);
|
||||
Assert.IsFalse(result.InstructionsWerePruned);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -209,14 +192,12 @@ internal sealed class CacheInstructionServiceTests : UmbracoIntegrationTest
|
||||
|
||||
var lastId = -1;
|
||||
// Run once
|
||||
var result = sut.ProcessInstructions(CacheRefreshers, ServerRoleAccessor.CurrentServerRole, CancellationToken,
|
||||
LocalIdentity, DateTime.UtcNow.AddSeconds(-1), lastId);
|
||||
var result = sut.ProcessInstructions(CacheRefreshers, CancellationToken, LocalIdentity, lastId);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(3, result.LastId); // 3 records found.
|
||||
Assert.AreEqual(2, result.NumberOfInstructionsProcessed); // 2 records processed (as one is for the same identity).
|
||||
Assert.IsFalse(result.InstructionsWerePruned);
|
||||
});
|
||||
|
||||
// DatabaseServerMessenger stores the LastID after ProcessInstructions has been run.
|
||||
@@ -224,13 +205,7 @@ internal sealed class CacheInstructionServiceTests : UmbracoIntegrationTest
|
||||
|
||||
// The instructions has now been processed and shouldn't be processed on the next call...
|
||||
// Run again.
|
||||
var secondResult = sut.ProcessInstructions(
|
||||
CacheRefreshers,
|
||||
ServerRoleAccessor.CurrentServerRole,
|
||||
CancellationToken,
|
||||
LocalIdentity,
|
||||
DateTime.UtcNow.AddSeconds(-1),
|
||||
lastId);
|
||||
var secondResult = sut.ProcessInstructions(CacheRefreshers, CancellationToken, LocalIdentity, lastId);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(
|
||||
@@ -238,7 +213,6 @@ internal sealed class CacheInstructionServiceTests : UmbracoIntegrationTest
|
||||
secondResult
|
||||
.LastId); // No instructions was processed so LastId is 0, this is consistent with behavior from V8
|
||||
Assert.AreEqual(0, secondResult.NumberOfInstructionsProcessed); // Nothing was processed.
|
||||
Assert.IsFalse(secondResult.InstructionsWerePruned);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -249,8 +223,7 @@ internal sealed class CacheInstructionServiceTests : UmbracoIntegrationTest
|
||||
CreateAndDeliveryMultipleInstructions(sut);
|
||||
|
||||
var lastId = -1;
|
||||
var result = sut.ProcessInstructions(CacheRefreshers, ServerRoleAccessor.CurrentServerRole, CancellationToken,
|
||||
LocalIdentity, DateTime.UtcNow.AddSeconds(-1), lastId);
|
||||
var result = sut.ProcessInstructions(CacheRefreshers, CancellationToken, LocalIdentity, lastId);
|
||||
|
||||
Assert.AreEqual(3, result.LastId); // Make sure LastId is 3, the rest is tested in other test.
|
||||
lastId = result.LastId;
|
||||
@@ -259,14 +232,12 @@ internal sealed class CacheInstructionServiceTests : UmbracoIntegrationTest
|
||||
var instructions = CreateInstructions();
|
||||
sut.DeliverInstructions(instructions, AlternateIdentity);
|
||||
|
||||
var secondResult = sut.ProcessInstructions(CacheRefreshers, ServerRoleAccessor.CurrentServerRole,
|
||||
CancellationToken, LocalIdentity, DateTime.UtcNow.AddSeconds(-1), lastId);
|
||||
var secondResult = sut.ProcessInstructions(CacheRefreshers, CancellationToken, LocalIdentity, lastId);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(4, secondResult.LastId);
|
||||
Assert.AreEqual(1, secondResult.NumberOfInstructionsProcessed);
|
||||
Assert.IsFalse(secondResult.InstructionsWerePruned);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -277,8 +248,7 @@ internal sealed class CacheInstructionServiceTests : UmbracoIntegrationTest
|
||||
CreateAndDeliveryMultipleInstructions(sut);
|
||||
|
||||
var lastId = -1;
|
||||
var result = sut.ProcessInstructions(CacheRefreshers, ServerRoleAccessor.CurrentServerRole, CancellationToken,
|
||||
LocalIdentity, DateTime.UtcNow.AddSeconds(-1), lastId);
|
||||
var result = sut.ProcessInstructions(CacheRefreshers, CancellationToken, LocalIdentity, lastId);
|
||||
|
||||
Assert.AreEqual(3, result.LastId); // Make sure LastId is 3, the rest is tested in other test.
|
||||
lastId = result.LastId;
|
||||
@@ -287,14 +257,12 @@ internal sealed class CacheInstructionServiceTests : UmbracoIntegrationTest
|
||||
var instructions = CreateInstructions();
|
||||
sut.DeliverInstructions(instructions, LocalIdentity);
|
||||
|
||||
var secondResult = sut.ProcessInstructions(CacheRefreshers, ServerRoleAccessor.CurrentServerRole,
|
||||
CancellationToken, LocalIdentity, DateTime.UtcNow.AddSeconds(-1), lastId);
|
||||
var secondResult = sut.ProcessInstructions(CacheRefreshers, CancellationToken, LocalIdentity, lastId);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(4, secondResult.LastId);
|
||||
Assert.AreEqual(0, secondResult.NumberOfInstructionsProcessed);
|
||||
Assert.IsFalse(secondResult.InstructionsWerePruned);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -306,10 +274,4 @@ internal sealed class CacheInstructionServiceTests : UmbracoIntegrationTest
|
||||
sut.DeliverInstructions(instructions, i == 2 ? LocalIdentity : AlternateIdentity);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureServerRegistered()
|
||||
{
|
||||
var serverRegistrationService = GetRequiredService<IServerRegistrationService>();
|
||||
serverRegistrationService.TouchServer("http://localhost", TimeSpan.FromMinutes(10));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.Data;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.BackgroundJobs.Jobs;
|
||||
|
||||
public class CacheInstructionsPruningJobTests
|
||||
{
|
||||
private readonly Mock<IOptions<GlobalSettings>> _globalSettingsMock = new(MockBehavior.Strict);
|
||||
private readonly Mock<ICacheInstructionRepository> _cacheInstructionRepositoryMock = new(MockBehavior.Strict);
|
||||
private readonly Mock<ICoreScopeProvider> _scopeProviderMock = new(MockBehavior.Strict);
|
||||
private readonly Mock<TimeProvider> _timeProviderMock = new(MockBehavior.Strict);
|
||||
|
||||
[Test]
|
||||
public void Run_Period_Is_Retrieved_From_GlobalSettings()
|
||||
{
|
||||
var timeBetweenPruneOperations = TimeSpan.FromMinutes(2);
|
||||
var job = CreateCacheInstructionsPruningJob(timeBetweenPruneOperations);
|
||||
Assert.AreEqual(timeBetweenPruneOperations, job.Period, "The run period should be the same as 'TimeBetweenPruneOperations'.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task RunJobAsync_Calls_DeleteInstructionsOlderThan_With_Expected_Date()
|
||||
{
|
||||
SetupScopeProviderMock();
|
||||
|
||||
var timeToRetainInstructions = TimeSpan.FromMinutes(30);
|
||||
var now = DateTime.UtcNow;
|
||||
var expectedPruneDate = now - timeToRetainInstructions;
|
||||
|
||||
_timeProviderMock.Setup(tp => tp.GetUtcNow()).Returns(now);
|
||||
_cacheInstructionRepositoryMock.Setup(repo => repo
|
||||
.DeleteInstructionsOlderThan(expectedPruneDate));
|
||||
|
||||
var job = CreateCacheInstructionsPruningJob(timeToRetainInstructions: timeToRetainInstructions);
|
||||
|
||||
await job.RunJobAsync();
|
||||
|
||||
_cacheInstructionRepositoryMock.Verify(repo => repo.DeleteInstructionsOlderThan(expectedPruneDate), Times.Once);
|
||||
}
|
||||
|
||||
private CacheInstructionsPruningJob CreateCacheInstructionsPruningJob(
|
||||
TimeSpan? timeBetweenPruneOperations = null,
|
||||
TimeSpan? timeToRetainInstructions = null)
|
||||
{
|
||||
timeBetweenPruneOperations ??= TimeSpan.FromMinutes(5);
|
||||
timeToRetainInstructions ??= TimeSpan.FromMinutes(20);
|
||||
|
||||
var globalSettings = new GlobalSettings
|
||||
{
|
||||
DatabaseServerMessenger = new DatabaseServerMessengerSettings
|
||||
{
|
||||
TimeBetweenPruneOperations = timeBetweenPruneOperations.Value,
|
||||
TimeToRetainInstructions = timeToRetainInstructions.Value,
|
||||
},
|
||||
};
|
||||
|
||||
_globalSettingsMock
|
||||
.Setup(g => g.Value)
|
||||
.Returns(globalSettings);
|
||||
|
||||
return new CacheInstructionsPruningJob(_globalSettingsMock.Object, _cacheInstructionRepositoryMock.Object, _scopeProviderMock.Object, _timeProviderMock.Object);
|
||||
}
|
||||
|
||||
private void SetupScopeProviderMock() =>
|
||||
_scopeProviderMock
|
||||
.Setup(x => x.CreateCoreScope(
|
||||
It.IsAny<IsolationLevel>(),
|
||||
It.IsAny<RepositoryCacheMode>(),
|
||||
It.IsAny<IEventDispatcher>(),
|
||||
It.IsAny<IScopedNotificationPublisher>(),
|
||||
It.IsAny<bool?>(),
|
||||
It.IsAny<bool>(),
|
||||
It.IsAny<bool>()))
|
||||
.Returns(Mock.Of<ICoreScope>());
|
||||
}
|
||||
Reference in New Issue
Block a user