Skip to content

Commit 1a4ca0b

Browse files
Sync: Fix SyncBootStateAccessor to use ILastSyncedManager to prevent unnecessary cold boots (#21109)
* Sync: Fix SyncBootStateAccessor to use ILastSyncedManager Update SyncBootStateAccessor to use the new ILastSyncedManager interface instead of the deprecated LastSyncedFileManager, which was causing cold boots to be triggered every time. - Replace LastSyncedFileManager field with ILastSyncedManager - Add new primary constructor accepting ILastSyncedManager - Add obsolete constructors for backwards compatibility using StaticServiceProvider - Update GetSyncBootState to use GetLastSyncedExternalAsync 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]> * Sync: Add integration tests for SyncBootStateAccessor Add integration tests to verify SyncBootStateAccessor correctly determines cold boot vs warm boot state based on ILastSyncedManager. - Test cold boot when no last synced ID exists - Test warm boot when last synced external ID matches a cache instruction 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]> --------- Co-authored-by: Claude Opus 4.5 <[email protected]>
1 parent 8626ae4 commit 1a4ca0b

File tree

2 files changed

+100
-5
lines changed

2 files changed

+100
-5
lines changed

src/Umbraco.Infrastructure/Sync/SyncBootStateAccessor.cs

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
using Microsoft.Extensions.DependencyInjection;
12
using Microsoft.Extensions.Logging;
23
using Microsoft.Extensions.Options;
34
using Umbraco.Cms.Core.Configuration.Models;
5+
using Umbraco.Cms.Core.DependencyInjection;
46
using Umbraco.Cms.Core.Services;
57
using Umbraco.Cms.Core.Sync;
68

@@ -9,7 +11,7 @@ namespace Umbraco.Cms.Infrastructure.Sync;
911
public class SyncBootStateAccessor : ISyncBootStateAccessor
1012
{
1113
private readonly ICacheInstructionService _cacheInstructionService;
12-
private readonly LastSyncedFileManager _lastSyncedFileManager;
14+
private readonly ILastSyncedManager _lastSyncedManager;
1315
private readonly ILogger<SyncBootStateAccessor> _logger;
1416
private GlobalSettings _globalSettings;
1517

@@ -19,24 +21,53 @@ public class SyncBootStateAccessor : ISyncBootStateAccessor
1921

2022
public SyncBootStateAccessor(
2123
ILogger<SyncBootStateAccessor> logger,
22-
LastSyncedFileManager lastSyncedFileManager,
2324
IOptionsMonitor<GlobalSettings> globalSettings,
24-
ICacheInstructionService cacheInstructionService)
25+
ICacheInstructionService cacheInstructionService,
26+
ILastSyncedManager lastSyncedManager)
2527
{
2628
_logger = logger;
27-
_lastSyncedFileManager = lastSyncedFileManager;
29+
_lastSyncedManager = lastSyncedManager;
2830
_globalSettings = globalSettings.CurrentValue;
2931
_cacheInstructionService = cacheInstructionService;
3032

3133
globalSettings.OnChange(x => _globalSettings = x);
3234
}
3335

36+
[Obsolete("Please use the constructor without LastSyncedFileManager. Scheduled for removal in Umbraco 18.")]
37+
public SyncBootStateAccessor(
38+
ILogger<SyncBootStateAccessor> logger,
39+
LastSyncedFileManager lastSyncedFileManager,
40+
IOptionsMonitor<GlobalSettings> globalSettings,
41+
ICacheInstructionService cacheInstructionService,
42+
ILastSyncedManager lastSyncedManager)
43+
: this(
44+
logger,
45+
globalSettings,
46+
cacheInstructionService,
47+
lastSyncedManager)
48+
{
49+
}
50+
51+
[Obsolete("Please use the constructor with ILastSyncedManager. Scheduled for removal in Umbraco 18.")]
52+
public SyncBootStateAccessor(
53+
ILogger<SyncBootStateAccessor> logger,
54+
LastSyncedFileManager lastSyncedFileManager,
55+
IOptionsMonitor<GlobalSettings> globalSettings,
56+
ICacheInstructionService cacheInstructionService)
57+
: this(
58+
logger,
59+
globalSettings,
60+
cacheInstructionService,
61+
StaticServiceProvider.Instance.GetRequiredService<ILastSyncedManager>())
62+
{
63+
}
64+
3465
public SyncBootState GetSyncBootState()
3566
=> LazyInitializer.EnsureInitialized(
3667
ref _syncBootState,
3768
ref _syncBootStateReady,
3869
ref _syncBootStateLock,
39-
() => InitializeColdBootState(_lastSyncedFileManager.LastSyncedId));
70+
() => InitializeColdBootState(_lastSyncedManager.GetLastSyncedExternalAsync().GetAwaiter().GetResult() ?? -1));
4071

4172
private SyncBootState InitializeColdBootState(int lastId)
4273
{
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using Microsoft.Extensions.Logging;
2+
using Microsoft.Extensions.Options;
3+
using NUnit.Framework;
4+
using Umbraco.Cms.Core.Configuration.Models;
5+
using Umbraco.Cms.Core.Models;
6+
using Umbraco.Cms.Core.Services;
7+
using Umbraco.Cms.Core.Sync;
8+
using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement;
9+
using Umbraco.Cms.Infrastructure.Scoping;
10+
using Umbraco.Cms.Infrastructure.Sync;
11+
using Umbraco.Cms.Tests.Common.Testing;
12+
using Umbraco.Cms.Tests.Integration.Testing;
13+
14+
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Sync;
15+
16+
[TestFixture]
17+
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
18+
public class SyncBootStateAccessorTest : UmbracoIntegrationTest
19+
{
20+
private ILastSyncedManager LastSyncedManager => GetRequiredService<ILastSyncedManager>();
21+
22+
private SyncBootStateAccessor CreateSyncBootStateAccessor() => new(
23+
GetRequiredService<ILogger<SyncBootStateAccessor>>(),
24+
GetRequiredService<IOptionsMonitor<GlobalSettings>>(),
25+
GetRequiredService<ICacheInstructionService>(),
26+
LastSyncedManager);
27+
28+
[Test]
29+
public void Returns_ColdBoot_When_No_Last_Synced_Id()
30+
{
31+
// Arrange - the database is fresh with no last synced id saved
32+
var syncBootStateAccessor = CreateSyncBootStateAccessor();
33+
34+
// Act
35+
var result = syncBootStateAccessor.GetSyncBootState();
36+
37+
// Assert
38+
Assert.AreEqual(SyncBootState.ColdBoot, result);
39+
}
40+
41+
[Test]
42+
public async Task Returns_WarmBoot_When_Last_Synced_Id_Exists()
43+
{
44+
// Arrange - create a cache instruction (ID is auto-incremented starting at 1)
45+
// and save the last synced external id matching it
46+
using (var scope = ScopeProvider.CreateScope())
47+
{
48+
var repo = new CacheInstructionRepository((IScopeAccessor)ScopeProvider);
49+
repo.Add(new CacheInstruction(0, DateTime.Now, "{}", "Test", 1));
50+
scope.Complete();
51+
}
52+
53+
// The auto-incremented ID will be 1 in a fresh database
54+
await LastSyncedManager.SaveLastSyncedExternalAsync(1);
55+
56+
var syncBootStateAccessor = CreateSyncBootStateAccessor();
57+
58+
// Act
59+
var result = syncBootStateAccessor.GetSyncBootState();
60+
61+
// Assert
62+
Assert.AreEqual(SyncBootState.WarmBoot, result);
63+
}
64+
}

0 commit comments

Comments
 (0)