You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sk...@apache.org on 2022/08/12 13:15:12 UTC

[ignite] branch master updated: IGNITE-17507 Fixed an issue that could lead to unexpected partition map exchange on client nodes. Fixes #10187

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

sk0x50 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 7338198eea6 IGNITE-17507 Fixed an issue that could lead to unexpected partition map exchange on client nodes. Fixes #10187
7338198eea6 is described below

commit 7338198eea66ff48186657c8a87d623c74edc51b
Author: Slava Koptilin <sl...@gmail.com>
AuthorDate: Fri Aug 12 16:13:51 2022 +0300

    IGNITE-17507 Fixed an issue that could lead to unexpected partition map exchange on client nodes. Fixes #10187
---
 .../managers/discovery/CustomMessageWrapper.java   |   3 +-
 .../managers/discovery/DiscoveryCustomMessage.java |  10 ++
 .../cache/CacheAffinityChangeMessage.java          |  53 ++++++++--
 .../cache/CacheAffinitySharedManager.java          |  13 ++-
 .../spi/discovery/DiscoverySpiCustomMessage.java   |   5 +-
 .../CacheLateAffinityAssignmentTest.java           | 115 +++++++++++++++++++--
 .../ZkCommunicationErrorResolveFinishMessage.java  |   1 -
 .../ZkCommunicationErrorResolveStartMessage.java   |   1 -
 .../zk/internal/ZkForceNodeFailMessage.java        |   1 -
 .../discovery/zk/internal/ZkNoServersMessage.java  |   1 -
 .../zk/internal/ZookeeperDiscoveryImpl.java        |  33 +++++-
 11 files changed, 212 insertions(+), 24 deletions(-)

diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/CustomMessageWrapper.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/CustomMessageWrapper.java
index 3d9bfccc737..29aa940ecf2 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/CustomMessageWrapper.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/CustomMessageWrapper.java
@@ -50,9 +50,8 @@ public class CustomMessageWrapper implements DiscoverySpiCustomMessage {
     }
 
     /** {@inheritDoc} */
-    @Deprecated
     @Override public boolean stopProcess() {
-        return false;
+        return delegate.stopProcess();
     }
 
     /**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/DiscoveryCustomMessage.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/DiscoveryCustomMessage.java
index 2309fc0238f..c3cd0aba66c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/DiscoveryCustomMessage.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/DiscoveryCustomMessage.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.managers.discovery;
 import java.io.Serializable;
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
 import org.apache.ignite.lang.IgniteUuid;
+import org.apache.ignite.spi.discovery.DiscoverySpiCustomMessage;
 import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryNodeAddFinishedMessage;
 import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryNodeAddedMessage;
 import org.jetbrains.annotations.Nullable;
@@ -91,6 +92,15 @@ public interface DiscoveryCustomMessage extends Serializable {
      */
     public boolean isMutable();
 
+    /**
+     * See {@link DiscoverySpiCustomMessage#stopProcess()}.
+     *
+     * @return {@code True} if message should not be sent to others nodes after it was processed on coordinator.
+     */
+    public default boolean stopProcess() {
+        return false;
+    }
+
     /**
      * Creates new discovery cache if message caused topology version change.
      *
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheAffinityChangeMessage.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheAffinityChangeMessage.java
index 708e3c15220..1330e88c6fe 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheAffinityChangeMessage.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheAffinityChangeMessage.java
@@ -28,6 +28,7 @@ import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.Gri
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionsFullMessage;
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.lang.IgniteUuid;
+import org.apache.ignite.spi.discovery.DiscoverySpiMutableCustomMessageSupport;
 import org.jetbrains.annotations.Nullable;
 
 /**
@@ -55,8 +56,18 @@ public class CacheAffinityChangeMessage implements DiscoveryCustomMessage {
     /** */
     private GridDhtPartitionsFullMessage partsMsg;
 
-    /** */
-    private transient boolean exchangeNeeded;
+    /** If this flag is {@code true} then this message should lead to partition map exchnage. */
+    private boolean exchangeNeeded;
+
+    /**
+     * This flag indicates that this message should not be passed to other nodes except the coordinator.
+     * Instead of this message, the message which is returned by {@link #ackMessage()} will be sent.
+     * See {@link DiscoveryCustomMessage#stopProcess()}.
+     *
+     * This flag is used when discovery SPI does not support mutable custom messages.
+     * See {@link DiscoverySpiMutableCustomMessageSupport}.
+     */
+    private transient boolean stopProc;
 
     /**
      * Constructor used when message is created after cache rebalance finished.
@@ -76,7 +87,8 @@ public class CacheAffinityChangeMessage implements DiscoveryCustomMessage {
      * @param partsMsg Partitions messages.
      * @param assignmentChange Assignment change.
      */
-    public CacheAffinityChangeMessage(GridDhtPartitionExchangeId exchId,
+    public CacheAffinityChangeMessage(
+        GridDhtPartitionExchangeId exchId,
         GridDhtPartitionsFullMessage partsMsg,
         Map<Integer, Map<Integer, List<UUID>>> assignmentChange) {
         this.exchId = exchId;
@@ -140,17 +152,44 @@ public class CacheAffinityChangeMessage implements DiscoveryCustomMessage {
 
     /** {@inheritDoc} */
     @Nullable @Override public DiscoveryCustomMessage ackMessage() {
-        return null;
+        if (!stopProc)
+            return null;
+
+        // If stopProc is equal to true, then Discovery SPI does not support mutable custom messages.
+        // Let's return the same message, that was muted on the coordinator node. This message will be sent to all nodes
+        // instead of the original one.
+        return this;
     }
 
     /** {@inheritDoc} */
     @Override public boolean isMutable() {
-        return false;
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean stopProcess() {
+        return stopProc;
+    }
+
+    /**
+     * Sets stop processing flag. If this flag is {@code true} then this message is not passed to other nodes after
+     * the coordinator node notitied its own listner. If method {@link #ackMessage()} returns non-null ack message,
+     * it is sent to all nodes.
+     * This flag is used when discovery SPI does not support mutable custom messages.
+     * See {@link DiscoverySpiMutableCustomMessageSupport}.
+     *
+     * @param stopProc If {@code true} then this message is not passed to other nodes.
+     */
+    public void stopProcess(boolean stopProc) {
+        this.stopProc = stopProc;
     }
 
     /** {@inheritDoc} */
-    @Nullable @Override public DiscoCache createDiscoCache(GridDiscoveryManager mgr,
-        AffinityTopologyVersion topVer, DiscoCache discoCache) {
+    @Nullable @Override public DiscoCache createDiscoCache(
+        GridDiscoveryManager mgr,
+        AffinityTopologyVersion topVer,
+        DiscoCache discoCache
+    ) {
         return discoCache.copy(topVer, null);
     }
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheAffinitySharedManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheAffinitySharedManager.java
index f6f4a954be2..fda6598653d 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheAffinitySharedManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheAffinitySharedManager.java
@@ -77,6 +77,7 @@ import org.apache.ignite.lang.IgniteClosure;
 import org.apache.ignite.lang.IgniteFuture;
 import org.apache.ignite.lang.IgniteInClosure;
 import org.apache.ignite.lang.IgniteUuid;
+import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
 import org.jetbrains.annotations.Nullable;
 import static org.apache.ignite.cache.CacheRebalanceMode.NONE;
 import static org.apache.ignite.events.EventType.EVT_NODE_FAILED;
@@ -219,11 +220,21 @@ public class CacheAffinitySharedManager<K, V> extends GridCacheSharedManagerAdap
             return false;
         }
 
+        boolean isClient = cctx.discovery().localNode().isClient();
+
+        if (cctx.kernalContext().config().getDiscoverySpi() instanceof TcpDiscoverySpi)
+            isClient &= !((TcpDiscoverySpi)cctx.kernalContext().config().getDiscoverySpi()).isForceServerMode();
+
         // Skip message if affinity was already recalculated.
-        boolean exchangeNeeded = lastAffVer == null || lastAffVer.equals(msg.topologyVersion());
+        // Client node should just accept the flag from the mutated message.
+        boolean exchangeNeeded = (isClient) ? msg.exchangeNeeded() :
+            lastAffVer == null || lastAffVer.equals(msg.topologyVersion());
 
         msg.exchangeNeeded(exchangeNeeded);
 
+        if (!cctx.discovery().mutableCustomMessages() && !isClient)
+            msg.stopProcess(true);
+
         if (exchangeNeeded) {
             if (log.isDebugEnabled()) {
                 log.debug("Need process affinity change message [lastAffVer=" + lastAffVer +
diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/DiscoverySpiCustomMessage.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/DiscoverySpiCustomMessage.java
index afd7093151e..6e11673d8e2 100644
--- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/DiscoverySpiCustomMessage.java
+++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/DiscoverySpiCustomMessage.java
@@ -45,10 +45,9 @@ public interface DiscoverySpiCustomMessage extends Serializable {
      * then message is not passed to others nodes, if after this method {@link #ackMessage()} returns non-null ack
      * message, it is sent to all nodes.
      *
-     * @return {@code True} if message should not be sent to all nodes.
+     * Note: this method is used then and only then the zookeeper discovery is configured.
      *
-     * @deprecated Is not used anymore and will be removed at 3.0.
+     * @return {@code True} if message should not be sent to all nodes.
      */
-    @Deprecated
     public boolean stopProcess();
 }
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/distributed/CacheLateAffinityAssignmentTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/distributed/CacheLateAffinityAssignmentTest.java
index 03094479434..d50c2c53187 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/distributed/CacheLateAffinityAssignmentTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/distributed/CacheLateAffinityAssignmentTest.java
@@ -105,7 +105,10 @@ import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL;
 import static org.apache.ignite.cache.CacheRebalanceMode.ASYNC;
 import static org.apache.ignite.cache.CacheRebalanceMode.SYNC;
 import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC;
+import static org.apache.ignite.internal.TestRecordingCommunicationSpi.blockSingleExhangeMessage;
 import static org.apache.ignite.internal.processors.cache.ExchangeContext.IGNITE_EXCHANGE_COMPATIBILITY_VER_1;
+import static org.apache.ignite.testframework.GridTestUtils.runAsync;
+import static org.apache.ignite.testframework.GridTestUtils.waitForCondition;
 
 /**
  *
@@ -1352,7 +1355,7 @@ public class CacheLateAffinityAssignmentTest extends GridCommonAbstractTest {
             Ignite ignite = grids.get(i);
 
             if (!blocked.contains(ignite.name())) {
-                GridTestUtils.waitForCondition(new GridAbsPredicate() {
+                waitForCondition(new GridAbsPredicate() {
                     @Override public boolean apply() {
                         return fut.isDone();
                     }
@@ -1527,7 +1530,7 @@ public class CacheLateAffinityAssignmentTest extends GridCommonAbstractTest {
             });
         }
 
-        IgniteInternalFuture<?> stopFut = GridTestUtils.runAsync(new Callable<Void>() {
+        IgniteInternalFuture<?> stopFut = runAsync(new Callable<Void>() {
             @Override public Void call() throws Exception {
                 for (int j = 1; j < NODES; j++) {
                     TestRecordingCommunicationSpi spi =
@@ -1560,6 +1563,106 @@ public class CacheLateAffinityAssignmentTest extends GridCommonAbstractTest {
         checkAffinity(NODES + 1, topVer(topVer, 1), true);
     }
 
+    /**
+     * Wait for rebalance, send affinity change message, but affinity already changed
+     * (new nodes joined: server + client). Checks that tere is no race that could lead to
+     * unexpected partition map exchange on the client node.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testDelayAssignmentAffinityChangedUnexpectedPME() throws Exception {
+        Ignite ignite0 = startServer(0, 1);
+
+        for (int i = 0; i < 1024; i++)
+            ignite0.cache(CACHE_NAME1).put(i, i);
+
+        DiscoverySpiTestListener lsnr = new DiscoverySpiTestListener();
+
+        ((IgniteDiscoverySpi)ignite0.configuration().getDiscoverySpi()).setInternalListener(lsnr);
+
+        TestRecordingCommunicationSpi commSpi0 =
+            (TestRecordingCommunicationSpi)ignite0.configuration().getCommunicationSpi();
+
+        // Starting a new client node should not lead to a rebalance obviously.
+        // So, it is expected that data distribution is ideal (ideal assignment).
+        startClient(1, 2);
+
+        checkAffinity(2, topVer(2, 0), true);
+
+        // Block late affinity assignment. (*)
+        lsnr.blockCustomEvent(CacheAffinityChangeMessage.class);
+
+        // Starting a new server node triggers data rebalancing.
+        // [3, 0] - is not ideal (expected)
+        startServer(2, 3);
+
+        checkAffinity(3, topVer(3, 0), false);
+
+        // Wait for sending late affinity assignment message (1) from the coordinator node.
+        // This message will be blocked (*)
+        lsnr.waitCustomEvent();
+
+        // Block rebalance messages.
+        blockSupplySend(commSpi0, CACHE_NAME1);
+
+        // Starting a new server node means that the late affinity assignment message (1) should be skipped.
+        startServer(3, 4);
+
+        TestRecordingCommunicationSpi clientSpi = new TestRecordingCommunicationSpi();
+        clientSpi.blockMessages(blockSingleExhangeMessage());
+        spiC = igniteInstanceName -> clientSpi;
+
+        IgniteInternalFuture<?> startClientFut = runAsync(() -> {
+            startClient(4, 5);
+        });
+
+        clientSpi.waitForBlocked();
+
+        // Unblock the late affinity assignment message (1).
+        lsnr.stopBlockCustomEvents();
+
+        clientSpi.stopBlock();
+
+        startClientFut.get(15_000);
+
+        // [5, 0] - is not ideal (expected)
+        checkAffinity(5, topVer(5, 0), false);
+
+        // Rebalance is blocked at this moment, so [5, 1] is not ready.
+        checkNoExchange(5, topVer(5, 1));
+
+        // Unblock rebalancing.
+        // The late affinity assignments message (2) should be fired after all.
+        commSpi0.stopBlock();
+
+        // [5, 1] should be ideal
+        checkAffinity(5, topVer(5, 1), true);
+
+        // The following output demonstrates the issue.
+        // The coordinator node and client initiate PME on the same toplogy version,
+        // but it relies to different custom messages.
+        // client:
+        //      Started exchange init [
+        //          topVer=AffinityTopologyVersion [topVer=5, minorTopVer=1],
+        //          crd=false,
+        //          evt=DISCOVERY_CUSTOM_EVT, evtNode=00ac9434-fd34-4aae-95d3-ceb477700000,
+        //          customEvt=CacheAffinityChangeMessage [
+        //              id=3ccc8984181-ea41279c-71cb-4b8c-8b48-1dee1baa6fe0,                       <<< (1)
+        //              topVer=AffinityTopologyVersion [topVer=3, minorTopVer=0], ...]             <<< !!!
+        // coordinator:
+        //      Started exchange init
+        //          [topVer=AffinityTopologyVersion [topVer=5, minorTopVer=1],
+        //          crd=true,
+        //          evt=DISCOVERY_CUSTOM_EVT, evtNode=00ac9434-fd34-4aae-95d3-ceb477700000,
+        //          customEvt=CacheAffinityChangeMessage [
+        //              id=d2ec8984181-ea41279c-71cb-4b8c-8b48-1dee1baa6fe0,                        <<< (2)
+        //              topVer=AffinityTopologyVersion [topVer=4, minorTopVer=0], ...]              <<< !!!
+        awaitPartitionMapExchange(true, true, null, false);
+
+        assertPartitionsSame(idleVerify(grid(0), CACHE_NAME1));
+    }
+
     /**
      * Wait for rebalance, send affinity change message, but affinity already changed (new node joined).
      *
@@ -1657,7 +1760,7 @@ public class CacheLateAffinityAssignmentTest extends GridCommonAbstractTest {
 
             lsnr.stopBlockCustomEvents();
 
-            boolean started = GridTestUtils.waitForCondition(new GridAbsPredicate() {
+            boolean started = waitForCondition(new GridAbsPredicate() {
                 @Override public boolean apply() {
                     return startedFuture.isDone();
                 }
@@ -2174,7 +2277,7 @@ public class CacheLateAffinityAssignmentTest extends GridCommonAbstractTest {
             //Ensure exchanges merge.
             spiC = igniteInstanceName -> testSpis[getTestIgniteInstanceIndex(igniteInstanceName)];
 
-            GridTestUtils.runAsync(() -> {
+            runAsync(() -> {
                 try {
                     for (int j = 1; j < NODES; j++)
                         testSpis[j].waitForBlocked();
@@ -2321,7 +2424,7 @@ public class CacheLateAffinityAssignmentTest extends GridCommonAbstractTest {
             }
         }, NODES, "update-thread");
 
-        IgniteInternalFuture<?> srvRestartFut = GridTestUtils.runAsync(new Callable<Void>() {
+        IgniteInternalFuture<?> srvRestartFut = runAsync(new Callable<Void>() {
             @Override public Void call() throws Exception {
                 while (!fail.get() && System.currentTimeMillis() < stopTime) {
                     Ignite node = startGrid(NODES);
@@ -2462,7 +2565,7 @@ public class CacheLateAffinityAssignmentTest extends GridCommonAbstractTest {
 
             final ClusterNode srvcNode = affinity.get(part).get(0);
 
-            boolean wait = GridTestUtils.waitForCondition(new PA() {
+            boolean wait = waitForCondition(new PA() {
                 @Override public boolean apply() {
                     TestService srvc = grid(srvcNode).services().service(srvcName);
 
diff --git a/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/ZkCommunicationErrorResolveFinishMessage.java b/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/ZkCommunicationErrorResolveFinishMessage.java
index 41a4761ab65..9b7476c5355 100644
--- a/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/ZkCommunicationErrorResolveFinishMessage.java
+++ b/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/ZkCommunicationErrorResolveFinishMessage.java
@@ -58,7 +58,6 @@ class ZkCommunicationErrorResolveFinishMessage implements DiscoverySpiCustomMess
     }
 
     /** {@inheritDoc} */
-    @Deprecated
     @Override public boolean stopProcess() {
         return false;
     }
diff --git a/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/ZkCommunicationErrorResolveStartMessage.java b/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/ZkCommunicationErrorResolveStartMessage.java
index 4c8d996b768..0c79c36aee0 100644
--- a/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/ZkCommunicationErrorResolveStartMessage.java
+++ b/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/ZkCommunicationErrorResolveStartMessage.java
@@ -50,7 +50,6 @@ public class ZkCommunicationErrorResolveStartMessage implements DiscoverySpiCust
     }
 
     /** {@inheritDoc} */
-    @Deprecated
     @Override public boolean stopProcess() {
         return false;
     }
diff --git a/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/ZkForceNodeFailMessage.java b/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/ZkForceNodeFailMessage.java
index bd6e1e8b6b4..de7291c0d45 100644
--- a/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/ZkForceNodeFailMessage.java
+++ b/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/ZkForceNodeFailMessage.java
@@ -54,7 +54,6 @@ public class ZkForceNodeFailMessage implements DiscoverySpiCustomMessage, ZkInte
     }
 
     /** {@inheritDoc} */
-    @Deprecated
     @Override public boolean stopProcess() {
         return false;
     }
diff --git a/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/ZkNoServersMessage.java b/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/ZkNoServersMessage.java
index 957310d5d59..626fe742d1c 100644
--- a/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/ZkNoServersMessage.java
+++ b/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/ZkNoServersMessage.java
@@ -39,7 +39,6 @@ class ZkNoServersMessage implements DiscoverySpiCustomMessage, ZkInternalMessage
     }
 
     /** {@inheritDoc} */
-    @Deprecated
     @Override public boolean stopProcess() {
         return false;
     }
diff --git a/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/ZookeeperDiscoveryImpl.java b/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/ZookeeperDiscoveryImpl.java
index 97f7b86d0c3..5225c9a1d9f 100644
--- a/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/ZookeeperDiscoveryImpl.java
+++ b/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/ZookeeperDiscoveryImpl.java
@@ -2588,11 +2588,39 @@ public class ZookeeperDiscoveryImpl {
         if (log.isDebugEnabled())
             log.debug("Generated CUSTOM event [evt=" + evtData + ", msg=" + msg + ']');
 
+        boolean fastStopProcess = false;
+
         if (msg instanceof ZkInternalMessage)
             processInternalMessage(evtData, (ZkInternalMessage)msg);
-        else
+        else {
             notifyCustomEvent(evtData, msg);
 
+            if (msg.stopProcess()) {
+                if (log.isDebugEnabled())
+                    log.debug("Fast stop process custom event [evt=" + evtData + ", msg=" + msg + ']');
+
+                fastStopProcess = true;
+
+                // No need to process this event on others nodes, skip this event.
+                evtsData.evts.remove(evtData.eventId());
+
+                evtsData.evtIdGen--;
+
+                DiscoverySpiCustomMessage ack = msg.ackMessage();
+
+                if (ack != null) {
+                    evtData = createAckEvent(ack, evtData);
+
+                    if (log.isDebugEnabled())
+                        log.debug("Generated CUSTOM event (ack for fast stop process) [evt=" + evtData + ", msg=" + msg + ']');
+
+                    notifyCustomEvent(evtData, ack);
+                }
+                else
+                    evtData = null;
+            }
+        }
+
         if (evtData != null) {
             evtsData.addEvent(rtState.top.nodesByOrder.values(), evtData);
 
@@ -2600,6 +2628,9 @@ public class ZookeeperDiscoveryImpl {
 
             saveAndProcessNewEvents();
 
+            if (fastStopProcess)
+                deleteCustomEventDataAsync(zkClient, evtPath);
+
             if (failedNode != null) {
                 deleteAliveNode(failedNode.internalId());