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 5b38be74f89 IGNITE-26555 Fix inconsistent RO TX read timestamp on 
client (#6672)
5b38be74f89 is described below

commit 5b38be74f89a8a5ddf5b115e26aed5e7e27e47cb
Author: Pavel Tupitsyn <ptupit...@apache.org>
AuthorDate: Thu Oct 2 12:09:58 2025 +0300

    IGNITE-26555 Fix inconsistent RO TX read timestamp on client (#6672)
    
    Obtain read timestamp when `ClientLazyTransaction` is created (not later) 
to match embedded behavior.
---
 .../internal/client/tx/ClientLazyTransaction.java     |  8 ++++++--
 .../ignite/internal/client/tx/ClientTransactions.java |  5 ++---
 .../ignite/internal/client/tx/DirectTxUtils.java      |  2 +-
 .../app/client/ItThinClientTransactionsTest.java      | 19 +++++++++++++++++++
 4 files changed, 28 insertions(+), 6 deletions(-)

diff --git 
a/modules/client/src/main/java/org/apache/ignite/internal/client/tx/ClientLazyTransaction.java
 
b/modules/client/src/main/java/org/apache/ignite/internal/client/tx/ClientLazyTransaction.java
index a816c050434..8e2a4e634cd 100644
--- 
a/modules/client/src/main/java/org/apache/ignite/internal/client/tx/ClientLazyTransaction.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/internal/client/tx/ClientLazyTransaction.java
@@ -35,14 +35,14 @@ import org.jetbrains.annotations.Nullable;
  * Lazy client transaction. Will be actually started on the first operation.
  */
 public class ClientLazyTransaction implements Transaction {
-    private final HybridTimestampTracker observableTimestamp;
+    private final long observableTimestamp;
 
     private final @Nullable TransactionOptions options;
 
     private volatile CompletableFuture<ClientTransaction> tx;
 
     ClientLazyTransaction(HybridTimestampTracker observableTimestamp, 
@Nullable TransactionOptions options) {
-        this.observableTimestamp = observableTimestamp;
+        this.observableTimestamp = observableTimestamp.getLong();
         this.options = options;
     }
 
@@ -200,4 +200,8 @@ public class ClientLazyTransaction implements Transaction {
 
         return tx0.join();
     }
+
+    public long observableTimestamp() {
+        return observableTimestamp;
+    }
 }
diff --git 
a/modules/client/src/main/java/org/apache/ignite/internal/client/tx/ClientTransactions.java
 
b/modules/client/src/main/java/org/apache/ignite/internal/client/tx/ClientTransactions.java
index 8127b5ad474..71557894d5c 100644
--- 
a/modules/client/src/main/java/org/apache/ignite/internal/client/tx/ClientTransactions.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/internal/client/tx/ClientTransactions.java
@@ -28,7 +28,6 @@ import org.apache.ignite.internal.client.PayloadInputChannel;
 import org.apache.ignite.internal.client.ReliableChannel;
 import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
 import org.apache.ignite.internal.client.proto.ClientOp;
-import org.apache.ignite.internal.hlc.HybridTimestampTracker;
 import org.apache.ignite.tx.IgniteTransactions;
 import org.apache.ignite.tx.Transaction;
 import org.apache.ignite.tx.TransactionOptions;
@@ -78,7 +77,7 @@ public class ClientTransactions implements IgniteTransactions 
{
     static CompletableFuture<ClientTransaction> beginAsync(
             ReliableChannel ch,
             @Nullable TransactionOptions options,
-            HybridTimestampTracker observableTimestamp,
+            long observableTimestamp,
             Supplier<CompletableFuture<ClientChannel>> channelResolver
     ) {
         boolean readOnly = options != null && options.readOnly();
@@ -89,7 +88,7 @@ public class ClientTransactions implements IgniteTransactions 
{
                 w -> {
                     w.out().packBoolean(readOnly);
                     w.out().packLong(timeout);
-                    w.out().packLong(observableTimestamp.get().longValue());
+                    w.out().packLong(observableTimestamp);
                 },
                 r -> readTx(r, readOnly, timeout),
                 channelResolver,
diff --git 
a/modules/client/src/main/java/org/apache/ignite/internal/client/tx/DirectTxUtils.java
 
b/modules/client/src/main/java/org/apache/ignite/internal/client/tx/DirectTxUtils.java
index 3f927cd38c5..e0e08bfbded 100644
--- 
a/modules/client/src/main/java/org/apache/ignite/internal/client/tx/DirectTxUtils.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/internal/client/tx/DirectTxUtils.java
@@ -128,7 +128,7 @@ public class DirectTxUtils {
                 if (ctx.firstReqFut != null) {
                     ClientLazyTransaction tx0 = (ClientLazyTransaction) tx;
                     out.out().packLong(TX_ID_FIRST_DIRECT);
-                    out.out().packLong(ctx.tracker.get().longValue());
+                    out.out().packLong(tx0.observableTimestamp());
                     out.out().packBoolean(tx.isReadOnly());
                     out.out().packLong(tx0.timeout());
                 } else {
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientTransactionsTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientTransactionsTest.java
index c249bc99a1e..1357420abc2 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientTransactionsTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientTransactionsTest.java
@@ -969,6 +969,25 @@ public class ItThinClientTransactionsTest extends 
ItAbstractThinClientTest {
         tx0.commit();
     }
 
+    @ParameterizedTest
+    @ValueSource(booleans = {true, false})
+    void testReadOnlyTxDoesNotSeeUpdatesAfterStart(boolean server) {
+        //noinspection resource
+        Ignite ignite = server ? server() : client();
+        KeyValueView<Integer, String> kvView = 
ignite.tables().table(TABLE_NAME).keyValueView(Integer.class, String.class);
+
+        // Start RO TX, don't access anything yet.
+        // Lazy client TX should record the start time at this point.
+        Transaction tx = ignite.transactions().begin(new 
TransactionOptions().readOnly(true));
+
+        // Put outside of TX.
+        kvView.put(null, 123, "123");
+
+        // RO tx does not see the value.
+        String val = kvView.get(tx, 123);
+        assertNull(val, "Read-only transaction should not see values committed 
after its start");
+    }
+
     @AfterEach
     protected void validateInflights() throws NoSuchFieldException {
         System.out.println("DBG: validateInflights");

Reply via email to