IGNITE-24329 .NET: Fix LazyTransaction observable timestamp (#5129)
Record observable timestamp when the user starts the transaction.
(cherry picked from commit 6c4f36a8fa5e6236425f3ea4f9d855bafe496a7c)
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/PartitionAwarenessTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/PartitionAwarenessTests.cs
index 143733c..516016f 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/PartitionAwarenessTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/PartitionAwarenessTests.cs
@@ -447,7 +447,7 @@
node.ClearOps();
node2?.ClearOps();
- ITransaction? tx = withTx ? new LazyTransaction(default) : null;
+ ITransaction? tx = withTx ? new LazyTransaction(default, 0) : null;
await action(tx);
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Transactions/TransactionsTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Transactions/TransactionsTests.cs
index 4f8e4ac..78beb7b 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Transactions/TransactionsTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Transactions/TransactionsTests.cs
@@ -196,25 +196,25 @@
}
[Test]
- public async Task TestReadOnlyTxSeesOldDataAfterUpdate()
+ public async Task TestReadOnlyTxSeesOldDataAfterUpdate([Values(true, false)] bool readBeforeUpdate)
{
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);
+ await using var roTx = await Client.Transactions.BeginAsync(new TransactionOptions { ReadOnly = true });
- // Update data in a different tx.
- await using (var tx2 = await Client.Transactions.BeginAsync())
+ if (readBeforeUpdate)
{
- await PocoView.UpsertAsync(null, new Poco { Key = key, Val = "22" });
- await tx2.CommitAsync();
+ Assert.AreEqual("11", (await PocoView.GetAsync(roTx, keyPoco)).Value.Val);
}
- // Old tx sees old data.
- Assert.AreEqual("11", (await PocoView.GetAsync(tx, keyPoco)).Value.Val);
+ // Update data in a different (implicit) tx.
+ await PocoView.UpsertAsync(transaction: null, new Poco { Key = key, Val = "22" });
+
+ // Old read-only tx sees old data.
+ Assert.AreEqual("11", (await PocoView.GetAsync(roTx, keyPoco)).Value.Val);
// New tx sees new data
await using var tx3 = await Client.Transactions.BeginAsync(new TransactionOptions { ReadOnly = true });
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/IgniteClientInternal.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/IgniteClientInternal.cs
index e04ca9c..28ca8eb 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/IgniteClientInternal.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/IgniteClientInternal.cs
@@ -47,7 +47,7 @@
var tables = new Tables(socket, sql);
Tables = tables;
- Transactions = new Transactions.Transactions();
+ Transactions = new Transactions.Transactions(socket);
Compute = new Compute.Compute(socket, tables);
Sql = sql;
}
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Transactions/LazyTransaction.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Transactions/LazyTransaction.cs
index 933ad33..c72f36f 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Transactions/LazyTransaction.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Transactions/LazyTransaction.cs
@@ -46,6 +46,8 @@
private readonly TransactionOptions _options;
+ private readonly long _observableTimestamp;
+
private int _state = StateOpen;
private volatile Task<Transaction>? _tx;
@@ -54,7 +56,12 @@
/// Initializes a new instance of the <see cref="LazyTransaction"/> class.
/// </summary>
/// <param name="options">Options.</param>
- public LazyTransaction(TransactionOptions options) => _options = options;
+ /// <param name="observableTimestamp">Observable timestamp.</param>
+ public LazyTransaction(TransactionOptions options, long observableTimestamp)
+ {
+ _options = options;
+ _observableTimestamp = observableTimestamp;
+ }
/// <inheritdoc/>
public bool IsReadOnly => _options.ReadOnly;
@@ -170,14 +177,14 @@
return txTask;
}
- txTask = BeginAsync(socket, preferredNode);
+ txTask = BeginAsync(socket, preferredNode, _observableTimestamp);
_tx = txTask;
return txTask;
}
}
- private async Task<Transaction> BeginAsync(ClientFailoverSocket failoverSocket, PreferredNode preferredNode)
+ private async Task<Transaction> BeginAsync(ClientFailoverSocket failoverSocket, PreferredNode preferredNode, long observableTimestamp)
{
using var writer = ProtoCommon.GetMessageWriter();
Write();
@@ -198,7 +205,7 @@
var w = writer.MessageWriter;
w.Write(_options.ReadOnly);
w.Write(_options.TimeoutMillis);
- w.Write(failoverSocket.ObservableTimestamp);
+ w.Write(observableTimestamp);
}
}
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Transactions/Transactions.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Transactions/Transactions.cs
index 02fb3f1..f182e5c 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Transactions/Transactions.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Transactions/Transactions.cs
@@ -27,10 +27,18 @@
/// </summary>
internal class Transactions : ITransactions
{
+ private readonly ClientFailoverSocket _socket;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Transactions"/> class.
+ /// </summary>
+ /// <param name="socket">Socket.</param>
+ public Transactions(ClientFailoverSocket socket) => _socket = socket;
+
/// <inheritdoc/>
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Tx is returned.")]
public ValueTask<ITransaction> BeginAsync(TransactionOptions options) =>
- ValueTask.FromResult((ITransaction)new LazyTransaction(options));
+ ValueTask.FromResult<ITransaction>(new LazyTransaction(options, _socket.ObservableTimestamp));
/// <inheritdoc />
public override string ToString() => IgniteToStringBuilder.Build(GetType());