You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ag...@apache.org on 2018/11/14 14:06:19 UTC

ignite git commit: IGNITE-10228 Fixed concurrent start caches race leading to a missed cache context - Fixes #5377.

Repository: ignite
Updated Branches:
  refs/heads/master 59a7986be -> d1a039d36


IGNITE-10228 Fixed concurrent start caches race leading to a missed cache context - Fixes #5377.

Signed-off-by: Alexey Goncharuk <al...@gmail.com>


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/d1a039d3
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/d1a039d3
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/d1a039d3

Branch: refs/heads/master
Commit: d1a039d36787d1920b631d71e8228fa5c47e5d20
Parents: 59a7986
Author: Pavel Voronkin <pv...@gridgain.com>
Authored: Wed Nov 14 15:40:57 2018 +0300
Committer: Alexey Goncharuk <al...@gmail.com>
Committed: Wed Nov 14 16:53:07 2018 +0300

----------------------------------------------------------------------
 .../processors/cache/CacheGroupContext.java     |  99 ++++-----
 .../ignite/internal/util/IgniteUtils.java       |  39 ++--
 .../distributed/CacheParallelStartTest.java     | 202 +++++++++++++++++++
 .../internal/util/IgniteUtilsSelfTest.java      |  61 +++---
 .../testsuites/IgniteCacheTestSuite6.java       |   3 +
 5 files changed, 305 insertions(+), 99 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/d1a039d3/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheGroupContext.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheGroupContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheGroupContext.java
index b0d6c07..93ba69c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheGroupContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheGroupContext.java
@@ -20,7 +20,7 @@ package org.apache.ignite.internal.processors.cache;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Iterator;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -115,10 +115,9 @@ public class CacheGroupContext {
     /** */
     private final boolean storeCacheId;
 
-    /** */
-    private volatile List<GridCacheContext> caches;
+    /** We modify content under lock, by making defencive copy, field always contains unmodifiable list. */
+    private volatile List<GridCacheContext> caches = Collections.unmodifiableList(new ArrayList<>());
 
-    /** */
     private volatile List<GridCacheContext> contQryCaches;
 
     /** */
@@ -152,16 +151,16 @@ public class CacheGroupContext {
     private final ReuseList reuseList;
 
     /** */
-    private boolean drEnabled;
+    private volatile boolean drEnabled;
 
     /** */
-    private boolean qryEnabled;
+    private volatile boolean qryEnabled;
 
     /** */
-    private boolean mvccEnabled;
+    private final boolean mvccEnabled;
 
     /** MXBean. */
-    private CacheGroupMetricsMXBean mxBean;
+    private final CacheGroupMetricsMXBean mxBean;
 
     /** */
     private volatile boolean localWalEnabled;
@@ -236,8 +235,6 @@ public class CacheGroupContext {
 
         log = ctx.kernalContext().log(getClass());
 
-        caches = new ArrayList<>();
-
         mxBean = new CacheGroupMetricsMXBeanImpl(this);
     }
 
@@ -307,10 +304,9 @@ public class CacheGroupContext {
     public boolean hasCache(String cacheName) {
         List<GridCacheContext> caches = this.caches;
 
-        for (int i = 0; i < caches.size(); i++) {
-            if (caches.get(i).name().equals(cacheName))
+        for (GridCacheContext cacheContext : caches)
+            if (cacheContext.name().equals(cacheName))
                 return true;
-        }
 
         return false;
     }
@@ -322,11 +318,17 @@ public class CacheGroupContext {
         assert cacheType.userCache() == cctx.userCache() : cctx.name();
         assert grpId == cctx.groupId() : cctx.name();
 
-        ArrayList<GridCacheContext> caches = new ArrayList<>(this.caches);
+        final boolean add;
 
-        assert sharedGroup() || caches.isEmpty();
+        synchronized (this) {
+            List<GridCacheContext> copy = new ArrayList<>(caches);
+
+            assert sharedGroup() || copy.isEmpty();
 
-        boolean add = caches.add(cctx);
+            add = copy.add(cctx);
+
+            caches = Collections.unmodifiableList(copy);
+        }
 
         assert add : cctx.name();
 
@@ -335,39 +337,39 @@ public class CacheGroupContext {
 
         if (!drEnabled && cctx.isDrEnabled())
             drEnabled = true;
-
-        this.caches = caches;
     }
 
     /**
      * @param cctx Cache context.
      */
     private void removeCacheContext(GridCacheContext cctx) {
-        ArrayList<GridCacheContext> caches = new ArrayList<>(this.caches);
+        final List<GridCacheContext> copy;
 
-        // It is possible cache was not added in case of errors on cache start.
-        for (Iterator<GridCacheContext> it = caches.iterator(); it.hasNext();) {
-            GridCacheContext next = it.next();
+        synchronized (this) {
+            copy = new ArrayList<>(caches);
 
-            if (next == cctx) {
-                assert sharedGroup() || caches.size() == 1 : caches.size();
+            for (GridCacheContext next : copy) {
+                if (next == cctx) {
+                    assert sharedGroup() || copy.size() == 1 : copy.size();
 
-                it.remove();
+                    copy.remove(next);
 
-                break;
+                    break;
+                }
             }
+
+            caches = Collections.unmodifiableList(copy);
         }
 
         if (QueryUtils.isEnabled(cctx.config())) {
             boolean qryEnabled = false;
 
-            for (int i = 0; i < caches.size(); i++) {
-                if (QueryUtils.isEnabled(caches.get(i).config())) {
+            for (GridCacheContext cacheContext : copy)
+                if (QueryUtils.isEnabled(cacheContext.config())) {
                     qryEnabled = true;
 
                     break;
                 }
-            }
 
             this.qryEnabled = qryEnabled;
         }
@@ -375,18 +377,15 @@ public class CacheGroupContext {
         if (cctx.isDrEnabled()) {
             boolean drEnabled = false;
 
-            for (int i = 0; i < caches.size(); i++) {
-                if (caches.get(i).isDrEnabled()) {
+            for (GridCacheContext cacheContext : copy)
+                if (QueryUtils.isEnabled(cacheContext.config())) {
                     drEnabled = true;
 
                     break;
                 }
-            }
 
             this.drEnabled = drEnabled;
         }
-
-        this.caches = caches;
     }
 
     /**
@@ -408,11 +407,8 @@ public class CacheGroupContext {
     public void unwindUndeploys() {
         List<GridCacheContext> caches = this.caches;
 
-        for (int i = 0; i < caches.size(); i++) {
-            GridCacheContext cctx = caches.get(i);
-
+        for (GridCacheContext cctx : caches)
             cctx.deploy().unwind(cctx);
-        }
     }
 
     /**
@@ -450,9 +446,7 @@ public class CacheGroupContext {
 
         List<GridCacheContext> caches = this.caches;
 
-        for (int i = 0; i < caches.size(); i++) {
-            GridCacheContext cctx = caches.get(i);
-
+        for (GridCacheContext cctx : caches)
             if (!cctx.config().isEventsDisabled() && cctx.recordEvent(type)) {
                 cctx.gridEvents().record(new CacheRebalancingEvent(cctx.name(),
                     cctx.localNode(),
@@ -463,7 +457,6 @@ public class CacheGroupContext {
                     discoType,
                     discoTs));
             }
-        }
     }
 
     /**
@@ -478,9 +471,7 @@ public class CacheGroupContext {
 
         List<GridCacheContext> caches = this.caches;
 
-        for (int i = 0; i < caches.size(); i++) {
-            GridCacheContext cctx = caches.get(i);
-
+        for (GridCacheContext cctx : caches)
             if (!cctx.config().isEventsDisabled())
                 cctx.gridEvents().record(new CacheRebalancingEvent(cctx.name(),
                     cctx.localNode(),
@@ -490,7 +481,6 @@ public class CacheGroupContext {
                     null,
                     0,
                     0));
-        }
     }
 
     /**
@@ -517,9 +507,7 @@ public class CacheGroupContext {
     ) {
         List<GridCacheContext> caches = this.caches;
 
-        for (int i = 0; i < caches.size(); i++) {
-            GridCacheContext cctx = caches.get(i);
-
+        for (GridCacheContext cctx : caches)
             if (!cctx.config().isEventsDisabled())
                 cctx.events().addEvent(part,
                     key,
@@ -535,7 +523,6 @@ public class CacheGroupContext {
                     null,
                     null,
                     keepBinary);
-        }
     }
 
     /**
@@ -976,17 +963,19 @@ public class CacheGroupContext {
 
         Set<Integer> ids = U.newHashSet(caches.size());
 
-        for (int i = 0; i < caches.size(); i++)
-            ids.add(caches.get(i).cacheId());
+        for (GridCacheContext cctx : caches)
+            ids.add(cctx.cacheId());
 
         return ids;
     }
 
     /**
      * @return Caches in this group.
+     *
+     * caches is already Unmodifiable list, so we don't need to explicitly wrap it here.
      */
     public List<GridCacheContext> caches() {
-        return this.caches;
+        return caches;
     }
 
     /**
@@ -1004,9 +993,7 @@ public class CacheGroupContext {
     public void onPartitionEvicted(int part) {
         List<GridCacheContext> caches = this.caches;
 
-        for (int i = 0; i < caches.size(); i++) {
-            GridCacheContext cctx = caches.get(i);
-
+        for (GridCacheContext cctx : caches) {
             if (cctx.isDrEnabled())
                 cctx.dr().partitionEvicted(part);
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/d1a039d3/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java
index f2be434..fc6be6d 100755
--- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java
@@ -249,7 +249,6 @@ import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
-import java.util.stream.IntStream;
 import java.util.zip.Deflater;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
@@ -10783,26 +10782,19 @@ public abstract class IgniteUtils {
     ) throws IgniteCheckedException, IgniteInterruptedCheckedException {
         if(srcDatas.isEmpty())
             return Collections.emptyList();
+        int[] batchSizes = calculateOptimalBatchSizes(parallelismLvl, srcDatas.size());
 
-        int batchSize = srcDatas.size() / parallelismLvl;
+        List<List<T>> batches = new ArrayList<>(batchSizes.length);
 
-        final int finalBatchSize = batchSize == 0 ? srcDatas.size() : batchSize;
+        Iterator<T> iterator = srcDatas.iterator();
 
-        List<List<T>> batches = IntStream.range(0, parallelismLvl)
-            .mapToObj(i -> new ArrayList<T>(finalBatchSize))
-            .collect(Collectors.toList());
-
-        int batchIndex = 0;
-
-        final int maxBatchIndex = batches.size() -1;
+        for (int batchSize : batchSizes) {
+            List<T> batch = new ArrayList<>(batchSize);
 
-        List<T> currentBatch = batches.get(batchIndex);
+            for (int i = 0; i < batchSize; i++)
+                batch.add(iterator.next());
 
-        for (T src : srcDatas) {
-            currentBatch.add(src);
-
-            if(currentBatch.size() >= batchSize && batchIndex < maxBatchIndex)
-                currentBatch = batches.get(++batchIndex);
+            batches.add(batch);
         }
 
         List<Future<Collection<R>>> consumerFutures = batches.stream()
@@ -10861,6 +10853,21 @@ public abstract class IgniteUtils {
     }
 
     /**
+     * Split number of tasks into optimized batches.
+     * @param parallelismLvl Level of parallelism.
+     * @param size number of tasks to split.
+     * @return array of batch sizes.
+     */
+    public static int[] calculateOptimalBatchSizes(int parallelismLvl, int size) {
+        int[] batcheSizes = new int[Math.min(parallelismLvl, size)];
+
+        for (int i = 0; i < size; i++)
+            batcheSizes[i % batcheSizes.length]++;
+
+        return batcheSizes;
+    }
+
+    /**
      * @param fut Future to wait for completion.
      * @throws ExecutionException If the future
      */

http://git-wip-us.apache.org/repos/asf/ignite/blob/d1a039d3/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/distributed/CacheParallelStartTest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/distributed/CacheParallelStartTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/distributed/CacheParallelStartTest.java
new file mode 100644
index 0000000..2a64485
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/distributed/CacheParallelStartTest.java
@@ -0,0 +1,202 @@
+/*
+ * 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.ArrayList;
+import java.util.Collection;
+import org.apache.ignite.IgniteSystemProperties;
+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.configuration.WALMode;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.processors.cache.CacheGroupContext;
+import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.util.typedef.internal.CU;
+import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Test covers parallel start and stop of caches.
+ */
+public class CacheParallelStartTest extends GridCommonAbstractTest {
+    /** */
+    private static final int CACHES_COUNT = 500;
+
+    /** */
+    private static final String STATIC_CACHE_PREFIX = "static-cache-";
+
+    /** */
+    private static final String STATIC_CACHE_CACHE_GROUP_NAME = "static-cache-group";
+
+    /** */
+    private static final TcpDiscoveryVmIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true);
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+        IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
+
+        cfg.setSystemThreadPoolSize(Runtime.getRuntime().availableProcessors() * 3);
+
+        TcpDiscoverySpi discoSpi = new TcpDiscoverySpi();
+
+        discoSpi.setIpFinder(IP_FINDER);
+
+        cfg.setDiscoverySpi(discoSpi);
+
+        long sz = 100 * 1024 * 1024;
+
+        DataStorageConfiguration memCfg = new DataStorageConfiguration().setPageSize(1024)
+                .setDefaultDataRegionConfiguration(
+                        new DataRegionConfiguration().setPersistenceEnabled(false).setInitialSize(sz).setMaxSize(sz))
+                .setWalMode(WALMode.LOG_ONLY).setCheckpointFrequency(24L * 60 * 60 * 1000);
+
+        cfg.setDataStorageConfiguration(memCfg);
+
+        ArrayList<Object> staticCaches = new ArrayList<>(CACHES_COUNT);
+
+        for (int i = 0; i < CACHES_COUNT; i++)
+            staticCaches.add(cacheConfiguration(STATIC_CACHE_PREFIX + i));
+
+        cfg.setCacheConfiguration(staticCaches.toArray(new CacheConfiguration[CACHES_COUNT]));
+
+        return cfg;
+    }
+
+    /**
+     * @param cacheName Cache name.
+     * @return Cache configuration.
+     */
+    private CacheConfiguration cacheConfiguration(String cacheName) {
+        CacheConfiguration cfg = defaultCacheConfiguration();
+
+        cfg.setName(cacheName);
+        cfg.setBackups(1);
+        cfg.setGroupName(STATIC_CACHE_CACHE_GROUP_NAME);
+        cfg.setIndexedTypes(Long.class, Long.class);
+
+        return cfg;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override protected void beforeTest() throws Exception {
+        super.beforeTest();
+
+        cleanupTestData();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override protected void afterTest() throws Exception {
+        super.afterTest();
+
+        cleanupTestData();
+    }
+
+    /** */
+    private void cleanupTestData() throws Exception {
+        stopAllGrids();
+
+        cleanPersistenceDir();
+
+        System.clearProperty(IgniteSystemProperties.IGNITE_ALLOW_START_CACHES_IN_PARALLEL);
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testParallelStartAndStop() throws Exception {
+        testParallelStartAndStop(true);
+    }
+
+    /**
+     * @throws Exception if failed.
+     */
+    public void testSequentialStartAndStop() throws Exception {
+        testParallelStartAndStop(false);
+    }
+
+    /**
+     *
+     */
+    private void testParallelStartAndStop(boolean parallel) throws Exception {
+        System.setProperty(IgniteSystemProperties.IGNITE_ALLOW_START_CACHES_IN_PARALLEL, String.valueOf(parallel));
+
+        IgniteEx igniteEx = startGrid(0);
+
+        IgniteEx igniteEx2 = startGrid(1);
+
+        igniteEx.cluster().active(true);
+
+        assertCaches(igniteEx);
+
+        assertCaches(igniteEx2);
+
+        igniteEx.cluster().active(false);
+
+        assertCachesAfterStop(igniteEx);
+
+        assertCachesAfterStop(igniteEx2);
+    }
+
+    /**
+     *
+     */
+    private void assertCachesAfterStop(IgniteEx igniteEx) {
+        assertNull(igniteEx
+                .context()
+                .cache()
+                .cacheGroup(CU.cacheId(STATIC_CACHE_CACHE_GROUP_NAME)));
+
+        assertTrue(igniteEx.context().cache().cacheGroups().isEmpty());
+
+        for (int i = 0; i < CACHES_COUNT; i++) {
+            assertNull(igniteEx.context().cache().cache(STATIC_CACHE_PREFIX + i));
+            assertNull(igniteEx.context().cache().internalCache(STATIC_CACHE_PREFIX + i));
+        }
+    }
+
+    /**
+     *
+     */
+    private void assertCaches(IgniteEx igniteEx) {
+        Collection<GridCacheContext> caches = igniteEx
+                .context()
+                .cache()
+                .cacheGroup(CU.cacheId(STATIC_CACHE_CACHE_GROUP_NAME))
+                .caches();
+
+        assertEquals(CACHES_COUNT, caches.size());
+
+        @Nullable CacheGroupContext cacheGroup = igniteEx
+                .context()
+                .cache()
+                .cacheGroup(CU.cacheId(STATIC_CACHE_CACHE_GROUP_NAME));
+
+        for (GridCacheContext cacheContext : caches)
+            assertEquals(cacheContext.group(), cacheGroup);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/d1a039d3/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java
index 070fd4a..2f52b21 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java
@@ -943,39 +943,46 @@ public class IgniteUtilsSelfTest extends GridCommonAbstractTest {
     }
 
     /**
+     * Test optimal splitting on batch sizes.
+     */
+    public void testOptimalBatchSize() {
+        assertArrayEquals(new int[]{1}, IgniteUtils.calculateOptimalBatchSizes(1, 1));
+
+        assertArrayEquals(new int[]{2}, IgniteUtils.calculateOptimalBatchSizes(1, 2));
+
+        assertArrayEquals(new int[]{1, 1, 1, 1}, IgniteUtils.calculateOptimalBatchSizes(6, 4));
+
+        assertArrayEquals(new int[]{1}, IgniteUtils.calculateOptimalBatchSizes(4, 1));
+
+        assertArrayEquals(new int[]{1, 1}, IgniteUtils.calculateOptimalBatchSizes(4, 2));
+
+        assertArrayEquals(new int[]{1, 1, 1}, IgniteUtils.calculateOptimalBatchSizes(4, 3));
+
+        assertArrayEquals(new int[]{1, 1, 1, 1}, IgniteUtils.calculateOptimalBatchSizes(4, 4));
+
+        assertArrayEquals(new int[]{2, 1, 1, 1}, IgniteUtils.calculateOptimalBatchSizes(4, 5));
+
+        assertArrayEquals(new int[]{2, 2, 1, 1}, IgniteUtils.calculateOptimalBatchSizes(4, 6));
+
+        assertArrayEquals(new int[]{2, 2, 2, 1}, IgniteUtils.calculateOptimalBatchSizes(4, 7));
+
+        assertArrayEquals(new int[]{2, 2, 2, 2}, IgniteUtils.calculateOptimalBatchSizes(4, 8));
+
+        assertArrayEquals(new int[]{3, 2, 2, 2}, IgniteUtils.calculateOptimalBatchSizes(4, 9));
+
+        assertArrayEquals(new int[]{3, 3, 2, 2}, IgniteUtils.calculateOptimalBatchSizes(4, 10));
+    }
+
+    /**
      * Test parallel execution in order.
      */
     public void testDoInParallelResultsOrder() throws IgniteCheckedException {
         ExecutorService executorService = Executors.newFixedThreadPool(4);
 
         try {
-            testOrder(executorService, 1, 1);
-            testOrder(executorService, 2, 1);
-            testOrder(executorService, 3, 1);
-            testOrder(executorService, 9, 1);
-            testOrder(executorService, 10, 1);
-            testOrder(executorService, 9999, 1);
-
-            testOrder(executorService, 1, 2);
-            testOrder(executorService, 2, 2);
-            testOrder(executorService, 3, 2);
-            testOrder(executorService, 9, 2);
-            testOrder(executorService, 10, 2);
-            testOrder(executorService, 9999, 2);
-
-            testOrder(executorService, 1, 3);
-            testOrder(executorService, 2, 3);
-            testOrder(executorService, 3, 3);
-            testOrder(executorService, 3, 3);
-            testOrder(executorService, 10, 3);
-            testOrder(executorService, 9999, 3);
-
-            testOrder(executorService, 1, 4);
-            testOrder(executorService, 2, 4);
-            testOrder(executorService, 3, 4);
-            testOrder(executorService, 9, 4);
-            testOrder(executorService, 10, 4);
-            testOrder(executorService, 9999, 4);
+            for(int parallelism = 1; parallelism < 16; parallelism++)
+                for(int size = 0; size < 10_000; size++)
+                    testOrder(executorService, size, parallelism);
         } finally {
             executorService.shutdownNow();
         }

http://git-wip-us.apache.org/repos/asf/ignite/blob/d1a039d3/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite6.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite6.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite6.java
index d601696..e9d9663 100644
--- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite6.java
+++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite6.java
@@ -27,6 +27,7 @@ import org.apache.ignite.internal.processors.cache.ReplicatedTransactionalOptimi
 import org.apache.ignite.internal.processors.cache.ReplicatedTransactionalPessimisticCacheGetsDistributionTest;
 import org.apache.ignite.internal.processors.cache.datastructures.IgniteExchangeLatchManagerCoordinatorFailTest;
 import org.apache.ignite.internal.processors.cache.distributed.CacheExchangeMergeTest;
+import org.apache.ignite.internal.processors.cache.distributed.CacheParallelStartTest;
 import org.apache.ignite.internal.processors.cache.distributed.CachePartitionStateTest;
 import org.apache.ignite.internal.processors.cache.distributed.CacheTryLockMultithreadedTest;
 import org.apache.ignite.internal.processors.cache.distributed.GridCachePartitionEvictionDuringReadThroughSelfTest;
@@ -109,6 +110,8 @@ public class IgniteCacheTestSuite6 extends TestSuite {
         suite.addTestSuite(PartitionsExchangeCoordinatorFailoverTest.class);
         suite.addTestSuite(CacheTryLockMultithreadedTest.class);
 
+        suite.addTestSuite(CacheParallelStartTest.class);
+
         //suite.addTestSuite(CacheClientsConcurrentStartTest.class);
         //suite.addTestSuite(GridCacheRebalancingOrderingTest.class);
         //suite.addTestSuite(IgniteCacheClientMultiNodeUpdateTopologyLockTest.class);