From c37e256cb78223a19d8e1317453f7cead914a37b Mon Sep 17 00:00:00 2001 From: Rob Austin Date: Mon, 27 Oct 2014 16:20:16 +0000 Subject: [PATCH 01/15] HCOLL-167 updated docs --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 72210ec3f..084ed755b 100644 --- a/README.md +++ b/README.md @@ -1134,6 +1134,22 @@ and just like map it support shared memory and TCP replication. # Performance Topics + +There are general principles we can give direction on - for specific advise we believe consulting + to be the most productive solution. + +We want the Map to be reasonably general purpose, so in broad terms we can say +- the key and values have to be self contained, ideally trees, rather than graphs. +- ideally values are similar lengths, however we support varying lengths. +- ideally you want to use primitives and use object recycling for performance, though this is not a requirement. +- ideally you have some idea as to the maximum number of entries, though it is not too important if the maximum entries is above what you need. +- if for example you are working with, market depth, this can be supported via an array of nested +types. +- we support code generation of efficient custom serializes - See the examples where you provide +an interface as the data type, the map will generate the implementation. + + + ### Tuning Chronicle Map with Large Data Generally speaking `ChronicleMap` is slower then ConcurrentHashMap for a small number of entries, but From b374a9bd50344af19edeace356d56437cc85a1bd Mon Sep 17 00:00:00 2001 From: Rob Austin Date: Mon, 27 Oct 2014 17:09:36 +0000 Subject: [PATCH 02/15] HCOLL-167 entrySet works for small chunks --- .../chronicle/map/StatelessChronicleMap.java | 35 ++-- .../openhft/chronicle/map/TcpReplicator.java | 65 +++++++- .../chronicle/map/StatelessClientTest.java | 3 - .../chronicle/map/example/ChannelExample.java | 157 ++++++++++++++++++ 4 files changed, 237 insertions(+), 23 deletions(-) create mode 100644 src/test/java/net/openhft/chronicle/map/example/ChannelExample.java diff --git a/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java b/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java index 63409557b..50e2beaae 100644 --- a/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java @@ -419,6 +419,7 @@ class StatelessChronicleMap implements ChronicleMap, Closeable { private void writeEntries(Map map) { + final int numberOfEntries = map.size(); int numberOfEntriesReadSoFar = 0; @@ -605,20 +606,25 @@ class StatelessChronicleMap implements ChronicleMap, Closeable { long sizeLocation = writeEvent(ENTRY_SET); // get the data back from the server - Bytes in = blockingFetch(sizeLocation); + final Bytes in = blockingFetch(sizeLocation); + final Set> result = new HashSet>(); - long size = in.readStopBit(); - LOG.info("size="+size); - Set> result = new HashSet>(); + boolean hasMoreEntries; + do { + hasMoreEntries = in.readBoolean(); - for (int i = 0; i < size; i++) { - K k = keyValueSerializer.readKey(in); - V v = keyValueSerializer.readValue(in); - LOG.info("k="+k); + // number of entries in the chunk + long size = in.readUnsignedShort(); - LOG.info("v="+v); - result.add(new Entry(k, v)); - } + LOG.info("size=" + size); + + for (int i = 0; i < size; i++) { + K k = keyValueSerializer.readKey(in); + V v = keyValueSerializer.readValue(in); + result.add(new Entry(k, v)); + } + + } while (hasMoreEntries); return result; } @@ -730,9 +736,12 @@ class StatelessChronicleMap implements ChronicleMap, Closeable { // the number of bytes in the response int size = receive(SIZE_OF_SIZE, timeoutTime).readInt(); - if (bytes.capacity() < size) - bytes = new ByteBufferBytes(allocateDirect(size)); + int requiredSize = size + SIZE_OF_SIZE; + if (bytes.capacity() < requiredSize) { + bytes = new ByteBufferBytes(allocateDirect(requiredSize)); + } + // block until we have received all the byte in this chunk receive(size, timeoutTime); boolean isException = bytes.readBoolean(); diff --git a/src/main/java/net/openhft/chronicle/map/TcpReplicator.java b/src/main/java/net/openhft/chronicle/map/TcpReplicator.java index 786d971e8..43efdeb39 100644 --- a/src/main/java/net/openhft/chronicle/map/TcpReplicator.java +++ b/src/main/java/net/openhft/chronicle/map/TcpReplicator.java @@ -548,9 +548,9 @@ class TcpReplicator extends AbstractChannelReplicator implements Closeable { if (completed) attached.entryWriter.workCompleted(); - // attached.hasRemoteHeartbeatInterval = false; + // attached.hasRemoteHeartbeatInterval = false; - } else if (attached.remoteModificationIterator != null) + } else if (attached.remoteModificationIterator != null) attached.entryWriter.entriesToBuffer(attached.remoteModificationIterator, key); try { @@ -1646,15 +1646,18 @@ class StatelessServerConnector { private Work entrySet(Bytes reader, Bytes writer) { - final long sizeLocation = reflectTransactionId(reader, writer); + final long transactionId = reader.readLong(); + + final Set> entries; try { entries = map.entrySet(); } catch (RuntimeException e) { + final long sizeLocation = reflectTransactionId(reader, writer); return sendException(writer, sizeLocation, e); } - writer.writeStopBit(entries.size()); + final Iterator> iterator = entries.iterator(); @@ -1664,23 +1667,71 @@ class StatelessServerConnector { @Override public boolean doWork(Bytes writer) { + final long sizeLocation = writer.position(); + + writer.skip(AbstractChannelReplicator.SIZE_OF_SIZE + 1); // SIZE_OF_SIZE + is + // exception + writer.writeLong(transactionId); + + // hasAnotherChunk + writer.skip(1); + + // count + writer.skip(2); + + int count = 0; while (iterator.hasNext()) { // we've filled up the buffer, so lets give another channel a chance to send // some data, we don't know the max key size, we will use the entrySize instead - if (writer.remaining() <= maxEntrySizeBytes) + if (writer.remaining() <= maxEntrySizeBytes) { + writeHeader(writer, sizeLocation, count, true); + return false; + } + + count++; final Map.Entry next = iterator.next(); writeKey(next.getKey(), writer); writeValue(next.getValue(), writer); } - writeSizeAndFlags(sizeLocation, false, writer); + writeHeader(writer, sizeLocation, count, false); + return true; } + + private void writeHeader(Bytes writer, long sizeLocation, int count, final boolean hasAnotherChunk) { + final long end = writer.position(); + final int size = (int) (end - sizeLocation); + writer.position(sizeLocation); + + // size in bytes + writer.writeInt(size); + + // is exception + writer.writeBoolean(false); + + //transaction id; + writer.skip(8); + + writer.writeBoolean(hasAnotherChunk); + + // count + writer.writeUnsignedShort(count); + writer.position(end); + } }; } + private void writeMoreData(long location, boolean hasMoreData, Bytes out) { + out.writeBoolean(location, hasMoreData); + } + + private void entryCount(long location, int count, Bytes out) { + + } + private Work putIfAbsent(Bytes reader, Bytes writer) { final K key = readKey(reader); final V v = readValue(reader); @@ -1726,7 +1777,7 @@ class StatelessServerConnector { private long reflectTransactionId(Bytes reader, Bytes writer) { final long sizeLocation = writer.position(); - writer.skip(1 + AbstractChannelReplicator.SIZE_OF_SIZE); // event size + SIZE_OF_SIZE + writer.skip(AbstractChannelReplicator.SIZE_OF_SIZE + 1); // isException + SIZE_OF_SIZE final long transactionId = reader.readLong(); writer.writeLong(transactionId); return sizeLocation; diff --git a/src/test/java/net/openhft/chronicle/map/StatelessClientTest.java b/src/test/java/net/openhft/chronicle/map/StatelessClientTest.java index 8e90519a8..84a531b40 100644 --- a/src/test/java/net/openhft/chronicle/map/StatelessClientTest.java +++ b/src/test/java/net/openhft/chronicle/map/StatelessClientTest.java @@ -72,10 +72,7 @@ public class StatelessClientTest { Set> entries = statelessMap.entrySet(); - - Map.Entry next = entries.iterator().next(); - Assert.assertEquals("some value=" + next.getKey(), next.getValue()); diff --git a/src/test/java/net/openhft/chronicle/map/example/ChannelExample.java b/src/test/java/net/openhft/chronicle/map/example/ChannelExample.java new file mode 100644 index 000000000..2716fbfc1 --- /dev/null +++ b/src/test/java/net/openhft/chronicle/map/example/ChannelExample.java @@ -0,0 +1,157 @@ +package net.openhft.chronicle.map.example; + +import net.openhft.chronicle.map.*; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import static org.junit.Assert.assertEquals; + +/** + * @author Rob Austin. + */ +public class ChannelExample { + + + + +/* + * Copyright 2014 Higher Frequency Trading + * + * http://www.higherfrequencytrading.com + * + * Licensed 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. + */ + + + /** + * Test ReplicatedChronicleMap where the Replicated is over a TCP Socket, but with 4 nodes + * + * @author Rob Austin. + */ + public class ChannelReplicationTest { + + private ChronicleMap favoriteComputerServer1; + private ChronicleMap favoriteComputerServer2; + + private ChronicleMap favoriteColourServer2; + private ChronicleMap favoriteColourServer1; + + + private ChannelProvider channelProviderServer1; + private ChannelProvider channelProviderServer2; + + + @Test + public void test() throws IOException, InterruptedException { + + // server 1 with identifier = 1 + { + byte identifier = (byte) 1; + + TcpReplicationConfig tcpConfig = TcpReplicationConfig + .of(8086, new InetSocketAddress("localhost", 8087)) + .heartBeatInterval(1, java.util.concurrent.TimeUnit.SECONDS); + + + channelProviderServer1 = new ChannelProviderBuilder() + .replicators(identifier, tcpConfig) + .create(); + + // this demotes favoriteColour + short channel1 = (short) 1; + + favoriteColourServer1 = ChronicleMapBuilder.of(CharSequence.class, CharSequence.class) + .entries(1000) + .channel(channelProviderServer1.createChannel(channel1)).create(); + + favoriteColourServer1.put("peter", "green"); + + // this demotes favoriteComputer + short channel2 = (short) 2; + + favoriteComputerServer1 = ChronicleMapBuilder.of(CharSequence.class, CharSequence.class) + .entries(1000) + .channel(channelProviderServer1.createChannel(channel2)).create(); + + favoriteComputerServer1.put("peter", "dell"); + + } + + // server 2 with identifier = 2 + { + + byte identifier = (byte) 2; + + TcpReplicationConfig tcpConfig = + TcpReplicationConfig.of(8087).heartBeatInterval(1, java.util.concurrent.TimeUnit.SECONDS); + + channelProviderServer2 = new ChannelProviderBuilder() + .replicators(identifier, tcpConfig) + .create(); + + + // this demotes favoriteColour + short channel1 = (short) 1; + + favoriteColourServer2 = ChronicleMapBuilder.of(CharSequence.class, CharSequence.class) + .entries(1000) + .channel(channelProviderServer2.createChannel(channel1)).create(); + + + favoriteColourServer2.put("rob", "blue"); + + // this demotes favoriteComputer + short channel2 = (short) 2; + + favoriteComputerServer2 = ChronicleMapBuilder.of(CharSequence.class, CharSequence.class) + .entries(1000) + .channel(channelProviderServer2.createChannel(channel2)).create(); + + favoriteComputerServer2.put("rob", "mac"); + favoriteComputerServer2.put("daniel", "mac"); + } + + + // allow time for the recompilation to resolve + for (int t = 0; t < 2500; t++) { + if (favoriteComputerServer2.equals(favoriteComputerServer1) && + favoriteColourServer2.equals(favoriteColourServer1)) + break; + Thread.sleep(1); + } + + + assertEquals(favoriteComputerServer1, favoriteComputerServer2); + Assert.assertEquals(3, favoriteComputerServer2.size()); + + + assertEquals(favoriteColourServer1, favoriteColourServer2); + Assert.assertEquals(2, favoriteColourServer1.size()); + + + favoriteColourServer1.close(); + favoriteComputerServer2.close(); + favoriteColourServer2.close(); + favoriteColourServer1.close(); +} + + + } + +} + + + From 0c45ee8aff17543acd4579c71c596c46ad2db4f9 Mon Sep 17 00:00:00 2001 From: leventov Date: Mon, 27 Oct 2014 20:05:26 +0300 Subject: [PATCH 03/15] HCOLL-136 HCOLL-137 HCOLL-153 HCOLL-160 1. Separated ChronicleMapBuilder (for non-Byteable values) and OffHeapUpdatableChronicleMapBuilder (for Byteable values) with common base AbstractChronicleMapBuilder (public just to inherit common javadocs) 2. Added ChronicleHashBuilder.keyDeserializationFactory() and AbstractChronicleMapBuilder.valueDeserializationFactory() (former ChronicleMapBuilder.valueFactory() method) 3. Changed ChronicleMap.get(), getUsing() API and impl to what was initially intended - now they ALWAYS returns null, if key is absent 4. acquireUsing() API, common for the cases with Byteable and non-Byteable values: puts defaultValue or defaultValueProvider.get(key), if the key is absent, then acquireUsing() behaves just like getUsing() 5. For off0heap updatable ChronicleMaps (Byteable values), added prepareValueBytes() configuration which formalizes zeroing out value bytes 6. Javadoc fixes and improvements --- .../common/ChronicleHashBuilder.java | 26 + .../chronicle/common/package-info.java | 26 + .../map/AbstractChronicleMapBuilder.java | 936 ++++++++++++++++++ .../net/openhft/chronicle/map/Alignment.java | 2 +- .../chronicle/map/ChannelProvider.java | 2 +- .../openhft/chronicle/map/ChronicleMap.java | 83 +- .../chronicle/map/ChronicleMapBuilder.java | 919 ++--------------- .../chronicle/map/ConstantValueProvider.java | 2 +- .../chronicle/map/DefaultValueProvider.java | 15 +- .../chronicle/map/MapEventListener.java | 6 +- .../chronicle/map/NullValueProvider.java | 2 +- .../OffHeapUpdatableChronicleMapBuilder.java | 98 ++ .../chronicle/map/PrepareValueBytes.java | 26 + .../map/PrepareValueBytesAsWriter.java | 56 ++ .../chronicle/map/ReplicatedChronicleMap.java | 110 +- .../chronicle/map/SerializationBuilder.java | 11 +- .../chronicle/map/StatelessChronicleMap.java | 4 +- .../openhft/chronicle/map/UdpReplicator.java | 2 +- .../chronicle/map/VanillaChronicleMap.java | 159 +-- .../chronicle/map/ZeroOutValueBytes.java | 34 + .../chronicle/set/ChronicleSetBuilder.java | 21 +- .../openhft/chronicle/set/package-info.java | 21 + .../chronicle/map/AcquireGetUsingMain.java | 2 +- .../chronicle/map/CHMLatencyTestMain.java | 2 +- .../net/openhft/chronicle/map/CHMTest5.java | 4 +- .../chronicle/map/ChronicleMapTest.java | 11 +- .../chronicle/map/EntryCountMapTest.java | 8 +- .../chronicle/map/ReplicationCheckingMap.java | 8 +- .../OpenJDKAndHashMapExamplesTest.java | 9 +- .../jrs166/map/StatelessChronicleMapTest.java | 8 +- 30 files changed, 1529 insertions(+), 1084 deletions(-) create mode 100644 src/main/java/net/openhft/chronicle/common/package-info.java create mode 100644 src/main/java/net/openhft/chronicle/map/AbstractChronicleMapBuilder.java create mode 100644 src/main/java/net/openhft/chronicle/map/OffHeapUpdatableChronicleMapBuilder.java create mode 100644 src/main/java/net/openhft/chronicle/map/PrepareValueBytes.java create mode 100644 src/main/java/net/openhft/chronicle/map/PrepareValueBytesAsWriter.java create mode 100644 src/main/java/net/openhft/chronicle/map/ZeroOutValueBytes.java create mode 100644 src/main/java/net/openhft/chronicle/set/package-info.java diff --git a/src/main/java/net/openhft/chronicle/common/ChronicleHashBuilder.java b/src/main/java/net/openhft/chronicle/common/ChronicleHashBuilder.java index 3036aa52f..6c5661272 100644 --- a/src/main/java/net/openhft/chronicle/common/ChronicleHashBuilder.java +++ b/src/main/java/net/openhft/chronicle/common/ChronicleHashBuilder.java @@ -21,7 +21,10 @@ package net.openhft.chronicle.common; import net.openhft.chronicle.map.*; import net.openhft.chronicle.set.ChronicleSet; import net.openhft.chronicle.set.ChronicleSetBuilder; +import net.openhft.lang.io.Bytes; import net.openhft.lang.io.serialization.*; +import net.openhft.lang.io.serialization.impl.AllocateInstanceObjectFactory; +import net.openhft.lang.io.serialization.impl.NewInstanceObjectFactory; import net.openhft.lang.io.serialization.impl.VanillaBytesMarshallerFactory; import net.openhft.lang.model.Byteable; import org.jetbrains.annotations.NotNull; @@ -345,6 +348,29 @@ public interface ChronicleHashBuilder keyMarshaller); + /** + * Configures factory which is used to create a new key instance, if key class is either + * {@link Byteable}, {@link BytesMarshallable} or {@link Externalizable} subclass in maps, + * created by this builder. If {@linkplain #keyMarshaller(BytesMarshaller) custom key + * marshaller} is configured, this configuration is unused, because it is incapsulated in + * {@link BytesMarshaller#read(Bytes)} method (without provided instance to read the data into), + * i. e. it's is the user-side responsibility. + * + *

Default key deserialization factory is {@link NewInstanceObjectFactory}, which creates + * a new key instance using {@link Class#newInstance()} default constructor. You could provide + * an {@link AllocateInstanceObjectFactory}, which uses {@code Unsafe.allocateInstance(Class)} + * (you might want to do this for better performance or if you don't want to initialize fields), + * or a factory which calls a key class constructor with some arguments, or a factory which + * internally delegates to instance pool or {@link ThreadLocal}, to reduce allocations. + * + * @param keyDeserializationFactory the key factory used to produce instances to deserialize + * data in + * @return this builder back + * @throws IllegalStateException if custom key marshaller is specified or key class is not + * either {@code Byteable}, {@code BytesMarshallable} or {@code Externalizable} + */ + B keyDeserializationFactory(@NotNull ObjectFactory keyDeserializationFactory); + /** * Specifies that key objects, queried with the hash containers, created by this builder, are * inherently immutable. Keys in {@link ChronicleMap} or {@link ChronicleSet} are not required diff --git a/src/main/java/net/openhft/chronicle/common/package-info.java b/src/main/java/net/openhft/chronicle/common/package-info.java new file mode 100644 index 000000000..68497fea7 --- /dev/null +++ b/src/main/java/net/openhft/chronicle/common/package-info.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com + * + * Licensed 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. + */ + +/** + * Contains common interfaces and utilities for {@link net.openhft.chronicle.map.ChronicleMap + * ChronicleMaps} (net.openhft.chronicle.map package) and + * {@link net.openhft.chronicle.set.ChronicleSet ChronicleSets} + * (net.openhft.chronicle.set package). This package should be the root of + * {@code net.openhft.chronicle.map} and {@code net.openhft.chronicle.set} packages, moved to + * the same level because {@code net.openhft.chronicle} package is occupied by Chronicle Queue project. + */ +package net.openhft.chronicle.common; \ No newline at end of file diff --git a/src/main/java/net/openhft/chronicle/map/AbstractChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/AbstractChronicleMapBuilder.java new file mode 100644 index 000000000..31b5d466c --- /dev/null +++ b/src/main/java/net/openhft/chronicle/map/AbstractChronicleMapBuilder.java @@ -0,0 +1,936 @@ +/* + * Copyright 2014 Higher Frequency Trading + * + * http://www.higherfrequencytrading.com + * + * Licensed 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 net.openhft.chronicle.map; + +import net.openhft.chronicle.common.*; +import net.openhft.chronicle.common.serialization.MetaBytesInterop; +import net.openhft.chronicle.common.serialization.MetaBytesWriter; +import net.openhft.chronicle.common.serialization.MetaProvider; +import net.openhft.chronicle.common.threadlocal.Provider; +import net.openhft.chronicle.common.threadlocal.ThreadLocalCopies; +import net.openhft.chronicle.set.ChronicleSetBuilder; +import net.openhft.lang.Maths; +import net.openhft.lang.io.ByteBufferBytes; +import net.openhft.lang.io.Bytes; +import net.openhft.lang.io.BytesStore; +import net.openhft.lang.io.DirectStore; +import net.openhft.lang.io.serialization.*; +import net.openhft.lang.io.serialization.impl.AllocateInstanceObjectFactory; +import net.openhft.lang.io.serialization.impl.NewInstanceObjectFactory; +import net.openhft.lang.io.serialization.impl.VanillaBytesMarshallerFactory; +import net.openhft.lang.model.Byteable; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static net.openhft.chronicle.map.Objects.builderEquals; + +/** + * {@code AbstractChronicleMapBuilder} manages most of {@link ChronicleMap} configurations; has two + * concrete subclasses: {@link ChronicleMapBuilder} should be used to create maps with ordinary + * values, {@link OffHeapUpdatableChronicleMapBuilder} -- maps with {@link Byteable} values, which + * point directly to off-heap memory; could be used as a classic builder and/or factory. This means + * that in addition to the standard builder usage pattern:

{@code
+ * ChronicleMap map = ChronicleMapBuilder
+ *     .of(Key.class, Value.class)
+ *     .entries(100500)
+ *     // ... other configurations
+ *     .create();}
+ * one of concrete {@code AbstractChronicleMapBuilder} subclasses, {@link ChronicleMapBuilder} or + * {@link OffHeapUpdatableChronicleMapBuilder}, could be prepared and used to create many similar + * maps:
{@code
+ * OffHeapUpdatableChronicleMapBuilder builder = OffHeapUpdatableChronicleMapBuilder
+ *     .of(Key.class, Value.class)
+ *     .entries(100500);
+ *
+ * ChronicleMap map1 = builder.create();
+ * ChronicleMap map2 = builder.create();}
+ * i. e. created {@code ChronicleMap} instances don't depend on the builder. + * + *

{@code AbstractChronicleMapBuilder} and it's subclasses are mutable, see a note in + * {@link ChronicleHashBuilder} interface documentation. + * + *

Later in this documentation, "ChronicleMap" means "ChronicleMaps, created by {@code + * AbstractChronicleMapBuilder}", unless specified different, because theoretically someone might + * provide {@code ChronicleMap} implementations with completely different properties. + * + *

{@code ChronicleMap} ("ChronicleMaps, created by {@code AbstractChronicleMapBuilder}") + * currently doesn't support resizing. That is why you should always configure {@linkplain + * #entries(long) number of entries} you are going to insert into the created map at most. + * See {@link #entries(long)} method documentation for more information on this. + * + *

{@code ChronicleMap} allocates memory by equally sized chunks. This size is called {@linkplain + * #entrySize(int) entry size}, you are strongly recommended to configure it to achieve least memory + * consumption and best speed. See {@link #entrySize(int)} method documentation for more information + * on this. + * + * @param key type of the maps, produced by this builder + * @param value type of the maps, produced by this builder + * @see ChronicleMap + * @see ChronicleSetBuilder + */ +public abstract class AbstractChronicleMapBuilder> + implements Cloneable, ChronicleHashBuilder, B> { + + private static final Bytes EMPTY_BYTES = new ByteBufferBytes(ByteBuffer.allocate(0)); + private static final int DEFAULT_KEY_OR_VALUE_SIZE = 120; + + private static final int MAX_SEGMENTS = (1 << 30); + private static final int MAX_SEGMENTS_TO_CHAISE_COMPACT_MULTI_MAPS = (1 << 20); + + static final short UDP_REPLICATION_MODIFICATION_ITERATOR_ID = 128; + private static final Logger LOG = + LoggerFactory.getLogger(AbstractChronicleMapBuilder.class.getName()); + + + SerializationBuilder keyBuilder; + SerializationBuilder valueBuilder; + + private Map, Replicator> replicators = new HashMap, Replicator>(); + + // used when configuring the number of segments. + private int minSegments = -1; + private int actualSegments = -1; + // used when reading the number of entries per + private long actualEntriesPerSegment = -1L; + private int keySize = 0; + private K sampleKey; + private int valueSize = 0; + private V sampleValue; + private int entrySize = 0; + private Alignment alignment = Alignment.OF_4_BYTES; + private long entries = 1 << 20; // 1 million by default + private long lockTimeOut = 2000; + private TimeUnit lockTimeOutUnit = TimeUnit.MILLISECONDS; + private int metaDataBytes = 0; + private ChronicleHashErrorListener errorListener = ChronicleHashErrorListeners.logging(); + private boolean putReturnsNull = false; + private boolean removeReturnsNull = false; + + // replication + private TimeProvider timeProvider = TimeProvider.SYSTEM; + private BytesMarshallerFactory bytesMarshallerFactory; + private ObjectSerializer objectSerializer; + private MapEventListener> eventListener = + MapEventListeners.nop(); + private V defaultValue = null; + private DefaultValueProvider defaultValueProvider = NullValueProvider.INSTANCE; + private PrepareValueBytes prepareValueBytes = null; + private byte identifier = -1; + + private StatelessBuilder statelessBuilder; + private File file; + + AbstractChronicleMapBuilder(Class keyClass, Class valueClass) { + keyBuilder = new SerializationBuilder(keyClass, SerializationBuilder.Role.KEY); + valueBuilder = new SerializationBuilder(valueClass, SerializationBuilder.Role.VALUE); + } + + + private static long roundUpMapHeaderSize(long headerSize) { + long roundUp = (headerSize + 127L) & ~127L; + if (roundUp - headerSize < 64) + roundUp += 128; + return roundUp; + } + + @Override + public B clone() { + try { + @SuppressWarnings("unchecked") + final B result = (B) super.clone(); + result.keyBuilder = keyBuilder.clone(); + result.valueBuilder = valueBuilder.clone(); + return result; + } catch (CloneNotSupportedException e) { + throw new AssertionError(e); + } + } + + abstract B self(); + + /** + * {@inheritDoc} + * + *

Example: if keys in your map(s) are English words in {@link String} form, keys size 10 + * (a bit more than average English word length) would be a good choice:

{@code
+     * ChronicleMap wordFrequencies = OffHeapUpdatableChronicleMapBuilder
+     *     .of(String.class, LongValue.class)
+     *     .entries(50000)
+     *     .keySize(10)
+     *     // shouldn't specify valueSize(), because it is statically known
+     *     .create();}
+ * (Note that 10 is chosen as key size in bytes despite strings in Java are UTF-16 encoded (and + * each character takes 2 bytes on-heap), because default off-heap {@link String} encoding is + * UTF-8 in {@code ChronicleMap}.) + * + * @see #constantKeySizeBySample(Object) + * @see #entrySize(int) + */ + @Override + public B keySize(int keySize) { + if (keySize <= 0) + throw new IllegalArgumentException("Key size must be positive"); + this.keySize = keySize; + return self(); + } + + /** + * {@inheritDoc} + * + *

For example, if your keys are Git commit hashes:

{@code
+     * Map gitCommitMessagesByHash =
+     *     ChronicleMapBuilder.of(byte[].class, String.class)
+     *     .constantKeySizeBySample(new byte[20])
+     *     .create();}
+ * + * @see #keySize(int) + */ + @Override + public B constantKeySizeBySample(K sampleKey) { + this.sampleKey = sampleKey; + return self(); + } + + private int keySize() { + return keyOrValueSize(keySize, keyBuilder); + } + + B valueSize(int valueSize) { + if (valueSize <= 0) + throw new IllegalArgumentException("Value size must be positive"); + this.valueSize = valueSize; + return self(); + } + + B constantValueSizeBySample(V sampleValue) { + this.sampleValue = sampleValue; + return self(); + } + + int valueSize() { + return keyOrValueSize(valueSize, valueBuilder); + } + + private int keyOrValueSize(int configuredSize, SerializationBuilder builder) { + if (configuredSize > 0) + return configuredSize; + // this means size is statically known + if (builder.sizeMarshaller().sizeEncodingSize(0L) == 0) + return (int) builder.sizeMarshaller().readSize(EMPTY_BYTES); + return DEFAULT_KEY_OR_VALUE_SIZE; + } + + /** + * {@inheritDoc} + * + *

In fully default case you can expect entry size to be about 256 bytes. But it is strongly + * recommended always to configure {@linkplain #keySize(int) key size} and {@linkplain + * #valueSize(int) value size}, if they couldn't be derived statically. + * + *

If entry size is not configured explicitly by calling this method, it is computed based on + * {@linkplain #metaDataBytes(int) meta data bytes}, plus {@linkplain #keySize(int) key size}, + * plus {@linkplain #valueSize(int) value size}, plus a few bytes required by implementations, + * with respect to {@linkplain #entryAndValueAlignment(Alignment) alignment}. + * + *

Note that the actual entrySize will be aligned to 4 (default {@linkplain + * #entryAndValueAlignment(Alignment) entry alignment}). I. e. if you set entry size to 30, the + * actual entry size will be 32 (30 aligned to 4 bytes). If you don't want entry size to be + * aligned, set {@code entryAndValueAlignment(Alignment.NO_ALIGNMENT)}. + * + * @see #entryAndValueAlignment(Alignment) + * @see #entries(long) + */ + @Override + public B entrySize(int entrySize) { + if (entrySize <= 0) + throw new IllegalArgumentException("Entry Size must be positive"); + this.entrySize = entrySize; + return self(); + } + + int entrySize() { + if (entrySize > 0) + return entryAndValueAlignment().alignSize(entrySize); + int size = metaDataBytes; + int keySize = keySize(); + size += keyBuilder.sizeMarshaller().sizeEncodingSize(keySize); + size += keySize; + if (useReplication()) + size += ReplicatedChronicleMap.ADDITIONAL_ENTRY_BYTES; + int valueSize = valueSize(); + size += valueBuilder.sizeMarshaller().sizeEncodingSize(valueSize); + size = entryAndValueAlignment().alignSize(size); + size += valueSize; + // Some cache line heuristics + for (int i = 1; i <= 4; i++) { + int bound = i * 64; + // Not more than 5% oversize. + // DEFAULT_KEY_OR_VALUE_SIZE and this heuristic are specially adjusted to produce + // entry size 256 -- the default value prior to keySize and valueSize -- when key and + // value sizes are both not static nor configured, in both vanilla and replicated modes. + if (size < bound && (bound - size) <= bound / 20) { + size = bound; + break; + } + } + return entryAndValueAlignment().alignSize(size); + } + + /** + * Configures alignment strategy of address in memory of entries and independently of address in + * memory of values within entries in ChronicleMaps, created by this builder. + * + *

Useful when values of the map are updated intensively, particularly fields with volatile + * access, because it doesn't work well if the value crosses cache lines. Also, on some + * (nowadays rare) architectures any misaligned memory access is more expensive than aligned. + * + *

Note that {@linkplain #entrySize(int) entry size} will be aligned according to this + * alignment. I. e. if you set {@code entrySize(20)} and {@link Alignment#OF_8_BYTES}, actual + * entry size will be 24 (20 aligned to 8 bytes). + * + *

Default is {@link Alignment#OF_4_BYTES}. + * + * @param alignment the new alignment of the maps constructed by this builder + * @return this {@code ChronicleMapBuilder} back + */ + public B entryAndValueAlignment(Alignment alignment) { + this.alignment = alignment; + return self(); + } + + Alignment entryAndValueAlignment() { + return alignment; + } + + @Override + public B entries(long entries) { + if (entries <= 0L) + throw new IllegalArgumentException("Entries should be positive, " + entries + " given"); + this.entries = entries; + return self(); + } + + long entries() { + return entries; + } + + @Override + public B actualEntriesPerSegment(long actualEntriesPerSegment) { + if (actualEntriesPerSegment <= 0L) + throw new IllegalArgumentException("entries per segment should be positive, " + + actualEntriesPerSegment + " given"); + if (tooManyEntriesPerSegment(actualEntriesPerSegment)) + throw new IllegalArgumentException("max entries per segment is " + + VanillaIntIntMultiMap.MAX_CAPACITY + ", " + actualEntriesPerSegment + " given"); + this.actualEntriesPerSegment = actualEntriesPerSegment; + return self(); + } + + private boolean tooManyEntriesPerSegment(long entriesPerSegment) { + return entriesPerSegment > VanillaIntIntMultiMap.MAX_CAPACITY; + } + + long actualEntriesPerSegment() { + if (actualEntriesPerSegment > 0L) + return actualEntriesPerSegment; + int actualSegments = actualSegments(); + long actualEntries = totalEntriesIfPoorDistribution(actualSegments); + long actualEntriesPerSegment = divideUpper(actualEntries, actualSegments); + if (tooManyEntriesPerSegment(actualEntriesPerSegment)) + throw new IllegalStateException("max entries per segment is " + + VanillaIntIntMultiMap.MAX_CAPACITY + " configured entries() and " + + "actualSegments() so that there should be " + actualEntriesPerSegment + + " entries per segment"); + return actualEntriesPerSegment; + } + + private long totalEntriesIfPoorDistribution(int segments) { + if (segments == 1) + return entries; + double poorDistEntriesScale = Math.log(segments) * entries; + if (segments <= 8) + return Math.min(entries * segments, + (long) (entries + poorDistEntriesScale * 0.08 + 64)); // 8% was min for tests + return (long) (entries + poorDistEntriesScale * 0.11 + 80); // 11% was min for tests + } + + @Override + public B minSegments(int minSegments) { + checkSegments(minSegments); + this.minSegments = minSegments; + return self(); + } + + int minSegments() { + return Math.max(estimateSegments(), minSegments); + } + + private int estimateSegments() { + for (int power = 0; power < 7; power++) { + if (entries < (1L << (7 + (2 * power)))) + return 1 << power; + } + // Heuristic -- number of segments ~= cube root from number of entries seems optimal + int maxSegments = 65536; + for (int segments = 4; segments < maxSegments; segments <<= 1) { + if (((long) segments) * segments * segments >= entries) + return segments; + } + return maxSegments; + } + + @Override + public B actualSegments(int actualSegments) { + checkSegments(actualSegments); + this.actualSegments = actualSegments; + return self(); + } + + private static void checkSegments(int segments) { + if (segments <= 0 || segments > MAX_SEGMENTS) + throw new IllegalArgumentException("segments should be positive, " + + segments + " given"); + if (segments > MAX_SEGMENTS) + throw new IllegalArgumentException("Max segments is " + MAX_SEGMENTS + ", " + + segments + " given"); + } + + int actualSegments() { + if (actualSegments > 0) + return actualSegments; + long shortMMapSegments = trySegments(VanillaShortShortMultiMap.MAX_CAPACITY, + MAX_SEGMENTS_TO_CHAISE_COMPACT_MULTI_MAPS); + if (shortMMapSegments > 0L) + return (int) shortMMapSegments; + long intMMapSegments = trySegments(VanillaIntIntMultiMap.MAX_CAPACITY, MAX_SEGMENTS); + if (intMMapSegments > 0L) + return (int) intMMapSegments; + throw new IllegalStateException("Max segments is " + MAX_SEGMENTS + ", configured so much" + + " entries() that builder automatically decided to use " + + (-intMMapSegments) + " segments"); + } + + private long trySegments(long maxSegmentCapacity, int maxSegments) { + long segments = divideUpper(totalEntriesIfPoorDistribution(minSegments()), + maxSegmentCapacity); + segments = Maths.nextPower2(Math.max(segments, minSegments()), 1L); + return segments <= maxSegments ? segments : -segments; + } + + private static long divideUpper(long dividend, long divisor) { + return ((dividend - 1L) / divisor) + 1L; + } + + private boolean canSupportShortShort() { + return entries > (long) minSegments() << 15; + } + + public B lockTimeOut(long lockTimeOut, TimeUnit unit) { + this.lockTimeOut = lockTimeOut; + lockTimeOutUnit = unit; + return self(); + } + + long lockTimeOut(TimeUnit unit) { + return unit.convert(lockTimeOut, lockTimeOutUnit); + } + + @Override + public B errorListener(ChronicleHashErrorListener errorListener) { + this.errorListener = errorListener; + return self(); + } + + ChronicleHashErrorListener errorListener() { + return errorListener; + } + + /** + * Configures if the maps created by this builder should return {@code null} instead of previous + * mapped values on {@link ChronicleMap#put(Object, Object) ChornicleMap.put(key, value)} + * calls. + * + *

{@link Map#put(Object, Object) Map.put()} returns the previous value, functionality which + * is rarely used but fairly cheap for {@link HashMap}. In the case, for an off heap collection, + * it has to create a new object and deserialize the data from off-heap memory. 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. + * + * @param putReturnsNull {@code true} if you want {@link ChronicleMap#put(Object, Object) + * ChronicleMap.put()} to not return the value that was replaced but + * instead return {@code null} + * @return an instance of the map builder back + * @see #removeReturnsNull(boolean) + */ + public B putReturnsNull(boolean putReturnsNull) { + this.putReturnsNull = putReturnsNull; + return self(); + } + + boolean putReturnsNull() { + return putReturnsNull; + } + + /** + * Configures if the maps created by this builder should return {@code null} instead of the last + * mapped value on {@link ChronicleMap#remove(Object) ChronicleMap.remove(key)} calls. + * + *

{@link Map#remove(Object) Map.remove()} returns the previous value, functionality which is + * rarely used but fairly cheap for {@link HashMap}. In the case, for an off heap collection, it + * has to create a new object and deserialize the data from off-heap memory. It's expensive for + * something you probably don't use. + * + * @param removeReturnsNull {@code true} if you want {@link ChronicleMap#remove(Object) + * ChronicleMap.remove()} to not return the value of the removed entry + * but instead return {@code null} + * @return an instance of the map builder back + * @see #putReturnsNull(boolean) + */ + public B removeReturnsNull(boolean removeReturnsNull) { + this.removeReturnsNull = removeReturnsNull; + return self(); + } + + boolean removeReturnsNull() { + return removeReturnsNull; + } + + @Override + public B metaDataBytes(int metaDataBytes) { + if ((metaDataBytes & 0xFF) != metaDataBytes) + throw new IllegalArgumentException("MetaDataBytes must be [0..255] was " + metaDataBytes); + this.metaDataBytes = metaDataBytes; + return self(); + } + + + int metaDataBytes() { + return metaDataBytes; + } + + @Override + public String toString() { + return "ChronicleMapBuilder{" + + "actualSegments=" + pretty(actualSegments) + + ", minSegments=" + pretty(minSegments) + + ", actualEntriesPerSegment=" + pretty(actualEntriesPerSegment) + + ", keySize=" + pretty(keySize) + + ", sampleKeyForConstantSizeComputation=" + pretty(sampleKey) + + ", valueSize=" + pretty(valueSize) + + ", sampleValueForConstantSizeComputation=" + pretty(sampleValue) + + ", entrySize=" + pretty(entrySize) + + ", entryAndValueAlignment=" + entryAndValueAlignment() + + ", entries=" + entries() + + ", lockTimeOut=" + lockTimeOut + " " + lockTimeOutUnit + + ", metaDataBytes=" + metaDataBytes() + + ", errorListener=" + errorListener() + + ", putReturnsNull=" + putReturnsNull() + + ", removeReturnsNull=" + removeReturnsNull() + + ", timeProvider=" + timeProvider() + + ", bytesMarshallerFactory=" + pretty(bytesMarshallerFactory) + + ", objectSerializer=" + pretty(objectSerializer) + + ", keyBuilder=" + keyBuilder + + ", valueBuilder=" + valueBuilder + + ", eventListener=" + eventListener + + ", defaultValue=" + defaultValue + + ", defaultValueProvider=" + pretty(defaultValueProvider) + + ", prepareValueBytes=" + pretty(prepareValueBytes) + + '}'; + } + + private static String pretty(int value) { + return value > 0 ? value + "" : "not configured"; + } + + private static String pretty(Object obj) { + return obj != null ? obj + "" : "not configured"; + } + + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + @Override + public boolean equals(Object o) { + return builderEquals(this, o); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + public B replicators(byte identifier, ReplicationConfig... replicationConfigs) { + + this.identifier = identifier; + this.replicators.clear(); + for (ReplicationConfig replicationConfig : replicationConfigs) { + + Replicator replicator; + if (replicationConfig instanceof TcpReplicationConfig) { + replicator = Replicators.tcp((TcpReplicationConfig) replicationConfig); + } else if (replicationConfig instanceof UdpReplicationConfig) { + replicator = Replicators.udp((UdpReplicationConfig) replicationConfig); + } else + throw new UnsupportedOperationException(); + + + this.replicators.put(replicator.getClass(), replicator); + } + + return self(); + } + + + public B channel(ChannelProvider.ChronicleChannel chronicleChannel) { + this.identifier = chronicleChannel.identifier(); + this.replicators.clear(); + replicators.put(chronicleChannel.getClass(), chronicleChannel); + + return self(); + } + + @Override + public B disableReplication() { + identifier = -1; + replicators.clear(); + return self(); + } + + @Override + public B timeProvider(TimeProvider timeProvider) { + this.timeProvider = timeProvider; + return self(); + } + + TimeProvider timeProvider() { + return timeProvider; + } + + BytesMarshallerFactory bytesMarshallerFactory() { + return bytesMarshallerFactory == null ? + bytesMarshallerFactory = new VanillaBytesMarshallerFactory() : + bytesMarshallerFactory; + } + + @Override + public B bytesMarshallerFactory(BytesMarshallerFactory bytesMarshallerFactory) { + this.bytesMarshallerFactory = bytesMarshallerFactory; + return self(); + } + + ObjectSerializer objectSerializer() { + return objectSerializer == null ? + objectSerializer = BytesMarshallableSerializer.create( + bytesMarshallerFactory(), JDKObjectSerializer.INSTANCE) : + objectSerializer; + } + + @Override + public B objectSerializer(ObjectSerializer objectSerializer) { + this.objectSerializer = objectSerializer; + return self(); + } + + + /** + * {@inheritDoc} + * + *

Example: + */ + @Override + public B keyMarshaller(@NotNull BytesMarshaller keyMarshaller) { + keyBuilder.marshaller(keyMarshaller, null); + return self(); + } + + @Override + public B keyDeserializationFactory(@NotNull ObjectFactory keyDeserializationFactory) { + keyBuilder.factory(keyDeserializationFactory); + return self(); + } + + @Override + public B immutableKeys() { + keyBuilder.instancesAreMutable(false); + return self(); + } + + @Override + public B stateless(@NotNull StatelessBuilder statelessBuilder) { + this.statelessBuilder = statelessBuilder; + return self(); + } + + @Override + public B file(File file) throws IOException { + this.file = file; + return self(); + } + + /** + * Configures factory which is used to create a new value instance, if value class is either + * {@link Byteable}, {@link BytesMarshallable} or {@link Externalizable} subclass in maps, + * created by this builder. + * + *

Default value deserialization factory is {@link NewInstanceObjectFactory}, which creates + * a new value instance using {@link Class#newInstance()} default constructor. You could provide + * an {@link AllocateInstanceObjectFactory}, which uses {@code Unsafe.allocateInstance(Class)} + * (you might want to do this for better performance or if you don't want to initialize fields), + * or a factory which calls a value class constructor with some arguments, or a factory which + * internally delegates to instance pool or {@link ThreadLocal}, to reduce allocations. + * + * @param valueDeserializationFactory the value factory used to produce instances to deserialize + * data in + * @return this builder back + */ + public B valueDeserializationFactory(@NotNull ObjectFactory valueDeserializationFactory) { + valueBuilder.factory(valueDeserializationFactory); + return self(); + } + + public B eventListener(MapEventListener> eventListener) { + this.eventListener = eventListener; + return self(); + } + + MapEventListener> eventListener() { + return eventListener; + } + + /** + * Specifies the value to be put for each key queried in {@link ChronicleMap#acquireUsing + * acquireUsing()} method, if the key is absent in the map, created by this builder. + * + *

This configuration overrides any previous {@link #defaultValueProvider( + * DefaultValueProvider)} configuration to this {@code AbstractChronicleMapBuilder}. + * + * @param defaultValue the default value to be put to the map for absent keys during {@code + * acquireUsing()} calls + * @return this builder object back + */ + public B defaultValue(V defaultValue) { + this.defaultValue = defaultValue; + this.defaultValueProvider = null; + if (defaultValue == null) + defaultValueProvider = NullValueProvider.INSTANCE; + this.prepareValueBytes = null; + return self(); + } + + /** + * 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 configuration overrides any previous {@link #defaultValue(Object)} configuration to + * this {@code AbstractChronicleMapBuilder}. + * + * @param defaultValueProvider the strategy to obtain a default value by the absent key + * @return this builder object back + */ + public B defaultValueProvider( + @NotNull DefaultValueProvider defaultValueProvider) { + this.defaultValueProvider = defaultValueProvider; + this.defaultValue = null; + this.prepareValueBytes = null; + return self(); + } + + B prepareValueBytesOnAcquire(@NotNull PrepareValueBytes prepareValueBytes) { + this.prepareValueBytes = prepareValueBytes; + this.defaultValue = null; + this.defaultValueProvider = null; + return self(); + } + + PrepareValueBytesAsWriter prepareValueBytesAsWriter() { + return new PrepareValueBytesAsWriter<>(prepareValueBytes, valueSize()); + } + + + /** + * Non-public because should be called only after {@link #preMapConstruction()} + */ + DefaultValueProvider defaultValueProvider() { + if (defaultValueProvider != null) + return defaultValueProvider; + if (defaultValue == null) + return null; + Object originalValueWriter = valueBuilder.interop(); + Provider writerProvider = Provider.of(originalValueWriter.getClass()); + ThreadLocalCopies copies = writerProvider.getCopies(null); + Object valueWriter = writerProvider.get(copies, originalValueWriter); + MetaProvider metaWriterProvider = valueBuilder.metaInteropProvider(); + copies = metaWriterProvider.getCopies(copies); + MetaBytesWriter metaValueWriter = metaWriterProvider.get(copies, + valueBuilder.metaInterop(), valueWriter, defaultValue); + return new ConstantValueProvider(defaultValue, metaValueWriter, valueWriter); + } + + public ChronicleMap createWithFile(File file) throws IOException { + for (int i = 0; i < 10; i++) { + if (file.exists() && file.length() > 0) { + FileInputStream fis = new FileInputStream(file); + ObjectInputStream ois = new ObjectInputStream(fis); + try { + VanillaChronicleMap map = + (VanillaChronicleMap) ois.readObject(); + map.headerSize = roundUpMapHeaderSize(fis.getChannel().position()); + map.createMappedStoreAndSegments(file); + return establishReplication(map); + } catch (ClassNotFoundException e) { + throw new IOException(e); + } finally { + ois.close(); + } + } + if (file.createNewFile() || file.length() == 0) { + break; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + throw new IOException(e); + } + } + // new file + if (!file.exists()) + throw new FileNotFoundException("Unable to create " + file); + + VanillaChronicleMap map = newMap(); + + FileOutputStream fos = new FileOutputStream(file); + ObjectOutputStream oos = new ObjectOutputStream(fos); + try { + oos.writeObject(map); + oos.flush(); + map.headerSize = roundUpMapHeaderSize(fos.getChannel().position()); + map.createMappedStoreAndSegments(file); + } finally { + oos.close(); + } + + return establishReplication(map); + } + + + @Override + public ChronicleMap create() throws IOException { + + if (statelessBuilder != null) + return createStatelessMap(statelessBuilder); + + return (file != null) ? createWithFile(file) : createWithoutFile(); + } + + private ChronicleMap createWithoutFile() throws IOException { + VanillaChronicleMap map = newMap(); + BytesStore bytesStore = new DirectStore(JDKObjectSerializer.INSTANCE, + map.sizeInBytes(), true); + map.createMappedStoreAndSegments(bytesStore); + return establishReplication(map); + } + + private ChronicleMap createStatelessMap(StatelessBuilder statelessBuilder) throws IOException { + preMapConstruction(); + + final KeyValueSerializer keyValueSerializer + = new KeyValueSerializer(keyBuilder, valueBuilder); + + final Class kClass = keyBuilder.eClass; + final Class vClass = valueBuilder.eClass; + + return new StatelessChronicleMap( + keyValueSerializer, + statelessBuilder, + entrySize(), + kClass, + vClass); + } + + private VanillaChronicleMap newMap() throws IOException { + preMapConstruction(); + if (useReplication()) { + return new ReplicatedChronicleMap, + V, Object, MetaBytesWriter>(this); + } else { + return new VanillaChronicleMap, + V, Object, MetaBytesWriter>(this); + } + } + + private boolean useReplication() { + return identifier != -1; + } + + void preMapConstruction() { + keyBuilder.objectSerializer(objectSerializer()); + valueBuilder.objectSerializer(objectSerializer()); + + long maxSize = (long) entrySize() * figureBufferAllocationFactor(); + keyBuilder.maxSize(maxSize); + valueBuilder.maxSize(maxSize); + + if (sampleKey != null) + keyBuilder.constantSizeBySample(sampleKey); + if (sampleValue != null) + valueBuilder.constantSizeBySample(sampleValue); + } + + private ChronicleMap establishReplication(ChronicleMap map) + throws IOException { + if (map instanceof ReplicatedChronicleMap) { + ReplicatedChronicleMap result = (ReplicatedChronicleMap) map; + for (Replicator replicator : replicators.values()) { + Closeable token = replicator.applyTo(this, result, result, map); + if (replicators.size() == 1 && token.getClass() == UdpReplicator.class) { + LOG.warn( + "MISSING TCP REPLICATION : The UdpReplicator only attempts to read data " + + "(it does not enforce or guarantee delivery), you should use" + + "the UdpReplicator if you have a large number of nodes, and you wish" + + "to receive the data before it becomes available on TCP/IP. Since data" + + "delivery is not guaranteed, it is recommended that you only use" + + "the UDP Replicator in conjunction with a TCP Replicator" + ); + } + result.addCloseable(token); + } + } + return map; + } + + private int figureBufferAllocationFactor() { + // if expected map size is about 1000, seems rather wasteful to allocate + // key and value serialization buffers each x64 of expected entry size.. + return (int) Math.min(Math.max(2L, entries() >> 10), + VanillaChronicleMap.MAX_ENTRY_OVERSIZE_FACTOR); + } + + public byte identifier() { + return identifier; + } +} + diff --git a/src/main/java/net/openhft/chronicle/map/Alignment.java b/src/main/java/net/openhft/chronicle/map/Alignment.java index d2ef30ae0..ebdd6728a 100644 --- a/src/main/java/net/openhft/chronicle/map/Alignment.java +++ b/src/main/java/net/openhft/chronicle/map/Alignment.java @@ -23,7 +23,7 @@ import net.openhft.lang.io.Bytes; /** * Memory addresses alignment strategies. * - * @see ChronicleMapBuilder#entryAndValueAlignment(Alignment) + * @see AbstractChronicleMapBuilder#entryAndValueAlignment(Alignment) */ public enum Alignment { /** diff --git a/src/main/java/net/openhft/chronicle/map/ChannelProvider.java b/src/main/java/net/openhft/chronicle/map/ChannelProvider.java index d6f2a61e2..fdff1ece9 100644 --- a/src/main/java/net/openhft/chronicle/map/ChannelProvider.java +++ b/src/main/java/net/openhft/chronicle/map/ChannelProvider.java @@ -409,7 +409,7 @@ public final class ChannelProvider implements Closeable { } @Override - protected Closeable applyTo(ChronicleMapBuilder builder, + protected Closeable applyTo(AbstractChronicleMapBuilder builder, Replica map, EntryExternalizable entryExternalizable, final ChronicleMap chronicleMap) { if (builder.entrySize() > maxEntrySize) { throw new IllegalArgumentException("During ReplicatingChannelBuilder setup, " + diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMap.java b/src/main/java/net/openhft/chronicle/map/ChronicleMap.java index 0649643ef..23599052d 100644 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMap.java @@ -22,7 +22,6 @@ import net.openhft.chronicle.common.ChronicleHash; import net.openhft.chronicle.common.serialization.BytesReader; import net.openhft.lang.io.Bytes; import net.openhft.lang.io.serialization.BytesMarshaller; -import net.openhft.lang.io.serialization.ObjectFactory; import net.openhft.lang.model.Byteable; import java.io.Closeable; @@ -38,11 +37,15 @@ import java.util.concurrent.ConcurrentMap; *

  • {@code ChronicleMap} flavors and properties
  • *
  • available configurations
  • * - * see {@link ChronicleMapBuilder} documentation. + * see {@link AbstractChronicleMapBuilder} documentation. * *

    Functionally this interface defines some methods supporting garbage-free off-heap programming: * {@link #getUsing(Object, Object)}, {@link #acquireUsing(Object, Object)}. * + *

    Roughly speaking, {@code ChronicleMap} compares keys and values by their binary serialized + * form, that shouldn't necessary be the same equality relation as defined by built-in {@link + * Object#equals(Object)} method, which is prescribed by general {@link Map} contract. + * *

    Note that {@code ChronicleMap} extends {@link Closeable}, don't forget * to {@linkplain #close() close} map when it is no longer needed. * @@ -62,15 +65,6 @@ public interface ChronicleMap extends ConcurrentMap, ChronicleHash { * Returns the value to which the specified key is mapped, or {@code null} if this map contains * no mapping for the key. * - *

    If the specified key if absent in the map, {@linkplain - * ChronicleMapBuilder#defaultValue(Object) default value} is taken or {@linkplain - * ChronicleMapBuilder#defaultValueProvider(DefaultValueProvider) default value provider} - * is called (depending on what was configured last in {@code ChronicleMapBuilder}). - * If it results to something different from {@code null}, this object is put to this map for - * the specified key, and then returned from this {@code get()} call. Otherwise, if default - * value is {@code null} and default value provider returns {@code null}, the map remains - * unchanged and {@code null} is returned. - * *

    If the value class allows reusing, particularly if it is a {@link Byteable} subclass, * consider {@link #getUsing(Object, Object)} method instead of this to reduce garbage creation. * @@ -90,40 +84,59 @@ public interface ChronicleMap extends ConcurrentMap, ChronicleHash { * {@code value} object via value marshaller's {@link BytesMarshaller#read(Bytes, Object) * read(Bytes, value)} or value reader's {@link BytesReader#read(Bytes, long, Object) * read(Bytes, size, value)} method, depending on what deserialization strategy is configured - * on this map builder. If the value deserializer is able to reuse the given {@code value} - * object, calling this method instead of {@link #get(Object)} could help to reduce garbage - * creation. + * on the builder, using which this map was constructed. If the value deserializer is able + * to reuse the given {@code value} object, calling this method instead of {@link #get(Object)} + * could help to reduce garbage creation. * *

    The provided {@code value} object is allowed to be {@code null}, in this case * {@code map.getUsing(key, null)} call is semantically equivalent to simple * {@code map.get(key)} call. * - *

    If the specified key if absent in the map, {@linkplain - * ChronicleMapBuilder#defaultValue(Object) default value} is taken or {@linkplain - * ChronicleMapBuilder#defaultValueProvider(DefaultValueProvider) default value provider} - * is called. If it results to something different from {@code null}, this object is put - * to this map for the specified key, and then a new value is returned from this - * {@code getUsing()} call. Otherwise, if default value is {@code null} and default value - * provider, if specified, returns {@code null}, the map remains unchanged and {@code null} - * is returned. The provided {@code value} is untouched anyway in this case. - * - * @param key the key whose associated value is to be returned - * @param value the object to read value data in, if possible - * @return the value mapped to the specified key after this method call, or {@code null} if no - * value is mapped + * @param key the key whose associated value is to be returned + * @param usingValue the object to read value data in, if possible + * @return the value to which the specified key is mapped, or {@code null} if this map contains + * no mapping for the key * @see #get(Object) - * @see ChronicleMapBuilder#valueMarshallerAndFactory(BytesMarshaller, ObjectFactory) + * @see #acquireUsing(Object, Object) + * @see ChronicleMapBuilder#valueMarshaller(BytesMarshaller) */ - V getUsing(K key, V value); + V getUsing(K key, V usingValue); /** - * Acquire a value for a key, creating if absent. If the value is Byteable, it will be assigned - * to reference the value, instead of copying the data. + * Acquire a value for a key, creating if absent. * - * @param key the key whose associated value is to be returned - * @param value to reuse if possible. If null, a new object will be created. - * @return value created or found. + *

    If the specified key if absent in the map, {@linkplain + * AbstractChronicleMapBuilder#defaultValue(Object) default value} is taken or {@linkplain + * AbstractChronicleMapBuilder#defaultValueProvider(DefaultValueProvider) default value + * provider} is called. Then this object is put to this map for the specified key. + * + *

    Then, either if the key was initially absent in the map or already present, + * the value is deserialized just as during {@link #getUsing(Object, Object) + * getUsing(key, usingValue)} call, passed the same {@code key} and {@code usingValue} as into + * this method call. This means, as in {@link #getUsing}, {@code usingValue} could safely be + * {@code null}, in this case a new value instance is created to deserialize the data. + * + *

    In code, {@code acquireUsing} is specified as

    {@code
    +     * V acquireUsing(K key, V usingValue) {
    +     *     if (!containsKey(key))
    +     *         put(key, defaultValue(key));
    +     *     return getUsing(key, usingValue);
    +     * }}
    Where {@code defaultValue(key)} returns either {@linkplain + * AbstractChronicleMapBuilder#defaultValue(Object) default value} or {@link + * AbstractChronicleMapBuilder#defaultValueProvider(DefaultValueProvider) defaultValueProvider.} + * + *

    If the {@code ChronicleMap} is off-heap updatable, i. e. created via {@link + * OffHeapUpdatableChronicleMapBuilder} builder (values are {@link Byteable}), there is one more + * option of what to do if the key is absent in the map, see {@link + * OffHeapUpdatableChronicleMapBuilder#prepareValueBytesOnAcquire(PrepareValueBytes)}. + * By default, value bytes are just zeroed out, no default value, either provided for key + * or constant, is put for the absent key. + * + * @param key the key whose associated value is to be returned + * @param usingValue the object to read value data in, if possible + * @return value to which the given key is mapping after this call, either found or created + * @see #getUsing(Object, Object) */ - V acquireUsing(K key, V value); + V acquireUsing(K key, V usingValue); } diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java index df2f5e776..755c59d8c 100644 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java @@ -1,7 +1,5 @@ /* - * Copyright 2014 Higher Frequency Trading - * - * http://www.higherfrequencytrading.com + * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,204 +16,92 @@ package net.openhft.chronicle.map; -import net.openhft.chronicle.common.*; +import net.openhft.chronicle.common.ChronicleHashBuilder; import net.openhft.chronicle.common.serialization.AgileBytesMarshaller; -import net.openhft.chronicle.common.serialization.MetaBytesInterop; -import net.openhft.chronicle.common.serialization.MetaBytesWriter; -import net.openhft.chronicle.common.serialization.MetaProvider; -import net.openhft.chronicle.common.threadlocal.Provider; -import net.openhft.chronicle.common.threadlocal.ThreadLocalCopies; -import net.openhft.chronicle.set.ChronicleSetBuilder; -import net.openhft.lang.Maths; -import net.openhft.lang.io.ByteBufferBytes; import net.openhft.lang.io.Bytes; -import net.openhft.lang.io.BytesStore; -import net.openhft.lang.io.DirectStore; -import net.openhft.lang.io.serialization.*; -import net.openhft.lang.io.serialization.impl.VanillaBytesMarshallerFactory; +import net.openhft.lang.io.serialization.BytesMarshallable; +import net.openhft.lang.io.serialization.BytesMarshaller; +import net.openhft.lang.io.serialization.ObjectFactory; +import net.openhft.lang.io.serialization.ObjectSerializer; import net.openhft.lang.model.Byteable; import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import java.io.*; -import java.nio.ByteBuffer; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; +import java.io.Externalizable; +import java.io.Serializable; -import static net.openhft.chronicle.map.Objects.builderEquals; /** - * {@code ChronicleMapBuilder} manages the whole set of {@link ChronicleMap} configurations, could - * be used as a classic builder and/or factory. This means that in addition to the standard builder - * usage pattern:

    {@code
    - * ChronicleMap map = ChronicleMapBuilder
    - *     .of(Key.class, Value.class)
    - *     .entries(100500)
    - *     // ... other configurations
    - *     .create();}
    - * {@code ChronicleMapBuilder} could be prepared and used to create many similar maps:
    {@code
    - * ChronicleMapBuilder builder = ChronicleMapBuilder
    - *     .of(Key.class, Value.class)
    - *     .entries(100500);
    - * 

    - * ChronicleMap map1 = builder.create(); - * ChronicleMap map2 = builder.create();}

    - * i. e. created {@code ChronicleMap} instances don't depend on the builder. + * {@code ChronicleMapBuilder} is intended to be used to configure {@link ChronicleMap}s with + * non-{@link Byteable} values, which don't point to off-heap memory directly, including primitives + * ({@link Long}, {@link Double}, etc.), {@link String}s and {@link CharSequence}s, values + * implementing {@link BytesMarshallable}, {@link Externalizable} or {@link Serializable} interface, + * or any other values for which {@linkplain #valueMarshaller(BytesMarshaller) custom marshaller} + * is provided. * - *

    Use static {@link #of(Class, Class)} method to obtain a {@code ChronicleMapBuilder} instance. + *

    Use static {@link #of(Class, Class) of(Key.class, Value.class)} method to obtain + * a {@code ChronicleMapBuilder} instance. * - *

    {@code ChronicleMapBuilder} is mutable, see a note in {@link net.openhft.chronicle.common.ChronicleHashBuilder} - * interface documentation. - * - *

    Later in this documentation, "ChronicleMap" means "ChronicleMaps, created by {@code - * ChronicleMapBuilder}", unless specified different, because theoretically someone might provide - * {@code ChronicleMap} implementations with completely different properties. - * - *

    {@code ChronicleMap} ("ChronicleMaps, created by {@code ChronicleMapBuilder}") currently - * doesn't support resizing. That is why you should always configure {@linkplain - * #entries(long) number of entries} you are going to insert into the created map at most. - * See {@link #entries(long)} method documentation for more information on this. - * - *

    {@code ChronicleMap} allocates memory by equally sized chunks. This size is called {@linkplain - * #entrySize(int) entry size}, you are strongly recommended to configure it to achieve least memory - * consumption and best speed. See {@link #entrySize(int)} method documentation for more information - * on this. - * - * @param key type of the maps, produced by this builder - * @param value type of the maps, produced by this builder - * @see ChronicleMap - * @see ChronicleSetBuilder + * @param key type of the maps, created by this builder + * @param value type of the maps, created by this builder + * @see OffHeapUpdatableChronicleMapBuilder + * @see AbstractChronicleMapBuilder + * @see ChronicleHashBuilder */ -public class ChronicleMapBuilder implements Cloneable, - ChronicleHashBuilder, ChronicleMapBuilder> { +public final class ChronicleMapBuilder + extends AbstractChronicleMapBuilder> { - private static final Bytes EMPTY_BYTES = new ByteBufferBytes(ByteBuffer.allocate(0)); - private static final int DEFAULT_KEY_OR_VALUE_SIZE = 120; - - private static final int MAX_SEGMENTS = (1 << 30); - private static final int MAX_SEGMENTS_TO_CHAISE_COMPACT_MULTI_MAPS = (1 << 20); - - static final short UDP_REPLICATION_MODIFICATION_ITERATOR_ID = 128; - private static final Logger LOG = LoggerFactory.getLogger(ChronicleMapBuilder.class.getName()); - - - SerializationBuilder keyBuilder; - SerializationBuilder valueBuilder; - - private Map, Replicator> replicators = new HashMap, Replicator>(); - - // used when configuring the number of segments. - private int minSegments = -1; - private int actualSegments = -1; - // used when reading the number of entries per - private long actualEntriesPerSegment = -1L; - private int keySize = 0; - private K sampleKey; - private int valueSize = 0; - private V sampleValue; - private int entrySize = 0; - private Alignment alignment = Alignment.OF_4_BYTES; - private long entries = 1 << 20; // 1 million by default - private long lockTimeOut = 2000; - private TimeUnit lockTimeOutUnit = TimeUnit.MILLISECONDS; - private int metaDataBytes = 0; - private ChronicleHashErrorListener errorListener = ChronicleHashErrorListeners.logging(); - private boolean putReturnsNull = false; - private boolean removeReturnsNull = false; - - // replication - private TimeProvider timeProvider = TimeProvider.SYSTEM; - private BytesMarshallerFactory bytesMarshallerFactory; - private ObjectSerializer objectSerializer; - private MapEventListener> eventListener = - MapEventListeners.nop(); - private V defaultValue = null; - private DefaultValueProvider defaultValueProvider = null; - private byte identifier = -1; - - private StatelessBuilder statelessBuilder; - private File file; - - ChronicleMapBuilder(Class keyClass, Class valueClass) { - - keyBuilder = new SerializationBuilder(keyClass, SerializationBuilder.Role.KEY); - - valueBuilder = new SerializationBuilder(valueClass, SerializationBuilder.Role.VALUE); - } - - public static ChronicleMapBuilder of(Class keyClass, Class valueClass) { + /** + * Returns a new {@code ChronicleMapBuilder} instance which is able to {@linkplain #create() + * create} maps with the specified key and value classes. + * + *

    {@code ChronicleMapBuilder} analyzes provided key and value classes and automatically + * chooses the most specific and effective serializer which it is aware about. Read + * the section about + * serialization in Chronicle Map manual for more information. + * + * @param keyClass class object used to infer key type and discover it's properties + * via reflection + * @param valueClass class object used to infer value type and discover it's properties + * via reflection + * @param key type of the maps, created by the returned builder + * @param value type of the maps, created by the returned builder + * @return a new builder for the given key and value classes + */ + public static ChronicleMapBuilder of( + @NotNull Class keyClass, @NotNull Class valueClass) { return new ChronicleMapBuilder(keyClass, valueClass); } - private static long roundUpMapHeaderSize(long headerSize) { - long roundUp = (headerSize + 127L) & ~127L; - if (roundUp - headerSize < 64) - roundUp += 128; - return roundUp; + ChronicleMapBuilder(Class keyClass, Class valueClass) { + super(keyClass, valueClass); } @Override - public ChronicleMapBuilder clone() { - try { - @SuppressWarnings("unchecked") - final ChronicleMapBuilder result = (ChronicleMapBuilder) super.clone(); - result.keyBuilder = keyBuilder.clone(); - result.valueBuilder = valueBuilder.clone(); - return result; - } catch (CloneNotSupportedException e) { - throw new AssertionError(e); - } + ChronicleMapBuilder self() { + return this; } /** * {@inheritDoc} * - *

    Example: if keys in your map(s) are English words in {@link String} form, keys size 10 - * (a bit more than average English word length) would be a good choice:

    {@code
    -     * ChronicleMap wordFrequencies = ChronicleMapBuilder
    -     *     .of(String.class, directClassFor(LongValue.class))
    -     *     .entries(50000)
    -     *     .keySize(10)
    -     *     // shouldn't specify valueSize(), because it is statically known
    -     *     .create();}
    - * (Note that 10 is chosen as key size in bytes despite strings in Java are UTF-16 encoded (and - * each character takes 2 bytes on-heap), because default off-heap {@link String} encoding is - * UTF-8 in {@code ChronicleMap}.) - * * @see #constantKeySizeBySample(Object) * @see #valueSize(int) * @see #entrySize(int) */ @Override public ChronicleMapBuilder keySize(int keySize) { - if (keySize <= 0) - throw new IllegalArgumentException("Key size must be positive"); - this.keySize = keySize; - return this; + return super.keySize(keySize); } /** * {@inheritDoc} * - *

    For example, if your keys are Git commit hashes:

    {@code
    -     * Map gitCommitMessagesByHash =
    -     *     ChronicleMapBuilder.of(byte[].class, String.class)
    -     *     .constantKeySizeBySample(new byte[20])
    -     *     .create();}
    - * * @see #keySize(int) * @see #constantValueSizeBySample(Object) */ @Override public ChronicleMapBuilder constantKeySizeBySample(K sampleKey) { - this.sampleKey = sampleKey; - return this; - } - - private int keySize() { - return keyOrValueSize(keySize, keyBuilder); + return super.constantKeySizeBySample(sampleKey); } /** @@ -223,8 +109,8 @@ public class ChronicleMapBuilder implements Cloneable, * created by this builder. If value size is always the same, call {@link * #constantValueSizeBySample(Object)} method instead of this one. * - *

    If value is a boxed primitive type or {@link Byteable} subclass, i. e. if value size is - * known statically, it is automatically accounted and shouldn't be specified by user. + *

    If value is a boxed primitive type, i. e. if value size is known statically, + * it is automatically accounted and shouldn't be specified by user. * *

    If value size varies moderately, specify the size higher than average, but lower than the * maximum possible, to minimize average memory overuse. If value size varies in a wide range, @@ -233,14 +119,13 @@ public class ChronicleMapBuilder implements Cloneable, * * @param valueSize number of bytes, taken by serialized form of values * @return this {@code ChronicleMapBuilder} back + * @see #constantValueSizeBySample(Object) * @see #keySize(int) * @see #entrySize(int) */ + @Override public ChronicleMapBuilder valueSize(int valueSize) { - if (valueSize <= 0) - throw new IllegalArgumentException("Value size must be positive"); - this.valueSize = valueSize; - return this; + return super.valueSize(valueSize); } /** @@ -259,426 +144,11 @@ public class ChronicleMapBuilder implements Cloneable, * @see #valueSize(int) * @see #constantKeySizeBySample(Object) */ - public ChronicleMapBuilder constantValueSizeBySample(V sampleValue) { - this.sampleValue = sampleValue; - return this; - } - - private int valueSize() { - return keyOrValueSize(valueSize, valueBuilder); - } - - private int keyOrValueSize(int configuredSize, SerializationBuilder builder) { - if (configuredSize > 0) - return configuredSize; - // this means size is statically known - if (builder.sizeMarshaller().sizeEncodingSize(0L) == 0) - return (int) builder.sizeMarshaller().readSize(EMPTY_BYTES); - return DEFAULT_KEY_OR_VALUE_SIZE; - } - - /** - * {@inheritDoc} - * - *

    In fully default case you can expect entry size to be about 256 bytes. But it is strongly - * recommended always to configure {@linkplain #keySize(int) key size} and {@linkplain - * #valueSize(int) value size}, if they couldn't be derived statically. - * - *

    If entry size is not configured explicitly by calling this method, it is computed based on - * {@linkplain #metaDataBytes(int) meta data bytes}, plus {@linkplain #keySize(int) key size}, - * plus {@linkplain #valueSize(int) value size}, plus a few bytes required by implementations, - * with respect to {@linkplain #entryAndValueAlignment(Alignment) alignment}. - * - *

    Note that the actual entrySize will be aligned to 4 (default {@linkplain - * #entryAndValueAlignment(Alignment) entry alignment}). I. e. if you set entry size to 30, the - * actual entry size will be 32 (30 aligned to 4 bytes). If you don't want entry size to be - * aligned, set {@code entryAndValueAlignment(Alignment.NO_ALIGNMENT)}. - * - * @see #entryAndValueAlignment(Alignment) - * @see #entries(long) - */ - @Override - public ChronicleMapBuilder entrySize(int entrySize) { - if (entrySize <= 0) - throw new IllegalArgumentException("Entry Size must be positive"); - this.entrySize = entrySize; - return this; - } - - int entrySize() { - if (entrySize > 0) - return entryAndValueAlignment().alignSize(entrySize); - int size = metaDataBytes; - int keySize = keySize(); - size += keyBuilder.sizeMarshaller().sizeEncodingSize(keySize); - size += keySize; - if (useReplication()) - size += ReplicatedChronicleMap.ADDITIONAL_ENTRY_BYTES; - int valueSize = valueSize(); - size += valueBuilder.sizeMarshaller().sizeEncodingSize(valueSize); - size = entryAndValueAlignment().alignSize(size); - size += valueSize; - // Some cache line heuristics - for (int i = 1; i <= 4; i++) { - int bound = i * 64; - // Not more than 5% oversize. - // DEFAULT_KEY_OR_VALUE_SIZE and this heuristic are specially adjusted to produce - // entry size 256 -- the default value prior to keySize and valueSize -- when key and - // value sizes are both not static nor configured, in both vanilla and replicated modes. - if (size < bound && (bound - size) <= bound / 20) { - size = bound; - break; - } - } - return entryAndValueAlignment().alignSize(size); - } - - /** - * Configures alignment strategy of address in memory of entries and independently of address in - * memory of values within entries in ChronicleMaps, created by this builder. - * - *

    Useful when values of the map are updated intensively, particularly fields with volatile - * access, because it doesn't work well if the value crosses cache lines. Also, on some - * (nowadays rare) architectures any misaligned memory access is more expensive than aligned. - * - *

    Note that {@linkplain #entrySize(int) entry size} will be aligned according to this - * alignment. I. e. if you set {@code entrySize(20)} and {@link Alignment#OF_8_BYTES}, actual - * entry size will be 24 (20 aligned to 8 bytes). - * - *

    Default is {@link Alignment#OF_4_BYTES}. - * - * @param alignment the new alignment of the maps constructed by this builder - * @return this {@code ChronicleMapBuilder} back - */ - public ChronicleMapBuilder entryAndValueAlignment(Alignment alignment) { - this.alignment = alignment; - return this; - } - - Alignment entryAndValueAlignment() { - return alignment; - } @Override - public ChronicleMapBuilder entries(long entries) { - if (entries <= 0L) - throw new IllegalArgumentException("Entries should be positive, " + entries + " given"); - this.entries = entries; - return this; - } - - long entries() { - return entries; - } - - @Override - public ChronicleMapBuilder actualEntriesPerSegment(long actualEntriesPerSegment) { - if (actualEntriesPerSegment <= 0L) - throw new IllegalArgumentException("entries per segment should be positive, " + - actualEntriesPerSegment + " given"); - if (tooManyEntriesPerSegment(actualEntriesPerSegment)) - throw new IllegalArgumentException("max entries per segment is " + - VanillaIntIntMultiMap.MAX_CAPACITY + ", " + actualEntriesPerSegment + " given"); - this.actualEntriesPerSegment = actualEntriesPerSegment; - return this; - } - - private boolean tooManyEntriesPerSegment(long entriesPerSegment) { - return entriesPerSegment > VanillaIntIntMultiMap.MAX_CAPACITY; - } - - long actualEntriesPerSegment() { - if (actualEntriesPerSegment > 0L) - return actualEntriesPerSegment; - int actualSegments = actualSegments(); - long actualEntries = totalEntriesIfPoorDistribution(actualSegments); - long actualEntriesPerSegment = divideUpper(actualEntries, actualSegments); - if (tooManyEntriesPerSegment(actualEntriesPerSegment)) - throw new IllegalStateException("max entries per segment is " + - VanillaIntIntMultiMap.MAX_CAPACITY + " configured entries() and " + - "actualSegments() so that there should be " + actualEntriesPerSegment + - " entries per segment"); - return actualEntriesPerSegment; - } - - private long totalEntriesIfPoorDistribution(int segments) { - if (segments == 1) - return entries; - double poorDistEntriesScale = Math.log(segments) * entries; - if (segments <= 8) - return Math.min(entries * segments, - (long) (entries + poorDistEntriesScale * 0.08 + 64)); // 8% was min for tests - return (long) (entries + poorDistEntriesScale * 0.11 + 80); // 11% was min for tests - } - - @Override - public ChronicleMapBuilder minSegments(int minSegments) { - checkSegments(minSegments); - this.minSegments = minSegments; - return this; - } - - int minSegments() { - return Math.max(estimateSegments(), minSegments); - } - - private int estimateSegments() { - for (int power = 0; power < 7; power++) { - if (entries < (1L << (7 + (2 * power)))) - return 1 << power; - } - // Heuristic -- number of segments ~= cube root from number of entries seems optimal - int maxSegments = 65536; - for (int segments = 4; segments < maxSegments; segments <<= 1) { - if (((long) segments) * segments * segments >= entries) - return segments; - } - return maxSegments; - } - - @Override - public ChronicleMapBuilder actualSegments(int actualSegments) { - checkSegments(actualSegments); - this.actualSegments = actualSegments; - return this; - } - - private static void checkSegments(int segments) { - if (segments <= 0 || segments > MAX_SEGMENTS) - throw new IllegalArgumentException("segments should be positive, " + - segments + " given"); - if (segments > MAX_SEGMENTS) - throw new IllegalArgumentException("Max segments is " + MAX_SEGMENTS + ", " + - segments + " given"); - } - - int actualSegments() { - if (actualSegments > 0) - return actualSegments; - long shortMMapSegments = trySegments(VanillaShortShortMultiMap.MAX_CAPACITY, - MAX_SEGMENTS_TO_CHAISE_COMPACT_MULTI_MAPS); - if (shortMMapSegments > 0L) - return (int) shortMMapSegments; - long intMMapSegments = trySegments(VanillaIntIntMultiMap.MAX_CAPACITY, MAX_SEGMENTS); - if (intMMapSegments > 0L) - return (int) intMMapSegments; - throw new IllegalStateException("Max segments is " + MAX_SEGMENTS + ", configured so much" + - " entries() that builder automatically decided to use " + - (-intMMapSegments) + " segments"); - } - - private long trySegments(long maxSegmentCapacity, int maxSegments) { - long segments = divideUpper(totalEntriesIfPoorDistribution(minSegments()), - maxSegmentCapacity); - segments = Maths.nextPower2(Math.max(segments, minSegments()), 1L); - return segments <= maxSegments ? segments : -segments; - } - - private static long divideUpper(long dividend, long divisor) { - return ((dividend - 1L) / divisor) + 1L; - } - - private boolean canSupportShortShort() { - return entries > (long) minSegments() << 15; - } - - public ChronicleMapBuilder lockTimeOut(long lockTimeOut, TimeUnit unit) { - this.lockTimeOut = lockTimeOut; - lockTimeOutUnit = unit; - return this; - } - - long lockTimeOut(TimeUnit unit) { - return unit.convert(lockTimeOut, lockTimeOutUnit); - } - - @Override - public ChronicleMapBuilder errorListener(ChronicleHashErrorListener errorListener) { - this.errorListener = errorListener; - return this; - } - - ChronicleHashErrorListener errorListener() { - return errorListener; - } - - /** - * Configures if the maps created by this builder should return {@code null} instead of previous - * mapped values on {@link ChronicleMap#put(Object, Object) ChornicleMap.put(key, value)} - * calls. - * - *

    {@link Map#put(Object, Object) Map.put()} returns the previous value, functionality which - * is rarely used but fairly cheap for {@link HashMap}. In the case, for an off heap collection, - * it has to create a new object and deserialize the data from off-heap memory. 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. - * - * @param putReturnsNull {@code true} if you want {@link ChronicleMap#put(Object, Object) - * ChronicleMap.put()} to not return the value that was replaced but - * instead return {@code null} - * @return an instance of the map builder back - * @see #removeReturnsNull(boolean) - */ - public ChronicleMapBuilder putReturnsNull(boolean putReturnsNull) { - this.putReturnsNull = putReturnsNull; - return this; - } - - boolean putReturnsNull() { - return putReturnsNull; - } - - /** - * Configures if the maps created by this builder should return {@code null} instead of the last - * mapped value on {@link ChronicleMap#remove(Object) ChronicleMap.remove(key)} calls. - * - *

    {@link Map#remove(Object) Map.remove()} returns the previous value, functionality which is - * rarely used but fairly cheap for {@link HashMap}. In the case, for an off heap collection, it - * has to create a new object and deserialize the data from off-heap memory. It's expensive for - * something you probably don't use. - * - * @param removeReturnsNull {@code true} if you want {@link ChronicleMap#remove(Object) - * ChronicleMap.remove()} to not return the value of the removed entry - * but instead return {@code null} - * @return an instance of the map builder back - * @see #putReturnsNull(boolean) - */ - public ChronicleMapBuilder removeReturnsNull(boolean removeReturnsNull) { - this.removeReturnsNull = removeReturnsNull; - return this; - } - - boolean removeReturnsNull() { - return removeReturnsNull; - } - - @Override - public ChronicleMapBuilder metaDataBytes(int metaDataBytes) { - if ((metaDataBytes & 0xFF) != metaDataBytes) - throw new IllegalArgumentException("MetaDataBytes must be [0..255] was " + metaDataBytes); - this.metaDataBytes = metaDataBytes; - return this; - } - - - int metaDataBytes() { - return metaDataBytes; - } - - @Override - public String toString() { - return "ChronicleMapBuilder{" + - "actualSegments=" + pretty(actualSegments) + - ", minSegments=" + pretty(minSegments) + - ", actualEntriesPerSegment=" + pretty(actualEntriesPerSegment) + - ", keySize=" + pretty(keySize) + - ", sampleKeyForConstantSizeComputation=" + pretty(sampleKey) + - ", valueSize=" + pretty(valueSize) + - ", sampleValueForConstantSizeComputation=" + pretty(sampleValue) + - ", entrySize=" + pretty(entrySize) + - ", entryAndValueAlignment=" + entryAndValueAlignment() + - ", entries=" + entries() + - ", lockTimeOut=" + lockTimeOut + " " + lockTimeOutUnit + - ", metaDataBytes=" + metaDataBytes() + - ", errorListener=" + errorListener() + - ", putReturnsNull=" + putReturnsNull() + - ", removeReturnsNull=" + removeReturnsNull() + - ", timeProvider=" + timeProvider() + - ", bytesMarshallerFactory=" + pretty(bytesMarshallerFactory) + - ", objectSerializer=" + pretty(objectSerializer) + - ", keyBuilder=" + keyBuilder + - ", valueBuilder=" + valueBuilder + - ", eventListener=" + eventListener + - ", defaultValue=" + defaultValue + - ", defaultValueProvider=" + pretty(defaultValueProvider) + - '}'; - } - - private static String pretty(int value) { - return value > 0 ? value + "" : "not configured"; - } - - private static String pretty(Object obj) { - return obj != null ? obj + "" : "not configured"; - } - - @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") - @Override - public boolean equals(Object o) { - return builderEquals(this, o); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - public ChronicleMapBuilder replicators(byte identifier, ReplicationConfig... replicationConfigs) { - - this.identifier = identifier; - this.replicators.clear(); - for (ReplicationConfig replicationConfig : replicationConfigs) { - - Replicator replicator; - if (replicationConfig instanceof TcpReplicationConfig) { - replicator = Replicators.tcp((TcpReplicationConfig) replicationConfig); - } else if (replicationConfig instanceof UdpReplicationConfig) { - replicator = Replicators.udp((UdpReplicationConfig) replicationConfig); - } else - throw new UnsupportedOperationException(); - - - this.replicators.put(replicator.getClass(), replicator); - } - - return this; - } - - - public ChronicleMapBuilder channel(ChannelProvider.ChronicleChannel chronicleChannel) { - this.identifier = chronicleChannel.identifier(); - this.replicators.clear(); - replicators.put(chronicleChannel.getClass(), chronicleChannel); - - return this; - } - - @Override - public ChronicleMapBuilder disableReplication() { - identifier = -1; - replicators.clear(); - return this; - } - - @Override - public ChronicleMapBuilder timeProvider(TimeProvider timeProvider) { - this.timeProvider = timeProvider; - return this; - } - - TimeProvider timeProvider() { - return timeProvider; - } - - BytesMarshallerFactory bytesMarshallerFactory() { - return bytesMarshallerFactory == null ? - bytesMarshallerFactory = new VanillaBytesMarshallerFactory() : - bytesMarshallerFactory; - } - - @Override - public ChronicleMapBuilder bytesMarshallerFactory(BytesMarshallerFactory bytesMarshallerFactory) { - this.bytesMarshallerFactory = bytesMarshallerFactory; - return this; - } - - ObjectSerializer objectSerializer() { - return objectSerializer == null ? - objectSerializer = BytesMarshallableSerializer.create( - bytesMarshallerFactory(), JDKObjectSerializer.INSTANCE) : - objectSerializer; + public ChronicleMapBuilder constantValueSizeBySample( + @NotNull V sampleValue) { + return super.constantValueSizeBySample(sampleValue); } /** @@ -694,273 +164,68 @@ public class ChronicleMapBuilder implements Cloneable, * *

    This serializer is used to serialize both keys and values, if they both require this: * loosely typed, nullable, and custom {@linkplain #keyMarshaller(BytesMarshaller) key} and - * {@linkplain #valueMarshallerAndFactory(BytesMarshaller, ObjectFactory) value} marshallers are - * not configured. + * {@linkplain #valueMarshaller(BytesMarshaller) value} marshallers are not configured. */ @Override - public ChronicleMapBuilder objectSerializer(ObjectSerializer objectSerializer) { - this.objectSerializer = objectSerializer; - return this; + public ChronicleMapBuilder objectSerializer( + @NotNull ObjectSerializer objectSerializer) { + return super.objectSerializer(objectSerializer); } - /** * {@inheritDoc} * - *

    Example: + *

    If {@linkplain #valueMarshaller(BytesMarshaller) custom value marshaller} is configured, + * this configuration is unused, because it is incapsulated in {@link + * BytesMarshaller#read(Bytes)} method (without provided instance to read the data into), + * i. e. it's is the user-side responsibility. Actually this is just a convenience method + * supporting value marshaller configurations, made initially during {@link #of(Class, Class)} + * call, if the value class is {@link BytesMarshallable} or {@link Externalizable} subclass. + * + * @throws IllegalStateException if custom value marshaller is specified or value class is not + * either {@code BytesMarshallable} or {@code Externalizable} */ @Override - public ChronicleMapBuilder keyMarshaller(@NotNull BytesMarshaller keyMarshaller) { - keyBuilder.marshaller(keyMarshaller, null); - return this; - } - - @Override - public ChronicleMapBuilder immutableKeys() { - keyBuilder.instancesAreMutable(false); - return this; - } - - @Override - public ChronicleMapBuilder stateless(@NotNull StatelessBuilder statelessBuilder) { - this.statelessBuilder = statelessBuilder; - return this; - } - - @Override - public ChronicleMapBuilder file(File file) throws IOException { - this.file = file; - return this; - } - - public ChronicleMapBuilder valueMarshallerAndFactory( - @NotNull BytesMarshaller valueMarshaller, @NotNull ObjectFactory valueFactory) { - valueBuilder.marshaller(valueMarshaller, valueFactory); - return this; - } - - public ChronicleMapBuilder valueMarshallerAndFactory( - @NotNull AgileBytesMarshaller valueMarshaller, - @NotNull ObjectFactory valueFactory) { - valueBuilder.agileMarshaller(valueMarshaller, valueFactory); - return this; - } - - public ChronicleMapBuilder valueFactory(@NotNull ObjectFactory valueFactory) { - valueBuilder.factory(valueFactory); - return this; - } - - public ChronicleMapBuilder eventListener( - MapEventListener> eventListener) { - this.eventListener = eventListener; - return this; - } - - MapEventListener> eventListener() { - return eventListener; + public ChronicleMapBuilder valueDeserializationFactory( + @NotNull ObjectFactory valueDeserializationFactory) { + return super.valueDeserializationFactory(valueDeserializationFactory); } /** - * Specifies the value to be put for each key queried in {@link ChronicleMap#get get()} and - * {@link ChronicleMap#getUsing(Object, Object) getUsing()} methods, if the key is absent in the - * map. Then this default value is returned from query method. + * {@inheritDoc} * - *

    Setting default value to {@code null} is interpreted as map shouldn't put any default - * value for absent keys. This is by default. + *

    By default, the default value is set to {@code null}. * - *

    This configuration overrides any previous {@link #defaultValueProvider(DefaultValueProvider)} - * configuration to this {@code ChronicleMapBuilder}. - * - * @param defaultValue the default value to be put to the map for absent keys during {@code - * get()} and {@code getUsing()} calls and returned from these calls - * @return this builder object back + * @see #defaultValueProvider(DefaultValueProvider) */ + @Override public ChronicleMapBuilder defaultValue(V defaultValue) { - this.defaultValue = defaultValue; - this.defaultValueProvider = null; - return this; + return super.defaultValue(defaultValue); } /** - * Specifies the function to obtain a value for the key during {@link ChronicleMap#get get()} - * and {@link ChronicleMap#getUsing(Object, Object) getUsing()} calls, if the key is absent in - * the map. If the obtained value is non-null, it is put for the key in the map and then - * returned from current {@code get()} or {@code getUsing()} call. + * {@inheritDoc} * - *

    This configuration overrides any previous {@link #defaultValue(Object)} configuration to - * this {@code ChronicleMapBuilder}. + *

    By default, default value provider is not specified, {@link #defaultValue(Object) default + * value} is specified instead. * - * @param defaultValueProvider the strategy to obtain a default value by the absent key - * @return this builder object back + * @see #defaultValue(Object) */ - public ChronicleMapBuilder defaultValueProvider( - DefaultValueProvider defaultValueProvider) { - this.defaultValueProvider = defaultValueProvider; - return this; - } - - - /** - * Non-public because should be called only after {@link #preMapConstruction()} - */ - DefaultValueProvider defaultValueProvider() { - if (defaultValueProvider != null) - return defaultValueProvider; - if (defaultValue == null) - return NullValueProvider.INSTANCE; - Object originalValueWriter = valueBuilder.interop(); - Provider writerProvider = Provider.of(originalValueWriter.getClass()); - ThreadLocalCopies copies = writerProvider.getCopies(null); - Object valueWriter = writerProvider.get(copies, originalValueWriter); - MetaProvider metaWriterProvider = valueBuilder.metaInteropProvider(); - copies = metaWriterProvider.getCopies(copies); - MetaBytesWriter metaValueWriter = metaWriterProvider.get(copies, - valueBuilder.metaInterop(), valueWriter, defaultValue); - return new ConstantValueProvider(defaultValue, metaValueWriter, valueWriter); - } - - public ChronicleMap createWithFile(File file) throws IOException { - for (int i = 0; i < 10; i++) { - if (file.exists() && file.length() > 0) { - FileInputStream fis = new FileInputStream(file); - ObjectInputStream ois = new ObjectInputStream(fis); - try { - VanillaChronicleMap map = - (VanillaChronicleMap) ois.readObject(); - map.headerSize = roundUpMapHeaderSize(fis.getChannel().position()); - map.createMappedStoreAndSegments(file); - return establishReplication(map); - } catch (ClassNotFoundException e) { - throw new IOException(e); - } finally { - ois.close(); - } - } - if (file.createNewFile() || file.length() == 0) { - break; - } - try { - Thread.sleep(100); - } catch (InterruptedException e) { - throw new IOException(e); - } - } - // new file - if (!file.exists()) - throw new FileNotFoundException("Unable to create " + file); - - VanillaChronicleMap map = newMap(); - - FileOutputStream fos = new FileOutputStream(file); - ObjectOutputStream oos = new ObjectOutputStream(fos); - try { - oos.writeObject(map); - oos.flush(); - map.headerSize = roundUpMapHeaderSize(fos.getChannel().position()); - map.createMappedStoreAndSegments(file); - } finally { - oos.close(); - } - - return establishReplication(map); - } - - @Override - public ChronicleMap create() throws IOException { - - if (statelessBuilder != null) - return createStatelessMap(statelessBuilder); - - return (file != null) ? createWithFile(file) : createWithoutFile(); + public ChronicleMapBuilder defaultValueProvider( + @NotNull DefaultValueProvider defaultValueProvider) { + return super.defaultValueProvider(defaultValueProvider); } - private ChronicleMap createWithoutFile() throws IOException { - VanillaChronicleMap map = newMap(); - BytesStore bytesStore = new DirectStore(JDKObjectSerializer.INSTANCE, - map.sizeInBytes(), true); - map.createMappedStoreAndSegments(bytesStore); - return establishReplication(map); + public ChronicleMapBuilder valueMarshaller( + @NotNull BytesMarshaller valueMarshaller) { + valueBuilder.marshaller(valueMarshaller, null); + return this; } - private ChronicleMap createStatelessMap(StatelessBuilder statelessBuilder) throws IOException { - preMapConstruction(); - - final KeyValueSerializer keyValueSerializer - = new KeyValueSerializer(keyBuilder, valueBuilder); - - final Class kClass = keyBuilder.eClass; - final Class vClass = valueBuilder.eClass; - - return new StatelessChronicleMap( - keyValueSerializer, - statelessBuilder, - entrySize(), - kClass, - vClass); - } - - private VanillaChronicleMap newMap() throws IOException { - preMapConstruction(); - if (useReplication()) { - return new ReplicatedChronicleMap, - V, Object, MetaBytesWriter>(this); - } else { - return new VanillaChronicleMap, - V, Object, MetaBytesWriter>(this); - } - } - - private boolean useReplication() { - return identifier != -1; - } - - void preMapConstruction() { - keyBuilder.objectSerializer(objectSerializer()); - valueBuilder.objectSerializer(objectSerializer()); - - long maxSize = (long) entrySize() * figureBufferAllocationFactor(); - keyBuilder.maxSize(maxSize); - valueBuilder.maxSize(maxSize); - - if (sampleKey != null) - keyBuilder.constantSizeBySample(sampleKey); - if (sampleValue != null) - valueBuilder.constantSizeBySample(sampleValue); - } - - private ChronicleMap establishReplication(ChronicleMap map) - throws IOException { - if (map instanceof ReplicatedChronicleMap) { - ReplicatedChronicleMap result = (ReplicatedChronicleMap) map; - for (Replicator replicator : replicators.values()) { - Closeable token = replicator.applyTo(this, result, result, map); - if (replicators.size() == 1 && token.getClass() == UdpReplicator.class) { - LOG.warn( - "MISSING TCP REPLICATION : The UdpReplicator only attempts to read data " + - "(it does not enforce or guarantee delivery), you should use" + - "the UdpReplicator if you have a large number of nodes, and you wish" + - "to receive the data before it becomes available on TCP/IP. Since data" + - "delivery is not guaranteed, it is recommended that you only use" + - "the UDP Replicator in conjunction with a TCP Replicator" - ); - } - result.addCloseable(token); - } - } - return map; - } - - private int figureBufferAllocationFactor() { - // if expected map size is about 1000, seems rather wasteful to allocate - // key and value serialization buffers each x64 of expected entry size.. - return (int) Math.min(Math.max(2L, entries() >> 10), - VanillaChronicleMap.MAX_ENTRY_OVERSIZE_FACTOR); - } - - public byte identifier() { - return identifier; + public ChronicleMapBuilder valueMarshaller( + @NotNull AgileBytesMarshaller valueMarshaller) { + valueBuilder.agileMarshaller(valueMarshaller, null); + return this; } } - diff --git a/src/main/java/net/openhft/chronicle/map/ConstantValueProvider.java b/src/main/java/net/openhft/chronicle/map/ConstantValueProvider.java index c4dedf426..d6f64e569 100644 --- a/src/main/java/net/openhft/chronicle/map/ConstantValueProvider.java +++ b/src/main/java/net/openhft/chronicle/map/ConstantValueProvider.java @@ -48,7 +48,7 @@ final class ConstantValueProvider implements DefaultValueProvider { } @Override - public V get(K key, V usingValue) { + public V get(K key) { return value; } } diff --git a/src/main/java/net/openhft/chronicle/map/DefaultValueProvider.java b/src/main/java/net/openhft/chronicle/map/DefaultValueProvider.java index f56d9428e..e21a6caef 100644 --- a/src/main/java/net/openhft/chronicle/map/DefaultValueProvider.java +++ b/src/main/java/net/openhft/chronicle/map/DefaultValueProvider.java @@ -22,26 +22,19 @@ import java.io.Serializable; /** * Default value computation strategy, used - * in {@link ChronicleMapBuilder#defaultValueProvider(DefaultValueProvider)} configuration. + * in {@link AbstractChronicleMapBuilder#defaultValueProvider(DefaultValueProvider)} configuration. * * @param map key class * @param map value class */ public interface DefaultValueProvider extends Serializable { /** - * Returns a value to be put and returned from {@link ChronicleMap#get(Object)} get()} or - * {@link ChronicleMap#getUsing(Object, Object)} call for the specified key, if it is absent - * in the map. - * - *

    If this method returns {@code null}, it isn't put for the key in the map. + * Returns a value to be put during {@link ChronicleMap#acquireUsing(Object, Object)} call + * for the specified key, if it is absent in the map. * * @param key key absent in the map - * @param usingValue an object provided as second parameter of current - * {@link ChronicleMap#getUsing(Object, Object)} call, or {@code null}, if this method is called - * within {@code get()} call. This object might be optionally initialized and returned back - * from this method. * @return value to be put for the specified key in the map, or {@code null}, if the key should * remain absent in the map */ - V get(K key, V usingValue); + V get(K key); } diff --git a/src/main/java/net/openhft/chronicle/map/MapEventListener.java b/src/main/java/net/openhft/chronicle/map/MapEventListener.java index c33986dbc..c7008e452 100644 --- a/src/main/java/net/openhft/chronicle/map/MapEventListener.java +++ b/src/main/java/net/openhft/chronicle/map/MapEventListener.java @@ -33,7 +33,7 @@ import java.io.Serializable; * class and override only methods corresponding the events you are interested in. * *

    To configure {@code MapEventListener} for {@code ChronicleMap}, use - * {@link ChronicleMapBuilder#eventListener(MapEventListener)} method. + * {@link AbstractChronicleMapBuilder#eventListener(MapEventListener)} method. * *

    {@link MapEventListeners} uninstantiable class contains several logging implementations and * the default {@linkplain MapEventListeners#nop() no-op implementation}. @@ -44,7 +44,7 @@ import java.io.Serializable; * @param key type of the maps, trackable by this event listener * @param value type of the maps, trackable by this event listener * @param {@code ChronicleMap} subtype, trackable by this event listener - * @see ChronicleMapBuilder#eventListener(MapEventListener) + * @see AbstractChronicleMapBuilder#eventListener(MapEventListener) * @see MapEventListeners */ public abstract class MapEventListener> @@ -75,7 +75,7 @@ public abstract class MapEventListener> * This method is call whenever a new value is put for the key in the map during calls of such * methods as {@link ChronicleMap#put put}, {@link ChronicleMap#putIfAbsent putIfAbsent}, * {@link ChronicleMap#replace(Object, Object, Object) replace}, etc. When a new value is - * {@linkplain ChronicleMapBuilder#defaultValue(Object) default} for the map or obtained during + * {@linkplain AbstractChronicleMapBuilder#defaultValue(Object) default} for the map or obtained during * {@link ChronicleMap#acquireUsing acquireUsing} call is put for the key, this method is called * as well. * diff --git a/src/main/java/net/openhft/chronicle/map/NullValueProvider.java b/src/main/java/net/openhft/chronicle/map/NullValueProvider.java index f37e7e185..9e79889e8 100644 --- a/src/main/java/net/openhft/chronicle/map/NullValueProvider.java +++ b/src/main/java/net/openhft/chronicle/map/NullValueProvider.java @@ -22,7 +22,7 @@ enum NullValueProvider implements DefaultValueProvider { INSTANCE; @Override - public Object get(Object key, Object usingValue) { + public Object get(Object key) { return null; } } diff --git a/src/main/java/net/openhft/chronicle/map/OffHeapUpdatableChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/OffHeapUpdatableChronicleMapBuilder.java new file mode 100644 index 000000000..50d0a1714 --- /dev/null +++ b/src/main/java/net/openhft/chronicle/map/OffHeapUpdatableChronicleMapBuilder.java @@ -0,0 +1,98 @@ +/* + * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com + * + * Licensed 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 net.openhft.chronicle.map; + +import net.openhft.lang.model.Byteable; +import net.openhft.lang.model.DataValueClasses; +import org.jetbrains.annotations.NotNull; + +public final class OffHeapUpdatableChronicleMapBuilder + extends AbstractChronicleMapBuilder> { + + public static OffHeapUpdatableChronicleMapBuilder of( + @NotNull Class keyClass, @NotNull Class valueClass) { + if (!Byteable.class.isAssignableFrom(valueClass)) { + if (!valueClass.isInterface()) { + throw new IllegalArgumentException( + "Value class should be either Byteable subclass or interface," + + "allowing direct Byteable implementation generation"); + } + valueClass = DataValueClasses.directClassFor(valueClass); + } + return new OffHeapUpdatableChronicleMapBuilder(keyClass, valueClass); + } + + OffHeapUpdatableChronicleMapBuilder(Class keyClass, Class valueClass) { + super(keyClass, valueClass); + prepareValueBytesOnAcquire(new ZeroOutValueBytes(valueSize())); + } + + @Override + OffHeapUpdatableChronicleMapBuilder self() { + return this; + } + + /** + * {@inheritDoc} Also, it overrides any previous {@link #prepareValueBytesOnAcquire} + * configuration to this {@code OffHeapUpdatableChronicleMapBuilder}. + * + *

    By default, the default value is not specified, default {@linkplain + * #prepareValueBytesOnAcquire prepare value bytes routine} is specified instead. + * + * @see #defaultValueProvider(DefaultValueProvider) + * @see #prepareValueBytesOnAcquire(PrepareValueBytes) + */ + @Override + public OffHeapUpdatableChronicleMapBuilder defaultValue(V defaultValue) { + return super.defaultValue(defaultValue); + } + + /** + * {@inheritDoc} Also, it overrides any previous {@link #prepareValueBytesOnAcquire} + * configuration to this {@code OffHeapUpdatableChronicleMapBuilder}. + * + *

    By default, the default value provider is not specified, default {@linkplain + * #prepareValueBytesOnAcquire prepare value bytes routine} is specified instead. + * + * @see #defaultValue(Object) + * @see #prepareValueBytesOnAcquire(PrepareValueBytes) + */ + @Override + public OffHeapUpdatableChronicleMapBuilder defaultValueProvider( + @NotNull DefaultValueProvider defaultValueProvider) { + return super.defaultValueProvider(defaultValueProvider); + } + + /** + * Configures the procedure which is called on the bytes, which later the returned value is + * pointing to, if the key is absent, on {@link ChronicleMap#acquireUsing(Object, Object) + * acquireUsing()} call on maps, created by this builder. + * + *

    The default preparation callback zeroes out the value bytes. + * + * @param prepareValueBytes what to do with the value bytes before assigning them into the + * {@link Byteable} value to return from {@code acquireUsing()} call + * @return this builder back + * @see #defaultValue(Object) + * @see #defaultValueProvider(DefaultValueProvider) + */ + @Override + OffHeapUpdatableChronicleMapBuilder prepareValueBytesOnAcquire( + @NotNull PrepareValueBytes prepareValueBytes) { + return super.prepareValueBytesOnAcquire(prepareValueBytes); + } +} diff --git a/src/main/java/net/openhft/chronicle/map/PrepareValueBytes.java b/src/main/java/net/openhft/chronicle/map/PrepareValueBytes.java new file mode 100644 index 000000000..bcaca2470 --- /dev/null +++ b/src/main/java/net/openhft/chronicle/map/PrepareValueBytes.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com + * + * Licensed 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 net.openhft.chronicle.map; + +import net.openhft.lang.io.Bytes; + +import java.io.Serializable; + +public interface PrepareValueBytes extends Serializable { + + void prepare(Bytes bytes, K key); +} diff --git a/src/main/java/net/openhft/chronicle/map/PrepareValueBytesAsWriter.java b/src/main/java/net/openhft/chronicle/map/PrepareValueBytesAsWriter.java new file mode 100644 index 000000000..7ed927c9d --- /dev/null +++ b/src/main/java/net/openhft/chronicle/map/PrepareValueBytesAsWriter.java @@ -0,0 +1,56 @@ +/* + * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com + * + * Licensed 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 net.openhft.chronicle.map; + +import net.openhft.chronicle.common.serialization.BytesWriter; +import net.openhft.chronicle.common.serialization.MetaBytesWriter; +import net.openhft.lang.io.Bytes; + +import java.io.Serializable; + +final class PrepareValueBytesAsWriter + implements BytesWriter, MetaBytesWriter>, + Serializable { + private static final long serialVersionUID = 0L; + private final PrepareValueBytes prepareValueBytes; + private final long valueSize; + + PrepareValueBytesAsWriter(PrepareValueBytes prepareValueBytes, long valueSize) { + this.prepareValueBytes = prepareValueBytes; + this.valueSize = valueSize; + } + + @Override + public long size(K k) { + return valueSize; + } + + @Override + public void write(Bytes bytes, K k) { + prepareValueBytes.prepare(bytes, k); + } + + @Override + public long size(PrepareValueBytesAsWriter writer, K k) { + return size(k); + } + + @Override + public void write(PrepareValueBytesAsWriter writer, Bytes bytes, K k) { + write(bytes, k); + } +} diff --git a/src/main/java/net/openhft/chronicle/map/ReplicatedChronicleMap.java b/src/main/java/net/openhft/chronicle/map/ReplicatedChronicleMap.java index 7b1618f47..9fecb2bfc 100644 --- a/src/main/java/net/openhft/chronicle/map/ReplicatedChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/ReplicatedChronicleMap.java @@ -106,7 +106,7 @@ class ReplicatedChronicleMap, private transient ModificationDelegator modificationDelegator; private transient long startOfModificationIterators; - public ReplicatedChronicleMap(@NotNull ChronicleMapBuilder builder) + public ReplicatedChronicleMap(@NotNull AbstractChronicleMapBuilder builder) throws IOException { super(builder); this.timeProvider = builder.timeProvider(); @@ -613,38 +613,39 @@ class ReplicatedChronicleMap, /** * @see VanillaChronicleMap.Segment#acquire */ - V acquire(ThreadLocalCopies copies, MKI metaKeyWriter, KI keyWriter, + V acquire(ThreadLocalCopies copies, MKI metaKeyInterop, KI keyInterop, K key, V usingValue, long hash2, boolean create, long timestamp) { if (create) writeLock(); else readLock(); try { - long keySize = metaKeyWriter.size(keyWriter, key); + long keySize = metaKeyInterop.size(keyInterop, key); MultiStoreBytes entry = acquireTmpBytes(); - long offset = searchKey(keyWriter, metaKeyWriter, key, keySize, hash2, entry, + long offset = searchKey(keyInterop, metaKeyInterop, key, keySize, hash2, entry, hashLookupLiveOnly); - if (offset >= 0) { - - // skip the timestamp, identifier and is deleted flag - entry.skip(ADDITIONAL_ENTRY_BYTES); - + if (offset >= 0L) { return onKeyPresentOnAcquire(copies, key, usingValue, offset, entry); } else { - boolean usingValuePassed = usingValue != null; - usingValue = tryObtainUsingValueOnAcquire(key, usingValue, create); - if (usingValue != null) { - // see VanillaChronicleMap.Segment.acquire() for explanation - // why `usingValue` is `create`. - offset = putEntryOnAcquire(copies, metaKeyWriter, keyWriter, key, keySize, - hash2, usingValue, create, timestamp); - incrementSize(); - if (usingValuePassed || !create) - notifyPut(offset, true, key, usingValue, posFromOffset(offset)); - return usingValue; - } else { + if (!create) return null; + if (defaultValueProvider != null) { + V defaultValue = defaultValueProvider.get(key); + copies = valueWriterProvider.getCopies(copies); + VW valueWriter = valueWriterProvider.get(copies, originalValueWriter); + copies = metaValueWriterProvider.getCopies(copies); + MetaBytesWriter metaValueWriter = metaValueWriterProvider.get( + copies, originalMetaValueWriter, valueWriter, defaultValue); + + offset = putEntry(metaKeyInterop, keyInterop, key, keySize, hash2, + localIdentifier, timestamp, hashLookupLiveOnly, + metaValueWriter, valueWriter, defaultValue); + } else { + offset = putEntry(metaKeyInterop, keyInterop, key, keySize, hash2, + localIdentifier, timestamp, hashLookupLiveOnly, + prepareValueBytesAsWriter, null, key); } + return onKeyAbsentOnAcquire(copies, key, keySize, usingValue, offset); } } finally { if (create) @@ -654,15 +655,11 @@ class ReplicatedChronicleMap, } } - private long putEntryOnAcquire(ThreadLocalCopies copies, - MKI metaKeyWriter, KI keyWriter, K key, - long keySize, long hash2, V value, boolean usingValue, - long timestamp) { - return putEntry(copies, metaKeyWriter, keyWriter, key, keySize, hash2, value, - usingValue, localIdentifier, timestamp, hashLookupLiveOnly); + @Override + void skipReplicationBytes(Bytes bytes) { + bytes.skip(ADDITIONAL_ENTRY_BYTES); } - /** * called from a remote node as part of replication */ @@ -858,7 +855,7 @@ class ReplicatedChronicleMap, // key is not found long offset = putEntry(copies, metaKeyWriter, keyWriter, key, keySize, hash2, value, - false, identifier, timestamp, hashLookup); + identifier, timestamp, hashLookup); incrementSize(); notifyPut(offset, true, key, value, posFromOffset(offset)); return null; @@ -905,9 +902,6 @@ class ReplicatedChronicleMap, * @param hash2 a hash of the {@code keyBytes}. Caller was searching for the * key in the {@code searchedHashLookup} using this hash. * @param value the value to put - * @param usingValue {@code true} if the value should be backed with the bytes of - * the entry, if it implements {@link Byteable} interface, {@code - * false} if it should put itself * @param identifier the identifier of the outer CHM node * @param timestamp the timestamp when the entry was put (this could be later if * it was a remote put) this method is called only from usual @@ -916,30 +910,25 @@ class ReplicatedChronicleMap, * @return offset of the written entry in the Segment bytes * @see VanillaChronicleMap.Segment#putEntry */ - private long putEntry(ThreadLocalCopies copies, MKI metaKeyWriter, - KI keyWriter, K key, long keySize, long hash2, V value, - boolean usingValue, final int identifier, final long timestamp, - MultiMap searchedHashLookup) { + private long putEntry(ThreadLocalCopies copies, + MKI metaKeyInterop, KI keyInterop, K key, long keySize, long hash2, + V value, + int identifier, long timestamp, MultiMap searchedHashLookup) { + copies = valueWriterProvider.getCopies(copies); + VW valueWriter = valueWriterProvider.get(copies, originalValueWriter); + copies = metaValueWriterProvider.getCopies(copies); + MetaBytesWriter metaValueWriter = metaValueWriterProvider.get( + copies, originalMetaValueWriter, valueWriter, value); - // "if-else polymorphism" is not very beautiful, but allows to - // reuse the rest code of this method and doesn't hurt performance. - boolean byteableValue = usingValue && value instanceof Byteable; - long valueSize; - MetaBytesWriter metaValueWriter = null; - VW valueWriter = null; - Byteable valueAsByteable = null; - if (!byteableValue) { - copies = valueWriterProvider.getCopies(copies); - valueWriter = valueWriterProvider.get(copies, originalValueWriter); - copies = metaValueWriterProvider.getCopies(copies); - metaValueWriter = metaValueWriterProvider.get( - copies, originalMetaValueWriter, valueWriter, value); - valueSize = metaValueWriter.size(valueWriter, value); - } else { - valueAsByteable = (Byteable) value; - valueSize = valueAsByteable.maxSize(); - } + return putEntry(metaKeyInterop, keyInterop, key, keySize, hash2, + identifier, timestamp, searchedHashLookup, metaValueWriter, valueWriter, value); + } + private long putEntry( + MKI metaKeyInterop, KI keyInterop, K key, long keySize, long hash2, + final int identifier, final long timestamp, MultiMap searchedHashLookup, + MetaBytesWriter metaElemWriter, EW elemWriter, E elem) { + long valueSize = metaElemWriter.size(elemWriter, elem); long entrySize = entrySize(keySize, valueSize); long pos = alloc(inBlocks(entrySize)); long offset = offsetFromPos(pos); @@ -947,14 +936,15 @@ class ReplicatedChronicleMap, NativeBytes entry = entry(offset); keySizeMarshaller.writeSize(entry, keySize); - metaKeyWriter.write(keyWriter, entry, key); + metaKeyInterop.write(keyInterop, entry, key); entry.writeLong(timestamp); entry.writeByte(identifier); entry.writeBoolean(false); - writeValueOnPutEntry(valueSize, metaValueWriter, valueWriter, value, valueAsByteable, - entry); + valueSizeMarshaller.writeSize(entry, valueSize); + alignment.alignPositionAddr(entry); + metaElemWriter.write(elemWriter, entry, elem); // we have to add it both to the live if (searchedHashLookup == hashLookupLiveAndDeleted) { @@ -1573,7 +1563,7 @@ abstract class Replicator { * already been applied to a map (or the specified number of * maps) */ - protected abstract Closeable applyTo(ChronicleMapBuilder builder, + protected abstract Closeable applyTo(AbstractChronicleMapBuilder builder, Replica map, Replica.EntryExternalizable entryExternalizable, final ChronicleMap chronicleMap) throws IOException; @@ -1589,7 +1579,7 @@ final class Replicators { return new Replicator() { @Override - protected Closeable applyTo(ChronicleMapBuilder builder, + protected Closeable applyTo(AbstractChronicleMapBuilder builder, Replica replica, Replica.EntryExternalizable entryExternalizable, final ChronicleMap chronicleMap) throws IOException { @@ -1610,7 +1600,7 @@ final class Replicators { return new Replicator() { @Override - protected Closeable applyTo(ChronicleMapBuilder builder, + protected Closeable applyTo(AbstractChronicleMapBuilder builder, Replica map, Replica.EntryExternalizable entryExternalizable, final ChronicleMap chronicleMap) throws IOException { return new UdpReplicator(map, entryExternalizable, replicationConfig, diff --git a/src/main/java/net/openhft/chronicle/map/SerializationBuilder.java b/src/main/java/net/openhft/chronicle/map/SerializationBuilder.java index a235cc686..47f52f9c3 100644 --- a/src/main/java/net/openhft/chronicle/map/SerializationBuilder.java +++ b/src/main/java/net/openhft/chronicle/map/SerializationBuilder.java @@ -78,12 +78,9 @@ final class SerializationBuilder implements Cloneable { this.eClass = eClass; instancesAreMutable = instancesAreMutable(eClass); - ObjectFactory factory = null; - if (role == Role.VALUE) { - factory = concreteClass(eClass) && marshallerUseFactory(eClass) ? - new NewInstanceObjectFactory(eClass) : - (ObjectFactory) NullObjectFactory.of(); - } + ObjectFactory factory = concreteClass(eClass) && marshallerUseFactory(eClass) ? + new NewInstanceObjectFactory(eClass) : + NullObjectFactory.of(); if (concreteClass(eClass) && Byteable.class.isAssignableFrom(eClass)) { agileMarshaller(ByteableMarshaller.of((Class) eClass), factory); @@ -109,7 +106,7 @@ final class SerializationBuilder implements Cloneable { if (marshaller != null) marshaller(marshaller, factory); } - if (role == Role.VALUE && concreteClass(eClass) && marshallerUseFactory(eClass)) { + if (concreteClass(eClass) && marshallerUseFactory(eClass)) { factory(factory); } } diff --git a/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java b/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java index 50e2beaae..03151ecc3 100644 --- a/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java @@ -390,12 +390,12 @@ class StatelessChronicleMap implements ChronicleMap, Closeable { } - public synchronized V getUsing(K key, V value) { + public synchronized V getUsing(K key, V usingValue) { throw new UnsupportedOperationException("getUsing is not supported for stateless clients"); } - public synchronized V acquireUsing(K key, V value) { + public synchronized V acquireUsing(K key, V usingValue) { throw new UnsupportedOperationException("getUsing is not supported for stateless clients"); } diff --git a/src/main/java/net/openhft/chronicle/map/UdpReplicator.java b/src/main/java/net/openhft/chronicle/map/UdpReplicator.java index 4af947ed1..8bd9b889e 100644 --- a/src/main/java/net/openhft/chronicle/map/UdpReplicator.java +++ b/src/main/java/net/openhft/chronicle/map/UdpReplicator.java @@ -49,7 +49,7 @@ class UdpReplicator extends UdpChannelReplicator implements Replica.Modification super(replicationConfig, serializedEntrySize, replica.identifier()); Replica.ModificationIterator modificationIterator = replica.acquireModificationIterator( - ChronicleMapBuilder.UDP_REPLICATION_MODIFICATION_ITERATOR_ID, this); + AbstractChronicleMapBuilder.UDP_REPLICATION_MODIFICATION_ITERATOR_ID, this); setReader(new UdpSocketChannelEntryReader(serializedEntrySize, entryExternalizable)); diff --git a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java index b0b386d6f..8eed059e9 100644 --- a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java @@ -22,7 +22,6 @@ import net.openhft.chronicle.common.ChronicleHashErrorListener; import net.openhft.chronicle.common.serialization.*; import net.openhft.chronicle.common.threadlocal.Provider; import net.openhft.chronicle.common.threadlocal.ThreadLocalCopies; -import net.openhft.lang.Maths; import net.openhft.lang.collection.DirectBitSet; import net.openhft.lang.collection.SingleThreadedDirectBitSet; import net.openhft.lang.io.*; @@ -77,6 +76,7 @@ class VanillaChronicleMap, final MetaProvider metaValueWriterProvider; final ObjectFactory valueFactory; final DefaultValueProvider defaultValueProvider; + final PrepareValueBytesAsWriter prepareValueBytesAsWriter; final int metaDataBytes; // private final int replicas; @@ -100,7 +100,7 @@ class VanillaChronicleMap, private final HashSplitting hashSplitting; - public VanillaChronicleMap(ChronicleMapBuilder builder) throws IOException { + public VanillaChronicleMap(AbstractChronicleMapBuilder builder) throws IOException { SerializationBuilder keyBuilder = builder.keyBuilder; kClass = keyBuilder.eClass; @@ -122,6 +122,7 @@ class VanillaChronicleMap, metaValueWriterProvider = (MetaProvider) valueBuilder.metaInteropProvider(); valueFactory = valueBuilder.factory(); defaultValueProvider = builder.defaultValueProvider(); + prepareValueBytesAsWriter = builder.prepareValueBytesAsWriter(); lockTimeOutNS = builder.lockTimeOut(TimeUnit.NANOSECONDS); @@ -355,13 +356,13 @@ class VanillaChronicleMap, } @Override - public V getUsing(K key, V value) { - return lookupUsing(key, value, false); + public V getUsing(K key, V usingValue) { + return lookupUsing(key, usingValue, false); } @Override - public V acquireUsing(K key, V value) { - return lookupUsing(key, value, true); + public V acquireUsing(K key, V usingValue) { + return lookupUsing(key, usingValue, true); } V lookupUsing(K key, V value, boolean create) { @@ -712,18 +713,6 @@ class VanillaChronicleMap, return (int) (sizeInBytes / entrySize) + 1; } - /** - * Used to acquire an object of type V from the Segment. {@code usingValue} is reused to read the - * value if key is present in this Segment, if key is absent in this Segment:

    1. If {@code - * create == false}, just {@code null} is returned (except when event listener provides a value "on - * get missing" - then it is put into this Segment for the key).
    2. If {@code create == - * true}, {@code usingValue} or a newly created instance of value class, if {@code usingValue == - * null}, is put into this Segment for the key.
    - * - * @param hash2 a hash code related to the {@code keyBytes} - * @return the value which is finally associated with the given key in this Segment after execution of - * this method, or {@code null}. - */ V acquire(ThreadLocalCopies copies, MKI metaKeyInterop, KI keyInterop, K key, V usingValue, long hash2, boolean create) { if (create) @@ -738,22 +727,23 @@ class VanillaChronicleMap, if (offset >= 0L) { return onKeyPresentOnAcquire(copies, key, usingValue, offset, entry); } else { - boolean usingValuePassed = usingValue != null; - usingValue = tryObtainUsingValueOnAcquire(key, usingValue, create); - if (usingValue != null) { - // If `create` is false, this method was called from get() or getUsing() - // and non-null `usingValue` was returned by notifyMissed() method. - // This "missed" default value is considered as genuine value - // rather than "using" container to fill up, even if it implements Byteable. - offset = putEntry(copies, metaKeyInterop, keyInterop, key, keySize, - usingValue, create); - incrementSize(); - if (usingValuePassed || !create) - notifyPut(offset, true, key, usingValue, posFromOffset(offset)); - return usingValue; - } else { + if (!create) return null; + if (defaultValueProvider != null) { + V defaultValue = defaultValueProvider.get(key); + copies = valueWriterProvider.getCopies(copies); + VW valueWriter = valueWriterProvider.get(copies, originalValueWriter); + copies = metaValueWriterProvider.getCopies(copies); + MetaBytesWriter metaValueWriter = metaValueWriterProvider.get( + copies, originalMetaValueWriter, valueWriter, defaultValue); + + offset = putEntry(metaKeyInterop, keyInterop, key, keySize, + metaValueWriter, valueWriter, defaultValue); + } else { + offset = putEntry(metaKeyInterop, keyInterop, key, keySize, + prepareValueBytesAsWriter, null, key); } + return onKeyAbsentOnAcquire(copies, key, keySize, usingValue, offset); } } finally { if (create) @@ -781,36 +771,25 @@ class VanillaChronicleMap, V onKeyPresentOnAcquire(ThreadLocalCopies copies, K key, V usingValue, long offset, MultiStoreBytes entry) { + skipReplicationBytes(entry); V v = readValue(copies, entry, usingValue); notifyGet(offset, key, v); return v; } - V tryObtainUsingValueOnAcquire(K key, V usingValue, boolean create) { - if (create) { - if (usingValue != null) { - return usingValue; - } else { - try { - usingValue = valueFactory.create(); - if (usingValue == null) { - throw new IllegalStateException("acquireUsing() method requires" + - "valueFactory.create() result to be non-null. " + - "By default it is so when value class is" + - "a Byteable/BytesMarshallable/Externalizable subclass." + - "Note that acquireUsing() anyway makes very little sense " + - "when value class is not a Byteable subclass."); - } - return usingValue; - } catch (Exception e) { - throw new IllegalStateException(e); - } - } - } else { - if (usingValue instanceof Byteable) - ((Byteable) usingValue).bytes(null, 0L); - return defaultValueProvider.get(key, usingValue); - } + V onKeyAbsentOnAcquire(ThreadLocalCopies copies, K key, long keySize, V usingValue, + long offset) { + NativeBytes entry = entry(offset); + entry.skip(keySizeMarshaller.sizeEncodingSize(keySize) + keySize); + skipReplicationBytes(entry); + V v = readValue(copies, entry, usingValue); + notifyPut(offset, true, key, v, posFromOffset(offset)); + notifyGet(offset, key, v); + return v; + } + + void skipReplicationBytes(Bytes bytes) { + // don't skip } V put(ThreadLocalCopies copies, MKI metaKeyInterop, KI keyInterop, K key, V value, @@ -834,8 +813,7 @@ class VanillaChronicleMap, } } // key is not found - long offset = putEntry(copies, metaKeyInterop, keyInterop, key, keySize, value, - false); + long offset = putEntry(copies, metaKeyInterop, keyInterop, key, keySize, value); incrementSize(); notifyPut(offset, true, key, value, posFromOffset(offset)); return null; @@ -860,37 +838,22 @@ class VanillaChronicleMap, return prevValue; } - /** - * Puts entry. If {@code value} implements {@link Byteable} interface and {@code usingValue} is {@code - * true}, the value is backed with the bytes of this entry. - * - * @param value the value to put - * @param usingValue {@code true} if the value should be backed with the bytes of the entry, if it - * implements {@link Byteable} interface, {@code false} if it should put itself - * @return offset of the written entry in the Segment bytes - */ private long putEntry(ThreadLocalCopies copies, MKI metaKeyInterop, KI keyInterop, K key, - long keySize, V value, boolean usingValue) { + long keySize, V value) { + copies = valueWriterProvider.getCopies(copies); + VW valueWriter = valueWriterProvider.get(copies, originalValueWriter); + copies = metaValueWriterProvider.getCopies(copies); + MetaBytesWriter metaValueWriter = metaValueWriterProvider.get( + copies, originalMetaValueWriter, valueWriter, value); - // "if-else polymorphism" is not very beautiful, but allows to - // reuse the rest code of this method and doesn't hurt performance. - boolean byteableValue = usingValue && value instanceof Byteable; - long valueSize; - MetaBytesWriter metaValueWriter = null; - VW valueWriter = null; - Byteable valueAsByteable = null; - if (!byteableValue) { - copies = valueWriterProvider.getCopies(copies); - valueWriter = valueWriterProvider.get(copies, originalValueWriter); - copies = metaValueWriterProvider.getCopies(copies); - metaValueWriter = metaValueWriterProvider.get( - copies, originalMetaValueWriter, valueWriter, value); - valueSize = metaValueWriter.size(valueWriter, value); - } else { - valueAsByteable = (Byteable) value; - valueSize = valueAsByteable.maxSize(); - } + return putEntry(metaKeyInterop, keyInterop, key, keySize, + metaValueWriter, valueWriter, value); + } + private long putEntry(MKI metaKeyInterop, KI keyInterop, K key, long keySize, + MetaBytesWriter metaElemWriter, EW elemWriter, + E elem) { + long valueSize = metaElemWriter.size(elemWriter, elem); long entrySize = entrySize(keySize, valueSize); long pos = alloc(inBlocks(entrySize)); long offset = offsetFromPos(pos); @@ -899,27 +862,13 @@ class VanillaChronicleMap, keySizeMarshaller.writeSize(entry, keySize); metaKeyInterop.write(keyInterop, entry, key); - - writeValueOnPutEntry(valueSize, metaValueWriter, valueWriter, value, valueAsByteable, - entry); - hashLookup.putAfterFailedSearch(pos); - return offset; - } - - void writeValueOnPutEntry(long valueSize, MetaBytesWriter metaValueWriter, - VW valueWriter, V value, Byteable valueAsByteable, - NativeBytes entry) { valueSizeMarshaller.writeSize(entry, valueSize); alignment.alignPositionAddr(entry); + metaElemWriter.write(elemWriter, entry, elem); - if (metaValueWriter != null) { - metaValueWriter.write(valueWriter, entry, value); - } else { - assert valueAsByteable != null; - long valueOffset = entry.positionAddr() - bytes.address(); - bytes.zeroOut(valueOffset, valueOffset + valueSize); - valueAsByteable.bytes(bytes, valueOffset); - } + hashLookup.putAfterFailedSearch(pos); + + return offset; } void clearMetaData(long offset) { diff --git a/src/main/java/net/openhft/chronicle/map/ZeroOutValueBytes.java b/src/main/java/net/openhft/chronicle/map/ZeroOutValueBytes.java new file mode 100644 index 000000000..67a4ea96f --- /dev/null +++ b/src/main/java/net/openhft/chronicle/map/ZeroOutValueBytes.java @@ -0,0 +1,34 @@ +/* + * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com + * + * Licensed 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 net.openhft.chronicle.map; + +import net.openhft.lang.io.Bytes; + +final class ZeroOutValueBytes implements PrepareValueBytes { + private static final long serialVersionUID = 0L; + private final long valueSize; + + ZeroOutValueBytes(long valueSize) { + this.valueSize = valueSize; + } + + @Override + public void prepare(Bytes bytes, K key) { + long pos = bytes.position(); + bytes.zeroOut(pos, pos + valueSize); + } +} diff --git a/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java b/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java index e81221168..6bf45441d 100644 --- a/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java +++ b/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java @@ -25,6 +25,7 @@ import net.openhft.chronicle.common.TimeProvider; import net.openhft.chronicle.map.*; import net.openhft.lang.io.serialization.BytesMarshaller; import net.openhft.lang.io.serialization.BytesMarshallerFactory; +import net.openhft.lang.io.serialization.ObjectFactory; import net.openhft.lang.io.serialization.ObjectSerializer; import net.openhft.lang.io.serialization.impl.NullObjectFactory; import org.jetbrains.annotations.NotNull; @@ -52,8 +53,7 @@ public class ChronicleSetBuilder ChronicleSetBuilder(Class keyClass) { chronicleMapBuilder = ChronicleMapBuilder.of(keyClass, DummyValue.class) .entryAndValueAlignment(Alignment.NO_ALIGNMENT) - .valueMarshallerAndFactory(DummyValueMarshaller.INSTANCE, - NullObjectFactory.of()); + .valueMarshaller(DummyValueMarshaller.INSTANCE); } public static ChronicleSetBuilder of(Class keyClass) { @@ -244,6 +244,23 @@ public class ChronicleSetBuilder return this; } + /** + * {@inheritDoc} + * + *

    Actually this is just a convenience method supporting key marshaller configurations, made + * initially during {@link #of(Class)} call. Because if you {@linkplain + * #keyMarshaller(BytesMarshaller) configure} own custom key marshaller, this method doesn't + * take any effect on the maps constructed by this builder. + * + * @see #of(Class) + */ + @Override + public ChronicleSetBuilder keyDeserializationFactory( + @NotNull ObjectFactory keyDeserializationFactory) { + chronicleMapBuilder.keyDeserializationFactory(keyDeserializationFactory); + return this; + } + @Override public ChronicleSetBuilder immutableKeys() { chronicleMapBuilder.immutableKeys(); diff --git a/src/main/java/net/openhft/chronicle/set/package-info.java b/src/main/java/net/openhft/chronicle/set/package-info.java new file mode 100644 index 000000000..181cfdf71 --- /dev/null +++ b/src/main/java/net/openhft/chronicle/set/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com + * + * Licensed 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. + */ + +/** + * Contains {@link net.openhft.chronicle.set.ChronicleSet} interface and + * {@link net.openhft.chronicle.set.ChronicleSetBuilder} class. + */ +package net.openhft.chronicle.set; \ No newline at end of file diff --git a/src/test/java/net/openhft/chronicle/map/AcquireGetUsingMain.java b/src/test/java/net/openhft/chronicle/map/AcquireGetUsingMain.java index db8ae2328..befa4148a 100644 --- a/src/test/java/net/openhft/chronicle/map/AcquireGetUsingMain.java +++ b/src/test/java/net/openhft/chronicle/map/AcquireGetUsingMain.java @@ -29,7 +29,7 @@ import static org.junit.Assert.fail; public class AcquireGetUsingMain { public static void main(String[] args) throws Exception { File file = new File(System.getProperty("java.io.tmpdir") + "/test1"); - ChronicleMap theSharedMap = ChronicleMapBuilder.of(String.class, Data.class).file(file).create(); + ChronicleMap theSharedMap = OffHeapUpdatableChronicleMapBuilder.of(String.class, Data.class).file(file).create(); Data data = DataValueClasses.newDirectReference(Data.class); String processType = "testkey"; if (theSharedMap.getUsing(processType, data) == null) { diff --git a/src/test/java/net/openhft/chronicle/map/CHMLatencyTestMain.java b/src/test/java/net/openhft/chronicle/map/CHMLatencyTestMain.java index 6b22de110..a9e8c3fd6 100644 --- a/src/test/java/net/openhft/chronicle/map/CHMLatencyTestMain.java +++ b/src/test/java/net/openhft/chronicle/map/CHMLatencyTestMain.java @@ -75,7 +75,7 @@ public class CHMLatencyTestMain { File file = File.createTempFile("testCHMLatency", "deleteme"); // File file = new File("testCHMLatency.deleteme"); file.delete(); - ChronicleMap countersMap = ChronicleMapBuilder.of(LongValue.class, LongValue.class) + ChronicleMap countersMap = OffHeapUpdatableChronicleMapBuilder.of(LongValue.class, LongValue.class) .entries(KEYS) .entrySize(24).create(); diff --git a/src/test/java/net/openhft/chronicle/map/CHMTest5.java b/src/test/java/net/openhft/chronicle/map/CHMTest5.java index 7520aeb13..948d1a796 100644 --- a/src/test/java/net/openhft/chronicle/map/CHMTest5.java +++ b/src/test/java/net/openhft/chronicle/map/CHMTest5.java @@ -34,8 +34,8 @@ public class CHMTest5 { public static void main(String[] args) throws Exception { //First create (or access if already created) the shared map - ChronicleMapBuilder builder = - ChronicleMapBuilder.of(String.class, CHMTest5Data.class) + OffHeapUpdatableChronicleMapBuilder builder = + OffHeapUpdatableChronicleMapBuilder.of(String.class, CHMTest5Data.class) .entries(1000); //// don't include this, just to check it is as expected. diff --git a/src/test/java/net/openhft/chronicle/map/ChronicleMapTest.java b/src/test/java/net/openhft/chronicle/map/ChronicleMapTest.java index 5dfd1d01f..a475bb424 100644 --- a/src/test/java/net/openhft/chronicle/map/ChronicleMapTest.java +++ b/src/test/java/net/openhft/chronicle/map/ChronicleMapTest.java @@ -110,13 +110,12 @@ public class ChronicleMapTest { private static ChronicleMap getSharedMap( long entries, int segments, int entrySize, Alignment alignment) throws IOException { - return ChronicleMapBuilder.of(CharSequence.class, LongValue.class) + return OffHeapUpdatableChronicleMapBuilder.of(CharSequence.class, LongValue.class) .entries(entries) .minSegments(segments) .entrySize(entrySize) .entryAndValueAlignment(alignment) - .valueMarshallerAndFactory(ByteableLongValueMarshaller.INSTANCE, - DirectLongValueFactory.INSTANCE).create(); + .create(); } private static void printStatus() { @@ -578,13 +577,11 @@ public class ChronicleMapTest { // int runs = Integer.getInteger("runs", 10); for (int runs : new int[]{10, 50, 250, 500, 1000, 2500}) { final long entries = runs * 1000 * 1000L; - ChronicleMapBuilder builder = ChronicleMapBuilder + OffHeapUpdatableChronicleMapBuilder builder = OffHeapUpdatableChronicleMapBuilder .of(CharSequence.class, IntValue.class) .entries(entries) .minSegments(1024) - .entrySize(20) - .valueMarshallerAndFactory(ByteableIntValueMarshaller.INSTANCE, - DirectIntValueFactory.INSTANCE); + .entrySize(20); final ChronicleMap map = builder.create(); diff --git a/src/test/java/net/openhft/chronicle/map/EntryCountMapTest.java b/src/test/java/net/openhft/chronicle/map/EntryCountMapTest.java index c6ca7641d..1440e0305 100644 --- a/src/test/java/net/openhft/chronicle/map/EntryCountMapTest.java +++ b/src/test/java/net/openhft/chronicle/map/EntryCountMapTest.java @@ -18,6 +18,7 @@ package net.openhft.chronicle.map; +import net.openhft.lang.model.DataValueClasses; import net.openhft.lang.values.LongValue; import org.junit.Ignore; import org.junit.Test; @@ -28,6 +29,7 @@ import java.util.Arrays; import java.util.Random; import static net.openhft.chronicle.map.Alignment.OF_4_BYTES; +import static net.openhft.lang.model.DataValueClasses.newDirectReference; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -138,7 +140,7 @@ public class EntryCountMapTest { private void testEntriesMaxSize(int segments, int minSize, int maxSize, int seed) throws IOException { int counter = minSize + new Random(seed + segments * 1000).nextInt(9999); ChronicleMap map = getSharedMap(minSize, segments, 32); - LongValue longValue = DirectLongValueFactory.INSTANCE.create(); + LongValue longValue = newDirectReference(LongValue.class); try { for (int j = 0; j < maxSize * 12 / 10 + 300; j++) { String key = "key:" + counter++; @@ -195,13 +197,11 @@ public class EntryCountMapTest { private static ChronicleMap getSharedMap( long entries, int segments, int entrySize, Alignment alignment) throws IOException { - ChronicleMapBuilder mapBuilder = ChronicleMapBuilder.of(CharSequence.class, LongValue.class) + OffHeapUpdatableChronicleMapBuilder mapBuilder = OffHeapUpdatableChronicleMapBuilder.of(CharSequence.class, LongValue.class) .entries(entries) .minSegments(segments) .entrySize(entrySize) .entryAndValueAlignment(alignment) - .valueMarshallerAndFactory(ByteableLongValueMarshaller.INSTANCE, - DirectLongValueFactory.INSTANCE) .file(getPersistenceFile()); return mapBuilder.create(); } diff --git a/src/test/java/net/openhft/chronicle/map/ReplicationCheckingMap.java b/src/test/java/net/openhft/chronicle/map/ReplicationCheckingMap.java index 886219c97..255308c93 100644 --- a/src/test/java/net/openhft/chronicle/map/ReplicationCheckingMap.java +++ b/src/test/java/net/openhft/chronicle/map/ReplicationCheckingMap.java @@ -242,13 +242,13 @@ public class ReplicationCheckingMap implements ChronicleMap { } @Override - public V getUsing(K key, V value) { - return map1.getUsing(key, value); + public V getUsing(K key, V usingValue) { + return map1.getUsing(key, usingValue); } @Override - public V acquireUsing(K key, V value) { - return map1.acquireUsing(key, value); + public V acquireUsing(K key, V usingValue) { + return map1.acquireUsing(key, usingValue); } @Override diff --git a/src/test/java/net/openhft/chronicle/map/fromdocs/OpenJDKAndHashMapExamplesTest.java b/src/test/java/net/openhft/chronicle/map/fromdocs/OpenJDKAndHashMapExamplesTest.java index 4cb1b3a38..35dc2aa58 100644 --- a/src/test/java/net/openhft/chronicle/map/fromdocs/OpenJDKAndHashMapExamplesTest.java +++ b/src/test/java/net/openhft/chronicle/map/fromdocs/OpenJDKAndHashMapExamplesTest.java @@ -21,6 +21,7 @@ package net.openhft.chronicle.map.fromdocs; import net.openhft.affinity.AffinitySupport; import net.openhft.chronicle.map.ChronicleMap; import net.openhft.chronicle.map.ChronicleMapBuilder; +import net.openhft.chronicle.map.OffHeapUpdatableChronicleMapBuilder; import net.openhft.lang.model.DataValueClasses; import org.junit.Test; @@ -55,8 +56,8 @@ public class OpenJDKAndHashMapExamplesTest { File file = new File(TMP + "/chm-myBondPortfolioCHM-" + System.nanoTime()); file.deleteOnExit(); - ChronicleMap chm = ChronicleMapBuilder - .of(String.class, directClassFor(BondVOInterface.class)) + ChronicleMap chm = OffHeapUpdatableChronicleMapBuilder + .of(String.class, BondVOInterface.class) .keySize(10) .file(file) .create(); @@ -76,8 +77,8 @@ public class OpenJDKAndHashMapExamplesTest { mpx1030.setAskPx(109.7); mpx1030.setBidPx(107.6); - ChronicleMap chmB = ChronicleMapBuilder - .of(String.class, directClassFor(BondVOInterface.class)) + ChronicleMap chmB = OffHeapUpdatableChronicleMapBuilder + .of(String.class, BondVOInterface.class) .keySize(10) .file(file) .create(); diff --git a/src/test/java/net/openhft/chronicle/map/jrs166/map/StatelessChronicleMapTest.java b/src/test/java/net/openhft/chronicle/map/jrs166/map/StatelessChronicleMapTest.java index 04dada102..520c2d31e 100644 --- a/src/test/java/net/openhft/chronicle/map/jrs166/map/StatelessChronicleMapTest.java +++ b/src/test/java/net/openhft/chronicle/map/jrs166/map/StatelessChronicleMapTest.java @@ -140,13 +140,13 @@ public class StatelessChronicleMapTest extends JSR166TestCase { } @Override - public V getUsing(K key, V value) { - return d.getUsing(key, value); + public V getUsing(K key, V usingValue) { + return d.getUsing(key, usingValue); } @Override - public V acquireUsing(K key, V value) { - return d.acquireUsing(key, value); + public V acquireUsing(K key, V usingValue) { + return d.acquireUsing(key, usingValue); } @Override From a20ccd97c0c3c4d1bd618acbb9e20e20ba8b1d34 Mon Sep 17 00:00:00 2001 From: leventov Date: Mon, 27 Oct 2014 20:35:09 +0300 Subject: [PATCH 04/15] HCOLL-160 fixes after the prev commit --- .../java/net/openhft/chronicle/map/VanillaChronicleMap.java | 3 ++- .../java/net/openhft/chronicle/map/DefaultValueTest.java | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java index 8eed059e9..705fa0ebc 100644 --- a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java @@ -779,7 +779,8 @@ class VanillaChronicleMap, V onKeyAbsentOnAcquire(ThreadLocalCopies copies, K key, long keySize, V usingValue, long offset) { - NativeBytes entry = entry(offset); + incrementSize(); + MultiStoreBytes entry = entry(offset); entry.skip(keySizeMarshaller.sizeEncodingSize(keySize) + keySize); skipReplicationBytes(entry); V v = readValue(copies, entry, usingValue); diff --git a/src/test/java/net/openhft/chronicle/map/DefaultValueTest.java b/src/test/java/net/openhft/chronicle/map/DefaultValueTest.java index ddcbcb58a..249b017ad 100644 --- a/src/test/java/net/openhft/chronicle/map/DefaultValueTest.java +++ b/src/test/java/net/openhft/chronicle/map/DefaultValueTest.java @@ -41,17 +41,17 @@ public class DefaultValueTest { .of(String.class, (Class>) ((Class) List.class)) .defaultValue(defaultValue).file(file).create()) { - assertEquals(defaultValue, map.get("a")); + assertEquals(defaultValue, map.acquireUsing("a", null)); assertEquals(1, map.size()); map.put("b", Arrays.asList(1, 2)); - assertEquals(Arrays.asList(1, 2), map.get("b")); + assertEquals(Arrays.asList(1, 2), map.acquireUsing("b", null)); } try (ChronicleMap> map = ChronicleMapBuilder .of(String.class, (Class>) ((Class) List.class)).file(file). create()) { - assertEquals(defaultValue, map.get("c")); + assertEquals(defaultValue, map.acquireUsing("c", null)); } } finally { file.delete(); From fcb80f592157fb2fc20f1575fde7010ae3b3acc7 Mon Sep 17 00:00:00 2001 From: Rob Austin Date: Mon, 27 Oct 2014 17:38:58 +0000 Subject: [PATCH 05/15] HCOLL-167 finally got chunking to work but it ugly - requires a bit of refactoring. --- .../chronicle/map/StatelessChronicleMap.java | 99 ++++++++++++------- .../openhft/chronicle/map/TcpReplicator.java | 8 +- .../chronicle/map/StatelessClientTest.java | 2 +- 3 files changed, 72 insertions(+), 37 deletions(-) diff --git a/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java b/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java index 50e2beaae..f39ab8d38 100644 --- a/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java @@ -559,7 +559,6 @@ class StatelessChronicleMap implements ChronicleMap, Closeable { assert bytes.limit() == bytes.capacity(); - } @@ -605,25 +604,38 @@ class StatelessChronicleMap implements ChronicleMap, Closeable { public synchronized Set> entrySet() { long sizeLocation = writeEvent(ENTRY_SET); + long startTime = System.currentTimeMillis(); + long transactionId = nextUniqueTransaction(startTime); + + // get the data back from the server - final Bytes in = blockingFetch(sizeLocation); + Bytes in = blockingFetch0(sizeLocation, transactionId, startTime); + final Set> result = new HashSet>(); boolean hasMoreEntries; do { hasMoreEntries = in.readBoolean(); - + LOG.info("entrySet - hasMoreEntries=" + hasMoreEntries); // number of entries in the chunk - long size = in.readUnsignedShort(); + long size = in.readInt(); - LOG.info("size=" + size); + LOG.info("entrySet - size=" + size); for (int i = 0; i < size; i++) { K k = keyValueSerializer.readKey(in); V v = keyValueSerializer.readValue(in); + // LOG.info("v="+v); result.add(new Entry(k, v)); } + if (hasMoreEntries) + try { + in = blockingFetch(System.currentTimeMillis() + builder.timeoutMs(), transactionId); + } catch (IOException e) { + e.printStackTrace(); + } + } while (hasMoreEntries); return result; @@ -697,7 +709,9 @@ class StatelessChronicleMap implements ChronicleMap, Closeable { private Bytes blockingFetch(long sizeLocation) { try { - return blockingFetchThrowable(sizeLocation, this.builder.timeoutMs()); + long startTime = System.currentTimeMillis(); + return blockingFetchThrowable(sizeLocation, this.builder.timeoutMs(), + nextUniqueTransaction(startTime), startTime); } catch (IOException e) { close(); throw new IORuntimeException(e); @@ -708,11 +722,25 @@ class StatelessChronicleMap implements ChronicleMap, Closeable { } - private Bytes blockingFetchThrowable(long sizeLocation, long timeOutMs) throws IOException { - long startTime = System.currentTimeMillis(); + private Bytes blockingFetch0(long sizeLocation, final long transactionId, long startTime) { + + try { + return blockingFetchThrowable(sizeLocation, this.builder.timeoutMs(), transactionId, startTime); + } catch (IOException e) { + close(); + throw new IORuntimeException(e); + } catch (Exception e) { + close(); + throw e; + } + + } + + + private Bytes blockingFetchThrowable(long sizeLocation, long timeOutMs, final long transactionId, final long startTime) throws IOException { + long timeoutTime = startTime + timeOutMs + 1000000; - long transactionId = nextUniqueTransaction(startTime); for (; ; ) { @@ -730,31 +758,8 @@ class StatelessChronicleMap implements ChronicleMap, Closeable { // send out all the bytes send(bytes, timeoutTime); - bytes.clear(); - bytes.buffer().clear(); + return blockingFetch(timeoutTime, transactionId); - // the number of bytes in the response - int size = receive(SIZE_OF_SIZE, timeoutTime).readInt(); - - int requiredSize = size + SIZE_OF_SIZE; - if (bytes.capacity() < requiredSize) { - bytes = new ByteBufferBytes(allocateDirect(requiredSize)); - } - - // block until we have received all the byte in this chunk - receive(size, timeoutTime); - - boolean isException = bytes.readBoolean(); - long inTransactionId = bytes.readLong(); - - if (inTransactionId != transactionId) - throw new IllegalStateException("the received transaction-id=" + inTransactionId + - ", does not match the expected transaction-id=" + transactionId); - - if (isException) - throw (RuntimeException) bytes.readObject(); - - return bytes; } catch (java.nio.channels.ClosedChannelException | ClosedConnectionException e) { checkTimeout(timeoutTime); clientChannel = lazyConnect(builder.timeoutMs(), builder.remoteAddress()); @@ -762,6 +767,34 @@ class StatelessChronicleMap implements ChronicleMap, Closeable { } } + private Bytes blockingFetch(long timeoutTime, long transactionId) throws IOException { + bytes.clear(); + bytes.buffer().clear(); + + // the number of bytes in the response + int size = receive(SIZE_OF_SIZE, timeoutTime).readInt(); + + int requiredSize = size + SIZE_OF_SIZE; + if (bytes.capacity() < requiredSize) { + bytes = new ByteBufferBytes(allocateDirect(requiredSize)); + } + + // block until we have received all the byte in this chunk + receive(size, timeoutTime); + + boolean isException = bytes.readBoolean(); + long inTransactionId = bytes.readLong(); + + if (inTransactionId != transactionId) + throw new IllegalStateException("the received transaction-id=" + inTransactionId + + ", does not match the expected transaction-id=" + transactionId); + + if (isException) + throw (RuntimeException) bytes.readObject(); + + return bytes; + } + private void write(long transactionId) { bytes.writeLong(transactionId); } diff --git a/src/main/java/net/openhft/chronicle/map/TcpReplicator.java b/src/main/java/net/openhft/chronicle/map/TcpReplicator.java index 43efdeb39..01203df82 100644 --- a/src/main/java/net/openhft/chronicle/map/TcpReplicator.java +++ b/src/main/java/net/openhft/chronicle/map/TcpReplicator.java @@ -1670,6 +1670,7 @@ class StatelessServerConnector { final long sizeLocation = writer.position(); writer.skip(AbstractChannelReplicator.SIZE_OF_SIZE + 1); // SIZE_OF_SIZE + is + // exception writer.writeLong(transactionId); @@ -1677,7 +1678,7 @@ class StatelessServerConnector { writer.skip(1); // count - writer.skip(2); + writer.skip(4); int count = 0; while (iterator.hasNext()) { @@ -1686,7 +1687,7 @@ class StatelessServerConnector { // some data, we don't know the max key size, we will use the entrySize instead if (writer.remaining() <= maxEntrySizeBytes) { writeHeader(writer, sizeLocation, count, true); - + LOG.info("One more chunk !"); return false; } @@ -1698,6 +1699,7 @@ class StatelessServerConnector { writeHeader(writer, sizeLocation, count, false); + LOG.info("Last chunk !"); return true; } @@ -1718,7 +1720,7 @@ class StatelessServerConnector { writer.writeBoolean(hasAnotherChunk); // count - writer.writeUnsignedShort(count); + writer.writeInt(count); writer.position(end); } }; diff --git a/src/test/java/net/openhft/chronicle/map/StatelessClientTest.java b/src/test/java/net/openhft/chronicle/map/StatelessClientTest.java index 84a531b40..973d8d35f 100644 --- a/src/test/java/net/openhft/chronicle/map/StatelessClientTest.java +++ b/src/test/java/net/openhft/chronicle/map/StatelessClientTest.java @@ -36,7 +36,7 @@ import static net.openhft.chronicle.common.StatelessBuilder.remoteAddress; public class StatelessClientTest { - public static final int SIZE = 100; + public static final int SIZE = 1000; @Test From fdcbc85bc002637f436190ba97629fe0f54791bf Mon Sep 17 00:00:00 2001 From: Rob Austin Date: Mon, 27 Oct 2014 18:08:22 +0000 Subject: [PATCH 06/15] HCOLL-167 showed how to config replication --- README.md | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 084ed755b..c6264a41d 100644 --- a/README.md +++ b/README.md @@ -521,19 +521,36 @@ The identifier is setup on the builder as follows. TcpReplicationConfig tcpConfig = ... map = ChronicleMapBuilder .of(Integer.class, CharSequence.class) - .addReplicator(Replicators.tcp(identifier, tcpConfig)) + .replicators(identifier, tcpReplicationConfig) .create(); ``` -and example of setting up three maps with TCP Replication, ideally they would be on different +and example of setting up three server with TCP Replication, ideally they would be on different servers, but for this example they are on the same host : ```java -map1 = newTcpSocketShmIntString((byte) 1, 8076, new InetSocketAddress("localhost", 8077), - new InetSocketAddress("localhost", 8079)); -map2 = newTcpSocketShmIntString((byte) 2, 8077, new InetSocketAddress("localhost", 8079)); -map3 = newTcpSocketShmIntString((byte) 3, 8079); - +String hostServer1 = "localhost"; // change this to your host +int serverPort1 = 8076; // change this to your port +InetSocketAddress inetSocketAddress1 = new InetSocketAddress(hostServer1, serverPort1); + +String hostServer2 = "localhost"; // change this to your host +int serverPort2= 8076; // change this to your port +InetSocketAddress inetSocketAddress2 = new InetSocketAddress(hostServer2, serverPort2); + +String hostServer3 = "localhost"; // change this to your host +int serverPort3 = 8076; // change this to your port +InetSocketAddress inetSocketAddress3 = new InetSocketAddress(hostServer3, serverPort3); + + +// this is to go on server 1 +TcpReplicationConfig tcpConfigServer1 = TcpReplicationConfig.of(serverPort1); + +// this is to go on server 2 +TcpReplicationConfig tcpConfigServer2 = TcpReplicationConfig.of(serverPort2,inetSocketAddress1); + +// this is to go on server 3 +TcpReplicationConfig tcpConfigServer3 = TcpReplicationConfig.of(serverPort3, + inetSocketAddress1,inetSocketAddress3); ``` ### Bootstrapping From e9df9778d366ea51a8379f2d6bf6870fa03e1ddc Mon Sep 17 00:00:00 2001 From: Rob Austin Date: Mon, 27 Oct 2014 18:10:01 +0000 Subject: [PATCH 07/15] HCOLL-167 showed how to config replication --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c6264a41d..7f83fa383 100644 --- a/README.md +++ b/README.md @@ -546,11 +546,12 @@ InetSocketAddress inetSocketAddress3 = new InetSocketAddress(hostServer3, server TcpReplicationConfig tcpConfigServer1 = TcpReplicationConfig.of(serverPort1); // this is to go on server 2 -TcpReplicationConfig tcpConfigServer2 = TcpReplicationConfig.of(serverPort2,inetSocketAddress1); +TcpReplicationConfig tcpConfigServer2 = TcpReplicationConfig.of(serverPort2, + inetSocketAddress1); // this is to go on server 3 TcpReplicationConfig tcpConfigServer3 = TcpReplicationConfig.of(serverPort3, - inetSocketAddress1,inetSocketAddress3); + inetSocketAddress1,inetSocketAddress2); ``` ### Bootstrapping From 133b63ea6c2ce9fb2362f6a317ca40e0d959c3ea Mon Sep 17 00:00:00 2001 From: Rob Austin Date: Mon, 27 Oct 2014 18:22:15 +0000 Subject: [PATCH 08/15] HCOLL-167 added an example of three way config --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 7f83fa383..6951b6c29 100644 --- a/README.md +++ b/README.md @@ -525,6 +525,13 @@ map = ChronicleMapBuilder .create(); ``` +### Configuring Three Way TCP/IP Replication + +![TCP/IP Replication 3Way](http://openhft +.net/wp-content/uploads/2014/09/Screen-Shot-2014-10-27-at-18 +.19.05.png) + + and example of setting up three server with TCP Replication, ideally they would be on different servers, but for this example they are on the same host : From bcc58c0668f014a65ea3760f1da979413215997d Mon Sep 17 00:00:00 2001 From: Rob Austin Date: Mon, 27 Oct 2014 18:23:33 +0000 Subject: [PATCH 09/15] HCOLL-167 showed how to config replication --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 6951b6c29..d0fe370ff 100644 --- a/README.md +++ b/README.md @@ -527,9 +527,7 @@ map = ChronicleMapBuilder ### Configuring Three Way TCP/IP Replication -![TCP/IP Replication 3Way](http://openhft -.net/wp-content/uploads/2014/09/Screen-Shot-2014-10-27-at-18 -.19.05.png) +![TCP/IP Replication 3Way](http://openhft.net/wp-content/uploads/2014/09/Screen-Shot-2014-10-27-at-18.19.05.png) and example of setting up three server with TCP Replication, ideally they would be on different From 2b703fe86205f838f6c20fb4152fcb9b9511435d Mon Sep 17 00:00:00 2001 From: Rob Austin Date: Mon, 27 Oct 2014 18:25:42 +0000 Subject: [PATCH 10/15] HCOLL-167 showed how to config replication --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d0fe370ff..2006ed019 100644 --- a/README.md +++ b/README.md @@ -530,8 +530,8 @@ map = ChronicleMapBuilder ![TCP/IP Replication 3Way](http://openhft.net/wp-content/uploads/2014/09/Screen-Shot-2014-10-27-at-18.19.05.png) -and example of setting up three server with TCP Replication, ideally they would be on different -servers, but for this example they are on the same host : +Below is example of what to set the tcpReplicationConfig for each host when setting up three +way. ```java String hostServer1 = "localhost"; // change this to your host From eb1074ab007eb4268dcced4d28c44cf214e58ecf Mon Sep 17 00:00:00 2001 From: Rob Austin Date: Mon, 27 Oct 2014 18:27:13 +0000 Subject: [PATCH 11/15] HCOLL-167 docs --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2006ed019..f24a28db2 100644 --- a/README.md +++ b/README.md @@ -530,8 +530,7 @@ map = ChronicleMapBuilder ![TCP/IP Replication 3Way](http://openhft.net/wp-content/uploads/2014/09/Screen-Shot-2014-10-27-at-18.19.05.png) -Below is example of what to set the tcpReplicationConfig for each host when setting up three -way. +Below is example how to set up tcpReplicationConfig for three host ```java String hostServer1 = "localhost"; // change this to your host @@ -548,14 +547,14 @@ InetSocketAddress inetSocketAddress3 = new InetSocketAddress(hostServer3, server // this is to go on server 1 -TcpReplicationConfig tcpConfigServer1 = TcpReplicationConfig.of(serverPort1); +TcpReplicationConfig tcpReplicationConfigServer1 = TcpReplicationConfig.of(serverPort1); // this is to go on server 2 -TcpReplicationConfig tcpConfigServer2 = TcpReplicationConfig.of(serverPort2, +TcpReplicationConfig tcpReplicationConfigServer2 = TcpReplicationConfig.of(serverPort2, inetSocketAddress1); // this is to go on server 3 -TcpReplicationConfig tcpConfigServer3 = TcpReplicationConfig.of(serverPort3, +TcpReplicationConfig tcpReplicationConfigServer3 = TcpReplicationConfig.of(serverPort3, inetSocketAddress1,inetSocketAddress2); ``` From d2ffaf7d4a94374278ba7da4319516d6df0d5b4b Mon Sep 17 00:00:00 2001 From: Rob Austin Date: Mon, 27 Oct 2014 18:28:21 +0000 Subject: [PATCH 12/15] HCOLL-167 showed how to config replication --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f24a28db2..59f502efe 100644 --- a/README.md +++ b/README.md @@ -538,11 +538,11 @@ int serverPort1 = 8076; // change this to your port InetSocketAddress inetSocketAddress1 = new InetSocketAddress(hostServer1, serverPort1); String hostServer2 = "localhost"; // change this to your host -int serverPort2= 8076; // change this to your port +int serverPort2= 8077; // change this to your port InetSocketAddress inetSocketAddress2 = new InetSocketAddress(hostServer2, serverPort2); String hostServer3 = "localhost"; // change this to your host -int serverPort3 = 8076; // change this to your port +int serverPort3 = 8078; // change this to your port InetSocketAddress inetSocketAddress3 = new InetSocketAddress(hostServer3, serverPort3); From 35319529e8028e65dff00589d60bfa706b3c0753 Mon Sep 17 00:00:00 2001 From: Rob Austin Date: Mon, 27 Oct 2014 18:29:20 +0000 Subject: [PATCH 13/15] HCOLL-167 showed how to config replication --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 59f502efe..af100059b 100644 --- a/README.md +++ b/README.md @@ -725,7 +725,8 @@ unique for each map you have. byte identifier = (byte) 2; TcpReplicationConfig tcpConfig = - TcpReplicationConfig.of(8087).heartBeatInterval(1, java.util.concurrent.TimeUnit.SECONDS); + TcpReplicationConfig.of(8087) + .heartBeatInterval(1, java.util.concurrent.TimeUnit.SECONDS); channelProviderServer2 = new ChannelProviderBuilder() .replicators(identifier, tcpConfig) From 9f5ee2da1ad0155481d339d9e1c65eefe714f202 Mon Sep 17 00:00:00 2001 From: Rob Austin Date: Mon, 27 Oct 2014 18:42:48 +0000 Subject: [PATCH 14/15] HCOLL-167 showed how to config replication --- README.md | 106 +++++++++++++++++++++++++++++------------------------- 1 file changed, 57 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index af100059b..0779baafb 100644 --- a/README.md +++ b/README.md @@ -525,12 +525,68 @@ map = ChronicleMapBuilder .create(); ``` + + +### Bootstrapping +When a node is connected over the network to an active grid of nodes. It must first receive any data +that it does not have from the other nodes. Eventually, all the nodes in the grid have to hold a +copy of exactly the same data. We refer to this initial data load phase as bootstrapping. +Bootstrapping by its very nature is point to point, so it is only performed over TCP replication. +For architectures that wish to use UDP replication it is advised you use TCP Replication as well. A +grid which only uses UDP replication will miss out on the bootstrapping, possibly leaving the nodes +in an inconsistent state. To avoid this, if you would rather reduce the amount of TCP traffic on +your network, we suggest you consider using a throttle TCP replication along with UDP replication. +Bootstrapping is not used when the nodes are on the same server, so for this case, TCP replication +is not required. + +### Identifier +Each map is allocated a unique identifier + +Server 1 has: +``` +.replicators((byte) 1, tcpReplicationConfigServer1) +``` + +Server 2 has: +``` +.replicators((byte) 2, tcpReplicationConfigServer2) +``` + +Server 3 has: +``` +.replicators((byte) 3, tcpReplicationConfigServer3) +``` + +If you fail to allocate a unique identifier replication will not work correctly. + +### Port +Each map must be allocated a unique port, the port has to be unique per server, if the maps are +running on different hosts they could be allocated the same port, but in our example we allocated +them different ports, we allocated map1 port 8076 and map2 port 8077. Currently we don't support +data forwarding, so it important to connect every remote map, to every other remote map, in other +words you can't have a hub configuration where all the data passes through a single map which every +other map is connected to. So currently, if you had 4 servers each with a Chronicle Map, you would +require 6 connections. + +In our case we are only using 2 maps, this is how we connected map1 to map 2. +``` +TcpReplicationConfig.of(8076, new InetSocketAddress("localhost", 8077)) + .heartBeatInterval(1, SECONDS); +``` +you could have put this instruction on map2 instead, like this +``` +TcpReplicationConfig.of(8077, new InetSocketAddress("localhost", 8076)) + .heartBeatInterval(1, SECONDS); +``` +even though data flows from map1 to map2 and map2 to map1 it doesn't matter which way you connected +this, in other words its a bidirectional connection. + ### Configuring Three Way TCP/IP Replication ![TCP/IP Replication 3Way](http://openhft.net/wp-content/uploads/2014/09/Screen-Shot-2014-10-27-at-18.19.05.png) -Below is example how to set up tcpReplicationConfig for three host +Below is example how to set up tcpReplicationConfig for 3 host ```java String hostServer1 = "localhost"; // change this to your host @@ -557,54 +613,6 @@ TcpReplicationConfig tcpReplicationConfigServer2 = TcpReplicationConfig.of(serve TcpReplicationConfig tcpReplicationConfigServer3 = TcpReplicationConfig.of(serverPort3, inetSocketAddress1,inetSocketAddress2); ``` - -### Bootstrapping -When a node is connected over the network to an active grid of nodes. It must first receive any data -that it does not have from the other nodes. Eventually, all the nodes in the grid have to hold a -copy of exactly the same data. We refer to this initial data load phase as bootstrapping. -Bootstrapping by its very nature is point to point, so it is only performed over TCP replication. -For architectures that wish to use UDP replication it is advised you use TCP Replication as well. A -grid which only uses UDP replication will miss out on the bootstrapping, possibly leaving the nodes -in an inconsistent state. To avoid this, if you would rather reduce the amount of TCP traffic on -your network, we suggest you consider using a throttle TCP replication along with UDP replication. -Bootstrapping is not used when the nodes are on the same server, so for this case, TCP replication -is not required. - -### Identifier -Each map is allocated a unique identifier - -Server 1 has: -``` -.addReplicator(Replicators.tcp((byte) 1, tcpConfig)) -``` - -Server 2 has: -``` -.addReplicator(Replicators.tcp((byte) 2, tcpConfig)) -``` -If you fail to allocate a unique identifier replication will not work correctly. - -### Port -Each map must be allocated a unique port, the port has to be unique per server, if the maps are -running on different hosts they could be allocated the same port, but in our example we allocated -them different ports, we allocated map1 port 8076 and map2 port 8077. Currently we don't support -data forwarding, so it important to connect every remote map, to every other remote map, in other -words you can't have a hub configuration where all the data passes through a single map which every -other map is connected to. So currently, if you had 4 servers each with a Chronicle Map, you would -require 6 connections. - -In our case we are only using 2 maps, this is how we connected map1 to map 2. -``` -TcpReplicationConfig.of(8076, new InetSocketAddress("localhost", 8077)) - .heartBeatInterval(1, SECONDS); -``` -you could have put this instruction on map2 instead, like this -``` -TcpReplicationConfig.of(8077, new InetSocketAddress("localhost", 8076)) - .heartBeatInterval(1, SECONDS); -``` -even though data flows from map1 to map2 and map2 to map1 it doesn't matter which way you connected -this, in other words its a bidirectional connection. ### Heart Beat Interval We set a heartBeatInterval, in our example to 1 second From 03236f31e530d39c698f938c39bd9880f7aabce6 Mon Sep 17 00:00:00 2001 From: Rob Austin Date: Mon, 27 Oct 2014 19:11:28 +0000 Subject: [PATCH 15/15] HCOLL-167 more refactors statelss client --- .../chronicle/map/StatelessChronicleMap.java | 44 +++++++----- .../openhft/chronicle/map/TcpReplicator.java | 69 ++++++++++--------- .../chronicle/map/StatelessClientTest.java | 50 ++++++++++++-- 3 files changed, 108 insertions(+), 55 deletions(-) diff --git a/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java b/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java index 9465b3d76..80399efc0 100644 --- a/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java @@ -602,41 +602,36 @@ class StatelessChronicleMap implements ChronicleMap, Closeable { @NotNull public synchronized Set> entrySet() { - long sizeLocation = writeEvent(ENTRY_SET); - - long startTime = System.currentTimeMillis(); - long transactionId = nextUniqueTransaction(startTime); + final long sizeLocation = writeEvent(ENTRY_SET); + final long startTime = System.currentTimeMillis(); + final long transactionId = nextUniqueTransaction(startTime); + final long timeoutTime = System.currentTimeMillis() + builder.timeoutMs(); // get the data back from the server Bytes in = blockingFetch0(sizeLocation, transactionId, startTime); final Set> result = new HashSet>(); - boolean hasMoreEntries; - do { - hasMoreEntries = in.readBoolean(); + for (; ; ) { + + boolean hasMoreEntries = in.readBoolean(); LOG.info("entrySet - hasMoreEntries=" + hasMoreEntries); + // number of entries in the chunk long size = in.readInt(); - LOG.info("entrySet - size=" + size); - for (int i = 0; i < size; i++) { K k = keyValueSerializer.readKey(in); V v = keyValueSerializer.readValue(in); - // LOG.info("v="+v); result.add(new Entry(k, v)); } - if (hasMoreEntries) - try { - in = blockingFetch(System.currentTimeMillis() + builder.timeoutMs(), transactionId); - } catch (IOException e) { - e.printStackTrace(); - } + if (!hasMoreEntries) + break; - } while (hasMoreEntries); + in = blockingFetchReadOnly(timeoutTime, transactionId); + } return result; } @@ -767,6 +762,21 @@ class StatelessChronicleMap implements ChronicleMap, Closeable { } } + private Bytes blockingFetchReadOnly(long timeoutTime, final long transactionId) { + + try { + return blockingFetch(timeoutTime, transactionId); + } catch (IOException e) { + close(); + throw new IORuntimeException(e); + } catch (Exception e) { + close(); + throw e; + } + + } + + private Bytes blockingFetch(long timeoutTime, long transactionId) throws IOException { bytes.clear(); bytes.buffer().clear(); diff --git a/src/main/java/net/openhft/chronicle/map/TcpReplicator.java b/src/main/java/net/openhft/chronicle/map/TcpReplicator.java index 01203df82..71df8e960 100644 --- a/src/main/java/net/openhft/chronicle/map/TcpReplicator.java +++ b/src/main/java/net/openhft/chronicle/map/TcpReplicator.java @@ -1667,18 +1667,7 @@ class StatelessServerConnector { @Override public boolean doWork(Bytes writer) { - final long sizeLocation = writer.position(); - - writer.skip(AbstractChannelReplicator.SIZE_OF_SIZE + 1); // SIZE_OF_SIZE + is - - // exception - writer.writeLong(transactionId); - - // hasAnotherChunk - writer.skip(1); - - // count - writer.skip(4); + final long sizeLocation = header(writer, transactionId); int count = 0; while (iterator.hasNext()) { @@ -1703,26 +1692,7 @@ class StatelessServerConnector { return true; } - private void writeHeader(Bytes writer, long sizeLocation, int count, final boolean hasAnotherChunk) { - final long end = writer.position(); - final int size = (int) (end - sizeLocation); - writer.position(sizeLocation); - // size in bytes - writer.writeInt(size); - - // is exception - writer.writeBoolean(false); - - //transaction id; - writer.skip(8); - - writer.writeBoolean(hasAnotherChunk); - - // count - writer.writeInt(count); - writer.position(end); - } }; } @@ -1734,6 +1704,43 @@ class StatelessServerConnector { } + private long header(Bytes writer, final long transactionId) { + final long sizeLocation = writer.position(); + + writer.skip(AbstractChannelReplicator.SIZE_OF_SIZE + 1); // SIZE_OF_SIZE + is + + // exception + writer.writeLong(transactionId); + + // hasAnotherChunk + writer.skip(1); + + // count + writer.skip(4); + return sizeLocation; + } + + private void writeHeader(Bytes writer, long sizeLocation, int count, final boolean hasAnotherChunk) { + final long end = writer.position(); + final int size = (int) (end - sizeLocation); + writer.position(sizeLocation); + + // size in bytes + writer.writeInt(size); + + // is exception + writer.writeBoolean(false); + + //transaction id; + writer.skip(8); + + writer.writeBoolean(hasAnotherChunk); + + // count + writer.writeInt(count); + writer.position(end); + } + private Work putIfAbsent(Bytes reader, Bytes writer) { final K key = readKey(reader); final V v = readValue(reader); diff --git a/src/test/java/net/openhft/chronicle/map/StatelessClientTest.java b/src/test/java/net/openhft/chronicle/map/StatelessClientTest.java index 973d8d35f..cbf02d834 100644 --- a/src/test/java/net/openhft/chronicle/map/StatelessClientTest.java +++ b/src/test/java/net/openhft/chronicle/map/StatelessClientTest.java @@ -36,7 +36,7 @@ import static net.openhft.chronicle.common.StatelessBuilder.remoteAddress; public class StatelessClientTest { - public static final int SIZE = 1000; + public static final int SIZE = 100000; @Test @@ -78,11 +78,50 @@ public class StatelessClientTest { serverMap.close(); statelessMap.close(); - - // System.out.print("" + entries); - } + + @Test + public void testBufferOverFlowPutAllAndKeySet() throws IOException, InterruptedException { + + final ChronicleMap serverMap; + final ChronicleMap statelessMap; + + + // stateless client + { + statelessMap = ChronicleMapBuilder.of(Integer + .class, CharSequence.class) + .stateless(remoteAddress(new InetSocketAddress("localhost", 8076))).create(); + } + + // server + { + serverMap = ChronicleMapBuilder.of(Integer.class, CharSequence.class) + .replicators((byte) 2, TcpReplicationConfig.of(8076)).create(); + } + + + Map payload = new HashMap(); + + for (int i = 0; i < SIZE; i++) { + payload.put(i, "some value=" + i); + } + + + statelessMap.putAll(payload); + + + final Set keys = statelessMap.keySet(); + + Assert.assertEquals(keys.size(), payload.size()); + + + serverMap.close(); + statelessMap.close(); + } + + @Test public void test() throws IOException, InterruptedException { @@ -187,9 +226,6 @@ public class StatelessClientTest { } - - - @Test public void testEquals() throws IOException, InterruptedException {