This is an automated email from the ASF dual-hosted git repository.

zstan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new cfb6e1f476c IGNITE-27678 Same partitions on different nodes can hold 
different updates if writeThrough is enabled (#12925)
cfb6e1f476c is described below

commit cfb6e1f476cff2dfa7db97225c06e49dc703457d
Author: Evgeniy Stanilovskiy <[email protected]>
AuthorDate: Mon Jun 29 09:45:52 2026 +0300

    IGNITE-27678 Same partitions on different nodes can hold different updates 
if writeThrough is enabled (#12925)
---
 .../ignite/internal/CoreMessagesProvider.java      |   2 +
 .../distributed/dht/GridDhtTxFinishFuture.java     |  38 ++-
 .../cache/distributed/dht/GridDhtTxLocal.java      |   6 +-
 .../cache/distributed/dht/GridDhtTxRemote.java     |   9 +-
 .../distributed/dht/GridDhtTxSalvageMessage.java   |  51 ++++
 .../preloader/GridDhtPartitionsExchangeFuture.java |   1 +
 .../dht/topology/GridDhtLocalPartition.java        |   4 +-
 .../distributed/near/GridNearTxFinishFuture.java   |   9 +
 .../cache/store/GridCacheStoreManagerAdapter.java  |  13 +
 .../cache/transactions/IgniteTxAdapter.java        |   2 +-
 .../cache/transactions/IgniteTxHandler.java        |  14 +
 .../cache/transactions/IgniteTxManager.java        |  63 +++-
 .../transactions/IgniteTxRemoteStateAdapter.java   |   2 +-
 .../transactions/IgniteTxRemoteStateImpl.java      |   4 +-
 .../cache/transactions/IgniteTxStateImpl.java      |  13 +-
 .../IgniteTxCacheWithWriteThroughCheckTest.java    | 316 +++++++++++++++++++++
 ...WriteSynchronizationModesMultithreadedTest.java |  14 +
 .../dht/IgniteCacheTxRecoveryRollbackTest.java     |   6 +-
 .../cache/query/IndexingSpiQueryTxSelfTest.java    |  32 ++-
 .../ignite/testsuites/IgniteCacheTestSuite19.java  |   2 +
 20 files changed, 570 insertions(+), 31 deletions(-)

diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java
index f40b14826cc..1af6f8945cf 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java
@@ -101,6 +101,7 @@ import 
org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxFini
 import 
org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxOnePhaseCommitAckRequest;
 import 
org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxPrepareRequest;
 import 
org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxPrepareResponse;
+import 
org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxSalvageMessage;
 import 
org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtUnlockRequest;
 import 
org.apache.ignite.internal.processors.cache.distributed.dht.PartitionUpdateCountersMessage;
 import 
org.apache.ignite.internal.processors.cache.distributed.dht.TransactionAttributesAwareRequest;
@@ -585,6 +586,7 @@ public class CoreMessagesProvider extends 
AbstractMarshallableMessageFactoryProv
         withNoSchema(StatisticsRequest.class);
         withNoSchema(StatisticsResponse.class);
         withNoSchema(CacheContinuousQueryBatchAck.class);
+        withNoSchema(GridDhtTxSalvageMessage.class);
         withSchema(CacheContinuousQueryEntry.class);
         withNoSchema(QueryInlineSizesDataBagItem.class);
         withSchema(QueryProposalsDataBagItem.class);
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxFinishFuture.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxFinishFuture.java
index 98c580203c2..a2befc5d999 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxFinishFuture.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxFinishFuture.java
@@ -473,7 +473,7 @@ public final class GridDhtTxFinishFuture<K, V> extends 
GridCacheCompoundIdentity
                 if (isNull(cctx.discovery().getAlive(n.id()))) {
                     log.error("Unable to send message (node left topology): " 
+ n);
 
-                    fut.onNodeLeft();
+                    fut.onNodeLeft(n.id());
                 }
                 else {
                     cctx.tm().sendTransactionMessage(n, req, tx, 
tx.ioPolicy());
@@ -493,7 +493,7 @@ public final class GridDhtTxFinishFuture<K, V> extends 
GridCacheCompoundIdentity
             catch (IgniteCheckedException e) {
                 // Fail the whole thing.
                 if (e instanceof ClusterTopologyCheckedException)
-                    fut.onNodeLeft();
+                    fut.onNodeLeft(n.id());
                 else {
                     if (msgLog.isDebugEnabled()) {
                         msgLog.debug("DHT finish fut, failed to send request 
dht [txId=" + tx.nearXidVersion() +
@@ -687,6 +687,40 @@ public final class GridDhtTxFinishFuture<K, V> extends 
GridCacheCompoundIdentity
             onDone(e);
         }
 
+        /** */
+        private void onNodeLeft(UUID nodeId) {
+            // Cause in common case #onNodeLeft() completes with no error it`s 
necessary to send salvage message.
+            if (tx.storeWriteThrough()) {
+                Map<UUID, Collection<UUID>> txNodes = tx.transactionNodes();
+
+                if (txNodes != null) {
+                    Collection<UUID> backups = txNodes.get(nodeId);
+
+                    if (!F.isEmpty(backups)) {
+                        GridDhtTxSalvageMessage salvageReq = null;
+
+                        for (UUID backupId : backups) {
+                            ClusterNode backup = 
cctx.discovery().node(backupId);
+
+                            if (backup != null && !backup.isLocal()) {
+                                if (salvageReq == null)
+                                    salvageReq = new 
GridDhtTxSalvageMessage(tx.nearXidVersion());
+
+                                try {
+                                    cctx.io().send(backup, salvageReq, 
tx.ioPolicy());
+                                }
+                                catch (IgniteCheckedException e) {
+                                    U.error(log, "Failed to send " + 
GridDhtTxSalvageMessage.class.getName() + " message.", e);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            onNodeLeft();
+        }
+
         /**
          */
         void onNodeLeft() {
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxLocal.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxLocal.java
index 93ec7a05543..67b32d38f86 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxLocal.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxLocal.java
@@ -460,8 +460,12 @@ public class GridDhtTxLocal extends GridDhtTxLocalAdapter 
implements GridCacheMa
             logTxFinishErrorSafe(log, commit, e);
 
             // Treat heuristic exception as critical.
-            if (X.hasCause(e, IgniteTxHeuristicCheckedException.class))
+            if (X.hasCause(e, IgniteTxHeuristicCheckedException.class)) {
+                if (storeWriteThrough() && local())
+                    salvageTx();
+
                 cctx.kernalContext().failure().process(new 
FailureContext(FailureType.CRITICAL_ERROR, e));
+            }
 
             err = e;
         }
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxRemote.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxRemote.java
index e60041e34ed..c2ad54b41b9 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxRemote.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxRemote.java
@@ -17,9 +17,9 @@
 
 package org.apache.ignite.internal.processors.cache.distributed.dht;
 
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 import javax.cache.processor.EntryProcessor;
@@ -226,12 +226,7 @@ public class GridDhtTxRemote extends 
GridDistributedTxRemoteAdapter {
 
     /** {@inheritDoc} */
     @Override public Collection<UUID> masterNodeIds() {
-        Collection<UUID> res = new ArrayList<>(2);
-
-        res.add(nearNodeId);
-        res.add(nodeId);
-
-        return res;
+        return List.of(nearNodeId, nodeId);
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxSalvageMessage.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxSalvageMessage.java
new file mode 100644
index 00000000000..ae2d0715cb8
--- /dev/null
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxSalvageMessage.java
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.cache.distributed.dht;
+
+import org.apache.ignite.internal.Order;
+import org.apache.ignite.internal.processors.cache.GridCacheMessage;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+
+/** Salvage tx. */
+public class GridDhtTxSalvageMessage extends GridCacheMessage {
+    /** */
+    @Order(0)
+    GridCacheVersion ver;
+
+    /** Empty constructor. */
+    public GridDhtTxSalvageMessage() {
+        // No-op.
+    }
+
+    /**
+     * @param ver Global transaction identifier within cluster, assigned by 
transaction coordinator.
+     */
+    public GridDhtTxSalvageMessage(GridCacheVersion ver) {
+        this.ver = ver;
+    }
+
+    /** Tx version. */
+    public GridCacheVersion version() {
+        return ver;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean addDeploymentInfo() {
+        return addDepInfo;
+    }
+}
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsExchangeFuture.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsExchangeFuture.java
index 5c0dc84875a..e3c7b42dc7f 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsExchangeFuture.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsExchangeFuture.java
@@ -270,6 +270,7 @@ public class GridDhtPartitionsExchangeFuture extends 
GridDhtTopologyFutureAdapte
     private final Map<ClusterNode, GridDhtPartitionsFullMessage> fullMsgs = 
new ConcurrentHashMap<>();
 
     /** */
+    @SuppressWarnings("unused")
     @GridToStringInclude
     private volatile IgniteInternalFuture<?> partReleaseFut;
 
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/topology/GridDhtLocalPartition.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/topology/GridDhtLocalPartition.java
index 1c565177cbd..34ab379e525 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/topology/GridDhtLocalPartition.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/topology/GridDhtLocalPartition.java
@@ -1194,7 +1194,9 @@ public class GridDhtLocalPartition extends 
GridCacheConcurrentMapImpl implements
         while (true) {
             long state = this.state.get();
 
-            assert getPartState(state) != EVICTED;
+            GridDhtPartitionState partState = getPartState(state);
+
+            assert partState != EVICTED : partState;
 
             if (this.state.compareAndSet(state, setSize(state, getSize(state) 
- 1)))
                 return;
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxFinishFuture.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxFinishFuture.java
index 307bdad29e4..6e8c24979a9 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxFinishFuture.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxFinishFuture.java
@@ -40,6 +40,7 @@ import 
org.apache.ignite.internal.processors.cache.GridCacheVersionedFuture;
 import 
org.apache.ignite.internal.processors.cache.distributed.GridDistributedTxMapping;
 import 
org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxFinishRequest;
 import 
org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxFinishResponse;
+import 
org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxSalvageMessage;
 import 
org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
 import org.apache.ignite.internal.processors.tracing.MTC;
@@ -983,6 +984,7 @@ public final class GridNearTxFinishFuture<K, V> extends 
GridCacheCompoundIdentit
                             });
 
                             GridDhtTxFinishRequest req = 
checkCommittedRequest(mini.futureId(), true);
+                            GridDhtTxSalvageMessage salvageReq = null;
 
                             for (UUID backupId : backups) {
                                 ClusterNode backup = 
cctx.discovery().node(backupId);
@@ -996,6 +998,13 @@ public final class GridNearTxFinishFuture<K, V> extends 
GridCacheCompoundIdentit
                                     else {
                                         try {
                                             cctx.io().send(backup, req, 
tx.ioPolicy());
+
+                                            if (tx.storeWriteThrough()) {
+                                                if (salvageReq == null)
+                                                    salvageReq = new 
GridDhtTxSalvageMessage(tx.xidVersion());
+
+                                                cctx.io().send(backup, 
salvageReq, tx.ioPolicy());
+                                            }
                                         }
                                         catch (ClusterTopologyCheckedException 
ignored) {
                                             mini.onNodeLeft(backupId, 
discoThread);
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/store/GridCacheStoreManagerAdapter.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/store/GridCacheStoreManagerAdapter.java
index b175df09a09..115fc73f4c4 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/store/GridCacheStoreManagerAdapter.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/store/GridCacheStoreManagerAdapter.java
@@ -964,6 +964,11 @@ public abstract class GridCacheStoreManagerAdapter extends 
GridCacheManagerAdapt
                     lsnr.onSessionStart(locSes);
             }
         }
+        catch (RuntimeException e) {
+            U.error(log, "Exception raised during the notification of cache 
store session listeners: ", e);
+
+            throw e;
+        }
         catch (Exception e) {
             throw new IgniteCheckedException("Failed to start store session: " 
+ e, e);
         }
@@ -984,6 +989,14 @@ public abstract class GridCacheStoreManagerAdapter extends 
GridCacheManagerAdapt
                     store.sessionEnd(!threwEx);
             }
         }
+        catch (RuntimeException e) {
+            U.error(log, "Exception raised during the notification of cache 
store session listeners: ", e);
+
+            if (!threwEx)
+                throw U.cast(e);
+            else
+                throw e;
+        }
         catch (Exception e) {
             if (!threwEx)
                 throw U.cast(e);
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxAdapter.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxAdapter.java
index 193eb2a4e8a..1529d53b5ce 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxAdapter.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxAdapter.java
@@ -628,7 +628,7 @@ public abstract class IgniteTxAdapter extends 
GridMetadataAwareAdapter implement
     /**
      * @return Finalization status.
      */
-    @Override @Nullable public FinalizationStatus finalizationStatus() {
+    @Override public FinalizationStatus finalizationStatus() {
         return finalizing;
     }
 
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxHandler.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxHandler.java
index 392f37d32b0..40dae253a43 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxHandler.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxHandler.java
@@ -59,6 +59,7 @@ import 
org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxPrep
 import 
org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxPrepareRequest;
 import 
org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxPrepareResponse;
 import 
org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxRemote;
+import 
org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxSalvageMessage;
 import 
org.apache.ignite.internal.processors.cache.distributed.dht.PartitionUpdateCountersMessage;
 import 
org.apache.ignite.internal.processors.cache.distributed.dht.TransactionAttributesAwareRequest;
 import 
org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtInvalidPartitionException;
@@ -223,6 +224,9 @@ public class IgniteTxHandler {
         ctx.io().addCacheHandler(GridDhtTxFinishRequest.class, (UUID nodeId, 
GridCacheMessage msg) ->
             processDhtTxFinishRequest(nodeId, (GridDhtTxFinishRequest)msg));
 
+        ctx.io().addCacheHandler(GridDhtTxSalvageMessage.class, (UUID nodeId, 
GridCacheMessage msg) ->
+            processDhtTxSalvageRequest((GridDhtTxSalvageMessage)msg));
+
         ctx.io().addCacheHandler(GridDhtTxOnePhaseCommitAckRequest.class, 
(UUID nodeId, GridCacheMessage msg) ->
             processDhtTxOnePhaseCommitAckRequest(nodeId, 
(GridDhtTxOnePhaseCommitAckRequest)msg));
 
@@ -1341,6 +1345,16 @@ public class IgniteTxHandler {
         }
     }
 
+    /**
+     * @param req Request.
+     */
+    private void processDhtTxSalvageRequest(GridDhtTxSalvageMessage req) {
+        for (IgniteInternalTx active : ctx.tm().activeTransactions()) {
+            if (active.nearXidVersion().equals(req.version()) && active 
instanceof GridDhtTxRemote)
+                ctx.tm().salvageTx(active);
+        }
+    }
+
     /**
      * @param nodeId Node ID.
      * @param req Request.
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxManager.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxManager.java
index cd2704daf90..7f9298c352e 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxManager.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxManager.java
@@ -44,6 +44,7 @@ import org.apache.ignite.IgniteException;
 import org.apache.ignite.IgniteSystemProperties;
 import org.apache.ignite.binary.BinaryObjectException;
 import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.configuration.CacheConfiguration;
 import org.apache.ignite.configuration.TransactionConfiguration;
 import org.apache.ignite.events.DiscoveryEvent;
 import org.apache.ignite.failure.FailureContext;
@@ -80,6 +81,7 @@ import 
org.apache.ignite.internal.processors.cache.distributed.GridDistributedTx
 import 
org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxLocal;
 import 
org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxOnePhaseCommitAckRequest;
 import 
org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxRemote;
+import 
org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxSalvageMessage;
 import 
org.apache.ignite.internal.processors.cache.distributed.dht.TransactionAttributesAwareRequest;
 import 
org.apache.ignite.internal.processors.cache.distributed.dht.colocated.GridDhtColocatedLockFuture;
 import 
org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtInvalidPartitionException;
@@ -130,6 +132,7 @@ import static 
org.apache.ignite.IgniteSystemProperties.IGNITE_LONG_OPERATIONS_DU
 import static 
org.apache.ignite.IgniteSystemProperties.IGNITE_MAX_COMPLETED_TX_COUNT;
 import static 
org.apache.ignite.IgniteSystemProperties.IGNITE_SLOW_TX_WARN_TIMEOUT;
 import static 
org.apache.ignite.IgniteSystemProperties.IGNITE_TX_DEADLOCK_DETECTION_MAX_ITERS;
+import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC;
 import static 
org.apache.ignite.configuration.TransactionConfiguration.TX_AWARE_QUERIES_SUPPORTED_MODES;
 import static org.apache.ignite.events.EventType.EVT_NODE_FAILED;
 import static org.apache.ignite.events.EventType.EVT_NODE_JOINED;
@@ -141,6 +144,7 @@ import static 
org.apache.ignite.internal.processors.cache.GridCacheOperation.REA
 import static 
org.apache.ignite.internal.processors.cache.GridCacheUtils.isNearEnabled;
 import static 
org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx.FinalizationStatus.RECOVERY_FINISH;
 import static 
org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx.FinalizationStatus.USER_FINISH;
+import static 
org.apache.ignite.internal.processors.cache.transactions.IgniteTxStateImpl.deriveSyncMode;
 import static 
org.apache.ignite.internal.processors.security.SecurityUtils.securitySubjectId;
 import static org.apache.ignite.internal.util.GridConcurrentFactory.newMap;
 import static org.apache.ignite.transactions.TransactionState.ACTIVE;
@@ -3123,8 +3127,28 @@ public class IgniteTxManager extends 
GridCacheSharedManagerAdapter {
                         ", failedNodeId=" + evtNodeId + ']');
 
                 for (final IgniteInternalTx tx : activeTransactions()) {
-                    if ((tx.near() && !tx.local() && 
tx.originatingNodeId().equals(evtNodeId))
-                        || (tx.storeWriteThrough() && 
tx.masterNodeIds().contains(evtNodeId))) {
+                    Map<UUID, Collection<UUID>> txNodes = 
tx.transactionNodes();
+
+                    if (tx.storeWriteThrough() && txNodes != null
+                        && tx.near() && txNodes.containsKey(evtNodeId)
+                        && (tx.state() == PREPARING || tx.state() == PREPARED
+                        || tx.state() == COMMITTING || tx.state() == 
COMMITTED)) {
+                        // Send a message, tx is applied on near node and can 
be processed on backup if postponed.
+                        sendTxSalvage(tx, evtNodeId);
+                    }
+
+                    if (tx.storeWriteThrough() && 
tx.masterNodeIds().contains(evtNodeId) && tx.eventNodeId().equals(evtNodeId))
+                        salvageTx(tx, RECOVERY_FINISH);
+                    else if (tx.storeWriteThrough() && 
!tx.masterNodeIds().contains(cctx.localNodeId())
+                        && tx.nodeId().equals(evtNodeId) && tx.state() == 
PREPARED) {
+                        // Delay a commit, on backup. It will be raised 
further after near or coord. node will confirm it.
+                        // In different from {@code FULL_SYNC} modes, 
appropriate recovery message can never be raized.
+                        if (deriveSyncMode(cctx, tx.txState().cacheIds()) != 
FULL_SYNC)
+                            cctx.time().schedule(() -> salvageTx(tx, 
RECOVERY_FINISH), 1000, -1);
+                    }
+                    else if ((tx.near() && !tx.local() && 
tx.originatingNodeId().equals(evtNodeId))
+                        || (tx.storeWriteThrough() && 
tx.masterNodeIds().contains(evtNodeId)) &&
+                        !tx.eventNodeId().equals(cctx.localNodeId())) {
                         // Invalidate transactions.
                         salvageTx(tx, RECOVERY_FINISH);
                     }
@@ -3166,6 +3190,41 @@ public class IgniteTxManager extends 
GridCacheSharedManagerAdapter {
             }
         }
 
+        /**
+         * Salvage progress can be postponed in special case when {@link 
CacheConfiguration#setWriteThrough} is enabled
+         * and current node is backup. It required for eliminate situaltions 
when backup node read data before primary will
+         * continue with transaction commit.
+         *
+         * @see CacheConfiguration#setWriteThrough
+         */
+        private void sendTxSalvage(IgniteInternalTx tx, UUID evtNodeId) {
+            Collection<UUID> involvedNodes = 
tx.transactionNodes().get(evtNodeId);
+
+            if (involvedNodes != null) {
+                GridDhtTxSalvageMessage salvageReq = null;
+
+                for (UUID nodeId : involvedNodes) {
+                    if (tx.masterNodeIds().contains(nodeId))
+                        continue;
+
+                    ClusterNode backupNode = cctx.discovery().node(nodeId);
+
+                    if (backupNode != null && !backupNode.isLocal()) {
+                        if (salvageReq == null)
+                            salvageReq = new 
GridDhtTxSalvageMessage(tx.nearXidVersion());
+
+                        try {
+                            cctx.io().send(nodeId, salvageReq, tx.ioPolicy());
+                        }
+                        catch (IgniteCheckedException e) {
+                            log.warning("Failed to send salvage message 
[failedNodeId=" + evtNodeId +
+                                ", nodeId=" + nodeId + ']', e);
+                        }
+                    }
+                }
+            }
+        }
+
         /**
          * @param tx Tx.
          * @param failedNode Failed node.
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxRemoteStateAdapter.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxRemoteStateAdapter.java
index 60248017ee1..d1826f7cc5a 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxRemoteStateAdapter.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxRemoteStateAdapter.java
@@ -33,7 +33,7 @@ import static 
org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_ASYNC;
  */
 public abstract class IgniteTxRemoteStateAdapter implements 
IgniteTxRemoteState {
     /** Active cache IDs. */
-    private GridIntList activeCacheIds = new GridIntList();
+    private final GridIntList activeCacheIds = new GridIntList();
 
     /** {@inheritDoc} */
     @Override public boolean implicitSingle() {
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxRemoteStateImpl.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxRemoteStateImpl.java
index 409cffab11a..7556efe6faf 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxRemoteStateImpl.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxRemoteStateImpl.java
@@ -40,11 +40,11 @@ import org.apache.ignite.internal.util.typedef.internal.S;
 public class IgniteTxRemoteStateImpl extends IgniteTxRemoteStateAdapter {
     /** Read set. */
     @GridToStringInclude
-    protected Map<IgniteTxKey, IgniteTxEntry> readMap;
+    protected final Map<IgniteTxKey, IgniteTxEntry> readMap;
 
     /** Write map. */
     @GridToStringInclude
-    protected Map<IgniteTxKey, IgniteTxEntry> writeMap;
+    protected final Map<IgniteTxKey, IgniteTxEntry> writeMap;
 
     /**
      * @param readMap Read map.
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxStateImpl.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxStateImpl.java
index 3577c114add..0ae820a82e0 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxStateImpl.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxStateImpl.java
@@ -167,10 +167,19 @@ public class IgniteTxStateImpl extends 
IgniteTxLocalStateAdapter {
 
     /** {@inheritDoc} */
     @Override public CacheWriteSynchronizationMode 
syncMode(GridCacheSharedContext cctx) {
+        return deriveSyncMode(cctx, activeCacheIds);
+    }
+
+    /**
+     * @param cctx Context.
+     * @param cacheIds Processed caches.
+     * @return Write synchronization mode.
+     */
+    public static CacheWriteSynchronizationMode 
deriveSyncMode(GridCacheSharedContext<?, ?> cctx, GridIntList cacheIds) {
         CacheWriteSynchronizationMode syncMode = 
CacheWriteSynchronizationMode.FULL_ASYNC;
 
-        for (int i = 0; i < activeCacheIds.size(); i++) {
-            int cacheId = activeCacheIds.get(i);
+        for (int i = 0; i < cacheIds.size(); i++) {
+            int cacheId = cacheIds.get(i);
 
             CacheWriteSynchronizationMode cacheSyncMode =
                 
cctx.cacheContext(cacheId).config().getWriteSynchronizationMode();
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/distributed/IgniteTxCacheWithWriteThroughCheckTest.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/distributed/IgniteTxCacheWithWriteThroughCheckTest.java
new file mode 100644
index 00000000000..b58264ff600
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/distributed/IgniteTxCacheWithWriteThroughCheckTest.java
@@ -0,0 +1,316 @@
+/*
+ * 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.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.cache.distributed;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import javax.cache.Cache;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheMode;
+import org.apache.ignite.cache.CacheWriteSynchronizationMode;
+import org.apache.ignite.cache.store.CacheStoreAdapter;
+import org.apache.ignite.cluster.ClusterState;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.DataRegionConfiguration;
+import org.apache.ignite.configuration.DataStorageConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.events.DiscoveryEvent;
+import org.apache.ignite.internal.GridTopic;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.TestRecordingCommunicationSpi;
+import org.apache.ignite.internal.managers.communication.GridMessageListener;
+import org.apache.ignite.internal.managers.discovery.DiscoCache;
+import org.apache.ignite.internal.managers.eventstorage.DiscoveryEventListener;
+import org.apache.ignite.internal.managers.eventstorage.HighPriorityListener;
+import 
org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxPrepareResponse;
+import 
org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx;
+import 
org.apache.ignite.internal.processors.cache.transactions.IgniteTxManager;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.util.typedef.G;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.lang.IgniteBiInClosure;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.apache.ignite.transactions.Transaction;
+import org.apache.ignite.transactions.TransactionConcurrency;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static org.apache.ignite.events.EventType.EVT_NODE_FAILED;
+import static org.apache.ignite.events.EventType.EVT_NODE_LEFT;
+import static org.apache.ignite.testframework.GridTestUtils.cartesianProduct;
+import static org.apache.ignite.transactions.TransactionConcurrency.OPTIMISTIC;
+import static 
org.apache.ignite.transactions.TransactionConcurrency.PESSIMISTIC;
+import static 
org.apache.ignite.transactions.TransactionIsolation.READ_COMMITTED;
+
+/** */
+@RunWith(Parameterized.class)
+public class IgniteTxCacheWithWriteThroughCheckTest extends 
GridCommonAbstractTest {
+    /** Node kill trigger. */
+    private static CountDownLatch nodeKillLatch;
+
+    /** Node left on backup. */
+    private static CountDownLatch nodeLeftRegisteredOnBackup;
+
+    /** */
+    @Parameterized.Parameter(0)
+    public Boolean withPersistence;
+
+    /** */
+    @Parameterized.Parameter(1)
+    public TransactionConcurrency conc;
+
+    /** */
+    @Parameterized.Parameters(name = "withPersistence={0}, concMode={1}")
+    public static Collection<Object[]> parameters() {
+        return cartesianProduct(
+            List.of(true, false),
+            List.of(OPTIMISTIC, PESSIMISTIC)
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        super.beforeTest();
+
+        stopAllGrids();
+
+        if (withPersistence)
+            cleanPersistenceDir();
+
+        nodeKillLatch = new CountDownLatch(1);
+        nodeLeftRegisteredOnBackup = new CountDownLatch(1);
+
+        MapCacheStore.salvagedLatch = new CountDownLatch(1);
+        MapCacheStore.txCoordStoreLatch = new CountDownLatch(2);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        try {
+            for (Ignite node : G.allGrids()) {
+                Collection<IgniteInternalTx> txs = 
((IgniteEx)node).context().cache().context().tm().activeTransactions();
+
+                assertTrue("Unfinished txs [node=" + node.name() + ", txs=" + 
txs + ']', txs.isEmpty());
+            }
+        }
+        finally {
+            stopAllGrids();
+
+            super.afterTest();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String 
igniteInstanceName) throws Exception {
+        return super.getConfiguration(igniteInstanceName)
+            .setDataStorageConfiguration(new DataStorageConfiguration()
+                .setDefaultDataRegionConfiguration(new 
DataRegionConfiguration().setPersistenceEnabled(withPersistence)))
+            .setConsistentId(igniteInstanceName)
+            .setCommunicationSpi(new TestRecordingCommunicationSpi());
+    }
+
+    /** Test scenario:
+     * <ul>
+     *   <li>Start 3 nodes [node0, node1, node2].</li>
+     *   <li>Initialize put operation into transactional cache where [node1] 
holds primary partition for such insertion.</li>
+     *   <li>Kill [node1] right after tx PREPARE stage is completed (it 
triggers tx recovery procedure).</li>
+     * </ul>
+     *
+     * @see IgniteTxManager#salvageTx(IgniteInternalTx)
+     */
+    @Test
+    public void testTxCoordinatorLeftClusterWithEnabledReadWriteThrough() 
throws Exception {
+        // Sequential start is important here.
+        IgniteEx nodeCoord = startGrid(0);
+        // Near node.
+        IgniteEx nodePrimary = startGrid(1);
+        // Backup node.
+        IgniteEx nodeBackup = startGrid(2);
+
+        int firstVal = 1;
+        int secondVal = 2;
+
+        nodeCoord.cluster().state(ClusterState.ACTIVE);
+
+        CacheConfiguration<Object, Object> ccfgWithWriteThrough = 
configureCache(DEFAULT_CACHE_NAME);
+        IgniteCache<Object, Object> cache = 
nodeCoord.createCache(ccfgWithWriteThrough);
+
+        Integer primaryKey = primaryKey(nodePrimary.cache(DEFAULT_CACHE_NAME));
+
+        try (Transaction tx = nodeCoord.transactions().txStart()) {
+            cache.put(primaryKey, firstVal);
+
+            tx.commit();
+        }
+
+        nodeCoord.cluster().state(ClusterState.INACTIVE);
+
+        GridMessageListener lsnr = new GridMessageListener() {
+            @Override public void onMessage(UUID nodeId, Object msg, byte plc) 
{
+                if (msg instanceof GridNearTxPrepareResponse) {
+                    IgniteTxManager txMgr = 
nodeBackup.context().cache().context().tm();
+                    Collection<IgniteInternalTx> txs = 
txMgr.activeTransactions();
+
+                    assertEquals(1, txs.size());
+                    IgniteInternalTx idleTx = txs.iterator().next();
+                    assertFalse(idleTx.local());
+
+                    Map<GridCacheVersion, IgniteInternalTx> activeTx = 
GridTestUtils.getFieldValue(txMgr, "idMap");
+                    assertEquals(1, activeTx.size());
+
+                    nodeKillLatch.countDown();
+
+                    U.awaitQuiet(nodeLeftRegisteredOnBackup);
+
+                    // Let`s wait until all discovery events have been 
processed on backup node.
+                    doSleep(1000);
+
+                    MapCacheStore.txCoordStoreLatch.countDown();
+                }
+            }
+        };
+
+        nodeCoord.context().io().removeMessageListener(GridTopic.TOPIC_CACHE); 
// Remove old cache listener.
+        nodeCoord.context().io().addMessageListener(GridTopic.TOPIC_CACHE, 
lsnr); // Register as first listener.
+        nodeCoord.context().cache().context().io().start0(); // Register cache 
listener again.
+
+        nodeCoord.cluster().state(ClusterState.ACTIVE);
+        awaitPartitionMapExchange(true, true, null);
+
+        nodeCoord.context().event().addDiscoveryEventListener(new 
BeforeRecoveryListener(), EVT_NODE_FAILED, EVT_NODE_LEFT);
+        nodeBackup.context().event().addDiscoveryEventListener(new 
BeforeBackupRecoveryListener(), EVT_NODE_FAILED, EVT_NODE_LEFT);
+
+        IgniteInternalFuture<Object> stopFut = GridTestUtils.runAsync(() -> {
+            nodeKillLatch.await();
+            nodePrimary.close();
+        });
+
+        try (Transaction tx = nodeCoord.transactions().txStart(conc, 
READ_COMMITTED)) {
+            cache.put(primaryKey, secondVal);
+
+            tx.commit();
+        }
+        catch (Throwable th) {
+            fail("Unexpected exception: " + th);
+        }
+
+        stopFut.get(getTestTimeout());
+
+        awaitPartitionMapExchange();
+
+        assertEquals(secondVal, 
nodeCoord.cache(DEFAULT_CACHE_NAME).get(primaryKey));
+        assertEquals(secondVal, 
nodeBackup.cache(DEFAULT_CACHE_NAME).get(primaryKey));
+
+        // Check value after restart.
+        stopAllGrids();
+        startGridsMultiThreaded(3);
+
+        awaitPartitionMapExchange(true, true, null);
+
+        for (Ignite ignite : G.allGrids())
+            assertEquals(secondVal, 
ignite.getOrCreateCache(ccfgWithWriteThrough).get(primaryKey));
+    }
+
+    /** */
+    private CacheConfiguration<Object, Object> configureCache(String 
cacheName) {
+        CacheConfiguration<Object, Object> ccfg = new 
CacheConfiguration<>(cacheName);
+        ccfg.setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL);
+        ccfg.setCacheMode(CacheMode.REPLICATED);
+        
ccfg.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC);
+
+        ccfg.setReadThrough(true);
+        ccfg.setWriteThrough(true);
+        ccfg.setCacheStoreFactory(MapCacheStore::new);
+
+        return ccfg;
+    }
+
+    /** */
+    public static class MapCacheStore extends CacheStoreAdapter<Object, 
Object> {
+        /** Store map. */
+        private static final Map<Object, Object> map = new 
ConcurrentHashMap<>();
+
+        /** */
+        static CountDownLatch salvagedLatch;
+
+        /** */
+        static CountDownLatch txCoordStoreLatch;
+
+        /** {@inheritDoc} */
+        @Override public void loadCache(IgniteBiInClosure<Object, Object> clo, 
Object... args) {
+            for (Map.Entry<Object, Object> e : map.entrySet())
+                clo.apply(e.getKey(), e.getValue());
+        }
+
+        /** {@inheritDoc} */
+        @Override public Object load(Object key) {
+            Object val = map.get(key);
+
+            salvagedLatch.countDown();
+
+            return val;
+        }
+
+        /** {@inheritDoc} */
+        @Override public void write(Cache.Entry<?, ?> e) {
+            map.put(e.getKey(), e.getValue());
+
+            txCoordStoreLatch.countDown();
+        }
+
+        /** {@inheritDoc} */
+        @Override public void delete(Object key) {
+            map.remove(key);
+        }
+    }
+
+    /** */
+    private static class BeforeRecoveryListener implements 
DiscoveryEventListener, HighPriorityListener {
+        /** {@inheritDoc} */
+        @Override public void onEvent(DiscoveryEvent evt, DiscoCache 
discoCache) {
+            U.awaitQuiet(MapCacheStore.txCoordStoreLatch);
+        }
+
+        /** {@inheritDoc} */
+        @Override public int order() {
+            return -1;
+        }
+    }
+
+    /** */
+    private static class BeforeBackupRecoveryListener implements 
DiscoveryEventListener, HighPriorityListener {
+        /** {@inheritDoc} */
+        @Override public void onEvent(DiscoveryEvent evt, DiscoCache 
discoCache) {
+            nodeLeftRegisteredOnBackup.countDown();
+        }
+
+        /** {@inheritDoc} */
+        @Override public int order() {
+            return -1;
+        }
+    }
+}
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/distributed/IgniteTxCacheWriteSynchronizationModesMultithreadedTest.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/distributed/IgniteTxCacheWriteSynchronizationModesMultithreadedTest.java
index 7d05a9bde65..a97158ada72 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/distributed/IgniteTxCacheWriteSynchronizationModesMultithreadedTest.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/distributed/IgniteTxCacheWriteSynchronizationModesMultithreadedTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.processors.cache.distributed;
 
+import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Objects;
@@ -37,7 +38,9 @@ import org.apache.ignite.cache.store.CacheStore;
 import org.apache.ignite.cache.store.CacheStoreAdapter;
 import org.apache.ignite.configuration.CacheConfiguration;
 import org.apache.ignite.configuration.NearCacheConfiguration;
+import org.apache.ignite.internal.IgniteEx;
 import org.apache.ignite.internal.IgniteInternalFuture;
+import 
org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx;
 import org.apache.ignite.internal.util.lang.GridAbsPredicate;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.G;
@@ -93,6 +96,17 @@ public class 
IgniteTxCacheWriteSynchronizationModesMultithreadedTest extends Gri
             assertTrue(grid(SRVS + i).configuration().isClientMode());
     }
 
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        for (Ignite node : G.allGrids()) {
+            Collection<IgniteInternalTx> txs = 
((IgniteEx)node).context().cache().context().tm().activeTransactions();
+
+            assertTrue("Unfinished txs [node=" + node.name() + ", txs=" + txs 
+ ']', txs.isEmpty());
+        }
+
+        super.afterTest();
+    }
+
     /**
      * @throws Exception If failed.
      */
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/distributed/dht/IgniteCacheTxRecoveryRollbackTest.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/distributed/dht/IgniteCacheTxRecoveryRollbackTest.java
index 937b51cd4de..35f8053d866 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/distributed/dht/IgniteCacheTxRecoveryRollbackTest.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/distributed/dht/IgniteCacheTxRecoveryRollbackTest.java
@@ -36,8 +36,8 @@ import org.apache.ignite.cluster.ClusterNode;
 import org.apache.ignite.configuration.CacheConfiguration;
 import org.apache.ignite.configuration.IgniteConfiguration;
 import org.apache.ignite.configuration.NearCacheConfiguration;
+import org.apache.ignite.internal.IgniteEx;
 import org.apache.ignite.internal.IgniteInternalFuture;
-import org.apache.ignite.internal.IgniteKernal;
 import org.apache.ignite.internal.TestRecordingCommunicationSpi;
 import 
org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxFinishRequest;
 import 
org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxPrepareResponse;
@@ -86,7 +86,7 @@ public class IgniteCacheTxRecoveryRollbackTest extends 
GridCommonAbstractTest {
     @Override protected void afterTest() throws Exception {
         try {
             for (Ignite node : G.allGrids()) {
-                Collection<IgniteInternalTx> txs = 
((IgniteKernal)node).context().cache().context().tm().activeTransactions();
+                Collection<IgniteInternalTx> txs = 
((IgniteEx)node).context().cache().context().tm().activeTransactions();
 
                 assertTrue("Unfinished txs [node=" + node.name() + ", txs=" + 
txs + ']', txs.isEmpty());
             }
@@ -379,7 +379,7 @@ public class IgniteCacheTxRecoveryRollbackTest extends 
GridCommonAbstractTest {
         final IgniteCache<Integer, Integer> clientCache = 
client.cache(DEFAULT_CACHE_NAME);
 
         IgniteInternalFuture<?> fut = GridTestUtils.runAsync(new 
Callable<Void>() {
-            @Override public Void call() throws Exception {
+            @Override public Void call() {
                 log.info("Start put");
 
                 clientCache.put(key, 2);
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/query/IndexingSpiQueryTxSelfTest.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/query/IndexingSpiQueryTxSelfTest.java
index 8b158292d52..2c345f773e1 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/query/IndexingSpiQueryTxSelfTest.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/query/IndexingSpiQueryTxSelfTest.java
@@ -22,13 +22,13 @@ import java.util.Iterator;
 import java.util.concurrent.Callable;
 import javax.cache.Cache;
 import org.apache.ignite.IgniteCache;
+import org.apache.ignite.IgniteException;
 import org.apache.ignite.IgniteTransactions;
 import org.apache.ignite.cache.CacheAtomicityMode;
 import org.apache.ignite.configuration.CacheConfiguration;
 import org.apache.ignite.configuration.IgniteConfiguration;
 import org.apache.ignite.internal.IgniteEx;
 import org.apache.ignite.internal.processors.cache.GridCacheAbstractSelfTest;
-import 
org.apache.ignite.internal.transactions.IgniteTxHeuristicCheckedException;
 import org.apache.ignite.spi.IgniteSpiAdapter;
 import org.apache.ignite.spi.IgniteSpiException;
 import org.apache.ignite.spi.indexing.IndexingQueryFilter;
@@ -36,11 +36,14 @@ import org.apache.ignite.spi.indexing.IndexingSpi;
 import org.apache.ignite.testframework.GridTestUtils;
 import org.apache.ignite.transactions.Transaction;
 import org.apache.ignite.transactions.TransactionConcurrency;
+import org.apache.ignite.transactions.TransactionHeuristicException;
 import org.apache.ignite.transactions.TransactionIsolation;
 import org.apache.ignite.transactions.TransactionState;
 import org.jetbrains.annotations.Nullable;
 import org.junit.Test;
 
+import static org.apache.ignite.testframework.GridTestUtils.waitForCondition;
+
 /**
  * Indexing Spi transactional query test
  */
@@ -75,7 +78,7 @@ public class IndexingSpiQueryTxSelfTest extends 
GridCacheAbstractSelfTest {
 
         assertNotNull(client.cache(DEFAULT_CACHE_NAME));
 
-        doTestIndexingSpiWithTx(client, 0);
+        doTestIndexingSpiWithTx(client, 0, false);
     }
 
     /** */
@@ -83,7 +86,7 @@ public class IndexingSpiQueryTxSelfTest extends 
GridCacheAbstractSelfTest {
     public void testIndexingSpiWithTxLocal() throws Exception {
         IgniteEx ignite = (IgniteEx)primaryNode(0, DEFAULT_CACHE_NAME);
 
-        doTestIndexingSpiWithTx(ignite, 0);
+        doTestIndexingSpiWithTx(ignite, 0, true);
     }
 
     /** */
@@ -91,13 +94,13 @@ public class IndexingSpiQueryTxSelfTest extends 
GridCacheAbstractSelfTest {
     public void testIndexingSpiWithTxNotLocal() throws Exception {
         IgniteEx ignite = (IgniteEx)primaryNode(0, DEFAULT_CACHE_NAME);
 
-        doTestIndexingSpiWithTx(ignite, 1);
+        doTestIndexingSpiWithTx(ignite, 1, false);
     }
 
     /**
      * @throws Exception If failed.
      */
-    private void doTestIndexingSpiWithTx(IgniteEx ignite, int key) throws 
Exception {
+    private void doTestIndexingSpiWithTx(IgniteEx ignite, int key, boolean 
heuristic) throws Exception {
         final IgniteCache<Integer, Integer> cache = 
ignite.cache(DEFAULT_CACHE_NAME);
 
         final IgniteTransactions txs = ignite.transactions();
@@ -107,7 +110,7 @@ public class IndexingSpiQueryTxSelfTest extends 
GridCacheAbstractSelfTest {
                 System.out.println("Run in transaction: " + concurrency + " " 
+ isolation);
 
                 GridTestUtils.assertThrowsWithCause(new Callable<Void>() {
-                    @Override public Void call() throws Exception {
+                    @Override public Void call() {
                         Transaction tx;
 
                         try (Transaction tx0 = tx = txs.txStart(concurrency, 
isolation)) {
@@ -120,9 +123,20 @@ public class IndexingSpiQueryTxSelfTest extends 
GridCacheAbstractSelfTest {
 
                         return null;
                     }
-                }, IgniteTxHeuristicCheckedException.class);
-
-                checkFutures();
+                }, heuristic ? TransactionHeuristicException.class : 
IgniteException.class);
+
+                // Cause asynced salvage in action
+                assertTrue(
+                    waitForCondition(() -> {
+                        try {
+                            checkFutures();
+                            return true;
+                        }
+                        catch (AssertionError err) {
+                            return false;
+                        }
+                    }, 5_000)
+                );
             }
         }
     }
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite19.java
 
b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite19.java
index 686ef4aa7b2..317f933c7c5 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite19.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite19.java
@@ -24,6 +24,7 @@ import 
org.apache.ignite.cache.ClientCreateCacheGroupOnJoinNodeMapsTest;
 import 
org.apache.ignite.internal.processors.cache.CacheStoreTxPutAllMultiNodeTest;
 import 
org.apache.ignite.internal.processors.cache.GridCacheOrderedPreloadingSelfTest;
 import 
org.apache.ignite.internal.processors.cache.IgniteCachePutKeyAttachedBinaryObjectTest;
+import 
org.apache.ignite.internal.processors.cache.distributed.IgniteTxCacheWithWriteThroughCheckTest;
 import 
org.apache.ignite.internal.processors.cache.distributed.rebalancing.GridCacheRabalancingDelayedPartitionMapExchangeSelfTest;
 import 
org.apache.ignite.internal.processors.cache.distributed.rebalancing.GridCacheRebalanceOrderTest;
 import 
org.apache.ignite.internal.processors.cache.distributed.rebalancing.GridCacheRebalancingAsyncSelfTest;
@@ -74,6 +75,7 @@ public class IgniteCacheTestSuite19 {
         GridTestUtils.addTestIfNeeded(suite, 
CleanupRestoredCachesSlowTest.class, ignoredTests);
         GridTestUtils.addTestIfNeeded(suite, 
ClientCreateCacheGroupOnJoinNodeMapsTest.class, ignoredTests);
         GridTestUtils.addTestIfNeeded(suite, 
IgniteCachePutKeyAttachedBinaryObjectTest.class, ignoredTests);
+        GridTestUtils.addTestIfNeeded(suite, 
IgniteTxCacheWithWriteThroughCheckTest.class, ignoredTests);
 
         return suite;
     }

Reply via email to