This is an automated email from the ASF dual-hosted git repository. ptupitsyn pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push: new 5d05ac362a IGNITE-18513 .NET: Ignore commit/rollback on completed tx (#1769) 5d05ac362a is described below commit 5d05ac362a746f996b19f36e8ee108febbf338b5 Author: Pavel Tupitsyn <ptupit...@apache.org> AuthorDate: Thu Mar 9 21:04:15 2023 +0300 IGNITE-18513 .NET: Ignore commit/rollback on completed tx (#1769) * Do not throw exception on `Commit` & `Rollback` when transaction is already completed (committed or rolled back). * Add `TroString` override. --- .../Transactions/TransactionsTests.cs | 36 ++++++++--- .../Internal/Transactions/Transaction.cs | 71 ++++++++-------------- 2 files changed, 53 insertions(+), 54 deletions(-) diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Transactions/TransactionsTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Transactions/TransactionsTests.cs index c7a0c3a699..ac85e20f77 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Tests/Transactions/TransactionsTests.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Transactions/TransactionsTests.cs @@ -19,6 +19,7 @@ namespace Apache.Ignite.Tests.Transactions { using System; using System.Linq; + using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Transactions; using Ignite.Transactions; @@ -119,23 +120,23 @@ namespace Apache.Ignite.Tests.Transactions } [Test] - public async Task TestCommitRollbackSameTxThrows() + public async Task TestCommitAfterRollbackIsIgnored() { await using var tx = await Client.Transactions.BeginAsync(); await tx.CommitAsync(); + await tx.RollbackAsync(); - var ex = Assert.ThrowsAsync<TransactionException>(async () => await tx.RollbackAsync()); - Assert.AreEqual("Transaction is already committed.", ex?.Message); + StringAssert.Contains("State = Committed", tx.ToString()); } [Test] - public async Task TestRollbackCommitSameTxThrows() + public async Task TestRollbackAfterCommitIsIgnored() { await using var tx = await Client.Transactions.BeginAsync(); await tx.RollbackAsync(); + await tx.CommitAsync(); - var ex = Assert.ThrowsAsync<TransactionException>(async () => await tx.CommitAsync()); - Assert.AreEqual("Transaction is already rolled back.", ex?.Message); + StringAssert.Contains("State = RolledBack", tx.ToString()); } [Test] @@ -145,9 +146,9 @@ namespace Apache.Ignite.Tests.Transactions await tx.DisposeAsync(); await tx.DisposeAsync(); + await tx.CommitAsync(); - var ex = Assert.ThrowsAsync<TransactionException>(async () => await tx.CommitAsync()); - Assert.AreEqual("Transaction is already rolled back.", ex?.Message); + StringAssert.Contains("State = RolledBack", tx.ToString()); } [Test] @@ -250,6 +251,7 @@ namespace Apache.Ignite.Tests.Transactions await using var tx = await Client.Transactions.BeginAsync(new TransactionOptions { ReadOnly = true }); Assert.IsTrue(tx.IsReadOnly); + StringAssert.Contains("State = Open, IsReadOnly = True", tx.ToString()); } [Test] @@ -258,6 +260,24 @@ namespace Apache.Ignite.Tests.Transactions await using var tx = await Client.Transactions.BeginAsync(); Assert.IsFalse(tx.IsReadOnly); + StringAssert.Contains("State = Open, IsReadOnly = False", tx.ToString()); + } + + [Test] + public async Task TestToString() + { + await using var tx1 = await Client.Transactions.BeginAsync(); + await using var tx2 = await Client.Transactions.BeginAsync(new(ReadOnly: true)); + await using var tx3 = await Client.Transactions.BeginAsync(); + + await tx2.RollbackAsync(); + await tx3.CommitAsync(); + + var id = int.Parse(Regex.Match(tx1.ToString()!, @"\d+").Value); + + Assert.AreEqual($"Transaction {{ Id = {id}, State = Open, IsReadOnly = False }}", tx1.ToString()); + Assert.AreEqual($"Transaction {{ Id = {id + 1}, State = RolledBack, IsReadOnly = True }}", tx2.ToString()); + Assert.AreEqual($"Transaction {{ Id = {id + 2}, State = Committed, IsReadOnly = False }}", tx3.ToString()); } private class CustomTx : ITransaction diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Transactions/Transaction.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Transactions/Transaction.cs index 312d149392..1a9676002f 100644 --- a/modules/platforms/dotnet/Apache.Ignite/Internal/Transactions/Transaction.cs +++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Transactions/Transaction.cs @@ -19,7 +19,6 @@ namespace Apache.Ignite.Internal.Transactions { using System.Threading; using System.Threading.Tasks; - using System.Transactions; using Ignite.Transactions; using Proto; using Proto.MsgPack; @@ -77,41 +76,46 @@ namespace Apache.Ignite.Internal.Transactions /// <inheritdoc/> public async Task CommitAsync() { - SetState(StateCommitted); - - using var writer = ProtoCommon.GetMessageWriter(); - Write(writer.MessageWriter); + if (TrySetState(StateCommitted)) + { + using var writer = ProtoCommon.GetMessageWriter(); + Write(writer.MessageWriter); - await Socket.DoOutInOpAsync(ClientOp.TxCommit, writer).ConfigureAwait(false); + await Socket.DoOutInOpAsync(ClientOp.TxCommit, writer).ConfigureAwait(false); + } } /// <inheritdoc/> - public async Task RollbackAsync() - { - SetState(StateRolledBack); + public async Task RollbackAsync() => await RollbackAsyncInternal().ConfigureAwait(false); - await RollbackAsyncInternal().ConfigureAwait(false); - } + /// <inheritdoc/> + public async ValueTask DisposeAsync() => await RollbackAsyncInternal().ConfigureAwait(false); /// <inheritdoc/> - public async ValueTask DisposeAsync() + public override string ToString() { - // Roll back if the transaction is still open, otherwise do nothing. - if (TrySetState(StateRolledBack)) + var state = _state switch { - await RollbackAsyncInternal().ConfigureAwait(false); - } + StateOpen => "Open", + StateCommitted => "Committed", + _ => "RolledBack" + }; + + return $"Transaction {{ Id = {Id}, State = {state}, IsReadOnly = {IsReadOnly} }}"; } /// <summary> /// Rolls back the transaction without state check. /// </summary> - private async Task RollbackAsyncInternal() + private async ValueTask RollbackAsyncInternal() { - using var writer = ProtoCommon.GetMessageWriter(); - Write(writer.MessageWriter); + if (TrySetState(StateRolledBack)) + { + using var writer = ProtoCommon.GetMessageWriter(); + Write(writer.MessageWriter); - await Socket.DoOutInOpAsync(ClientOp.TxRollback, writer).ConfigureAwait(false); + await Socket.DoOutInOpAsync(ClientOp.TxRollback, writer).ConfigureAwait(false); + } } /// <summary> @@ -119,32 +123,7 @@ namespace Apache.Ignite.Internal.Transactions /// </summary> /// <param name="state">State to set.</param> /// <returns>True when specified state was set successfully; false otherwise.</returns> - private bool TrySetState(int state) - { - return Interlocked.CompareExchange(ref _state, state, StateOpen) == StateOpen; - } - - /// <summary> - /// Sets the specified state. Throws <see cref="TransactionException"/> when current state is different from - /// <see cref="StateOpen"/>. - /// </summary> - /// <param name="state">State to set.</param> - /// <exception cref="TransactionException">When current state is not <see cref="StateOpen"/>.</exception> - private void SetState(int state) - { - var oldState = Interlocked.CompareExchange(ref _state, state, StateOpen); - - if (oldState == StateOpen) - { - return; - } - - var message = oldState == StateCommitted - ? "Transaction is already committed." - : "Transaction is already rolled back."; - - throw new TransactionException(message); - } + private bool TrySetState(int state) => Interlocked.CompareExchange(ref _state, state, StateOpen) == StateOpen; /// <summary> /// Writes the transaction.