diff --git a/README.md b/README.md index a7414bd17..6e51e9db1 100644 --- a/README.md +++ b/README.md @@ -256,6 +256,7 @@ interface PostalCodeRange { ChronicleMapBuilder cityPostalCodesMapBuilder = ChronicleMapBuilder.of(CharSequence.class, PostalCodeRange.class) + .name("city-postal-codes-map") .averageKey("Amsterdam") .entries(50_000); ChronicleMap cityPostalCodes = @@ -265,6 +266,7 @@ ChronicleMap cityPostalCodes = ChronicleMap cityPostalCodes = ChronicleMap .of(CharSequence.class, PostalCodeRange.class) + .name("city-postal-codes-map") .averageKey("Amsterdam") .entries(50_000) .create(); @@ -367,12 +369,14 @@ Example: ```java ChronicleMap cityPostalCodes = ChronicleMap .of(CharSequence.class, PostalCodeRange.class) + .name("city-postal-codes-map") .averageKey("Amsterdam") .entries(50_000) .recoverPersistedTo(cityPostalCodesFile, true); // or ChronicleMap cityPostalCodes = ChronicleMap .of(CharSequence.class, PostalCodeRange.class) + .name("city-postal-codes-map") // assuming ChronicleMapBuilder configurations at the moment of // cityPostalCodes Chronicle Map creation are not known .recoverPersistedTo(cityPostalCodesFile, false); @@ -468,6 +472,7 @@ friends is 150. Configure the `ChronicleMap` as follows: ```java Map socialGraph = ChronicleMap .of(Long.class, long[].class) + .name("social-graph-map") .entries(1_000_000_000L) .averageValue(new long[150]) .create(); @@ -482,6 +487,7 @@ your Chronicle Map domain, you should prefer to configure `constantKeySizeBySamp ```java ChronicleSet uuids = ChronicleSet.of(UUID.class) + .name("uuids") // All UUIDs take 16 bytes. .constantKeySizeBySample(UUID.randomUUID()) .entries(1_000_000) @@ -1369,6 +1375,7 @@ key: ```java ChronicleMap orders = ChronicleMap .of(LongValue.class, Order.class) + .name("orders-map") .entries(1_000_000) .create(); @@ -1613,6 +1620,7 @@ for (int i = 0; i < AVERAGE_CONNECTIVITY; i++) { } ChronicleMap> graph = ChronicleMapBuilder .of(Integer.class, (Class>) (Class) Set.class) + .name("graph") .entries(100) .averageValue(averageValue) .create(); @@ -1929,6 +1937,7 @@ Usage: BiMapEntryOperations biMapOps1 = new BiMapEntryOperations<>(); ChronicleMap map1 = ChronicleMapBuilder .of(Integer.class, CharSequence.class) + .name("direct-bimap") .entries(100) .actualSegments(1) .averageValueSize(10) @@ -1939,6 +1948,7 @@ ChronicleMap map1 = ChronicleMapBuilder BiMapEntryOperations biMapOps2 = new BiMapEntryOperations<>(); ChronicleMap map2 = ChronicleMapBuilder .of(CharSequence.class, Integer.class) + .name("reverse-bimap") .entries(100) .actualSegments(1) .averageKeySize(10) diff --git a/src/main/java/net/openhft/chronicle/hash/ChronicleHash.java b/src/main/java/net/openhft/chronicle/hash/ChronicleHash.java index 23e82cd0f..551527a2f 100644 --- a/src/main/java/net/openhft/chronicle/hash/ChronicleHash.java +++ b/src/main/java/net/openhft/chronicle/hash/ChronicleHash.java @@ -26,6 +26,8 @@ import org.jetbrains.annotations.NotNull; import java.io.Closeable; import java.io.File; +import java.util.Map; +import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; @@ -45,6 +47,34 @@ public interface ChronicleHash, SC extends HashSegment */ File file(); + /** + * Returns the name of this {@code ChronicleHash}, configured by {@link + * ChronicleHashBuilder#name(String)}, or {@code null}, if not configured. + * + * @return the name of this this {@link ChronicleMap} or {@link ChronicleSet} + */ + String name(); + + /** + * Returns a {@code String}, useful for identifying this {@code ChronicleHash} in debugging, + * logging, and error reporting. {@link #toString()} of concrete {@code ChronicleHash} + * subinterfaces, {@link ChronicleMap} and {@link ChronicleSet}, has to follow {@link + * Map#toString()} and {@link Set#toString()} contracts respectively, making it not always + * useful (or even impossible to use, if this {@code ChronicleHash} contains a lot of entries) + * for the purposes listed above. + * + *

This method return a string of the form:

+ * [ChronicleMap|ChronicleSet]{name={@link #name()}, file={@link #file()}, + * identityHashCode={@link System#identityHashCode System.identityHashCode(thisChronicleHash)}} + *

+ * This form could be changed in any subsequent Chronicle Map library release (including patch + * release). The user code shouldn't depend on this form. + * + * @return a {@code String}, useful for identifying this {@code ChronicleHash} in debugging, + * logging, and error reporting + */ + String toIdentityString(); + /** * Returns the number of entries in this store. * diff --git a/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java b/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java index 5c0b44136..f578fc1d8 100755 --- a/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java +++ b/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java @@ -68,12 +68,32 @@ public interface ChronicleHashBuilder, */ B clone(); + /** + * Specify the name which will be given to a ChronicleHash, created by this builder. It is + * recommended to give some distinctive name to each created {@code ChronicleHash}, because this + * name is used when logging errors and warnings inside Chronicle Map library itself, so having + * the concrete {@code ChronicleHash} name in logs may help to debug. + * + *

{@code name()} is a JVM-level configuration, it is not stored in the persistence file (or + * the other way to say this: they are not parts of the Chronicle Map data store + * specification) and have to be configured explicitly for each created on-heap {@code + * ChronicleHash} instance, even if it is a view of an existing Chronicle Map data store. On the + * other hand, {@code name()} could be different for different views of the same Chronicle + * Map data store. + * + * @param name the name for a ChronicleHash, created by this builder + * @return this builder back + * @see ChronicleHash#name() + */ + B name(String name); + /** * Set minimum number of segments in hash containers, constructed by this builder. See * concurrencyLevel in {@link ConcurrentHashMap}. * * @param minSegments the minimum number of segments in containers, constructed by this builder - * @return this builder object back + * @return this builder back */ B minSegments(int minSegments); diff --git a/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilderPrivateAPI.java b/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilderPrivateAPI.java index 8049c92ce..3174388e1 100644 --- a/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilderPrivateAPI.java +++ b/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilderPrivateAPI.java @@ -24,6 +24,8 @@ import java.util.concurrent.TimeUnit; public interface ChronicleHashBuilderPrivateAPI { + String name(); + SerializationBuilder keyBuilder(); int segmentEntrySpaceInnerOffset(); diff --git a/src/main/java/net/openhft/chronicle/hash/impl/VanillaChronicleHash.java b/src/main/java/net/openhft/chronicle/hash/impl/VanillaChronicleHash.java index deccf7361..85ce9d1be 100644 --- a/src/main/java/net/openhft/chronicle/hash/impl/VanillaChronicleHash.java +++ b/src/main/java/net/openhft/chronicle/hash/impl/VanillaChronicleHash.java @@ -22,7 +22,6 @@ import net.openhft.chronicle.bytes.BytesStore; import net.openhft.chronicle.bytes.MappedBytesStoreFactory; import net.openhft.chronicle.bytes.NativeBytesStore; import net.openhft.chronicle.core.Maths; -import net.openhft.chronicle.core.Memory; import net.openhft.chronicle.core.OS; import net.openhft.chronicle.hash.*; import net.openhft.chronicle.hash.impl.stage.hash.ChainingInterface; @@ -73,7 +72,6 @@ public abstract class VanillaChronicleHash keyDataAccess; ///////////////////////////////////////////////// - // Checksum entries public boolean checksumEntries; ///////////////////////////////////////////////// diff --git a/src/main/java/net/openhft/chronicle/hash/impl/stage/iter/TierRecovery.java b/src/main/java/net/openhft/chronicle/hash/impl/stage/iter/TierRecovery.java index d6d6eb84a..4347b0acc 100644 --- a/src/main/java/net/openhft/chronicle/hash/impl/stage/iter/TierRecovery.java +++ b/src/main/java/net/openhft/chronicle/hash/impl/stage/iter/TierRecovery.java @@ -90,7 +90,7 @@ public class TierRecovery { // the slot cleared at (*) should be clear, if it is dirty, only // a concurrent modification thread could occupy it throw new ChronicleHashRecoveryFailedException( - "Concurrent modification of ChronicleMap at " + h.file() + + "Concurrent modification of " + h.toIdentityString() + " while recovery procedure is in progress"); } checkDuplicateKeys: @@ -124,8 +124,8 @@ public class TierRecovery { } while (insertPos != startInsertPos); throw new ChronicleHashRecoveryFailedException( "HashLookup overflow should never occur. " + - "It might also be concurrent access to ChronicleMap at " + - h.file() + " while recovery procedure is in progress"); + "It might also be concurrent access to " + h.toIdentityString() + + " while recovery procedure is in progress"); } hlPos = hl.step(hlPos); } while (hlPos != 0); diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java index 8359665ce..e6104e052 100755 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java @@ -105,6 +105,23 @@ import static net.openhft.chronicle.map.VanillaChronicleMap.alignAddr; * #constantKeySizeBySample(Object)}, otherwise {@link #averageKey(Object)} or {@link * #averageKeySize(double)} method, and accordingly for values. * + *

+ * There are some JVM-level configurations, which are not stored in the ChronicleMap's persistence + * file (or the other way to say this: they are not parts of the Chronicle Map data store + * specification) and have to be configured explicitly for each created on-heap {@code + * ChronicleMap} instance, even if it is a view of an existing Chronicle Map data store. On the + * other hand, JVM-level configurations could be different for different views of the same Chronicle + * Map data store. The list of JVM-level configurations: + *

    + *
  • {@link #name(String)}
  • + *
  • {@link #putReturnsNull(boolean)}
  • + *
  • {@link #removeReturnsNull(boolean)}
  • + *
  • {@link #entryOperations(MapEntryOperations)}
  • + *
  • {@link #mapMethods(MapMethods)}
  • + *
  • {@link #defaultValueProvider(DefaultValueProvider)}
  • + *
+ * * @param key type of the maps, produced by this builder * @param value type of the maps, produced by this builder * @see ChronicleHashBuilder @@ -133,6 +150,9 @@ public final class ChronicleMapBuilder implements private static final ConcurrentHashMap fileLockingControl = new ConcurrentHashMap<>(128); private static int MAX_BOOTSTRAPPING_HEADER_SIZE = (int) MemoryUnit.KILOBYTES.toBytes(16); + + + private String name; SerializationBuilder keyBuilder; SerializationBuilder valueBuilder; K averageKey; @@ -413,6 +433,20 @@ public final class ChronicleMapBuilder implements return privateAPI; } + /** + * {@inheritDoc} + * Read more about JVM-level configurations. + */ + @Override + public ChronicleMapBuilder name(String name) { + this.name = name; + return this; + } + + String name() { + return this.name; + } + /** * {@inheritDoc} * @@ -812,7 +846,7 @@ public final class ChronicleMapBuilder implements * explicitly for being sure and to compare performance in your case). * * @param alignment the new alignment of the maps constructed by this builder - * @return this {@code ChronicleMapOnHeapUpdatableBuilder} back + * @return this builder back * @throws IllegalStateException if values of maps, created by this builder, couldn't reference * off-heap memory */ @@ -1112,8 +1146,10 @@ public final class ChronicleMapBuilder implements * the data from off-heap memory. A collection hiding remote queries over the network should * send the value back in addition to that. It's expensive for something you probably don't use. * - *

By default, of cause, {@code ChronicleMap} conforms the general {@code Map} contract and - * returns the previous mapped value on {@code put()} calls. + *

This is a JVM-level configuration. + * + *

By default, {@code ChronicleMap} conforms the general {@code Map} contract and returns the + * previous mapped value on {@code put()} calls. * * @param putReturnsNull {@code true} if you want {@link ChronicleMap#put(Object, Object) * ChronicleMap.put()} to not return the value that was replaced but @@ -1141,8 +1177,10 @@ public final class ChronicleMapBuilder implements * from off-heap memory. A collection hiding remote queries over the network should send * the value back in addition to that. It's expensive for something you probably don't use. * - *

By default, of cause, {@code ChronicleMap} conforms the general {@code Map} contract and - * returns the mapped value on {@code remove()} calls. + *

This is a JVM-level configuration. + * + *

By default, {@code ChronicleMap} conforms the general {@code Map} contract and returns the + * mapped value on {@code remove()} calls. * * @param removeReturnsNull {@code true} if you want {@link ChronicleMap#remove(Object) * ChronicleMap.remove()} to not return the value of the removed entry @@ -1408,8 +1446,10 @@ public final class ChronicleMapBuilder implements * Specifies the function to obtain a value for the key during {@link ChronicleMap#acquireUsing * acquireUsing()} calls, if the key is absent in the map, created by this builder. * + *

This is a JVM-level configuration. + * * @param defaultValueProvider the strategy to obtain a default value by the absent key - * @return this builder object back + * @return this builder back */ public ChronicleMapBuilder defaultValueProvider( @NotNull DefaultValueProvider defaultValueProvider) { @@ -1771,6 +1811,10 @@ public final class ChronicleMapBuilder implements *

This affects behaviour of ordinary map.put(), map.remove(), etc. calls, as well as removes * and replacing values during iterations, remote map calls and * internal replication operations. + * + *

This is a JVM-level configuration. + * + * @return this builder back */ public ChronicleMapBuilder entryOperations(MapEntryOperations entryOperations) { Objects.requireNonNull(entryOperations); @@ -1784,6 +1828,10 @@ public final class ChronicleMapBuilder implements * {@link ChronicleMap#merge}. * *

This affects behaviour of ordinary map calls, as well as remote calls. + * + *

This is a JVM-level configuration. + * + * @return this builder back */ public ChronicleMapBuilder mapMethods(MapMethods mapMethods) { Objects.requireNonNull(mapMethods); diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilderPrivateAPI.java b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilderPrivateAPI.java index aada7b68c..e03fabd7c 100644 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilderPrivateAPI.java +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilderPrivateAPI.java @@ -32,6 +32,11 @@ class ChronicleMapBuilderPrivateAPI this.b = b; } + @Override + public String name() { + return b.name(); + } + @Override public SerializationBuilder keyBuilder() { return b.keyBuilder(); diff --git a/src/main/java/net/openhft/chronicle/map/OldDeletedEntriesCleanupThread.java b/src/main/java/net/openhft/chronicle/map/OldDeletedEntriesCleanupThread.java index 5796c2f0e..71b49e1e6 100644 --- a/src/main/java/net/openhft/chronicle/map/OldDeletedEntriesCleanupThread.java +++ b/src/main/java/net/openhft/chronicle/map/OldDeletedEntriesCleanupThread.java @@ -75,7 +75,7 @@ class OldDeletedEntriesCleanupThread extends Thread private long removedCompletely; OldDeletedEntriesCleanupThread(ReplicatedChronicleMap map) { - super("Cleanup Thread for ReplicatedChronicleMap persisted to " + map.file()); + super("Cleanup Thread for " + map.toIdentityString()); this.mapRef = new WeakReference<>(map); cleanupTimeout = map.cleanupTimeout; cleanupTimeoutUnit = map.cleanupTimeoutUnit; diff --git a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java index 002e23f71..c87410fcb 100755 --- a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java @@ -66,6 +66,9 @@ public class VanillaChronicleMap public int alignment; public int worstAlignment; + ///////////////////////////////////////////////// + private transient String name; + public transient boolean couldNotDetermineAlignmentBeforeAllocation; ///////////////////////////////////////////////// @@ -134,6 +137,7 @@ public class VanillaChronicleMap } void initTransientsFromBuilder(ChronicleMapBuilder builder) { + name = builder.name(); putReturnsNull = builder.putReturnsNull(); removeReturnsNull = builder.removeReturnsNull(); @@ -239,6 +243,22 @@ public class VanillaChronicleMap public String toString() { return mapToString(); } + + @Override + public String name() { + return name; + } + + @Override + public String toIdentityString() { + if (chronicleSet != null) + return chronicleSet.toIdentityString(); + return "ChronicleMap{" + + "name=" + name() + + ", file=" + file() + + ", identityHashCode=" + System.identityHashCode(this) + + "}"; + } @Override public void clear() { diff --git a/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java b/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java index bd9d23b4e..6d90efe26 100755 --- a/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java +++ b/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java @@ -81,6 +81,12 @@ public final class ChronicleSetBuilder } } + @Override + public ChronicleSetBuilder name(String name) { + chronicleMapBuilder.name(name); + return this; + } + @Override public ChronicleSetBuilder actualSegments(int actualSegments) { chronicleMapBuilder.actualSegments(actualSegments); diff --git a/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilderPrivateAPI.java b/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilderPrivateAPI.java index a6db0b9ad..c168baa62 100644 --- a/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilderPrivateAPI.java +++ b/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilderPrivateAPI.java @@ -37,6 +37,11 @@ class ChronicleSetBuilderPrivateAPI this.mapB = mapB; } + @Override + public String name() { + return mapB.name(); + } + @Override public SerializationBuilder keyBuilder() { return mapB.keyBuilder(); diff --git a/src/main/java/net/openhft/chronicle/set/SetFromMap.java b/src/main/java/net/openhft/chronicle/set/SetFromMap.java index 705527c26..dba35f2ea 100644 --- a/src/main/java/net/openhft/chronicle/set/SetFromMap.java +++ b/src/main/java/net/openhft/chronicle/set/SetFromMap.java @@ -90,6 +90,15 @@ class SetFromMap extends AbstractSet implements ChronicleSet { return s.toString(); } + @Override + public String toIdentityString() { + return "ChronicleSet{" + + "name=" + name() + + ", file=" + file() + + ", identityHashCode=" + System.identityHashCode(this) + + "}"; + } + public int hashCode() { return s.hashCode(); } @@ -179,6 +188,11 @@ class SetFromMap extends AbstractSet implements ChronicleSet { return m.file(); } + @Override + public String name() { + return m.name(); + } + @Override public void close() { m.close(); diff --git a/src/test/java/net/openhft/chronicle/map/ChronicleMapNameTest.java b/src/test/java/net/openhft/chronicle/map/ChronicleMapNameTest.java new file mode 100644 index 000000000..b39f7b01b --- /dev/null +++ b/src/test/java/net/openhft/chronicle/map/ChronicleMapNameTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2016 Roman Leventov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.openhft.chronicle.map; + +import net.openhft.chronicle.set.ChronicleSet; +import org.junit.Assert; +import org.junit.Test; + +public class ChronicleMapNameTest { + + @Test + public void testChronicleMapName() { + ChronicleMap map = ChronicleMap + .of(Integer.class, Integer.class) + .entries(1) + .name("foo") + .create(); + Assert.assertTrue(map.toIdentityString().contains("foo")); + } + + @Test + public void testChronicleSetName() { + ChronicleSet set = ChronicleSet + .of(Integer.class) + .entries(1) + .name("foo") + .create(); + Assert.assertTrue(set.toIdentityString().contains("foo")); + } +} diff --git a/src/test/java/net/openhft/chronicle/map/ReplicationCheckingMap.java b/src/test/java/net/openhft/chronicle/map/ReplicationCheckingMap.java index c8a9a285f..0099390e2 100644 --- a/src/test/java/net/openhft/chronicle/map/ReplicationCheckingMap.java +++ b/src/test/java/net/openhft/chronicle/map/ReplicationCheckingMap.java @@ -305,6 +305,16 @@ public class ReplicationCheckingMap implements ChronicleMap { throw new UnsupportedOperationException(); } + @Override + public String name() { + throw new UnsupportedOperationException(); + } + + @Override + public String toIdentityString() { + throw new UnsupportedOperationException(); + } + @Override public void close() { map1.close();