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");