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)
{