You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by na...@apache.org on 2026/10/12 15:42:53 UTC
(ignite) branch master updated: IGNITE-29099 Avoid boxing of cache ID and message direct type on the cache operation hot path (#13662)
This is an automated email from the ASF dual-hosted git repository.
NSAmelchev 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 bb7f60f26cf IGNITE-29099 Avoid boxing of cache ID and message direct type on the cache operation hot path (#13662)
bb7f60f26cf is described below
commit bb7f60f26cfeede822ae008e38bb832f63b6c9b0
Author: Nikita Amelchev <ns...@gmail.com>
AuthorDate: Mon Oct 12 18:42:45 2026 +0300
IGNITE-29099 Avoid boxing of cache ID and message direct type on the cache operation hot path (#13662)
---
.../processors/cache/GridCacheSharedContext.java | 7 +-
.../util/collection/CopyOnWriteIntMap.java | 164 +++++++++++++++++++
.../internal/util/collection/IntHashMap.java | 71 +++++++--
.../tcp/TcpCommunicationMetricsListener.java | 42 +----
.../util/collection/CopyOnWriteIntMapTest.java | 176 +++++++++++++++++++++
.../internal/util/collection/IntHashMapTest.java | 59 +++++++
.../ignite/testsuites/IgniteBasicTestSuite2.java | 2 +
7 files changed, 473 insertions(+), 48 deletions(-)
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheSharedContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheSharedContext.java
index aae8f54411c..ce09ab360ca 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheSharedContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheSharedContext.java
@@ -23,7 +23,6 @@ import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteLogger;
@@ -59,6 +58,7 @@ import org.apache.ignite.internal.processors.cache.version.GridCacheVersionManag
import org.apache.ignite.internal.processors.cluster.IgniteChangeGlobalStateSupport;
import org.apache.ignite.internal.processors.timeout.GridTimeoutProcessor;
import org.apache.ignite.internal.util.GridIntList;
+import org.apache.ignite.internal.util.collection.CopyOnWriteIntMap;
import org.apache.ignite.internal.util.future.GridCompoundFuture;
import org.apache.ignite.internal.util.future.GridEmbeddedFuture;
import org.apache.ignite.internal.util.future.GridFinishedFuture;
@@ -136,7 +136,7 @@ public class GridCacheSharedContext<K, V> {
private PartitionsEvictManager evictMgr;
/** Cache contexts map. */
- private final ConcurrentHashMap<Integer, GridCacheContext<K, V>> ctxMap;
+ private final CopyOnWriteIntMap<GridCacheContext<K, V>> ctxMap = new CopyOnWriteIntMap<>();
/** Tx metrics. */
private final TransactionMetricsAdapter txMetrics;
@@ -252,8 +252,6 @@ public class GridCacheSharedContext<K, V> {
txMetrics = new TransactionMetricsAdapter(kernalCtx);
- ctxMap = new ConcurrentHashMap<>();
-
kernalCtx.systemView().registerView(new ScanQuerySystemView<>(ctxMap.values()));
locStoreCnt = new AtomicInteger();
@@ -537,6 +535,7 @@ public class GridCacheSharedContext<K, V> {
/**
* @param cacheCtx Cache context to remove.
*/
+ @SuppressWarnings("unchecked")
void removeCacheContext(GridCacheContext cacheCtx) {
int cacheId = cacheCtx.cacheId();
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/collection/CopyOnWriteIntMap.java b/modules/core/src/main/java/org/apache/ignite/internal/util/collection/CopyOnWriteIntMap.java
new file mode 100644
index 00000000000..c648942fa5e
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/collection/CopyOnWriteIntMap.java
@@ -0,0 +1,164 @@
+/*
+ * 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.util.collection;
+
+import java.util.AbstractCollection;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.function.IntFunction;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Thread-safe {@link IntMap} for data that is looked up on hot paths but changes rarely: reads never block or box,
+ * every write copies the map. With a noticeable share of writes it is slower than a concurrent map.
+ */
+public class CopyOnWriteIntMap<V> implements IntMap<V> {
+ /** Current map, never modified after publication: writes replace it with a copy. */
+ private volatile IntHashMap<V> map = new IntHashMap<>();
+
+ /** Live view over the current map. */
+ private final Collection<V> vals = new AbstractCollection<V>() {
+ @Override public Iterator<V> iterator() {
+ return map.valuesIterator();
+ }
+
+ @Override public int size() {
+ return map.size();
+ }
+ };
+
+ /** {@inheritDoc} */
+ @Override public V get(int key) {
+ return map.get(key);
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean containsKey(int key) {
+ return map.containsKey(key);
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean containsValue(V val) {
+ return map.containsValue(val);
+ }
+
+ /** {@inheritDoc} */
+ @Override public <E extends Throwable> void forEach(EntryConsumer<V, E> act) throws E {
+ map.forEach(act);
+ }
+
+ /** {@inheritDoc} */
+ @Override public int size() {
+ return map.size();
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean isEmpty() {
+ return map.isEmpty();
+ }
+
+ /** {@inheritDoc} */
+ @Override public int[] keys() {
+ return map.keys();
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The collection reflects the current state; its iterator traverses a snapshot taken at creation time and does not
+ * support removal.
+ */
+ @Override public Collection<V> values() {
+ return vals;
+ }
+
+ /** {@inheritDoc} */
+ @Override public synchronized V put(int key, V val) {
+ IntHashMap<V> copy = map.copy();
+
+ V old = copy.put(key, val);
+
+ map = copy;
+
+ return old;
+ }
+
+ /** {@inheritDoc} */
+ @Override public synchronized V putIfAbsent(int key, V val) {
+ IntHashMap<V> map = this.map;
+
+ return map.containsKey(key) ? map.get(key) : put(key, val);
+ }
+
+ /** {@inheritDoc} */
+ @Override public synchronized @Nullable V computeIfAbsent(int key, IntFunction<? extends V> mappingFunction) {
+ // The default implementation is a lookup followed by a put, the lock makes the pair atomic.
+ return IntMap.super.computeIfAbsent(key, mappingFunction);
+ }
+
+ /** {@inheritDoc} */
+ @Override public synchronized V remove(int key) {
+ IntHashMap<V> map = this.map;
+
+ return map.containsKey(key) ? removeFromCopy(map, key) : null;
+ }
+
+ /**
+ * Removes the mapping if the key is mapped to the given value.
+ *
+ * @param key Key.
+ * @param val Expected value.
+ * @return {@code True} if the mapping was removed.
+ */
+ public synchronized boolean remove(int key, V val) {
+ IntHashMap<V> map = this.map;
+
+ if (!map.containsKey(key) || !Objects.equals(map.get(key), val))
+ return false;
+
+ removeFromCopy(map, key);
+
+ return true;
+ }
+
+ /**
+ * @param map Current map, must contain the key.
+ * @param key Key.
+ * @return Removed value.
+ */
+ private V removeFromCopy(IntHashMap<V> map, int key) {
+ IntHashMap<V> copy = map.copy();
+
+ V old = copy.remove(key);
+
+ this.map = copy;
+
+ return old;
+ }
+
+ /** {@inheritDoc} */
+ @Override public synchronized void clear() {
+ map = new IntHashMap<>();
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return map.toString();
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/collection/IntHashMap.java b/modules/core/src/main/java/org/apache/ignite/internal/util/collection/IntHashMap.java
index 38bb575f5cf..cdf62389403 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/util/collection/IntHashMap.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/collection/IntHashMap.java
@@ -20,7 +20,9 @@ package org.apache.ignite.internal.util.collection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Iterator;
import java.util.List;
+import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.stream.Collectors;
@@ -93,18 +95,33 @@ public class IntHashMap<V> implements IntMap<V> {
/** Default constructor. */
public IntHashMap() {
- entries = (Entry<V>[])new Entry[INITIAL_CAPACITY];
+ this(INITIAL_CAPACITY);
}
/** Create map with preallocated array. */
public IntHashMap(int cap) {
- int entriesSize = tableSize(cap);
+ this((Entry<V>[])new Entry[tableSize(cap)], 0);
+ }
- compactThreshold = (int)(COMPACT_LOAD_FACTOR * (entriesSize >> 1));
+ /**
+ * @param entries Table, owned by the map.
+ * @param size Number of entries in the table.
+ */
+ private IntHashMap(Entry<V>[] entries, int size) {
+ init(entries, size);
+ }
- scaleThreshold = (int)(entriesSize * SCALE_LOAD_FACTOR);
+ /**
+ * @param entries Table, owned by the map.
+ * @param size Number of entries in the table.
+ */
+ private void init(Entry<V>[] entries, int size) {
+ this.entries = entries;
+ this.size = size;
- entries = (Entry<V>[])new Entry[entriesSize];
+ compactThreshold = (int)(COMPACT_LOAD_FACTOR * (entries.length >> 1));
+
+ scaleThreshold = (int)(entries.length * SCALE_LOAD_FACTOR);
}
/**
@@ -116,6 +133,44 @@ public class IntHashMap<V> implements IntMap<V> {
other.forEach(this::put);
}
+ /** @return Iterator over the values without copying them. The map must not be modified while iterating. */
+ Iterator<V> valuesIterator() {
+ Entry<V>[] entries = this.entries;
+
+ return new Iterator<V>() {
+ /** Index of the next non-empty slot. */
+ private int idx = nextIdx(0);
+
+ @Override public boolean hasNext() {
+ return idx < entries.length;
+ }
+
+ @Override public V next() {
+ if (idx >= entries.length)
+ throw new NoSuchElementException();
+
+ V val = entries[idx].val;
+
+ idx = nextIdx(idx + 1);
+
+ return val;
+ }
+
+ /** @return Index of the first non-empty slot starting from the given one. */
+ private int nextIdx(int from) {
+ while (from < entries.length && entries[from] == null)
+ from++;
+
+ return from;
+ }
+ };
+ }
+
+ /** @return Shallow copy of the same table size: the entries are immutable and shared with this map. */
+ IntHashMap<V> copy() {
+ return new IntHashMap<>(entries.clone(), size);
+ }
+
/** {@inheritDoc} */
@Override public V get(int key) {
int idx = find(key);
@@ -230,11 +285,7 @@ public class IntHashMap<V> implements IntMap<V> {
/** {@inheritDoc} */
@Override public void clear() {
- entries = (Entry<V>[])new Entry[INITIAL_CAPACITY];
-
- compactThreshold = 0;
- scaleThreshold = 0;
- size = 0;
+ init((Entry<V>[])new Entry[INITIAL_CAPACITY], 0);
}
/** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/spi/communication/tcp/TcpCommunicationMetricsListener.java b/modules/core/src/main/java/org/apache/ignite/spi/communication/tcp/TcpCommunicationMetricsListener.java
index 4bf17f1a969..afc30e9e9d5 100644
--- a/modules/core/src/main/java/org/apache/ignite/spi/communication/tcp/TcpCommunicationMetricsListener.java
+++ b/modules/core/src/main/java/org/apache/ignite/spi/communication/tcp/TcpCommunicationMetricsListener.java
@@ -30,6 +30,7 @@ import org.apache.ignite.internal.managers.communication.GridIoMessage;
import org.apache.ignite.internal.managers.communication.IgniteMessageFactoryImpl;
import org.apache.ignite.internal.processors.metric.MetricRegistryImpl;
import org.apache.ignite.internal.processors.metric.impl.LongAdderMetric;
+import org.apache.ignite.internal.util.collection.CopyOnWriteIntMap;
import org.apache.ignite.internal.util.collection.IntHashMap;
import org.apache.ignite.internal.util.collection.IntMap;
import org.apache.ignite.lang.IgniteBiTuple;
@@ -113,11 +114,8 @@ public class TcpCommunicationMetricsListener {
/** Counters of sent and received messages by direct type. */
private final IntMap<IgniteBiTuple<LongAdderMetric, LongAdderMetric>> msgCntrsByType;
- /** Method to synchronize access to message type map. */
- private final Object msgTypeMapMux = new Object();
-
- /** Message type map. */
- private volatile Map<Short, String> msgTypeMap;
+ /** Message class names by direct type, filled on the first message of each type. */
+ private final IntMap<String> msgTypeMap = new CopyOnWriteIntMap<>();
/**
* @param ignite Ignite instance.
@@ -341,14 +339,10 @@ public class TcpCommunicationMetricsListener {
if (metric.name().startsWith(prefix)) {
short directType = Short.parseShort(metric.name().substring(prefix.length()));
- Map<Short, String> msgTypeMap0 = msgTypeMap;
-
- if (msgTypeMap0 != null) {
- String typeName = msgTypeMap0.get(directType);
+ String typeName = msgTypeMap.get(directType);
- if (typeName != null)
- res.put(typeName, ((LongMetric)metric).value());
- }
+ if (typeName != null)
+ res.put(typeName, ((LongMetric)metric).value());
}
}
@@ -430,28 +424,8 @@ public class TcpCommunicationMetricsListener {
private void updateMessageTypeMap(Message msg) {
short typeId = msg.directType();
- Map<Short, String> msgTypeMap0 = msgTypeMap;
-
- if (msgTypeMap0 == null || !msgTypeMap0.containsKey(typeId)) {
- synchronized (msgTypeMapMux) {
- if (msgTypeMap == null) {
- msgTypeMap0 = new HashMap<>();
-
- msgTypeMap0.put(typeId, msg.getClass().getName());
-
- msgTypeMap = msgTypeMap0;
- }
- else {
- if (!msgTypeMap.containsKey(typeId)) {
- msgTypeMap0 = new HashMap<>(msgTypeMap);
-
- msgTypeMap0.put(typeId, msg.getClass().getName());
-
- msgTypeMap = msgTypeMap0;
- }
- }
- }
- }
+ if (!msgTypeMap.containsKey(typeId))
+ msgTypeMap.putIfAbsent(typeId, msg.getClass().getName());
}
/**
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/util/collection/CopyOnWriteIntMapTest.java b/modules/core/src/test/java/org/apache/ignite/internal/util/collection/CopyOnWriteIntMapTest.java
new file mode 100644
index 00000000000..b001befcf28
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/util/collection/CopyOnWriteIntMapTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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.util.collection;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Random;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test for copy-on-write int map. Every write copies the map, so the sizes are kept small on purpose.
+ */
+public class CopyOnWriteIntMapTest {
+ /** */
+ @Test
+ public void putGetRemove() {
+ CopyOnWriteIntMap<String> map = new CopyOnWriteIntMap<>();
+
+ assertTrue(map.isEmpty());
+ assertNull(map.get(1));
+ assertFalse(map.containsKey(1));
+
+ assertNull(map.put(1, "one"));
+ assertEquals("one", map.put(1, "uno"));
+ assertNull(map.put(-2, "two"));
+
+ assertEquals(2, map.size());
+ assertEquals("uno", map.get(1));
+ assertEquals("two", map.get(-2));
+ assertTrue(map.containsKey(-2));
+ assertTrue(map.containsValue("two"));
+ assertFalse(map.containsValue("one"));
+
+ assertEquals("uno", map.remove(1));
+ assertNull(map.remove(1));
+
+ assertEquals(1, map.size());
+ assertFalse(map.containsKey(1));
+
+ map.clear();
+
+ assertTrue(map.isEmpty());
+ assertNull(map.get(-2));
+ }
+
+ /** */
+ @Test
+ public void putIfAbsentAndComputeIfAbsent() {
+ CopyOnWriteIntMap<String> map = new CopyOnWriteIntMap<>();
+
+ assertNull(map.putIfAbsent(1, "one"));
+ assertEquals("one", map.putIfAbsent(1, "uno"));
+ assertEquals("one", map.get(1));
+
+ assertEquals("one", map.computeIfAbsent(1, k -> "computed"));
+ assertEquals("2", map.computeIfAbsent(2, String::valueOf));
+ assertEquals("2", map.get(2));
+ }
+
+ /** */
+ @Test
+ public void compareWithReferenceImplementation() {
+ CopyOnWriteIntMap<Integer> map = new CopyOnWriteIntMap<>();
+ Map<Integer, Integer> ref = new HashMap<>();
+
+ Random rnd = new Random(0);
+
+ for (int i = 0; i < 10_000; i++) {
+ int key = rnd.nextInt(500);
+
+ switch (rnd.nextInt(3)) {
+ case 0:
+ assertEquals(ref.put(key, i), map.put(key, i));
+
+ break;
+
+ case 1:
+ assertEquals(ref.putIfAbsent(key, i), map.putIfAbsent(key, i));
+
+ break;
+
+ default:
+ assertEquals(ref.remove(key), map.remove(key));
+ }
+
+ assertEquals(ref.get(key), map.get(key));
+ assertEquals(ref.size(), map.size());
+ }
+ }
+
+ /** */
+ @Test
+ public void valuesIsLiveView() {
+ CopyOnWriteIntMap<String> map = new CopyOnWriteIntMap<>();
+
+ Collection<String> vals = map.values();
+
+ assertSame(vals, map.values());
+ assertTrue(vals.isEmpty());
+
+ map.put(1, "one");
+
+ assertEquals(1, vals.size());
+ assertTrue(vals.contains("one"));
+
+ map.remove(1);
+
+ assertTrue(vals.isEmpty());
+ }
+
+ /** */
+ @Test
+ public void iteratorSeesSnapshot() {
+ CopyOnWriteIntMap<String> map = new CopyOnWriteIntMap<>();
+
+ map.put(1, "one");
+
+ Iterator<String> it = map.values().iterator();
+
+ map.put(2, "two");
+ map.remove(1);
+
+ assertEquals("one", it.next());
+ assertFalse(it.hasNext());
+ }
+
+ /** */
+ @Test
+ public void conditionalRemove() {
+ CopyOnWriteIntMap<String> map = new CopyOnWriteIntMap<>();
+
+ String val = "one";
+
+ map.put(1, val);
+
+ assertFalse(map.remove(1, "other"));
+ assertEquals(val, map.get(1));
+
+ assertTrue(map.remove(1, new String(val)));
+ assertFalse(map.containsKey(1));
+ assertFalse(map.remove(1, val));
+ }
+
+ /** */
+ @Test(expected = UnsupportedOperationException.class)
+ public void throwExceptionForValuesModification() {
+ CopyOnWriteIntMap<String> map = new CopyOnWriteIntMap<>();
+
+ map.put(1, "one");
+
+ map.values().iterator().remove();
+ }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/util/collection/IntHashMapTest.java b/modules/core/src/test/java/org/apache/ignite/internal/util/collection/IntHashMapTest.java
index 0991774df6b..2bdabfc46fd 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/util/collection/IntHashMapTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/util/collection/IntHashMapTest.java
@@ -19,7 +19,10 @@ package org.apache.ignite.internal.util.collection;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
import java.util.Map;
+import java.util.Set;
import java.util.stream.IntStream;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.junit.Test;
@@ -30,6 +33,7 @@ import static org.apache.ignite.testframework.GridTestUtils.assertThrows;
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
/**
@@ -100,6 +104,61 @@ public class IntHashMapTest extends AbstractBaseIntMapTest {
assertEquals(256, realCapacityForInitialSize(155));
}
+ /** */
+ @Test
+ public void shouldNotResizeOnFirstPut() {
+ IntHashMap<String> map = new IntHashMap<>();
+
+ map.put(1, "1");
+
+ assertEquals(INITIAL_CAPACITY, ((Object[])U.field(map, "entries")).length);
+
+ map.clear();
+ map.put(1, "1");
+
+ assertEquals(INITIAL_CAPACITY, ((Object[])U.field(map, "entries")).length);
+ }
+
+ /** */
+ @Test
+ public void shouldIterateValues() {
+ IntHashMap<String> map = new IntHashMap<>();
+
+ assertFalse(map.valuesIterator().hasNext());
+
+ for (int i = 0; i < 20; i++)
+ map.put(i, String.valueOf(i));
+
+ Set<String> vals = new HashSet<>();
+
+ for (Iterator<String> it = map.valuesIterator(); it.hasNext(); )
+ vals.add(it.next());
+
+ assertEquals(new HashSet<>(map.values()), vals);
+ assertEquals(20, vals.size());
+ }
+
+ /** */
+ @Test
+ public void shouldCopyIndependently() {
+ IntHashMap<String> map = new IntHashMap<>();
+
+ for (int i = 0; i < 20; i++)
+ map.put(i, String.valueOf(i));
+
+ IntHashMap<String> copy = map.copy();
+
+ assertEquals(20, copy.size());
+ assertEquals("7", copy.get(7));
+
+ copy.remove(7);
+ copy.put(100, "100");
+
+ assertEquals("7", map.get(7));
+ assertFalse(map.containsKey(100));
+ assertEquals(20, map.size());
+ }
+
/**
*
*/
diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite2.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite2.java
index 4a97a041dc2..a6f9e966907 100644
--- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite2.java
+++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite2.java
@@ -84,6 +84,7 @@ import org.apache.ignite.internal.processors.metastorage.persistence.Distributed
import org.apache.ignite.internal.processors.metastorage.persistence.DmsDataWriterTest;
import org.apache.ignite.internal.processors.metastorage.persistence.InMemoryCachedDistributedMetaStorageBridgeTest;
import org.apache.ignite.internal.util.collection.BitSetIntSetTest;
+import org.apache.ignite.internal.util.collection.CopyOnWriteIntMapTest;
import org.apache.ignite.internal.util.collection.ImmutableIntSetTest;
import org.apache.ignite.internal.util.collection.IntHashMapTest;
import org.apache.ignite.internal.util.collection.IntRWHashMapTest;
@@ -140,6 +141,7 @@ import org.junit.runners.Suite;
MetricConfigurationTest.class,
SwapPathConstructionSelfTest.class,
BitSetIntSetTest.class,
+ CopyOnWriteIntMapTest.class,
ImmutableIntSetTest.class,
IntHashMapTest.class,
IntRWHashMapTest.class,