diff --git a/pom.xml b/pom.xml index 8c879de44..3f565c3c6 100755 --- a/pom.xml +++ b/pom.xml @@ -212,6 +212,7 @@ 1.5.0 test + diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMap.java b/src/main/java/net/openhft/chronicle/map/ChronicleMap.java index 6618dc2ac..f1d42cdbf 100755 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMap.java @@ -213,5 +213,36 @@ public interface ChronicleMap extends ConcurrentMap, * @return the value Class or UnresolvedType if unknown. */ Type valueType(); -} + /** + * @return the amount of free space in the map as a percentage. + * When the free space gets low ( around 5% ) the map will automatically expand. The + * number of times it can automatically expand is based on the + * {@code net.openhft.chronicle.map.ChronicleMapBuilder#maxBloatFactor}. If the map + * expands you will see an increase in the available free space. It is not possible to + * expand the chronicle map manually. + * + * see also {@see net.openhft.chronicle.map.ChronicleMap#remainingAutoResizes} as these operations are related. + */ + default short percentageFreeSpace() { + throw new UnsupportedOperationException("todo"); + } + + /** + * @return the number of times in the future the map can expand its capacity + * ( by expending capacity we mean the number of possible entries that + * Can be stored into the map), the map will expand automatically, however, there is an upper limit to the + * number of times the map can expand. + * This limit is set via the {@code net.openhft.chronicle.map.ChronicleMapBuilder#maxBloatFactor} if + * the {@code remainingResizes} drops to zero, + * then the map is no longer able to resize, if subsequently, the free space + * ( see {@link net.openhft.chronicle.map.ChronicleMap#percentageFreeSpace}) if the map becomes low ( around 5% ), + * the map will not be able to take more entries and will fail with an {@code + * java.lang.IllegalStateException} for production systems it is recommended you periodically monitor this result and the both the + * {@link net.openhft.chronicle.map.ChronicleMap#percentageFreeSpace}. + */ + default int remainingAutoResizes() { + throw new UnsupportedOperationException("todo"); + } + +} \ No newline at end of file diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java index 7c3927b25..98b3d004a 100755 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java @@ -204,7 +204,7 @@ public final class ChronicleMapBuilder implements private int maxChunksPerEntry = -1; private int alignment = UNDEFINED_ALIGNMENT_CONFIG; private long entries = -1L; - private double maxBloatFactor = 1.0; + double maxBloatFactor = 1.0; private boolean allowSegmentTiering = true; private double nonTieredSegmentsPercentile = 0.99999; private boolean aligned64BitMemoryOperationsAtomic = OS.is64Bit(); diff --git a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java index 78d93b377..d385f8cc6 100755 --- a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java @@ -16,6 +16,9 @@ package net.openhft.chronicle.map; +import impl.CompiledMapIterationContext; +import impl.CompiledMapQueryContext; +import net.openhft.chronicle.algo.bitset.ReusableBitSet; import net.openhft.chronicle.algo.hashing.LongHashFunction; import net.openhft.chronicle.bytes.Bytes; import net.openhft.chronicle.bytes.BytesStore; @@ -34,7 +37,9 @@ import net.openhft.chronicle.hash.serialization.DataAccess; import net.openhft.chronicle.hash.serialization.SizeMarshaller; import net.openhft.chronicle.hash.serialization.SizedReader; import net.openhft.chronicle.hash.serialization.impl.SerializationBuilder; -import net.openhft.chronicle.map.impl.*; +import net.openhft.chronicle.map.impl.IterationContext; +import net.openhft.chronicle.map.impl.NullReturnValue; +import net.openhft.chronicle.map.impl.QueryContextInterface; import net.openhft.chronicle.map.impl.ret.InstanceReturnValue; import net.openhft.chronicle.set.ChronicleSet; import net.openhft.chronicle.wire.WireIn; @@ -51,11 +56,14 @@ import java.util.function.Function; import static net.openhft.chronicle.map.ChronicleMapBuilder.greatestCommonDivisor; +@SuppressWarnings("JavadocReference") public class VanillaChronicleMap extends VanillaChronicleHash, MapSegmentContext, ExternalMapQueryContext> implements AbstractChronicleMap { + private final double maxBloatFactor; + public SizeMarshaller valueSizeMarshaller; public SizedReader valueReader; public DataAccess valueDataAccess; @@ -98,6 +106,7 @@ public class VanillaChronicleMap valueSizeMarshaller = valueBuilder.sizeMarshaller(); valueReader = valueBuilder.reader(); valueDataAccess = valueBuilder.dataAccess(); + maxBloatFactor = builder.maxBloatFactor; constantlySizedEntry = builder.constantlySizedEntries(); @@ -226,6 +235,51 @@ public class VanillaChronicleMap return valueClass; } + private long tiersUsed() { + return globalMutableState().getExtraTiersInUse(); + } + + private long maxTiers() { + return (long) (maxBloatFactor * actualSegments); + } + + @Override + public int remainingAutoResizes() { + return (int) (maxTiers() - tiersUsed()); + } + + @Override + public short percentageFreeSpace() { + + double totalUsed = 0; + double totalSize = 0; + + try (IterationContext c = iterationContext()) { + for (int segmentIndex = 0; segmentIndex < segments(); segmentIndex++) { + c.initSegmentIndex(segmentIndex); + + if (!(c instanceof CompiledMapIterationContext)) + continue; + CompiledMapIterationContext c1 = (CompiledMapIterationContext) c; + + c1.goToFirstTier(); + ReusableBitSet freeList = c1.freeList(); + totalUsed += freeList.cardinality(); + totalSize += freeList.logicalSize(); + + while (c1.hasNextTier()) { + c1.nextTier(); + ReusableBitSet freeList0 = c1.freeList(); + totalUsed += freeList0.cardinality(); + totalSize += freeList.logicalSize(); + } + + } + } + + return (short) (100 - (int) (100 * totalUsed / totalSize)); + } + @NotNull @Override public final Closeable acquireContext(K key, V usingValue) { diff --git a/src/test/java/net/openhft/chronicle/map/jsr166/map/ChronicleMapTest.java b/src/test/java/net/openhft/chronicle/map/jsr166/map/ChronicleMapTest.java index 02a71c560..8ce65c4ba 100644 --- a/src/test/java/net/openhft/chronicle/map/jsr166/map/ChronicleMapTest.java +++ b/src/test/java/net/openhft/chronicle/map/jsr166/map/ChronicleMapTest.java @@ -19,6 +19,7 @@ package net.openhft.chronicle.map.jsr166.map; import net.openhft.chronicle.map.ChronicleMap; import net.openhft.chronicle.map.ChronicleMapBuilder; import net.openhft.chronicle.map.jsr166.JSR166TestCase; +import org.junit.Assert; import org.junit.Test; import java.io.IOException; @@ -670,40 +671,37 @@ public class ChronicleMapTest extends JSR166TestCase { } } - /** - * A deserialized map equals original - */ - /* @Test public void testSerialization() { - Map x = map5(); - Map y = serialClone(x); + @Test + public void testPercentageComplete() { - assertNotSame(x, y); - assertEquals(x.size(), y.size()); - assertEquals(x, y); - assertEquals(y, x); - }*/ + try (ChronicleMap map = ChronicleMap + .of(Integer.class, Integer.class) + .entries(1600) + .actualSegments(3) + .maxBloatFactor(2) + .create()) { + + long remainingAutoResizes = 0; + short percentageFreeSpace = 0; + try { + for (int i = 0; ; i++) { + + map.put(i, 0); + remainingAutoResizes = map.remainingAutoResizes(); + percentageFreeSpace = map.percentageFreeSpace(); + + } + } catch (IllegalStateException e) { + if (e.getMessage().contains("Attempt to allocate")) { + Assert.assertEquals(0, (int) remainingAutoResizes); + Assert.assertTrue(percentageFreeSpace < 6); + return; + } + Assert.fail(); + } - /** - * TODO : SetValue of an EntrySet entry sets value in the map. - */ - /*@Test public void testSetValueWriteThrough() { - // Adapted from a bug report by Eric Zoerner - ChronicleMap map = newShmIntString(2, 5.0f, 1); - assertTrue(map.isEmpty()); - for (int i = 0; i < 20; i++) - map.put(new Integer(i), new Integer(i)); - assertFalse(map.isEmpty()); - Map.Entry entry1 = (Map.Entry) map.entrySet().iterator().next(); - // Unless it happens to be first (in which case remainder of - // test is skipped), remove a possibly-colliding key from map - // which, under some implementations, may cause entry1 to be - // cloned in map - if (!entry1.getKey().equals(new Integer(16))) { - map.remove(new Integer(16)); - entry1.setValue("XYZ"); - assertTrue(map.containsValue("XYZ")); // fails if write-through broken } - }*/ + } }