/** @throws Exception If failed. */ public void testNotifier() throws Exception { String nodeVer = IgniteProperties.get("ignite.version"); GridUpdateNotifier ntf = new GridUpdateNotifier( null, nodeVer, TEST_GATEWAY, Collections.<PluginProvider>emptyList(), false); ntf.checkForNewVersion(log); String ver = ntf.latestVersion(); // Wait 60 sec for response. for (int i = 0; ver == null && i < 600; i++) { Thread.sleep(100); ver = ntf.latestVersion(); } info("Latest version: " + ver); assertNotNull("Ignite latest version has not been detected.", ver); byte nodeMaintenance = IgniteProductVersion.fromString(nodeVer).maintenance(); byte lastMaintenance = IgniteProductVersion.fromString(ver).maintenance(); assertTrue( "Wrong latest version.", (nodeMaintenance == 0 && lastMaintenance == 0) || (nodeMaintenance > 0 && lastMaintenance > 0)); ntf.reportStatus(log); }
/** @return {@code True} if need to send finish request for one phase commit transaction. */ private boolean needFinishOnePhase() { if (tx.mappings().empty()) return false; boolean finish = tx.txState().hasNearCache(cctx); if (finish) { GridDistributedTxMapping mapping = tx.mappings().singleMapping(); if (FINISH_NEAR_ONE_PHASE_SINCE.compareTo(mapping.node().version()) > 0) finish = false; } return finish; }
/** @throws Exception If failed. */ public void testFromString() throws Exception { IgniteProductVersion ver = IgniteProductVersion.fromString("1.2.3"); assertEquals(1, ver.major()); assertEquals(2, ver.minor()); assertEquals(3, ver.maintenance()); assertEquals("", ver.stage()); assertEquals(0, ver.revisionTimestamp()); assertArrayEquals(new byte[20], ver.revisionHash()); ver = IgniteProductVersion.fromString("1.2.3-0-DEV"); assertEquals(1, ver.major()); assertEquals(2, ver.minor()); assertEquals(3, ver.maintenance()); assertEquals(0, ver.revisionTimestamp()); assertArrayEquals(new byte[20], ver.revisionHash()); ver = IgniteProductVersion.fromString("1.2.3.b1-4-DEV"); assertEquals(1, ver.major()); assertEquals(2, ver.minor()); assertEquals(3, ver.maintenance()); assertEquals("b1", ver.stage()); assertEquals(4, ver.revisionTimestamp()); assertArrayEquals(new byte[20], ver.revisionHash()); ver = IgniteProductVersion.fromString("1.2.3.final-4-DEV"); assertEquals(1, ver.major()); assertEquals(2, ver.minor()); assertEquals(3, ver.maintenance()); assertEquals("final", ver.stage()); assertEquals(4, ver.revisionTimestamp()); assertArrayEquals(new byte[20], ver.revisionHash()); ver = IgniteProductVersion.fromString("1.2.3"); assertEquals(1, ver.major()); assertEquals(2, ver.minor()); assertEquals(3, ver.maintenance()); assertEquals("", ver.stage()); assertEquals(0, ver.revisionTimestamp()); assertArrayEquals(new byte[20], ver.revisionHash()); ver = IgniteProductVersion.fromString("1.2.3-4"); assertEquals(1, ver.major()); assertEquals(2, ver.minor()); assertEquals(3, ver.maintenance()); assertEquals("", ver.stage()); assertEquals(4, ver.revisionTimestamp()); assertArrayEquals(new byte[20], ver.revisionHash()); ver = IgniteProductVersion.fromString("1.2.3-4-18e5a7ec9e3202126a69bc231a6b965bc1d73dee"); assertEquals(1, ver.major()); assertEquals(2, ver.minor()); assertEquals(3, ver.maintenance()); assertEquals("", ver.stage()); assertEquals(4, ver.revisionTimestamp()); assertArrayEquals( new byte[] { 24, -27, -89, -20, -98, 50, 2, 18, 106, 105, -68, 35, 26, 107, -106, 91, -63, -41, 61, -18 }, ver.revisionHash()); ver = IgniteProductVersion.fromString("1.2.3.b1-4-18e5a7ec9e3202126a69bc231a6b965bc1d73dee"); assertEquals(1, ver.major()); assertEquals(2, ver.minor()); assertEquals(3, ver.maintenance()); assertEquals("b1", ver.stage()); assertEquals(4, ver.revisionTimestamp()); assertArrayEquals( new byte[] { 24, -27, -89, -20, -98, 50, 2, 18, 106, 105, -68, 35, 26, 107, -106, 91, -63, -41, 61, -18 }, ver.revisionHash()); ver = IgniteProductVersion.fromString("1.2.3-rc1-4-18e5a7ec9e3202126a69bc231a6b965bc1d73dee"); assertEquals(1, ver.major()); assertEquals(2, ver.minor()); assertEquals(3, ver.maintenance()); assertEquals("rc1", ver.stage()); assertEquals(4, ver.revisionTimestamp()); assertArrayEquals( new byte[] { 24, -27, -89, -20, -98, 50, 2, 18, 106, 105, -68, 35, 26, 107, -106, 91, -63, -41, 61, -18 }, ver.revisionHash()); ver = IgniteProductVersion.fromString( "1.2.3-SNAPSHOT-4-18e5a7ec9e3202126a69bc231a6b965bc1d73dee"); assertEquals(1, ver.major()); assertEquals(2, ver.minor()); assertEquals(3, ver.maintenance()); assertEquals("SNAPSHOT", ver.stage()); assertEquals(4, ver.revisionTimestamp()); assertArrayEquals( new byte[] { 24, -27, -89, -20, -98, 50, 2, 18, 106, 105, -68, 35, 26, 107, -106, 91, -63, -41, 61, -18 }, ver.revisionHash()); ver = IgniteProductVersion.fromString( "1.2.3.b1-SNAPSHOT-4-18e5a7ec9e3202126a69bc231a6b965bc1d73dee"); assertEquals(1, ver.major()); assertEquals(2, ver.minor()); assertEquals(3, ver.maintenance()); assertEquals("b1-SNAPSHOT", ver.stage()); assertEquals(4, ver.revisionTimestamp()); assertArrayEquals( new byte[] { 24, -27, -89, -20, -98, 50, 2, 18, 106, 105, -68, 35, 26, 107, -106, 91, -63, -41, 61, -18 }, ver.revisionHash()); IgniteProductVersion.fromString(VER_STR + '-' + BUILD_TSTAMP + '-' + REV_HASH_STR); }
public final class GridNearTxFinishFuture<K, V> extends GridCompoundIdentityFuture<IgniteInternalTx> implements GridCacheFuture<IgniteInternalTx> { /** */ public static final IgniteProductVersion FINISH_NEAR_ONE_PHASE_SINCE = IgniteProductVersion.fromString("1.4.0"); /** */ public static final IgniteProductVersion WAIT_REMOTE_TXS_SINCE = IgniteProductVersion.fromString("1.5.1"); /** */ private static final long serialVersionUID = 0L; /** Logger reference. */ private static final AtomicReference<IgniteLogger> logRef = new AtomicReference<>(); /** Logger. */ private static IgniteLogger log; /** Context. */ private GridCacheSharedContext<K, V> cctx; /** Future ID. */ private final IgniteUuid futId; /** Transaction. */ @GridToStringInclude private GridNearTxLocal tx; /** Commit flag. */ private boolean commit; /** Node mappings. */ private IgniteTxMappings mappings; /** Trackable flag. */ private boolean trackable = true; /** */ private boolean finishOnePhaseCalled; /** * @param cctx Context. * @param tx Transaction. * @param commit Commit flag. */ public GridNearTxFinishFuture( GridCacheSharedContext<K, V> cctx, GridNearTxLocal tx, boolean commit) { super(F.<IgniteInternalTx>identityReducer(tx)); this.cctx = cctx; this.tx = tx; this.commit = commit; ignoreInterrupts(true); mappings = tx.mappings(); futId = IgniteUuid.randomUuid(); if (log == null) log = U.logger(cctx.kernalContext(), logRef, GridNearTxFinishFuture.class); } /** {@inheritDoc} */ @Override public IgniteUuid futureId() { return futId; } /** {@inheritDoc} */ @SuppressWarnings("unchecked") @Override public boolean onNodeLeft(UUID nodeId) { boolean found = false; for (IgniteInternalFuture<?> fut : futures()) if (isMini(fut)) { MinFuture f = (MinFuture) fut; if (f.onNodeLeft(nodeId)) { // Remove previous mapping. mappings.remove(nodeId); found = true; } } return found; } /** {@inheritDoc} */ @Override public boolean trackable() { return trackable; } /** Marks this future as not trackable. */ @Override public void markNotTrackable() { trackable = false; } /** * @param nodeId Sender. * @param res Result. */ @SuppressWarnings("ForLoopReplaceableByForEach") public void onResult(UUID nodeId, GridNearTxFinishResponse res) { if (!isDone()) { FinishMiniFuture finishFut = null; synchronized (futs) { for (int i = 0; i < futs.size(); i++) { IgniteInternalFuture<IgniteInternalTx> fut = futs.get(i); if (fut.getClass() == FinishMiniFuture.class) { FinishMiniFuture f = (FinishMiniFuture) fut; if (f.futureId().equals(res.miniId())) { assert f.node().id().equals(nodeId); finishFut = f; break; } } } } if (finishFut != null) finishFut.onNearFinishResponse(res); } } /** * @param nodeId Sender. * @param res Result. */ public void onResult(UUID nodeId, GridDhtTxFinishResponse res) { if (!isDone()) for (IgniteInternalFuture<IgniteInternalTx> fut : futures()) { if (fut.getClass() == CheckBackupMiniFuture.class) { CheckBackupMiniFuture f = (CheckBackupMiniFuture) fut; if (f.futureId().equals(res.miniId())) { assert f.node().id().equals(nodeId); f.onDhtFinishResponse(res); } } else if (fut.getClass() == CheckRemoteTxMiniFuture.class) { CheckRemoteTxMiniFuture f = (CheckRemoteTxMiniFuture) fut; if (f.futureId().equals(res.miniId())) f.onDhtFinishResponse(nodeId); } } } /** {@inheritDoc} */ @Override public boolean onDone(IgniteInternalTx tx0, Throwable err) { if (isDone()) return false; synchronized (this) { if (isDone()) return false; if (err != null) { tx.commitError(err); boolean marked = tx.setRollbackOnly(); if (err instanceof IgniteTxRollbackCheckedException) { if (marked) { try { tx.rollback(); } catch (IgniteCheckedException ex) { U.error(log, "Failed to automatically rollback transaction: " + tx, ex); } } } else if (tx.implicit() && tx.isSystemInvalidate()) { // Finish implicit transaction on heuristic error. try { tx.close(); } catch (IgniteCheckedException ex) { U.error(log, "Failed to invalidate transaction: " + tx, ex); } } } if (initialized() || err != null) { if (tx.needCheckBackup()) { assert tx.onePhaseCommit(); if (err != null) err = new TransactionRollbackException("Failed to commit transaction.", err); try { tx.finish(err == null); } catch (IgniteCheckedException e) { if (err != null) err.addSuppressed(e); else err = e; } } if (tx.onePhaseCommit()) { boolean commit = this.commit && err == null; finishOnePhase(commit); tx.tmFinish(commit); } if (super.onDone(tx0, err)) { if (error() instanceof IgniteTxHeuristicCheckedException) { AffinityTopologyVersion topVer = tx.topologyVersion(); for (IgniteTxEntry e : tx.writeMap().values()) { GridCacheContext cacheCtx = e.context(); try { if (e.op() != NOOP && !cacheCtx.affinity().localNode(e.key(), topVer)) { GridCacheEntryEx entry = cacheCtx.cache().peekEx(e.key()); if (entry != null) entry.invalidate(null, tx.xidVersion()); } } catch (Throwable t) { U.error(log, "Failed to invalidate entry.", t); if (t instanceof Error) throw (Error) t; } } } // Don't forget to clean up. cctx.mvcc().removeFuture(futId); return true; } } } return false; } /** * @param fut Future. * @return {@code True} if mini-future. */ private boolean isMini(IgniteInternalFuture<?> fut) { return fut.getClass() == FinishMiniFuture.class || fut.getClass() == CheckBackupMiniFuture.class || fut.getClass() == CheckRemoteTxMiniFuture.class; } /** Completeness callback. */ private void onComplete() { onDone(tx); } /** @return Synchronous flag. */ private boolean isSync() { return tx.explicitLock() || (commit ? tx.syncCommit() : tx.syncRollback()); } /** Initializes future. */ @SuppressWarnings("ForLoopReplaceableByForEach") void finish() { if (tx.onNeedCheckBackup()) { assert tx.onePhaseCommit(); checkBackup(); // If checkBackup is set, it means that primary node has crashed and we will not need to send // finish request to it, so we can mark future as initialized. markInitialized(); return; } try { if (tx.finish(commit) || (!commit && tx.state() == UNKNOWN)) { if ((tx.onePhaseCommit() && needFinishOnePhase()) || (!tx.onePhaseCommit() && mappings != null)) { if (mappings.single()) { GridDistributedTxMapping mapping = mappings.singleMapping(); if (mapping != null) finish(mapping); } else finish(mappings.mappings()); } markInitialized(); if (!isSync() && !isDone()) { boolean complete = true; synchronized (futs) { // Avoid collection copy and iterator creation. for (int i = 0; i < futs.size(); i++) { IgniteInternalFuture<IgniteInternalTx> f = futs.get(i); if (isMini(f) && !f.isDone()) { complete = false; break; } } } if (complete) onComplete(); } } else onDone(new IgniteCheckedException("Failed to commit transaction: " + CU.txString(tx))); } catch (Error | RuntimeException e) { onDone(e); throw e; } catch (IgniteCheckedException e) { onDone(e); } } /** */ private void checkBackup() { GridDistributedTxMapping mapping = mappings.singleMapping(); if (mapping != null) { UUID nodeId = mapping.node().id(); Collection<UUID> backups = tx.transactionNodes().get(nodeId); if (!F.isEmpty(backups)) { assert backups.size() == 1; UUID backupId = F.first(backups); ClusterNode backup = cctx.discovery().node(backupId); // Nothing to do if backup has left the grid. if (backup == null) { readyNearMappingFromBackup(mapping); ClusterTopologyCheckedException cause = new ClusterTopologyCheckedException("Backup node left grid: " + backupId); cause.retryReadyFuture(cctx.nextAffinityReadyFuture(tx.topologyVersion())); onDone( new IgniteTxRollbackCheckedException( "Failed to commit transaction " + "(backup has left grid): " + tx.xidVersion(), cause)); } else { final CheckBackupMiniFuture mini = new CheckBackupMiniFuture(backup, mapping); add(mini); if (backup.isLocal()) { boolean committed = !cctx.tm().addRolledbackTx(tx); readyNearMappingFromBackup(mapping); if (committed) { if (tx.syncCommit()) { GridCacheVersion nearXidVer = tx.nearXidVersion(); assert nearXidVer != null : tx; IgniteInternalFuture<?> fut = cctx.tm().remoteTxFinishFuture(nearXidVer); fut.listen( new CI1<IgniteInternalFuture<?>>() { @Override public void apply(IgniteInternalFuture<?> fut) { mini.onDone(tx); } }); return; } mini.onDone(tx); } else { ClusterTopologyCheckedException cause = new ClusterTopologyCheckedException("Primary node left grid: " + nodeId); cause.retryReadyFuture(cctx.nextAffinityReadyFuture(tx.topologyVersion())); mini.onDone( new IgniteTxRollbackCheckedException( "Failed to commit transaction " + "(transaction has been rolled back on backup node): " + tx.xidVersion(), cause)); } } else { GridDhtTxFinishRequest finishReq = checkCommittedRequest(mini.futureId()); // Preserve old behavior, otherwise response is not sent. if (WAIT_REMOTE_TXS_SINCE.compareTo(backup.version()) > 0) finishReq.syncCommit(true); try { if (FINISH_NEAR_ONE_PHASE_SINCE.compareTo(backup.version()) <= 0) cctx.io().send(backup, finishReq, tx.ioPolicy()); else { mini.onDone( new IgniteTxHeuristicCheckedException( "Failed to check for tx commit on " + "the backup node (node has an old Ignite version) [rmtNodeId=" + backup.id() + ", ver=" + backup.version() + ']')); } } catch (ClusterTopologyCheckedException e) { mini.onNodeLeft(backupId); } catch (IgniteCheckedException e) { mini.onDone(e); } } } } else readyNearMappingFromBackup(mapping); } } /** @return {@code True} if need to send finish request for one phase commit transaction. */ private boolean needFinishOnePhase() { if (tx.mappings().empty()) return false; boolean finish = tx.txState().hasNearCache(cctx); if (finish) { GridDistributedTxMapping mapping = tx.mappings().singleMapping(); if (FINISH_NEAR_ONE_PHASE_SINCE.compareTo(mapping.node().version()) > 0) finish = false; } return finish; } /** @param commit Commit flag. */ private void finishOnePhase(boolean commit) { assert Thread.holdsLock(this); if (finishOnePhaseCalled) return; finishOnePhaseCalled = true; GridDistributedTxMapping locMapping = mappings.localMapping(); if (locMapping != null) { // No need to send messages as transaction was already committed on remote node. // Finish local mapping only as we need send commit message to backups. IgniteInternalFuture<IgniteInternalTx> fut = cctx.tm().txHandler().finishColocatedLocal(commit, tx); // Add new future. if (fut != null) add(fut); } } /** @param mapping Mapping to finish. */ private void readyNearMappingFromBackup(GridDistributedTxMapping mapping) { if (mapping.near()) { GridCacheVersion xidVer = tx.xidVersion(); mapping.dhtVersion(xidVer, xidVer); tx.readyNearLocks( mapping, Collections.<GridCacheVersion>emptyList(), Collections.<GridCacheVersion>emptyList(), Collections.<GridCacheVersion>emptyList()); } } /** @param mappings Mappings. */ private void finish(Iterable<GridDistributedTxMapping> mappings) { // Create mini futures. for (GridDistributedTxMapping m : mappings) finish(m); } /** @param m Mapping. */ private void finish(GridDistributedTxMapping m) { ClusterNode n = m.node(); assert !m.empty(); GridNearTxFinishRequest req = new GridNearTxFinishRequest( futId, tx.xidVersion(), tx.threadId(), commit, tx.isInvalidate(), tx.system(), tx.ioPolicy(), tx.syncCommit(), tx.syncRollback(), m.explicitLock(), tx.storeEnabled(), tx.topologyVersion(), null, null, null, tx.size(), tx.subjectId(), tx.taskNameHash(), tx.activeCachesDeploymentEnabled()); // If this is the primary node for the keys. if (n.isLocal()) { req.miniId(IgniteUuid.randomUuid()); IgniteInternalFuture<IgniteInternalTx> fut = cctx.tm().txHandler().finish(n.id(), tx, req); // Add new future. if (fut != null) add(fut); } else { FinishMiniFuture fut = new FinishMiniFuture(m); req.miniId(fut.futureId()); add(fut); // Append new future. if (tx.pessimistic()) cctx.tm().beforeFinishRemote(n.id(), tx.threadId()); try { cctx.io().send(n, req, tx.ioPolicy()); // If we don't wait for result, then mark future as done. if (!isSync() && !m.explicitLock()) fut.onDone(); } catch (ClusterTopologyCheckedException e) { // Remove previous mapping. mappings.remove(m.node().id()); fut.onNodeLeft(n.id()); } catch (IgniteCheckedException e) { // Fail the whole thing. fut.onDone(e); } } } /** {@inheritDoc} */ @Override public String toString() { Collection<String> futs = F.viewReadOnly( futures(), new C1<IgniteInternalFuture<?>, String>() { @SuppressWarnings("unchecked") @Override public String apply(IgniteInternalFuture<?> f) { if (f.getClass() == FinishMiniFuture.class) { FinishMiniFuture fut = (FinishMiniFuture) f; ClusterNode node = fut.node(); if (node != null) { return "FinishFuture[node=" + node.id() + ", loc=" + node.isLocal() + ", done=" + fut.isDone() + ']'; } else { return "FinishFuture[node=null, done=" + fut.isDone() + ']'; } } else if (f.getClass() == CheckBackupMiniFuture.class) { CheckBackupMiniFuture fut = (CheckBackupMiniFuture) f; ClusterNode node = fut.node(); if (node != null) { return "CheckBackupFuture[node=" + node.id() + ", loc=" + node.isLocal() + ", done=" + f.isDone() + "]"; } else { return "CheckBackupFuture[node=null, done=" + f.isDone() + "]"; } } else if (f.getClass() == CheckRemoteTxMiniFuture.class) { CheckRemoteTxMiniFuture fut = (CheckRemoteTxMiniFuture) f; return "CheckRemoteTxMiniFuture[nodes=" + fut.nodes() + ", done=" + f.isDone() + "]"; } else return "[loc=true, done=" + f.isDone() + "]"; } }); return S.toString( GridNearTxFinishFuture.class, this, "innerFuts", futs, "super", super.toString()); } /** * @param miniId Mini future ID. * @return Finish request. */ private GridDhtTxFinishRequest checkCommittedRequest(IgniteUuid miniId) { GridDhtTxFinishRequest finishReq = new GridDhtTxFinishRequest( cctx.localNodeId(), futureId(), miniId, tx.topologyVersion(), tx.xidVersion(), tx.commitVersion(), tx.threadId(), tx.isolation(), true, false, tx.system(), tx.ioPolicy(), false, tx.syncCommit(), tx.syncRollback(), null, null, null, null, 0, null, 0, tx.activeCachesDeploymentEnabled()); finishReq.checkCommitted(true); return finishReq; } /** */ private abstract class MinFuture extends GridFutureAdapter<IgniteInternalTx> { /** */ private final IgniteUuid futId = IgniteUuid.randomUuid(); /** * @param nodeId Node ID. * @return {@code True} if future processed node failure. */ abstract boolean onNodeLeft(UUID nodeId); /** @return Future ID. */ final IgniteUuid futureId() { return futId; } } /** * Mini-future for get operations. Mini-futures are only waiting on a single node as opposed to * multiple nodes. */ private class FinishMiniFuture extends MinFuture { /** */ private static final long serialVersionUID = 0L; /** Keys. */ @GridToStringInclude private GridDistributedTxMapping m; /** @param m Mapping. */ FinishMiniFuture(GridDistributedTxMapping m) { this.m = m; } /** @return Node ID. */ ClusterNode node() { return m.node(); } /** @return Keys. */ public GridDistributedTxMapping mapping() { return m; } /** @param nodeId Failed node ID. */ boolean onNodeLeft(UUID nodeId) { if (nodeId.equals(m.node().id())) { if (log.isDebugEnabled()) log.debug("Remote node left grid while sending or waiting for reply: " + this); if (isSync()) { Map<UUID, Collection<UUID>> txNodes = tx.transactionNodes(); if (txNodes != null) { Collection<UUID> backups = txNodes.get(nodeId); if (!F.isEmpty(backups)) { final CheckRemoteTxMiniFuture mini = new CheckRemoteTxMiniFuture(new HashSet<>(backups)); add(mini); GridDhtTxFinishRequest req = checkCommittedRequest(mini.futureId()); req.waitRemoteTransactions(true); for (UUID backupId : backups) { ClusterNode backup = cctx.discovery().node(backupId); if (backup != null && WAIT_REMOTE_TXS_SINCE.compareTo(backup.version()) <= 0) { if (backup.isLocal()) { IgniteInternalFuture<?> fut = cctx.tm().remoteTxFinishFuture(tx.nearXidVersion()); fut.listen( new CI1<IgniteInternalFuture<?>>() { @Override public void apply(IgniteInternalFuture<?> fut) { mini.onDhtFinishResponse(cctx.localNodeId()); } }); } else { try { cctx.io().send(backup, req, tx.ioPolicy()); } catch (ClusterTopologyCheckedException e) { mini.onNodeLeft(backupId); } catch (IgniteCheckedException e) { mini.onDone(e); } } } else mini.onDhtFinishResponse(backupId); } } } } onDone(tx); return true; } return false; } /** @param res Result callback. */ void onNearFinishResponse(GridNearTxFinishResponse res) { if (res.error() != null) onDone(res.error()); else onDone(tx); } /** {@inheritDoc} */ @Override public String toString() { return S.toString( FinishMiniFuture.class, this, "done", isDone(), "cancelled", isCancelled(), "err", error()); } } /** */ private class CheckBackupMiniFuture extends MinFuture { /** Keys. */ @GridToStringInclude private GridDistributedTxMapping m; /** Backup node to check. */ private ClusterNode backup; /** * @param backup Backup to check. * @param m Mapping associated with the backup. */ CheckBackupMiniFuture(ClusterNode backup, GridDistributedTxMapping m) { this.backup = backup; this.m = m; } /** @return Node ID. */ public ClusterNode node() { return backup; } /** {@inheritDoc} */ @Override boolean onNodeLeft(UUID nodeId) { if (nodeId.equals(backup.id())) { readyNearMappingFromBackup(m); onDone(new ClusterTopologyCheckedException("Remote node left grid: " + nodeId)); return true; } return false; } /** @param res Response. */ void onDhtFinishResponse(GridDhtTxFinishResponse res) { readyNearMappingFromBackup(m); Throwable err = res.checkCommittedError(); if (err != null) { if (err instanceof IgniteCheckedException) { ClusterTopologyCheckedException cause = ((IgniteCheckedException) err).getCause(ClusterTopologyCheckedException.class); if (cause != null) cause.retryReadyFuture(cctx.nextAffinityReadyFuture(tx.topologyVersion())); } onDone(err); } else onDone(tx); } } /** */ private class CheckRemoteTxMiniFuture extends MinFuture { /** */ private Set<UUID> nodes; /** @param nodes Backup nodes. */ public CheckRemoteTxMiniFuture(Set<UUID> nodes) { this.nodes = nodes; } /** @return Backup nodes. */ Set<UUID> nodes() { synchronized (this) { return new HashSet<>(nodes); } } /** {@inheritDoc} */ @Override boolean onNodeLeft(UUID nodeId) { return onResponse(nodeId); } /** @param nodeId Node ID. */ void onDhtFinishResponse(UUID nodeId) { onResponse(nodeId); } /** * @param nodeId Node ID. * @return {@code True} if processed node response. */ private boolean onResponse(UUID nodeId) { boolean done; boolean ret; synchronized (this) { ret = nodes.remove(nodeId); done = nodes.isEmpty(); } if (done) onDone(tx); return ret; } /** {@inheritDoc} */ @Override public String toString() { return S.toString(CheckRemoteTxMiniFuture.class, this); } } }
private void checkBackup() { GridDistributedTxMapping mapping = mappings.singleMapping(); if (mapping != null) { UUID nodeId = mapping.node().id(); Collection<UUID> backups = tx.transactionNodes().get(nodeId); if (!F.isEmpty(backups)) { assert backups.size() == 1; UUID backupId = F.first(backups); ClusterNode backup = cctx.discovery().node(backupId); // Nothing to do if backup has left the grid. if (backup == null) { readyNearMappingFromBackup(mapping); ClusterTopologyCheckedException cause = new ClusterTopologyCheckedException("Backup node left grid: " + backupId); cause.retryReadyFuture(cctx.nextAffinityReadyFuture(tx.topologyVersion())); onDone( new IgniteTxRollbackCheckedException( "Failed to commit transaction " + "(backup has left grid): " + tx.xidVersion(), cause)); } else { final CheckBackupMiniFuture mini = new CheckBackupMiniFuture(backup, mapping); add(mini); if (backup.isLocal()) { boolean committed = !cctx.tm().addRolledbackTx(tx); readyNearMappingFromBackup(mapping); if (committed) { if (tx.syncCommit()) { GridCacheVersion nearXidVer = tx.nearXidVersion(); assert nearXidVer != null : tx; IgniteInternalFuture<?> fut = cctx.tm().remoteTxFinishFuture(nearXidVer); fut.listen( new CI1<IgniteInternalFuture<?>>() { @Override public void apply(IgniteInternalFuture<?> fut) { mini.onDone(tx); } }); return; } mini.onDone(tx); } else { ClusterTopologyCheckedException cause = new ClusterTopologyCheckedException("Primary node left grid: " + nodeId); cause.retryReadyFuture(cctx.nextAffinityReadyFuture(tx.topologyVersion())); mini.onDone( new IgniteTxRollbackCheckedException( "Failed to commit transaction " + "(transaction has been rolled back on backup node): " + tx.xidVersion(), cause)); } } else { GridDhtTxFinishRequest finishReq = checkCommittedRequest(mini.futureId()); // Preserve old behavior, otherwise response is not sent. if (WAIT_REMOTE_TXS_SINCE.compareTo(backup.version()) > 0) finishReq.syncCommit(true); try { if (FINISH_NEAR_ONE_PHASE_SINCE.compareTo(backup.version()) <= 0) cctx.io().send(backup, finishReq, tx.ioPolicy()); else { mini.onDone( new IgniteTxHeuristicCheckedException( "Failed to check for tx commit on " + "the backup node (node has an old Ignite version) [rmtNodeId=" + backup.id() + ", ver=" + backup.version() + ']')); } } catch (ClusterTopologyCheckedException e) { mini.onNodeLeft(backupId); } catch (IgniteCheckedException e) { mini.onDone(e); } } } } else readyNearMappingFromBackup(mapping); } }