You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by pt...@apache.org on 2023/02/06 08:53:37 UTC

[ignite-3] branch main updated: IGNITE-18696 .NET: Add read-only transactions (#1634)

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 fd16bed21c IGNITE-18696 .NET: Add read-only transactions (#1634)
fd16bed21c is described below

commit fd16bed21ccf99df3a187c0779942fb6b027cf7c
Author: Pavel Tupitsyn <pt...@apache.org>
AuthorDate: Mon Feb 6 10:53:31 2023 +0200

    IGNITE-18696 .NET: Add read-only transactions (#1634)
---
 .../org/apache/ignite/tx/IgniteTransactions.java   |  3 ++
 .../dotnet/Apache.Ignite.Tests/.editorconfig       |  2 +-
 .../Transactions/TransactionsTests.cs              | 58 ++++++++++++++++++++++
 .../Internal/Transactions/Transactions.cs          |  6 +--
 .../Apache.Ignite/Transactions/ITransaction.cs     |  2 +-
 .../Apache.Ignite/Transactions/ITransactions.cs    | 11 +++-
 .../{ITransactions.cs => TransactionOptions.cs}    | 25 ++++------
 7 files changed, 84 insertions(+), 23 deletions(-)

diff --git a/modules/api/src/main/java/org/apache/ignite/tx/IgniteTransactions.java b/modules/api/src/main/java/org/apache/ignite/tx/IgniteTransactions.java
index f4b90ea139..d2ba3b74fc 100644
--- a/modules/api/src/main/java/org/apache/ignite/tx/IgniteTransactions.java
+++ b/modules/api/src/main/java/org/apache/ignite/tx/IgniteTransactions.java
@@ -73,6 +73,9 @@ public interface IgniteTransactions {
     /**
      * Returns a decorated {@code IgniteTransactions} instance that will start read-only transactions.
      *
+     * <p>Read-only transactions provide a snapshot view of data at a certain point in time.
+     * They are lock-free and perform better than normal transactions, but do not permit data modifications.
+     *
      * @return Decorated {@code IgniteTransactions} instance that will start read-only transactions.
      */
     IgniteTransactions readOnly();
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/.editorconfig b/modules/platforms/dotnet/Apache.Ignite.Tests/.editorconfig
index 5cfad90769..3a49c19db7 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/.editorconfig
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/.editorconfig
@@ -40,4 +40,4 @@ dotnet_diagnostic.CA1508.severity = none # Avoid dead conditional code
 dotnet_diagnostic.CA1305.severity = none # Specify IFormatProvider
 dotnet_diagnostic.CA1819.severity = none # Properties should not return arrays
 dotnet_diagnostic.CA1812.severity = none # Avoid uninstantiated internal classes
-
+dotnet_diagnostic.CA5394.severity = none # Use secure random
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Transactions/TransactionsTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Transactions/TransactionsTests.cs
index 937ab937f5..694af4d5f5 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Transactions/TransactionsTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Transactions/TransactionsTests.cs
@@ -17,11 +17,15 @@
 
 namespace Apache.Ignite.Tests.Transactions
 {
+    using System;
+    using System.Diagnostics.CodeAnalysis;
     using System.Linq;
     using System.Threading.Tasks;
     using System.Transactions;
     using Ignite.Transactions;
     using NUnit.Framework;
+    using Table;
+    using TransactionOptions = Ignite.Transactions.TransactionOptions;
 
     /// <summary>
     /// Tests for <see cref="ITransactions"/> and <see cref="ITransaction"/>.
@@ -187,6 +191,60 @@ namespace Apache.Ignite.Tests.Transactions
             Assert.AreEqual("Specified transaction belongs to a different IgniteClient instance.", ex!.Message);
         }
 
+        [Test]
+        public async Task TestReadOnlyTxSeesOldDataAfterUpdate()
+        {
+            var key = Random.Shared.NextInt64(1000, long.MaxValue);
+            var keyPoco = new Poco { Key = key };
+
+            await PocoView.UpsertAsync(null, new Poco { Key = key, Val = "11" });
+
+            await using var tx = await Client.Transactions.BeginAsync(new TransactionOptions { ReadOnly = true });
+            Assert.AreEqual("11", (await PocoView.GetAsync(tx, keyPoco)).Value.Val);
+
+            // Update data in a different tx.
+            await using (var tx2 = await Client.Transactions.BeginAsync())
+            {
+                await PocoView.UpsertAsync(null, new Poco { Key = key, Val = "22" });
+                await tx2.CommitAsync();
+            }
+
+            // Old tx sees old data.
+            Assert.AreEqual("11", (await PocoView.GetAsync(tx, keyPoco)).Value.Val);
+
+            // New tx sees new data
+            await using var tx3 = await Client.Transactions.BeginAsync(new TransactionOptions { ReadOnly = true });
+            Assert.AreEqual("22", (await PocoView.GetAsync(tx3, keyPoco)).Value.Val);
+        }
+
+        [Test]
+        public async Task TestUpdateInReadOnlyTxThrows()
+        {
+            await using var tx = await Client.Transactions.BeginAsync(new TransactionOptions { ReadOnly = true });
+            var ex = Assert.ThrowsAsync<Tx.TransactionException>(async () => await TupleView.UpsertAsync(tx, GetTuple(1, "1")));
+
+            Assert.AreEqual(ErrorGroups.Transactions.TxFailedReadWriteOperation, ex!.Code, ex.Message);
+            StringAssert.Contains("Failed to enlist read-write operation into read-only transaction", ex.Message);
+        }
+
+        [Test]
+        public async Task TestCommitRollbackReadOnlyTxDoesNothing([Values(true, false)] bool commit)
+        {
+            await using var tx = await Client.Transactions.BeginAsync(new TransactionOptions { ReadOnly = true });
+            var res = await PocoView.GetAsync(tx, new Poco { Key = 123 });
+
+            if (commit)
+            {
+                await tx.CommitAsync();
+            }
+            else
+            {
+                await tx.RollbackAsync();
+            }
+
+            Assert.IsFalse(res.HasValue);
+        }
+
         private class CustomTx : ITransaction
         {
             public ValueTask DisposeAsync()
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Transactions/Transactions.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Transactions/Transactions.cs
index a096cdc7dc..2ea139d736 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Transactions/Transactions.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Transactions/Transactions.cs
@@ -39,12 +39,10 @@ namespace Apache.Ignite.Internal.Transactions
         }
 
         /// <inheritdoc/>
-        public async Task<ITransaction> BeginAsync()
+        public async Task<ITransaction> BeginAsync(TransactionOptions options)
         {
             using var writer = ProtoCommon.GetMessageWriter();
-
-            // TODO: IGNITE-18696
-            writer.MessageWriter.Write(false); // Read-only.
+            writer.MessageWriter.Write(options.ReadOnly);
 
             // Transaction and all corresponding operations must be performed using the same connection.
             var (resBuf, socket) = await _socket.DoOutInOpAndGetSocketAsync(ClientOp.TxBegin, request: writer).ConfigureAwait(false);
diff --git a/modules/platforms/dotnet/Apache.Ignite/Transactions/ITransaction.cs b/modules/platforms/dotnet/Apache.Ignite/Transactions/ITransaction.cs
index e5c4cea932..36d36f59da 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Transactions/ITransaction.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Transactions/ITransaction.cs
@@ -23,7 +23,7 @@ namespace Apache.Ignite.Transactions
     /// <summary>
     /// Ignite transaction.
     /// <para />
-    /// Use <see cref="ITransactions.BeginAsync"/> to start a new transaction.
+    /// Use <see cref="ITransactions.BeginAsync()"/> to start a new transaction.
     /// </summary>
     public interface ITransaction : IAsyncDisposable
     {
diff --git a/modules/platforms/dotnet/Apache.Ignite/Transactions/ITransactions.cs b/modules/platforms/dotnet/Apache.Ignite/Transactions/ITransactions.cs
index f2904e7edf..6914d1a7da 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Transactions/ITransactions.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Transactions/ITransactions.cs
@@ -1,4 +1,4 @@
-/*
+ /*
  * Licensed to the Apache Software Foundation (ASF) under one or more
  * contributor license agreements. See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -27,7 +27,14 @@ namespace Apache.Ignite.Transactions
         /// <summary>
         /// Starts a new transaction.
         /// </summary>
+        /// <param name="options">Transaction options.</param>
         /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
-        Task<ITransaction> BeginAsync();
+        Task<ITransaction> BeginAsync(TransactionOptions options);
+
+        /// <summary>
+        /// Starts a new transaction.
+        /// </summary>
+        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
+        Task<ITransaction> BeginAsync() => BeginAsync(default);
     }
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite/Transactions/ITransactions.cs b/modules/platforms/dotnet/Apache.Ignite/Transactions/TransactionOptions.cs
similarity index 64%
copy from modules/platforms/dotnet/Apache.Ignite/Transactions/ITransactions.cs
copy to modules/platforms/dotnet/Apache.Ignite/Transactions/TransactionOptions.cs
index f2904e7edf..acc005bdcc 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Transactions/ITransactions.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Transactions/TransactionOptions.cs
@@ -15,19 +15,14 @@
  * limitations under the License.
  */
 
-namespace Apache.Ignite.Transactions
-{
-    using System.Threading.Tasks;
+namespace Apache.Ignite.Transactions;
 
-    /// <summary>
-    /// Ignite transactions API.
-    /// </summary>
-    public interface ITransactions
-    {
-        /// <summary>
-        /// Starts a new transaction.
-        /// </summary>
-        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
-        Task<ITransaction> BeginAsync();
-    }
-}
+/// <summary>
+/// Ignite transaction options.
+/// </summary>
+/// <param name="ReadOnly">
+/// Whether to start a read-only transaction.
+/// Read-only transactions provide a snapshot view of data at a certain point in time.
+/// They are lock-free and perform better than normal transactions, but do not permit data modifications.
+/// </param>
+public readonly record struct TransactionOptions(bool ReadOnly);