diff --git a/Directory.Build.props b/Directory.Build.props index d222b0599..9470283e4 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -11,7 +11,7 @@ 1570;1571;1572;1573;1574;1587;1591;1701;1702;1711;1735;0618 true enable - 5.19.0 + 5.19.1 $(PackageProjectUrl) true true diff --git a/src/Persistence/SqlServerTests/solo_mode_does_not_release_orphaned_messages.cs b/src/Persistence/SqlServerTests/solo_mode_does_not_release_orphaned_messages.cs new file mode 100644 index 000000000..5cdae0179 --- /dev/null +++ b/src/Persistence/SqlServerTests/solo_mode_does_not_release_orphaned_messages.cs @@ -0,0 +1,92 @@ +using IntegrationTests; +using Microsoft.Extensions.Hosting; +using Shouldly; +using Wolverine; +using Wolverine.ComplianceTests; +using Wolverine.RDBMS; +using Wolverine.RDBMS.Durability; +using Wolverine.RDBMS.Polling; +using Wolverine.Runtime; +using Wolverine.SqlServer; +using Wolverine.Tracking; + +namespace SqlServerTests; + +public class solo_mode_does_not_release_orphaned_messages : IAsyncLifetime +{ + private IHost theSoloHost; + private IHost theBalancedHost; + private const string SoloSchema = "solo_orphan"; + private const string BalancedSchema = "balanced_orphan"; + + public async Task InitializeAsync() + { + theSoloHost = await Host.CreateDefaultBuilder() + .UseWolverine(opts => + { + opts.PersistMessagesWithSqlServer(Servers.SqlServerConnectionString, SoloSchema); + opts.Durability.Mode = DurabilityMode.Solo; + }).StartAsync(); + + await theSoloHost.RebuildAllEnvelopeStorageAsync(); + + theBalancedHost = await Host.CreateDefaultBuilder() + .UseWolverine(opts => + { + opts.PersistMessagesWithSqlServer(Servers.SqlServerConnectionString, BalancedSchema); + opts.Durability.Mode = DurabilityMode.Balanced; + }).StartAsync(); + + await theBalancedHost.RebuildAllEnvelopeStorageAsync(); + } + + public async Task DisposeAsync() + { + await theSoloHost.StopAsync(); + await theBalancedHost.StopAsync(); + } + + [Fact] + public void solo_mode_build_operation_batch_excludes_orphaned_release() + { + var runtime = theSoloHost.GetRuntime(); + var database = (IMessageDatabase)runtime.Storage; + + var agent = new DurabilityAgent(runtime, database); + + var operations = agent.buildOperationBatch(); + + // Should NOT contain any orphaned message release operations in Solo mode + operations.ShouldNotContain(op => op is ReleaseOrphanedMessagesOperation); + operations.ShouldNotContain(op => op is ReleaseOrphanedMessagesForAncillaryOperation); + } + + [Fact] + public void solo_mode_build_operation_batch_excludes_orphaned_release_even_with_active_nodes() + { + var runtime = theSoloHost.GetRuntime(); + var database = (IMessageDatabase)runtime.Storage; + + var agent = new DurabilityAgent(runtime, database); + + var operations = agent.buildOperationBatch(activeNodeNumbers: new List { 1, 2, 3 }); + + // Should NOT contain any orphaned message release operations in Solo mode + operations.ShouldNotContain(op => op is ReleaseOrphanedMessagesOperation); + operations.ShouldNotContain(op => op is ReleaseOrphanedMessagesForAncillaryOperation); + } + + [Fact] + public void balanced_mode_build_operation_batch_includes_orphaned_release() + { + var runtime = theBalancedHost.GetRuntime(); + var database = (IMessageDatabase)runtime.Storage; + + var agent = new DurabilityAgent(runtime, database); + + var operations = agent.buildOperationBatch(); + + // SHOULD contain the orphaned message release operation for main databases in Balanced mode + operations.ShouldContain(op => op is ReleaseOrphanedMessagesOperation); + } +} diff --git a/src/Persistence/Wolverine.RDBMS/DurabilityAgent.cs b/src/Persistence/Wolverine.RDBMS/DurabilityAgent.cs index e1379af18..7b49706f7 100644 --- a/src/Persistence/Wolverine.RDBMS/DurabilityAgent.cs +++ b/src/Persistence/Wolverine.RDBMS/DurabilityAgent.cs @@ -79,7 +79,7 @@ public Task StartAsync(CancellationToken cancellationToken) _recoveryTimer = new Timer(async _ => { IReadOnlyList? activeNodeNumbers = null; - if (_database.Settings.Role != MessageStoreRole.Main) + if (_settings.Mode != DurabilityMode.Solo && _database.Settings.Role != MessageStoreRole.Main) { try { @@ -170,7 +170,7 @@ private bool isTimeToPruneNodeEventRecords() return false; } - private IDatabaseOperation[] buildOperationBatch(IReadOnlyList? activeNodeNumbers = null) + internal IDatabaseOperation[] buildOperationBatch(IReadOnlyList? activeNodeNumbers = null) { var incomingTable = new DbObjectName(_database.SchemaName, DatabaseConstants.IncomingTable); var now = DateTimeOffset.UtcNow; @@ -183,19 +183,25 @@ private IDatabaseOperation[] buildOperationBatch(IReadOnlyList? activeNodeN new MoveReplayableErrorMessagesToIncomingOperation(_database) ]; - if (_database.Settings.Role == MessageStoreRole.Main) + if (_settings.Mode != DurabilityMode.Solo) { - ops.Add(new ReleaseOrphanedMessagesOperation(_database)); + if (_database.Settings.Role == MessageStoreRole.Main) + { + ops.Add(new ReleaseOrphanedMessagesOperation(_database)); + } + else if (activeNodeNumbers is { Count: > 0 }) + { + ops.Add(new ReleaseOrphanedMessagesForAncillaryOperation(_database, activeNodeNumbers)); + } + } + if (_database.Settings.Role == MessageStoreRole.Main) + { if (isTimeToPruneNodeEventRecords()) { ops.Add(new DeleteOldNodeEventRecords(_database, _settings)); } } - else if (activeNodeNumbers is { Count: > 0 }) - { - ops.Add(new ReleaseOrphanedMessagesForAncillaryOperation(_database, activeNodeNumbers)); - } if (_runtime.Options.Durability.OutboxStaleTime.HasValue) {