This is an automated email from the ASF dual-hosted git repository.
sk0x50 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 f5f6f77f415 IGNITE-27864 Fix TxIdMismatchException that happened due
to absent WI cleanup after WI resolution (#7603)
f5f6f77f415 is described below
commit f5f6f77f41556613004013c4656d4039573778d4
Author: Denis Chudov <[email protected]>
AuthorDate: Fri Feb 20 19:39:15 2026 +0200
IGNITE-27864 Fix TxIdMismatchException that happened due to absent WI
cleanup after WI resolution (#7603)
---
.../internal/testframework/IgniteTestUtils.java | 16 ++-
.../replicator/PartitionReplicaListener.java | 57 ++++++--
...riteIntentResolutionWhenPrimaryExpiredTest.java | 150 ++++++++++++++++++++-
.../internal/tx/impl/TransactionStateResolver.java | 15 +++
.../ignite/internal/tx/impl/TxManagerImpl.java | 12 +-
5 files changed, 232 insertions(+), 18 deletions(-)
diff --git
a/modules/core/src/testFixtures/java/org/apache/ignite/internal/testframework/IgniteTestUtils.java
b/modules/core/src/testFixtures/java/org/apache/ignite/internal/testframework/IgniteTestUtils.java
index f632b3fdc5c..2cc9da54449 100644
---
a/modules/core/src/testFixtures/java/org/apache/ignite/internal/testframework/IgniteTestUtils.java
+++
b/modules/core/src/testFixtures/java/org/apache/ignite/internal/testframework/IgniteTestUtils.java
@@ -17,7 +17,6 @@
package org.apache.ignite.internal.testframework;
-import static java.lang.Thread.sleep;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.WRITE;
import static java.util.function.Function.identity;
@@ -1099,6 +1098,21 @@ public final class IgniteTestUtils {
return new UUID(str.hashCode(), new
StringBuilder(str).reverse().toString().hashCode());
}
+ /**
+ * Sleep for a while.
+ *
+ * @param millis Time to sleep in milliseconds.
+ */
+ public static void sleep(long millis) {
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+
+ throw new RuntimeException(e);
+ }
+ }
+
/**
* Converts a result set to a list of rows.
*
diff --git
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/PartitionReplicaListener.java
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/PartitionReplicaListener.java
index e0d2b048f5c..a62bb2fee84 100644
---
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/PartitionReplicaListener.java
+++
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/PartitionReplicaListener.java
@@ -1684,11 +1684,11 @@ public class PartitionReplicaListener implements
ReplicaTableProcessor {
return inBusyLockAsync(busyLock, () ->
resolveWriteIntentReadability(writeIntent, ts)
- .thenApply(writeIntentReadable ->
+ .thenApply(wiResolutionResult ->
inBusyLock(busyLock, () -> {
metrics.onRead(true, true);
- if (writeIntentReadable) {
+ if
(wiResolutionResult.writeIntentReadable) {
return findAny(writeIntents,
wi -> !wi.isEmpty()).map(ReadResult::binaryRow).orElse(null);
} else {
for (ReadResult wi :
writeIntents) {
@@ -3391,16 +3391,15 @@ public class PartitionReplicaListener implements
ReplicaTableProcessor {
) {
return inBusyLockAsync(busyLock, () ->
resolveWriteIntentReadability(readResult, timestamp)
- .thenApply(writeIntentReadable ->
+ .thenApply(wiResolutionResult ->
inBusyLock(busyLock, () -> {
- if (writeIntentReadable) {
+ if
(wiResolutionResult.writeIntentReadable) {
// Even though this readResult
is still a write intent entry in the storage
// (therefore it contains
txId), we already know it relates to a committed transaction
// and will be cleaned up by
an asynchronous task
// started in
scheduleTransactionRowAsyncCleanup().
// So it's safe to assume that
that this is the latest committed entry.
- HybridTimestamp
commitTimestamp =
-
txManager.stateMeta(readResult.transactionId()).commitTimestamp();
+ HybridTimestamp
commitTimestamp = wiResolutionResult.transactionMeta.commitTimestamp();
return new
TimedBinaryRow(readResult.binaryRow(), commitTimestamp);
}
@@ -3470,10 +3469,12 @@ public class PartitionReplicaListener implements
ReplicaTableProcessor {
*
* @param writeIntent Write intent to resolve.
* @param timestamp Timestamp.
- * @return The future completes with {@code true} when the transaction is
committed and commit time <= read time, {@code false}
- * otherwise (whe the transaction is either in progress, or
aborted, or committed and commit time > read time).
+ * @return Write intent resolution result, see {@link
WriteIntentResolutionResult}.
*/
- private CompletableFuture<Boolean>
resolveWriteIntentReadability(ReadResult writeIntent, @Nullable HybridTimestamp
timestamp) {
+ private CompletableFuture<WriteIntentResolutionResult>
resolveWriteIntentReadability(
+ ReadResult writeIntent,
+ @Nullable HybridTimestamp timestamp
+ ) {
UUID txId = writeIntent.transactionId();
HybridTimestamp now = clockService.current();
@@ -3482,19 +3483,35 @@ public class PartitionReplicaListener implements
ReplicaTableProcessor {
? null
: replicaMeta.getStartTime().longValue();
+ ZonePartitionId commitPartitionId = new
ZonePartitionId(writeIntent.commitZoneId(), writeIntent.commitPartitionId());
+
return transactionStateResolver.resolveTxState(
txId,
- new ZonePartitionId(writeIntent.commitZoneId(),
writeIntent.commitPartitionId()),
+ commitPartitionId,
timestamp,
currentConsistencyToken,
replicationGroupId
)
.thenApply(transactionMeta -> {
+ boolean writeIntentReadable = canReadFromWriteIntent(txId,
txManager, transactionMeta, timestamp);
+
if (isFinalState(transactionMeta.txState())) {
scheduleAsyncWriteIntentSwitch(txId,
writeIntent.rowId(), transactionMeta);
+ } else {
+ LOG.info(
+ "Received non-final transaction state after tx
state resolution [txId={}, groupId={}, txMeta={}, "
+ + "timestamp={}, commitPartId={},
currentConsistencyToken={}, writeIntentReadable={}].",
+ txId,
+ replicationGroupId,
+ transactionMeta,
+ timestamp,
+ commitPartitionId,
+ currentConsistencyToken,
+ writeIntentReadable
+ );
}
- return canReadFromWriteIntent(txId, txManager,
transactionMeta, timestamp);
+ return new
WriteIntentResolutionResult(writeIntentReadable, transactionMeta);
});
}
@@ -3513,6 +3530,7 @@ public class PartitionReplicaListener implements
ReplicaTableProcessor {
: format("Unexpected state defined by write intent resolution
[{}, txMeta={}].",
formatTxInfo(txId, txManager, false), txMeta);
+ // TODO IGNITE-27494 double check UNKNOWN state works correctly here.
if (txMeta.txState() == COMMITTED) {
boolean readLatest = timestamp == null;
@@ -3944,4 +3962,21 @@ public class PartitionReplicaListener implements
ReplicaTableProcessor {
public void cleanupLocally(UUID txId, boolean commit, @Nullable
HybridTimestamp commitTimestamp) {
storageUpdateHandler.switchWriteIntents(txId, commit, commitTimestamp,
null);
}
+
+ private static class WriteIntentResolutionResult {
+ /**
+ * This value is assigned with awareness of read timestamp in case of
WI resolution by read-only transaction. It is {@code true}
+ * when the transaction is committed and commit time <= read time,
{@code false} otherwise (when the transaction
+ * is either in progress, or aborted, or committed and commit time >
read time).
+ */
+ private final boolean writeIntentReadable;
+
+ /** Transaction meta. */
+ private final TransactionMeta transactionMeta;
+
+ public WriteIntentResolutionResult(boolean writeIntentReadable,
TransactionMeta transactionMeta) {
+ this.writeIntentReadable = writeIntentReadable;
+ this.transactionMeta = transactionMeta;
+ }
+ }
}
diff --git
a/modules/transactions/src/integrationTest/java/org/apache/ignite/tx/distributed/ItTxAbortOnCoordinatorOnWriteIntentResolutionWhenPrimaryExpiredTest.java
b/modules/transactions/src/integrationTest/java/org/apache/ignite/tx/distributed/ItTxAbortOnCoordinatorOnWriteIntentResolutionWhenPrimaryExpiredTest.java
index a26094e092f..a05243ff932 100644
---
a/modules/transactions/src/integrationTest/java/org/apache/ignite/tx/distributed/ItTxAbortOnCoordinatorOnWriteIntentResolutionWhenPrimaryExpiredTest.java
+++
b/modules/transactions/src/integrationTest/java/org/apache/ignite/tx/distributed/ItTxAbortOnCoordinatorOnWriteIntentResolutionWhenPrimaryExpiredTest.java
@@ -17,9 +17,14 @@
package org.apache.ignite.tx.distributed;
+import static java.util.stream.Collectors.toList;
+import static org.apache.ignite.internal.TestWrappers.unwrapIgniteImpl;
import static
org.apache.ignite.internal.catalog.CatalogService.DEFAULT_STORAGE_PROFILE;
import static
org.apache.ignite.internal.sql.engine.util.SqlTestUtils.executeUpdate;
+import static org.apache.ignite.internal.table.NodeUtils.transferPrimary;
import static
org.apache.ignite.internal.testframework.IgniteTestUtils.runInExecutor;
+import static org.apache.ignite.internal.tx.TxState.PENDING;
+import static org.apache.ignite.internal.tx.TxState.isFinalState;
import static
org.apache.ignite.internal.tx.test.ItTransactionTestUtils.findTupleToBeHostedOnNode;
import static
org.apache.ignite.internal.tx.test.ItTransactionTestUtils.partitionIdForTuple;
import static org.apache.ignite.internal.tx.test.ItTransactionTestUtils.table;
@@ -30,11 +35,14 @@ import static
org.apache.ignite.internal.util.IgniteUtils.shutdownAndAwaitTermin
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.client.IgniteClient;
import org.apache.ignite.internal.ClusterPerTestIntegrationTest;
import org.apache.ignite.internal.TestWrappers;
import org.apache.ignite.internal.app.IgniteImpl;
@@ -52,13 +60,20 @@ import org.apache.ignite.internal.table.TableImpl;
import
org.apache.ignite.internal.table.distributed.TableSchemaAwareIndexStorage;
import org.apache.ignite.internal.thread.IgniteThreadFactory;
import org.apache.ignite.internal.thread.ThreadOperation;
+import org.apache.ignite.internal.tx.TxStateMeta;
+import org.apache.ignite.internal.tx.message.TxCleanupMessage;
+import org.apache.ignite.internal.tx.message.WriteIntentSwitchReplicaRequest;
+import
org.apache.ignite.internal.tx.message.WriteIntentSwitchReplicaRequestBase;
import org.apache.ignite.table.QualifiedName;
import org.apache.ignite.table.RecordView;
import org.apache.ignite.table.Tuple;
+import org.apache.ignite.tx.IgniteTransactions;
import org.apache.ignite.tx.Transaction;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
/**
* Test for transaction abort on coordinator when write-intent resolution
happens after primary replica expiration.
@@ -103,8 +118,18 @@ public class
ItTxAbortOnCoordinatorOnWriteIntentResolutionWhenPrimaryExpiredTest
return new int[]{0, 1, 2};
}
- @Test
- public void test() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ public void testCoordinatorAbortsTransaction(boolean withThinClient)
throws Exception {
+ IgniteClient client = IgniteClient.builder()
+
.addresses(getClientAddresses(runningNodes().collect(toList())).toArray(new
String[0]))
+ .operationTimeout(15_000)
+ .build();
+
+ if (withThinClient) {
+ await().atMost(3, TimeUnit.SECONDS).until(() ->
client.connections().size() == initialNodes());
+ }
+
IgniteImpl firstPrimaryNode = anyNode();
IgniteImpl coordinatorNode = findNode(n ->
!n.name().equals(firstPrimaryNode.name()));
@@ -114,17 +139,32 @@ public class
ItTxAbortOnCoordinatorOnWriteIntentResolutionWhenPrimaryExpiredTest
RecordView<Tuple> view =
coordinatorNode.tables().table(TABLE_NAME).recordView();
Transaction txx = coordinatorNode.transactions().begin();
+ Tuple tuple0 = findTupleToBeHostedOnNode(coordinatorNode, TABLE_NAME,
txx, INITIAL_TUPLE, NEXT_TUPLE, true);
Tuple tuple = findTupleToBeHostedOnNode(firstPrimaryNode, TABLE_NAME,
txx, INITIAL_TUPLE, NEXT_TUPLE, true);
int partId = partitionIdForTuple(firstPrimaryNode, TABLE_NAME, tuple,
txx);
var groupId = new ZonePartitionId(zoneId(firstPrimaryNode,
TABLE_NAME), partId);
log.info("Test: groupId: " + groupId);
+ view.upsert(txx, tuple0);
view.upsert(txx, tuple);
txx.commit();
- Transaction tx0 = coordinatorNode.transactions().begin();
- log.info("Test: unfinished tx id: " + txId(tx0));
- view.upsert(tx0, tuple);
+ // Unfinished transaction.
+ RecordView<Tuple> unfinishedTxView = withThinClient
+ ? client.tables().table(TABLE_NAME).recordView()
+ : coordinatorNode.tables().table(TABLE_NAME).recordView();
+ IgniteTransactions unfinishedTxTransactions = withThinClient
+ ? client.transactions()
+ : coordinatorNode.transactions();
+
+ Transaction tx0 = unfinishedTxTransactions.begin();
+
+ if (!withThinClient) {
+ log.info("Test: unfinished tx id: " + txId(tx0));
+ }
+
+ unfinishedTxView.upsert(tx0, tuple0);
+ unfinishedTxView.upsert(tx0, tuple);
// Don't commit or rollback tx0.
// Wait for replication of write intent.
@@ -164,6 +204,88 @@ public class
ItTxAbortOnCoordinatorOnWriteIntentResolutionWhenPrimaryExpiredTest
assertEquals(newTuple, actual);
}
+ @Test
+ public void testWriteIntentResolutionUsesCorrectStateAndCommitTimestamp()
throws Exception {
+ // Coordinator node will also be the commit partition primary node.
+ IgniteImpl coordinatorNode = anyNode();
+ IgniteImpl firstPrimaryNode = findNode(n ->
!n.name().equals(coordinatorNode.name()));
+ IgniteImpl secondPrimaryNode = findNode(n ->
!n.name().equals(coordinatorNode.name()) &&
!n.name().equals(firstPrimaryNode.name()));
+
+ log.info("Test: coordinatorNode: {}, coordinatorNode id: {}",
coordinatorNode.name(), coordinatorNode.id());
+ log.info("Test: firstPrimaryNode: {}, firstPrimaryNode id: {}",
firstPrimaryNode.name(), firstPrimaryNode.id());
+ log.info("Test: secondPrimaryNode: {}, secondPrimaryNode id: {}",
secondPrimaryNode.name(), secondPrimaryNode.id());
+
+ RecordView<Tuple> view =
coordinatorNode.tables().table(TABLE_NAME).recordView();
+
+ Transaction txx = coordinatorNode.transactions().begin();
+ Tuple tuple1 = findTupleToBeHostedOnNode(coordinatorNode, TABLE_NAME,
txx, INITIAL_TUPLE, NEXT_TUPLE, true);
+ Tuple tuple2 = findTupleToBeHostedOnNode(firstPrimaryNode, TABLE_NAME,
txx, INITIAL_TUPLE, NEXT_TUPLE, true);
+ int partId1 = partitionIdForTuple(coordinatorNode, TABLE_NAME, tuple1,
txx);
+ var groupId1 = new ZonePartitionId(zoneId(firstPrimaryNode,
TABLE_NAME), partId1);
+ int partId2 = partitionIdForTuple(coordinatorNode, TABLE_NAME, tuple2,
txx);
+ var groupId2 = new ZonePartitionId(zoneId(firstPrimaryNode,
TABLE_NAME), partId2);
+ log.info("Test: groupId1: " + groupId1);
+ log.info("Test: groupId2: " + groupId2);
+ view.upsert(txx, tuple1);
+ view.upsert(txx, tuple2);
+
+ txx.commit();
+
+ cluster.runningNodes().forEach(node -> {
+ unwrapIgniteImpl(node).dropMessages((dest, msg) -> {
+ boolean wiSwitch = msg instanceof TxCleanupMessage || msg
instanceof WriteIntentSwitchReplicaRequestBase;
+ return wiSwitch && secondPrimaryNode.name().equals(dest);
+ });
+ });
+
+ coordinatorNode.dropMessages((dest, msg) -> msg instanceof
TxCleanupMessage || msg instanceof WriteIntentSwitchReplicaRequest);
+ firstPrimaryNode.dropMessages((dest, msg) -> msg instanceof
TxCleanupMessage || msg instanceof WriteIntentSwitchReplicaRequest);
+
+ Transaction tx0 = coordinatorNode.transactions().begin();
+ UUID tx0Id = txId(tx0);
+ log.info("Test: cleanup unfinished tx id: " + txId(tx0));
+ view.upsert(tx0, tuple1);
+ view.upsert(tx0, tuple2);
+
+ tx0.commitAsync();
+
+ await().atMost(5, TimeUnit.SECONDS)
+ .until(() -> txFinishedStateOnNode(coordinatorNode, tx0Id));
+
+ // Wait for replication of write intent.
+ Tuple keyTuple = Tuple.create().set("key", tuple2.longValue("key"));
+ await().atMost(5, TimeUnit.SECONDS)
+ .until(() -> checkWriteIntentInStorageOnAllNodes(partId2,
keyTuple));
+
+
transferPrimary(runningNodes().map(TestWrappers::unwrapIgniteImpl).collect(toList()),
groupId2, secondPrimaryNode.name());
+
+ await().atMost(5, TimeUnit.SECONDS)
+ .until(() -> txFinishedStateOnNode(secondPrimaryNode, tx0Id));
+
+ Transaction tx = coordinatorNode.transactions().begin();
+ log.info("Test: new tx: " + txId(tx));
+ log.info("Test: upsert");
+
+ Tuple newTuple = Tuple.create().set("key",
tuple2.longValue("key")).set("val", "v");
+
+ // Tx cleanup is blocked but tx state could be propagated if second
tuple's primary node is commit partition's backup.
+ // Transfer it back to pending, imitating obsolete transaction state
on secondPrimaryNode.
+ secondPrimaryNode.txManager().updateTxMeta(tx0Id, old -> null);
+ secondPrimaryNode.txManager().updateTxMeta(tx0Id, old ->
TxStateMeta.builder(PENDING).build());
+
+ // If coordinator of tx0 doesn't abort it, tx will stumble into write
intent and fail with TxIdMismatchException.
+ view.upsert(tx, newTuple);
+
+ coordinatorNode.stopDroppingMessages();
+ firstPrimaryNode.stopDroppingMessages();
+
+ tx.commit();
+
+ // Check that new value is written successfully.
+ Tuple actual = view.get(null, keyTuple);
+ assertEquals(newTuple, actual);
+ }
+
private boolean checkWriteIntentInStorageOnAllNodes(int partId, Tuple key)
{
return cluster.runningNodes()
.map(TestWrappers::unwrapIgniteImpl)
@@ -195,4 +317,22 @@ public class
ItTxAbortOnCoordinatorOnWriteIntentResolutionWhenPrimaryExpiredTest
return false;
});
}
+
+ private static boolean txFinishedStateOnNode(IgniteImpl node, UUID txId) {
+ TxStateMeta meta = node.txManager().stateMeta(txId);
+
+ return meta != null && isFinalState(meta.txState());
+ }
+
+ private static List<String> getClientAddresses(List<Ignite> nodes) {
+ return getClientPorts(nodes).stream()
+ .map(port -> "127.0.0.1" + ":" + port)
+ .collect(toList());
+ }
+
+ private static List<Integer> getClientPorts(List<Ignite> nodes) {
+ return nodes.stream()
+ .map(ignite -> unwrapIgniteImpl(ignite).clientAddress().port())
+ .collect(toList());
+ }
}
diff --git
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TransactionStateResolver.java
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TransactionStateResolver.java
index 57470203bf4..847d3b6d250 100644
---
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TransactionStateResolver.java
+++
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TransactionStateResolver.java
@@ -32,6 +32,8 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import org.apache.ignite.internal.hlc.ClockService;
import org.apache.ignite.internal.hlc.HybridTimestamp;
+import org.apache.ignite.internal.logger.IgniteLogger;
+import org.apache.ignite.internal.logger.Loggers;
import org.apache.ignite.internal.network.ClusterNodeResolver;
import org.apache.ignite.internal.network.InternalClusterNode;
import org.apache.ignite.internal.network.MessagingService;
@@ -58,6 +60,8 @@ import org.jetbrains.annotations.Nullable;
* Helper class that allows to resolve transaction state mainly for the
purpose of write intent resolution.
*/
public class TransactionStateResolver {
+ private static final IgniteLogger LOG =
Loggers.forClass(TransactionStateResolver.class);
+
/** Tx messages factory. */
private static final TxMessagesFactory TX_MESSAGES_FACTORY = new
TxMessagesFactory();
@@ -463,10 +467,21 @@ public class TransactionStateResolver {
if (tx != null && !tx.isReadOnly() && currentConsistencyToken
!= null && groupId != null) {
return
txManager.checkEnlistedPartitionsAndAbortIfNeeded(txStateMeta, tx,
currentConsistencyToken, groupId);
+ } else {
+ LOG.info("Failed to abort on coordinator a transaction
that lost its primary replica's volatile state "
+ + "[txId={}, internalTx={}, readOnly={},
senderCurrentConsistencyToken={}, senderGroupId={}].",
+ txId,
+ tx,
+ (tx == null ? null : tx.isReadOnly()),
+ currentConsistencyToken,
+ groupId
+ );
}
}
}
+ LOG.info("Transaction meta is absent on coordinator [txId={}].", txId);
+
return completedFuture(txStateMeta);
}
diff --git
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TxManagerImpl.java
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TxManagerImpl.java
index 0e4dcb9e88c..69ffa216e6a 100644
---
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TxManagerImpl.java
+++
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TxManagerImpl.java
@@ -591,7 +591,8 @@ public class TxManagerImpl implements TxManager,
NetworkMessageHandler, SystemVi
) {
PendingTxPartitionEnlistment enlistment =
tx.enlistedPartition(senderGroupId);
- if (enlistment != null && enlistment.consistencyToken() !=
currentEnlistmentConsistencyToken) {
+ // Enlistment for thin client direct request may be absent on
coordinator.
+ if (enlistment == null || enlistment.consistencyToken() !=
currentEnlistmentConsistencyToken) {
// Remote partition already has different consistency token, so we
can't commit this transaction anyway.
// Even when graceful primary replica switch is done, we can get
here only if the write intent that requires resolution
// is not under lock.
@@ -606,6 +607,15 @@ public class TxManagerImpl implements TxManager,
NetworkMessageHandler, SystemVi
});
}
+ LOG.info("Skipped aborting on coordinator a transaction that lost its
primary replica's volatile state "
+ + "[txId={}, internalTx={}, enlistment={},
senderCurrentConsistencyToken={}, txMeta={}].",
+ tx.id(),
+ tx,
+ enlistment,
+ currentEnlistmentConsistencyToken,
+ txMeta
+ );
+
return completedFuture(txMeta);
}