diff --git a/src/Orleans.Transactions.TestKit.Base/Grains/ITransactionCoordinatorGrain.cs b/src/Orleans.Transactions.TestKit.Base/Grains/ITransactionCoordinatorGrain.cs index f82baff7ee5..e7d0222e81a 100644 --- a/src/Orleans.Transactions.TestKit.Base/Grains/ITransactionCoordinatorGrain.cs +++ b/src/Orleans.Transactions.TestKit.Base/Grains/ITransactionCoordinatorGrain.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Orleans.Concurrency; using Orleans.Transactions.Abstractions; using Orleans.Transactions.TestKit.Correctnesss; @@ -36,5 +37,9 @@ public interface ITransactionCoordinatorGrain : IGrainWithGuidKey [Transaction(TransactionOption.Create)] Task MultiGrainAdd(ITransactionCommitterTestGrain committer, ITransactionCommitOperation operation, List grains, int numberToAdd); + + [Transaction(TransactionOption.Create)] + [ReadOnly] + Task UpdateViolated(ITransactionTestGrain grains, int numberToAdd); } } diff --git a/src/Orleans.Transactions.TestKit.Base/Grains/TransactionCoordinatorGrain.cs b/src/Orleans.Transactions.TestKit.Base/Grains/TransactionCoordinatorGrain.cs index 2c5aa8bfb83..6d1c38b67fc 100644 --- a/src/Orleans.Transactions.TestKit.Base/Grains/TransactionCoordinatorGrain.cs +++ b/src/Orleans.Transactions.TestKit.Base/Grains/TransactionCoordinatorGrain.cs @@ -57,6 +57,11 @@ public Task MultiGrainAdd(ITransactionCommitterTestGrain committer, ITransaction return Task.WhenAll(tasks); } + public Task UpdateViolated(ITransactionTestGrain grain, int numberToAdd) + { + return grain.Add(numberToAdd); + } + private async Task Double(ITransactionTestGrain grain) { int[] values = await grain.Get(); diff --git a/src/Orleans.Transactions.TestKit.Base/TestRunners/GrainFaultTransactionTestRunner.cs b/src/Orleans.Transactions.TestKit.Base/TestRunners/GrainFaultTransactionTestRunner.cs index 2fb18a1fd9d..e2fca0a4286 100644 --- a/src/Orleans.Transactions.TestKit.Base/TestRunners/GrainFaultTransactionTestRunner.cs +++ b/src/Orleans.Transactions.TestKit.Base/TestRunners/GrainFaultTransactionTestRunner.cs @@ -33,6 +33,27 @@ await TestAfterDustSettles(async () => }); } + public virtual async Task AbortTransactionOnReadOnlyViolatedException(string grainStates) + { + const int expected = 5; + + ITransactionTestGrain grain = RandomTestGrain(grainStates); + ITransactionCoordinatorGrain coordinator = this.grainFactory.GetGrain(Guid.NewGuid()); + + await coordinator.MultiGrainSet(new List { grain }, expected); + Func task = () => coordinator.UpdateViolated(grain, expected); + await task.Should().ThrowAsync(); + + await TestAfterDustSettles(async () => + { + int[] actualValues = await grain.Get(); + foreach (var actual in actualValues) + { + actual.Should().Be(expected); + } + }); + } + public virtual async Task MultiGrainAbortTransactionOnExceptions(string grainStates) { const int grainCount = TransactionTestConstants.MaxCoordinatedTransactions - 1; diff --git a/src/Orleans.Transactions.TestKit.xUnit/GrainFaultTransactionTestRunner.cs b/src/Orleans.Transactions.TestKit.xUnit/GrainFaultTransactionTestRunner.cs index 564f51ed23b..f3276ca1c30 100644 --- a/src/Orleans.Transactions.TestKit.xUnit/GrainFaultTransactionTestRunner.cs +++ b/src/Orleans.Transactions.TestKit.xUnit/GrainFaultTransactionTestRunner.cs @@ -19,6 +19,15 @@ public override Task AbortTransactionOnExceptions(string grainStates) return base.AbortTransactionOnExceptions(grainStates); } + [SkippableTheory] + [InlineData(TransactionTestConstants.SingleStateTransactionalGrain)] + [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain)] + [InlineData(TransactionTestConstants.MaxStateTransactionalGrain)] + public override Task AbortTransactionOnReadOnlyViolatedException(string grainStates) + { + return base.AbortTransactionOnReadOnlyViolatedException(grainStates); + } + [SkippableTheory] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain)] diff --git a/src/Orleans.Transactions/DistributedTM/TransactionAgent.cs b/src/Orleans.Transactions/DistributedTM/TransactionAgent.cs index eadf9d5a98d..d8b6d387f6f 100644 --- a/src/Orleans.Transactions/DistributedTM/TransactionAgent.cs +++ b/src/Orleans.Transactions/DistributedTM/TransactionAgent.cs @@ -37,7 +37,7 @@ public Task StartTransaction(bool readOnly, TimeSpan timeout) LogTraceStartTransaction(new(stopwatch), guid, new(ts)); this.statistics.TrackTransactionStarted(); - return Task.FromResult(new TransactionInfo(guid, ts, ts)); + return Task.FromResult(new TransactionInfo(guid, ts, ts, readOnly)); } public async Task<(TransactionalStatus, Exception)> Resolve(TransactionInfo transactionInfo) diff --git a/src/Orleans.Transactions/TransactionAttribute.cs b/src/Orleans.Transactions/TransactionAttribute.cs index 61a7d2c1335..e32d09cd86e 100644 --- a/src/Orleans.Transactions/TransactionAttribute.cs +++ b/src/Orleans.Transactions/TransactionAttribute.cs @@ -132,7 +132,7 @@ async Task IOutgoingGrainCallFilter.Invoke(IOutgoingGrainCallContext context) } private TransactionInfo SetTransactionInfo() - { + { // Clear transaction info if transaction operation requires new transaction. var transactionInfo = TransactionContext.GetTransactionInfo(); @@ -180,7 +180,7 @@ public override async ValueTask Invoke() var transactionTimeout = Debugger.IsAttached ? TimeSpan.FromMinutes(30) : TimeSpan.FromSeconds(10); // Start a new transaction - var isReadOnly = (this.Options | InvokeMethodOptions.ReadOnly) == InvokeMethodOptions.ReadOnly; + var isReadOnly = this.Options.HasFlag(InvokeMethodOptions.ReadOnly); transactionInfo = await TransactionAgent.StartTransaction(isReadOnly, transactionTimeout); startedNewTransaction = true; } @@ -312,7 +312,7 @@ public override void Dispose() } [SerializerTransparent] - public abstract class TransactionRequest : TransactionRequestBase + public abstract class TransactionRequest : TransactionRequestBase { protected TransactionRequest(Serializer exceptionSerializer, IServiceProvider serviceProvider) : base(exceptionSerializer, serviceProvider) { @@ -482,4 +482,4 @@ private static async ValueTask CompleteInvokeAsync(Task resultTask) // Generated protected abstract Task InvokeInner(); } -} \ No newline at end of file +}