diff --git a/Directory.Build.props b/Directory.Build.props
index 7e9b76f2e..29f30553e 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;VSTHRD200
true
enable
- 5.36.1
+ 5.36.2
$(PackageProjectUrl)
true
true
diff --git a/src/Http/Wolverine.Http/HttpChain.Codegen.cs b/src/Http/Wolverine.Http/HttpChain.Codegen.cs
index 68c1fa0be..87ec49bfb 100644
--- a/src/Http/Wolverine.Http/HttpChain.Codegen.cs
+++ b/src/Http/Wolverine.Http/HttpChain.Codegen.cs
@@ -174,6 +174,8 @@ internal IEnumerable DetermineFrames(GenerationRules rules)
private bool requiresFlush(Frame[] actionsOnOtherReturnValues)
{
+ if (Middleware.Any(x => x is IFlushesMessages)) return false;
+ if (Postprocessors.Any(x => x is IFlushesMessages)) return false;
if (Postprocessors.Any(x => x.MaySendMessages())) return true;
if (actionsOnOtherReturnValues.Any(x => x.MaySendMessages())) return true;
diff --git a/src/Persistence/EfCoreTests/Optimistic_concurrency_with_ef_core.cs b/src/Persistence/EfCoreTests/Optimistic_concurrency_with_ef_core.cs
index 68d69459f..ae8167b8e 100644
--- a/src/Persistence/EfCoreTests/Optimistic_concurrency_with_ef_core.cs
+++ b/src/Persistence/EfCoreTests/Optimistic_concurrency_with_ef_core.cs
@@ -20,7 +20,6 @@
namespace EfCoreTests;
[Collection("sqlserver")]
-[Trait("Category", "Flaky")]
public class Optimistic_concurrency_with_ef_core
{
private readonly ITestOutputHelper _output;
@@ -56,15 +55,27 @@ public async Task detect_concurrency_exception_as_SagaConcurrencyException()
using var scope = host.Services.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService();
+ // The saga's Id and the message's Id must match — Wolverine looks up the
+ // saga by the message's correlation Id (the `Id` field on
+ // UpdateConcurrencyTestSaga). Without a matching row the load throws
+ // UnknownSagaException before the handler can fake a concurrent update,
+ // which is what was making this test unconditionally fail (it was tagged
+ // [Flaky] but the failure was deterministic, not racy). With matching
+ // ids the saga loads, the handler's `OriginalValue = 999` trick simulates
+ // a stale read, SaveChangesAsync raises DbUpdateConcurrencyException, and
+ // EFCorePersistenceFrameProvider.WrapSagaConcurrencyException rethrows it
+ // as SagaConcurrencyException.
+ var sagaId = Guid.NewGuid();
await dbContext.ConcurrencyTestSagas.AddAsync(new()
{
- Id = Guid.NewGuid(),
+ Id = sagaId,
Value = "initial value",
Version = 0,
});
await dbContext.SaveChangesAsync();
- await Should.ThrowAsync(() => host.InvokeMessageAndWaitAsync(new UpdateConcurrencyTestSaga(Guid.NewGuid(), "updated value")));
+ await Should.ThrowAsync(() =>
+ host.InvokeMessageAndWaitAsync(new UpdateConcurrencyTestSaga(sagaId, "updated value")));
}
finally
{
diff --git a/src/Persistence/Wolverine.EntityFrameworkCore/Codegen/EnrollDbContextInTransaction.cs b/src/Persistence/Wolverine.EntityFrameworkCore/Codegen/EnrollDbContextInTransaction.cs
index 9df0fc91a..fece85018 100644
--- a/src/Persistence/Wolverine.EntityFrameworkCore/Codegen/EnrollDbContextInTransaction.cs
+++ b/src/Persistence/Wolverine.EntityFrameworkCore/Codegen/EnrollDbContextInTransaction.cs
@@ -8,7 +8,7 @@
namespace Wolverine.EntityFrameworkCore.Codegen;
-internal class EnrollDbContextInTransaction : AsyncFrame
+internal class EnrollDbContextInTransaction : AsyncFrame, IFlushesMessages
{
private readonly Type _dbContextType;
private readonly IdempotencyStyle _idempotencyStyle;
diff --git a/src/Wolverine/Persistence/IFlushesMessages.cs b/src/Wolverine/Persistence/IFlushesMessages.cs
new file mode 100644
index 000000000..355278488
--- /dev/null
+++ b/src/Wolverine/Persistence/IFlushesMessages.cs
@@ -0,0 +1,7 @@
+namespace Wolverine.Persistence;
+
+///
+/// Just a marker interface for the codegen that lets the codegen know
+/// that a Frame is taking care of flushing messages itself
+///
+public interface IFlushesMessages;
\ No newline at end of file