diff --git a/.gitignore b/.gitignore index 4e4647b1a..bd747a416 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ *.war *.ear *.iml +.idea* diff --git a/README.md b/README.md index 9e26588df..245ce3a54 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ requirements. - [Contact Us](sales@higherfrequencytrading.com)* # Chronicle Map -Replicate your Key Value Store across your network, with consistency, durability and performance. +Replicate your Key Value Store across your network, with eventual consistency, persistence and performance. ![Chronicle Map](http://openhft.net/wp-content/uploads/2014/07/ChronicleMap_200px.png) #### Maven Artifact Download @@ -30,7 +30,7 @@ Click here to get the [Latest Version Number](http://search.maven.org/#search%7C * [Maven Download](https://github.com/OpenHFT/Chronicle-Map#maven-artifact-download-1) * [Snapshot Download](https://github.com/OpenHFT/Chronicle-Map#maven-snapshot-download) * [Key Value Object Types](https://github.com/OpenHFT/Chronicle-Map#key-value-object-types) - * [Off Heap and How to improve performance]() + * [Off Heap and How to improve performance](https://github.com/OpenHFT/Chronicle-Map#off-heap-storage-and-how-using-a-proxy-object-can-improve-performance) * [Sharing Data Between Two or More Maps](https://github.com/OpenHFT/Chronicle-Map#sharing-data-between-two-or-more-maps) * [Entries](https://github.com/OpenHFT/Chronicle-Map#entries) * [Size of Space Reserved on Disk](https://github.com/OpenHFT/Chronicle-Map#size-of-space-reserved-on-disk) @@ -54,6 +54,7 @@ Click here to get the [Latest Version Number](http://search.maven.org/#search%7C * [Heart Beat Interval](https://github.com/OpenHFT/Chronicle-Map#heart-beat-interval) * [Multi Chronicle Maps - Network Distributed](https://github.com/OpenHFT/Chronicle-Map#multiple-chronicle-maps---network-distributed) * [Stateless Client](https://github.com/OpenHFT/Chronicle-Map#stateless-client) +* [How to speed up the Chronicle Map Stateless Client](https://github.com/OpenHFT/Chronicle-Map#how-to-speed-up-the-chronicle-map-stateless-client) * [Questions/Answers](https://github.com/OpenHFT/Chronicle-Map#questions-and-answers) #### Miscellaneous @@ -168,7 +169,7 @@ and define the snapshot version in your pom.xml, for example: net.openhft chronicle-map - 1.0.1-SNAPSHOT + ``` @@ -237,7 +238,7 @@ between processes by just using memory and in around 40 nanoseconds. ``` java ConcurrentMap map1, map2; -// this could could be on one process +// this could be on one process map1 = ChronicleMapBuilder.of(Integer.class, CharSequence.class).createPersistedTo(file); // this could be on the other process @@ -355,7 +356,7 @@ These methods let you provide the object which the data will be written to, but ``` java CharSequence using = new StringBuilder(); CharSequence myResult = map.getUsing("key", using); -// at this point the myString and myResult will both point to the same object +// at this point "using" and "myResult" will both point to the same object ``` The `map.getUsing()` method is similar to `map.get()`, but because Chronicle Map stores its data off @@ -381,6 +382,12 @@ the chronicle map is able to distribute entries between processes with extremely as both processes are sharing the same off heap memory space. Since your objects are already stored off heap ( as a series of bytes ), replicating entries over the network adds relatively low over head. +For more information on the benefit's of off heap memory, see our article on - [On +heap vs off +heap memory +usage] +(http://vanillajava.blogspot.co.uk/2014/12/on-heap-vs-off-heap-memory-usage.html) + One of the downsides of an Off Heap Map is that whenever you wish to get a value ( on heap ) from an entry which is off heap, for example calling : @@ -461,10 +468,10 @@ the off heap data structures directly, this reduced serialisation can give you a Below we show you how you can work directly with the off heap entries. ``` java - ChronicleMap chm = ChronicleMapBuilder - .of(String.class, BondVOInterface.class) - .keySize(10) - .create(); +ChronicleMap map = ChronicleMapBuilder + .of(String.class, BondVOInterface.class) + .keySize(10) + .create(); ``` notice that the @@ -476,29 +483,29 @@ notice that the value class, in our case `BondVOInterface.class` is an `interface` rather than a `class`, now like before, we can use the `getUsing(key,using)` method, but this time we have to create the ‘using’ instance slightly -differently, we have to call the `newDirectReference(..)` method. +differently, we have to call either the `map.newValueInstance()` or `map.newKeyInstance()` method. ``` java -BondVOInterface using = DataValueClasses.newDirectReference(BondVOInterface.class); +BondVOInterface using = map.newValueInstance(); ``` the call to `getUsing(key,value)` is the same as we had in the earlier example ``` java -BondVOInterface using = DataValueClasses.newDirectReference(BondVOInterface.class); +BondVOInterface using = map.newValueInstance(); BondVOInterface bond = map.getUsing(key,using); assert using == bond; // this will always be the same instance ``` `getUsing(key,using)` won’t create any on heap objects, and it won’t deserialize the ‘bond’ entry from off heap to on heap, all it does is sets the bond as a proxy to the off heap memory, this proxy object was created by -`DataValueClasses.newDirectReference(BondVOInterface.class)`, it allows us access to the fields of our +`map.newValueInstance()`, it allows us access to the fields of our entry, directly into the off heap storage. As above, ideally you would reuse the `using` variable. ``` java -BondVOInterface using = DataValueClasses.newDirectReference(BondVOInterface.class); +BondVOInterface using = map.newValueInstance(); for(int i=1;i<=10;i++) { Value bond = map.getUsing(key,using); // this won’t create a new value each time. @@ -543,8 +550,7 @@ context.close() // the lock will get released when this is called #### acquireUsingLocked() Just like getUsing(), acquireUsing() will also recycle the value you pass it, the following -code is a pattern that you will often come across, `acquireUsing(key,value)` offers this -functionality i the example, below with a single method call : +code is a pattern that you will often come across, ``` java V acquireUsing(key,value) { @@ -563,6 +569,10 @@ V acquireUsing(key,value) { } ``` +`acquireUsing(key,value)` offers this functionality from a single method call, an be cause it +implemented inside the chronicle map we are able to reduce the hash lookups and make it more +efficient. + If you are only accessing ChronicleMap from a single thread. If you are not doing replication and don't care about atomic reads. Then its simpler ( and faster ) to use acquireUsing() otherwise we recommend you use `acquireUsingLocked(key,value)` @@ -599,6 +609,9 @@ entries are no where near the max entry size. ## Serialization +![Serialization](http://openhft.net/wp-content/uploads/2014/09/Serialization_01.jpg) + + Chronicle Map stores your data into off heap memory, so when you give it a Key or Value, it will serialise these objects into bytes. @@ -1041,11 +1054,15 @@ unique for each map you have. A stateless client is an instance of a `ChronicleMap` or a `ChronicleSet` that does not hold any data locally, all the Map or Set operations are delegated via a Remote Procedure Calls ( RPC ) to - another `ChronicleMap` or `ChronicleSet` which we will refer to as the server. The server will - hold all your data, the server can not it’s self be a stateless client. Your stateless client must - be connected to the server via TCP/IP. The stateless client will delegate all your method calls to - the remote server. The stateless client operations will block, in other words the stateless client - will wait for the server to send a response before continuing to the next operation. The stateless + another `ChronicleMap` or `ChronicleSet` which we will refer to as the server. The server + holds all your data, the server can not it’s self be a stateless client. Your stateless client must + be connected to the server via TCP/IP. + + ![Chronicle Map](http://openhft.net/wp-content/uploads/2014/09/State-Transition_1-thread_02.jpg) + + The stateless client delegates all your method calls to + the remote server. The stateless client operations will block, in other words the stateless + client waits for the server to send a response before continuing to the next operation. The stateless client could be consider to be a ClientProxy to `ChronicleMap` or `ChronicleSet` running on another host. @@ -1103,24 +1120,32 @@ but in your example you should choose the host of the state-full server and the ``` the ".statelessClient(..)" returns an instance of `StatelessClientConfig`, which has only a few -own configurations and `create()` method to create a new stateless client. -method tells `ChronicleMap` that its going to build a stateless client. -If you don’t add this line a normal state-full `ChronicleMap` will be created. For this example +of its own configurations, such as the `create()` method, which can be used to create a new +stateless client. +If you don’t add this line, a normal state-full `ChronicleMap` will be created. For this example we ran both the client an the server on the same host ( hence the “localhost" setting ), but in a real life example the stateless client will typically be on a different server than the state-full host. If you are aiming to create a stateless client and server on the same host, its -better not to do this, as the stateless client connects to the server via TCP/IP, -you would get better performance if you connect to the server via heap memory, to read more about -sharing a map with heap memory +better not to do this, as the stateless client connects to the server via TCP/IP. It better to +share the maps via memory as this will give you better performance ( read more about +this at [Sharing Data Between Two or More Maps](https://github +.com/OpenHFT/Chronicle-Map#sharing-data-between-two-or-more-maps). + click [here](https://github.com/OpenHFT/Chronicle-Map#sharing-data-between-two-or-more-maps ) ### How to speed up the Chronicle Map Stateless Client -By default when you make a method call to a `ChronicleMap` stateless client, -your method call is wrapped into an event which is sent over tcp and processed by the server, -your stateless client will block until an acknowledgement has been received from the server that -your event was processed. When you are calling methods that return a value like get() this +When calling the stateless client, you will get better throughput if you invoke your requests from a + number of threads, this is because by default when you make a method call to a `ChronicleMap` + stateless client, your method call is wrapped into an event which is sent over tcp and processed + by the server, +your stateless client will block until an acknowledgement has been received from the server that +your event was processed. + +![Chronicle Map](http://openhft.net/wp-content/uploads/2014/09/State-Transition_2-thread_03.jpg) + +When you are calling methods that return a value like get() this blocking adds no additional overhead, because you have to wait for the return value anyway, In some cases you could get better performance if you don't have to wait for the acknowledgement, This maybe the case when you are calling the `put()` method, but the problem with this method is it returns the old value even though you may not use it. diff --git a/pom.xml b/pom.xml index 7092ad641..8e2f67a20 100644 --- a/pom.xml +++ b/pom.xml @@ -17,18 +17,20 @@ ~ limitations under the License. --> - + net.openhft java-parent-pom 1.1.2 - + 4.0.0 chronicle-map - 2.0.13b-SNAPSHOT + 2.0.15b-SNAPSHOT OpenHFT/Chronicle-Map Chronicle-Map @@ -46,7 +48,7 @@ net.openhft lang - 6.4.16 + 6.4.18-SNAPSHOT @@ -73,21 +75,35 @@ 1.3.6 + + org.ops4j.pax.url + pax-url-aether + 2.2.0 + + + true xstream xstream + true org.codehaus.jettison jettison + + true + org.ops4j.pax.url + pax-url-aether + + net.openhft compiler @@ -172,6 +188,23 @@ + + + + org.apache.maven.plugins + maven-jar-plugin + 2.1 + + + + + true + true + + + + + diff --git a/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java b/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java index 7a12a592d..8dce56d17 100755 --- a/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java +++ b/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java @@ -23,6 +23,7 @@ import net.openhft.chronicle.hash.replication.TcpTransportAndNetworkConfig; import net.openhft.chronicle.hash.replication.TimeProvider; import net.openhft.chronicle.hash.serialization.*; import net.openhft.chronicle.map.ChronicleMap; +import net.openhft.chronicle.map.ChronicleMapBuilder; import net.openhft.chronicle.set.ChronicleSet; import net.openhft.chronicle.set.ChronicleSetBuilder; import net.openhft.lang.io.Bytes; @@ -46,7 +47,7 @@ import java.util.concurrent.TimeUnit; /** * This interface defines the meaning of configurations, common to {@link - * net.openhft.chronicle.map.ChronicleMapBuilder} and {@link ChronicleSetBuilder}, i. + * ChronicleMapBuilder} and {@link ChronicleSetBuilder}, i. * e. Chronicle hash container configurations. * *

{@code ChronicleHashBuilder} is mutable. Configuration methods mutate the builder and return @@ -57,7 +58,7 @@ import java.util.concurrent.TimeUnit; * @param the type of keys in hash containers, created by this builder * @param the container type, created by this builder, i. e. {@link ChronicleMap} or {@link * ChronicleSet} - * @param the concrete builder type, i. e. {@link net.openhft.chronicle.map.ChronicleMapBuilder} + * @param the concrete builder type, i. e. {@link ChronicleMapBuilder} * or {@link ChronicleSetBuilder} */ public interface ChronicleHashBuilderIf key is a boxed primitive type or {@link Byteable} subclass, i. e. if key size is known + * statically, it is automatically accounted and shouldn't be specified by user. + * * @param keySize number of bytes, taken by serialized form of keys * @return this builder back + * @throws IllegalStateException if key size is known statically and shouldn't be configured + * by user + * @throws IllegalArgumentException if the given {@code keySize} is non-positive * @see #constantKeySizeBySample(Object) * @see #entrySize(int) */ @@ -131,7 +138,7 @@ public interface ChronicleHashBuilderThere are three major patterns of this configuration usage:

  1. Key (and value, in * {@code ChronicleMap} case) sizes are constant. Configure them via {@link - * #constantKeySizeBySample(Object)} and {@link net.openhft.chronicle.map.ChronicleMapBuilder#constantValueSizeBySample(Object)} + * #constantKeySizeBySample(Object)} and {@link ChronicleMapBuilder#constantValueSizeBySample(Object)} * methods, and you will experience no memory waste at all.
  2. Key (and/or value size, in * {@code ChronicleMap} case) varies moderately. Specify them using corresponding methods, or * specify entry size directly by calling this method, by sizes somewhere between average and @@ -145,10 +152,11 @@ public interface ChronicleHashBuilderHowever, remember that
    • Operations with entries that span several "entry sizes" * are a bit slower, than entries which take a single "entry size". That is why "chunk" approach * is not recommended, when key (and/or value size) varies moderately.
    • The maximum - * number of chunks could be taken by an entry is 64. {@link IllegalArgumentException} is thrown - * on attempt to insert too large entry, compared to the configured or computed entry size.
    • - *
    • The number of "entries" {@linkplain #entries(long) in the whole map} and {@linkplain - * #actualEntriesPerSegment(long) per segment} is actually the number "chunks".
    + * number of chunks could be taken by an entry is {@link #maxEntryOversizeFactor(int)}, of max + * 64 chunks. {@link IllegalArgumentException} is thrown on attempt to insert too large entry, + * compared to the configured or computed entry size.
  3. The number of "entries" + * {@linkplain #entries(long) in the whole map} and {@linkplain #actualEntriesPerSegment(long) + * per segment} is actually the number "chunks".
  4. * *

    Example: if values in your {@code ChronicleMap} are adjacency lists of some social graph, * where nodes are represented as {@code long} ids, and adjacency lists are serialized in @@ -169,9 +177,22 @@ public interface ChronicleHashBuilder

  5. If key (and/or value, in {@code ChronicleMap} case) size varies * moderately, you should pass to this method the maximum number of entries + 5-25%, depending * on your data properties and configured {@linkplain #keySize(int) key}/{@linkplain - * net.openhft.chronicle.map.ChronicleMapBuilder#valueSize(int) + * ChronicleMapBuilder#valueSize(int) * value}/{@linkplain #entrySize(int) entry} sizes.
  6. If your data size varies in a wide * range, pass the maximum number of entries multiplied by average data size and divided by the * configured "entry size" (i. e. chunk size). See an example in the documentation to {@link @@ -292,7 +313,7 @@ public interface ChronicleHashBuilder keyMarshaller); + B keyMarshaller(@NotNull BytesMarshaller keyMarshaller); /** * Configures the marshallers, used to serialize/deserialize keys to/from off-heap memory in @@ -332,11 +353,10 @@ public interface ChronicleHashBuilderDefault key deserialization factory is {@link NewInstanceObjectFactory}, which creates a * new key instance using {@link Class#newInstance()} default constructor. You could provide an @@ -348,9 +368,8 @@ public interface ChronicleHashBuilder keyDeserializationFactory); @@ -401,8 +420,6 @@ public interface ChronicleHashBuilder statelessClient(InetSocketAddress remoteAddress); - ChronicleHashInstanceConfig instance(); /** @@ -414,12 +431,10 @@ public interface ChronicleHashBuilderThis method is a shortcut for {@code instance().create()}. * * @return a new off-heap hash container - * @throws IOException if any IO error relates to off-heap memory allocation, or establishing - * replication connections, occurs * @see #createPersistedTo(File) * @see #instance() */ - C create() throws IOException; + C create(); /** * Opens a hash container residing the specified file, or creates a new one if the file not yet @@ -450,5 +465,7 @@ public interface ChronicleHashBuilder statelessClient(InetSocketAddress remoteAddress); + + C createStatelessClient(InetSocketAddress serverAddress) throws IOException; } diff --git a/src/main/java/net/openhft/chronicle/hash/ChronicleHashInstanceConfig.java b/src/main/java/net/openhft/chronicle/hash/ChronicleHashInstanceConfig.java index 21b01661b..8afd3d188 100644 --- a/src/main/java/net/openhft/chronicle/hash/ChronicleHashInstanceConfig.java +++ b/src/main/java/net/openhft/chronicle/hash/ChronicleHashInstanceConfig.java @@ -53,5 +53,7 @@ public interface ChronicleHashInstanceConfig { ChronicleHashInstanceConfig persistedTo(File file); + ChronicleHashInstanceConfig name(String name); + C create() throws IOException; } diff --git a/src/main/java/net/openhft/chronicle/hash/StatelessClientConfig.java b/src/main/java/net/openhft/chronicle/hash/StatelessClientConfig.java index 01836241e..38add9b6a 100644 --- a/src/main/java/net/openhft/chronicle/hash/StatelessClientConfig.java +++ b/src/main/java/net/openhft/chronicle/hash/StatelessClientConfig.java @@ -18,8 +18,10 @@ package net.openhft.chronicle.hash; import net.openhft.chronicle.map.ChronicleMap; import net.openhft.chronicle.set.ChronicleSet; +import org.jetbrains.annotations.NotNull; import java.io.IOException; +import java.net.InetSocketAddress; import java.util.concurrent.TimeUnit; /** @@ -41,5 +43,7 @@ import java.util.concurrent.TimeUnit; public interface StatelessClientConfig { StatelessClientConfig timeout(long timeout, TimeUnit units); + StatelessClientConfig name(String name); + C create() throws IOException; } diff --git a/src/main/java/net/openhft/chronicle/hash/hashing/Access.java b/src/main/java/net/openhft/chronicle/hash/hashing/Access.java new file mode 100644 index 000000000..44088b816 --- /dev/null +++ b/src/main/java/net/openhft/chronicle/hash/hashing/Access.java @@ -0,0 +1,140 @@ +/* + * 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.hash.hashing; + +import java.nio.ByteOrder; + +/** + * Strategy of reading bytes, defines the abstraction of {@code T} class instances as ordered byte + * sequence. All {@code getXXX(input, offset)} should be consistent to each other in terms of + * ordered byte sequence each {@code T} instance represents. For example, if some {@code + * Access} implementation returns {@link ByteOrder#LITTLE_ENDIAN} on {@link #byteOrder(Object) + * byteOrder(input)} call, the following expressions should always have the same value: + *
      + *
    • {@code getLong(input, 0)}
    • + *
    • {@code getUnsignedInt(input, 0) | (getUnsignedInt(input, 4) << 32)}
    • + *
    • {@code getUnsignedInt(input, 0) |
      + *    ((long) getUnsignedShort(input, 4) << 32) |
      + *    ((long) getUnsignedByte(input, 6) << 48) |
      + *    ((long) getUnsignedByte(input, 7) << 56)}
    • + *
    • And so on
    • + *
    + * + *

    {@code getXXX(input, offset)} methods could throw unchecked exceptions when requested bytes + * range is outside of the bounds of the byte sequence, represented by the given {@code input}. + * However, they could omit checks for better performance. + * + *

    {@code Access} API is designed for inputs, that actually represent byte sequences that lay + * continuously in memory. Theoretically {@code Access} strategy could be implemented for + * non-continuous byte sequences, or abstractions which aren't actually present in memory as they + * are accessed, but this should be awkward, and hashing using such {@code Access} is expected to + * be slow. + * + * @param the type of the object to access + */ +public interface Access { + + /** + * Reads {@code [offset, offset + 7]} bytes of the byte sequence represented by the given + * {@code input} as a single {@code long} value. + * + * @param input the object to access + * @param offset offset to the first byte to read within the byte sequence represented + * by the given object + * @return eight bytes as a {@code long} value, in {@linkplain #byteOrder(Object) the expected + * order} + */ + long getLong(T input, long offset); + + /** + * Shortcut for {@code getInt(input, offset) & 0xFFFFFFFFL}. Could be implemented more + * efficiently. + * + * @param input the object to access + * @param offset offset to the first byte to read within the byte sequence represented + * by the given object + * @return four bytes as an unsigned int value, in {@linkplain #byteOrder(Object) the expected + * order} + */ + long getUnsignedInt(T input, long offset); + + /** + * Reads {@code [offset, offset + 3]} bytes of the byte sequence represented by the given + * {@code input} as a single {@code int} value. + * + * @param input the object to access + * @param offset offset to the first byte to read within the byte sequence represented + * by the given object + * @return four bytes as an {@code int} value, in {@linkplain #byteOrder(Object) the expected + * order} + */ + int getInt(T input, long offset); + + /** + * Shortcut for {@code getShort(input, offset) & 0xFFFF}. Could be implemented more + * efficiently. + * + * @param input the object to access + * @param offset offset to the first byte to read within the byte sequence represented + * by the given object + * @return two bytes as an unsigned short value, in {@linkplain #byteOrder(Object) the expected + * order} + */ + int getUnsignedShort(T input, long offset); + + /** + * Reads {@code [offset, offset + 1]} bytes of the byte sequence represented by the given + * {@code input} as a single {@code short} value, returned widened to {@code int}. + * + * @param input the object to access + * @param offset offset to the first byte to read within the byte sequence represented + * by the given object + * @return two bytes as a {@code short} value, in {@linkplain #byteOrder(Object) the expected + * order}, widened to {@code int} + */ + int getShort(T input, long offset); + + /** + * Shortcut for {@code getByte(input, offset) & 0xFF}. Could be implemented more efficiently. + * + * @param input the object to access + * @param offset offset to the byte to read within the byte sequence represented + * by the given object + * @return a byte by the given {@code offset}, interpreted as unsigned + */ + int getUnsignedByte(T input, long offset); + + /** + * Reads a single byte at the given {@code offset} in the byte sequence represented by the given + * {@code input}, returned widened to {@code int}. + * + * @param input the object to access + * @param offset offset to the byte to read within the byte sequence represented + * by the given object + * @return a byte by the given {@code offset}, widened to {@code int} + */ + int getByte(T input, long offset); + + /** + * The byte order in which all multi-byte {@code getXXX()} reads from the given {@code input} + * are performed. + * + * @param input the accessed object + * @return the byte order of all multi-byte reads from the given {@code input} + */ + ByteOrder byteOrder(T input); +} diff --git a/src/main/java/net/openhft/chronicle/hash/hashing/Accesses.java b/src/main/java/net/openhft/chronicle/hash/hashing/Accesses.java new file mode 100644 index 000000000..d9b71abd5 --- /dev/null +++ b/src/main/java/net/openhft/chronicle/hash/hashing/Accesses.java @@ -0,0 +1,69 @@ +/* + * 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.hash.hashing; + +import net.openhft.lang.io.Bytes; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Static methods returning useful {@link Access} implementations. + */ +public final class Accesses { + + /** + * Returns the {@code Access} delegating {@code getXXX(input, offset)} methods to {@code + * sun.misc.Unsafe.getXXX(input, offset)}. + * + *

    Usage example:

    {@code
    +     * class Pair {
    +     *     long first, second;
    +     *
    +     *     static final long pairDataOffset =
    +     *         theUnsafe.objectFieldOffset(Pair.class.getDeclaredField("first"));
    +     *
    +     *     static long hashPair(Pair pair, LongHashFunction hashFunction) {
    +     *         return hashFunction.hash(pair, Accesses.unsafe(), pairDataOffset, 16L);
    +     *     }
    +     * }}
    + * + *

    {@code null} is a valid input, on accepting {@code null} {@code Unsafe} just interprets + * the given offset as a wild memory address. + * + * @param the type of objects to access + * @return the unsafe memory {@code Access} + */ + public static Access unsafe() { + return (Access) UnsafeAccess.INSTANCE; + } + + /** + * Returns the {@code Access} to any {@link ByteBuffer}. + * + * @return the {@code Access} to {@link ByteBuffer}s + */ + public static Access toByteBuffer() { + return ByteBufferAccess.INSTANCE; + } + + public static Access toBytes() { + return BytesAccess.INSTANCE; + } + + private Accesses() {} +} diff --git a/src/main/java/net/openhft/chronicle/hash/hashing/ByteBufferAccess.java b/src/main/java/net/openhft/chronicle/hash/hashing/ByteBufferAccess.java new file mode 100644 index 000000000..703111dff --- /dev/null +++ b/src/main/java/net/openhft/chronicle/hash/hashing/ByteBufferAccess.java @@ -0,0 +1,64 @@ +/* + * 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.hash.hashing; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +enum ByteBufferAccess implements Access { + INSTANCE; + + @Override + public long getLong(ByteBuffer input, long offset) { + return input.getLong((int) offset); + } + + @Override + public long getUnsignedInt(ByteBuffer input, long offset) { + return Primitives.unsignedInt(getInt(input, offset)); + } + + @Override + public int getInt(ByteBuffer input, long offset) { + return input.getInt((int) offset); + } + + @Override + public int getUnsignedShort(ByteBuffer input, long offset) { + return Primitives.unsignedShort(getShort(input, offset)); + } + + @Override + public int getShort(ByteBuffer input, long offset) { + return input.getShort((int) offset); + } + + @Override + public int getUnsignedByte(ByteBuffer input, long offset) { + return Primitives.unsignedByte(getByte(input, offset)); + } + + @Override + public int getByte(ByteBuffer input, long offset) { + return input.get((int) offset); + } + + @Override + public ByteOrder byteOrder(ByteBuffer input) { + return input.order(); + } +} diff --git a/src/main/java/net/openhft/chronicle/hash/hashing/BytesAccess.java b/src/main/java/net/openhft/chronicle/hash/hashing/BytesAccess.java new file mode 100644 index 000000000..4e67b40f8 --- /dev/null +++ b/src/main/java/net/openhft/chronicle/hash/hashing/BytesAccess.java @@ -0,0 +1,66 @@ +/* + * 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.hash.hashing; + +import net.openhft.lang.io.Bytes; + +import java.nio.ByteOrder; + +enum BytesAccess implements Access { + INSTANCE; + + @Override + public long getLong(Bytes input, long offset) { + return input.readLong(offset); + } + + @Override + public long getUnsignedInt(Bytes input, long offset) { + return input.readUnsignedInt(offset); + } + + @Override + public int getInt(Bytes input, long offset) { + return input.readInt(offset); + } + + @Override + public int getUnsignedShort(Bytes input, long offset) { + return input.readUnsignedShort(offset); + } + + @Override + public int getShort(Bytes input, long offset) { + return input.readShort(offset); + } + + @Override + public int getUnsignedByte(Bytes input, long offset) { + return input.readUnsignedByte(offset); + } + + @Override + public int getByte(Bytes input, long offset) { + return input.readByte(offset); + } + + @Override + public ByteOrder byteOrder(Bytes input) { + return input.byteOrder(); + } + +} diff --git a/src/main/java/net/openhft/chronicle/hash/hashing/Hasher.java b/src/main/java/net/openhft/chronicle/hash/hashing/Hasher.java new file mode 100644 index 000000000..a7c97393c --- /dev/null +++ b/src/main/java/net/openhft/chronicle/hash/hashing/Hasher.java @@ -0,0 +1,194 @@ +/* + * 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.hash.hashing; + +import net.openhft.lang.io.Bytes; + +import static net.openhft.chronicle.hash.hashing.UnsafeAccess.BYTE_BASE; +import static net.openhft.chronicle.hash.hashing.UnsafeAccess.CHAR_BASE; + +public final class Hasher { + + // MurmurHash3, derived from + // https://github.com/google/guava/blob/fa95e381e665d8ee9639543b99ed38020c8de5ef + // /guava/src/com/google/common/hash/Murmur3_128HashFunction.java + + + private static final long C1 = 0x87c37b91114253d5L; + private static final long C2 = 0x4cf5ad432745937fL; + + /** + * Returns the hash code for {@code len} continuous bytes of the given {@code input} object, + * starting from the given offset. The abstraction of input as ordered byte sequence and + * "offset within the input" is defined by the given {@code access} strategy. + * + *

    This method doesn't promise to throw a {@code RuntimeException} if {@code + * [off, off + len - 1]} subsequence exceeds the bounds of the bytes sequence, defined by {@code + * access} strategy for the given {@code input}, so use this method with caution. + * + * @param input the object to read bytes from + * @param access access which defines the abstraction of the given input + * as ordered byte sequence + * @param offset offset to the first byte of the subsequence to hash + * @param length length of the subsequence to hash + * @param the type of the input + * @return hash code for the specified bytes subsequence + */ + public static long hash(T input, Access access, long offset, long length) { + assert offset >= 0L && length >= 0L; + + long h1 = 0L; + long h2 = 0L; + long remaining = length; + while (remaining >= 16L) { + long k1 = access.getLong(input, offset); + long k2 = access.getLong(input, offset); + offset += 16L; + remaining -= 16L; + h1 ^= mixK1(k1); + + h1 = Long.rotateLeft(h1, 27); + h1 += h2; + h1 = h1 * 5L + 0x52dce729L; + + h2 ^= mixK2(k2); + + h2 = Long.rotateLeft(h2, 31); + h2 += h1; + h2 = h2 * 5L + 0x38495ab5L; + } + + if (remaining > 0L) { + long k1 = 0L; + long k2 = 0L; + switch ((int) remaining) { + case 15: + k2 ^= ((long) access.getUnsignedByte(input, offset + 14L)) << 48;// fall through + case 14: + k2 ^= ((long) access.getUnsignedByte(input, offset + 13L)) << 40;// fall through + case 13: + k2 ^= ((long) access.getUnsignedByte(input, offset + 12L)) << 32;// fall through + case 12: + k2 ^= ((long) access.getUnsignedByte(input, offset + 11L)) << 24;// fall through + case 11: + k2 ^= ((long) access.getUnsignedByte(input, offset + 10L)) << 16;// fall through + case 10: + k2 ^= ((long) access.getUnsignedByte(input, offset + 9L)) << 8; // fall through + case 9: + k2 ^= ((long) access.getUnsignedByte(input, offset + 8L)); // fall through + case 8: + k1 ^= access.getLong(input, offset); + break; + case 7: + k1 ^= ((long) access.getUnsignedByte(input, offset + 6L)) << 48; // fall through + case 6: + k1 ^= ((long) access.getUnsignedByte(input, offset + 5L)) << 40; // fall through + case 5: + k1 ^= ((long) access.getUnsignedByte(input, offset + 4L)) << 32; // fall through + case 4: + k1 ^= access.getUnsignedInt(input, offset); + break; + case 3: + k1 ^= ((long) access.getUnsignedByte(input, offset + 2L)) << 16; // fall through + case 2: + k1 ^= ((long) access.getUnsignedByte(input, offset + 1L)) << 8; // fall through + case 1: + k1 ^= ((long) access.getUnsignedByte(input, offset)); + case 0: + break; + default: + throw new AssertionError("Should never get here."); + } + h1 ^= mixK1(k1); + h2 ^= mixK2(k2); + } + + return finalize(length, h1, h2); + } + + private static long finalize(long length, long h1, long h2) { + h1 ^= length; + h2 ^= length; + + h1 += h2; + h2 += h1; + + h1 = fmix64(h1); + h2 = fmix64(h2); + + h1 += h2; + return h1; + } + + + private static long fmix64(long k) { + k ^= k >>> 33; + k *= 0xff51afd7ed558ccdL; + k ^= k >>> 33; + k *= 0xc4ceb9fe1a85ec53L; + k ^= k >>> 33; + return k; + } + + private static long mixK1(long k1) { + k1 *= C1; + k1 = Long.rotateLeft(k1, 31); + k1 *= C2; + return k1; + } + + private static long mixK2(long k2) { + k2 *= C2; + k2 = Long.rotateLeft(k2, 33); + k2 *= C1; + return k2; + } + + public static long hash(Bytes bytes) { + return hash(bytes, bytes.position(), bytes.limit()); + } + + public static long hash(Bytes bytes, long offset, long limit) { + return hash(bytes, Accesses.toBytes(), offset, limit - offset); + } + + public static long hash(byte[] array) { + return hash(array, Accesses.unsafe(), BYTE_BASE, (long) array.length); + } + + public static long hash(char[] array) { + return hash(array, Accesses.unsafe(), CHAR_BASE, array.length * 2L); + } + + public static long hash(int value) { + long k1 = Primitives.unsignedInt(value); + long h1 = 0L ^ mixK1(k1); + long h2 = 0L; + return finalize(4L, h1, h2); + } + + public static long hash(long value) { + long k1 = value; + long h1 = 0L ^ mixK1(k1); + long h2 = 0L; + return finalize(8L, h1, h2); + } + + private Hasher() {} +} diff --git a/src/main/java/net/openhft/chronicle/hash/hashing/Primitives.java b/src/main/java/net/openhft/chronicle/hash/hashing/Primitives.java new file mode 100644 index 000000000..ba1ac75a9 --- /dev/null +++ b/src/main/java/net/openhft/chronicle/hash/hashing/Primitives.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.hash.hashing; + +final class Primitives { + + private Primitives() {} + + static long unsignedInt(int i) { + return i & 0xFFFFFFFFL; + } + + static int unsignedShort(int s) { + return s & 0xFFFF; + } + + static int unsignedByte(int b) { + return b & 0xFF; + } +} diff --git a/src/main/java/net/openhft/chronicle/hash/hashing/UnsafeAccess.java b/src/main/java/net/openhft/chronicle/hash/hashing/UnsafeAccess.java new file mode 100644 index 000000000..36d54a1cb --- /dev/null +++ b/src/main/java/net/openhft/chronicle/hash/hashing/UnsafeAccess.java @@ -0,0 +1,93 @@ +/* + * 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.hash.hashing; + +import sun.misc.Unsafe; + +import java.lang.reflect.Field; +import java.nio.ByteOrder; + +import static net.openhft.chronicle.hash.hashing.Primitives.unsignedByte; +import static net.openhft.chronicle.hash.hashing.Primitives.unsignedInt; +import static net.openhft.chronicle.hash.hashing.Primitives.unsignedShort; + +enum UnsafeAccess implements Access { + INSTANCE; + + static final Unsafe UNSAFE; + static final long BYTE_BASE; + static final long CHAR_BASE; + static final long SHORT_BASE; + static final long INT_BASE; + static final long LONG_BASE; + + static { + try { + Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + UNSAFE = (Unsafe) theUnsafe.get(null); + BYTE_BASE = UNSAFE.arrayBaseOffset(byte[].class); + CHAR_BASE = UNSAFE.arrayBaseOffset(char[].class); + SHORT_BASE = UNSAFE.arrayBaseOffset(short[].class); + INT_BASE = UNSAFE.arrayBaseOffset(int[].class); + LONG_BASE = UNSAFE.arrayBaseOffset(long[].class); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + @Override + public long getLong(Object input, long offset) { + return UNSAFE.getLong(input, offset); + } + + @Override + public long getUnsignedInt(Object input, long offset) { + return unsignedInt(getInt(input, offset)); + } + + @Override + public int getInt(Object input, long offset) { + return UNSAFE.getInt(input, offset); + } + + @Override + public int getUnsignedShort(Object input, long offset) { + return unsignedShort(getShort(input, offset)); + } + + @Override + public int getShort(Object input, long offset) { + return UNSAFE.getShort(input, offset); + } + + @Override + public int getUnsignedByte(Object input, long offset) { + return unsignedByte(getByte(input, offset)); + } + + @Override + public int getByte(Object input, long offset) { + return UNSAFE.getByte(input, offset); + } + + @Override + public ByteOrder byteOrder(Object input) { + return ByteOrder.nativeOrder(); + } + +} diff --git a/src/main/java/net/openhft/chronicle/hash/replication/AbstractReplication.java b/src/main/java/net/openhft/chronicle/hash/replication/AbstractReplication.java index 881fa91dd..e85fec8ff 100644 --- a/src/main/java/net/openhft/chronicle/hash/replication/AbstractReplication.java +++ b/src/main/java/net/openhft/chronicle/hash/replication/AbstractReplication.java @@ -35,6 +35,7 @@ public abstract class AbstractReplication implements Serializable { private final @Nullable RemoteNodeValidator remoteNodeValidator; + private final boolean bootstrapOnlyLocalEntries; // package-private to forbid subclassing from outside of the package AbstractReplication(byte localIdentifier, Builder builder) { @@ -42,14 +43,16 @@ public abstract class AbstractReplication implements Serializable { tcpConfig = builder.tcpConfig; udpConfig = builder.udpConfig; remoteNodeValidator = builder.remoteNodeValidator; + bootstrapOnlyLocalEntries = builder.bootstrapOnlyLocalEntries; } @Override public String toString() { - return ", localIdentifier=" + localIdentifier + + return "localIdentifier=" + localIdentifier + ", tcpConfig=" + tcpConfig + ", udpConfig=" + udpConfig + - ", remoteNodeValidator=" + remoteNodeValidator; + ", remoteNodeValidator=" + remoteNodeValidator + + ", bootstrapOnlyLocalEntries=" + bootstrapOnlyLocalEntries; } public byte identifier() { @@ -71,6 +74,10 @@ public abstract class AbstractReplication implements Serializable { return remoteNodeValidator; } + public boolean bootstrapOnlyLocalEntries() { + return bootstrapOnlyLocalEntries; + } + /** * Builder of {@link AbstractReplication} configurations. This class and it's subclasses are mutable, * configuration methods mutate the builder and return it back for convenient chaining. @@ -84,6 +91,7 @@ public abstract class AbstractReplication implements Serializable { private TcpTransportAndNetworkConfig tcpConfig = null; private UdpTransportConfig udpConfig = null; private RemoteNodeValidator remoteNodeValidator = null; + private boolean bootstrapOnlyLocalEntries = false; // package-private to forbid subclassing from outside of the package Builder() { @@ -115,6 +123,29 @@ public abstract class AbstractReplication implements Serializable { return (B) this; } + + /** + * Configures if the node, provided with replication, created by this builder, should + * replicate only local data, last updated with own identifier, or all data currently + * present on the node, when a new node joins the replication grid. + * + *

    Default configuration is {@code false}, potentially swamping new nodes with + * duplicates. However, this does guarantee that all the data is replicated over to the new + * node and is useful especially in the case that the originating node of some data is not + * currently running. + * + * @param bootstrapOnlyLocalEntries if a node provided with this replication should + * replicate only local data to the new nodes joining + * the replication network + * @return this builder back + */ + @NotNull + public B bootstrapOnlyLocalEntries(boolean bootstrapOnlyLocalEntries) { + this.bootstrapOnlyLocalEntries = bootstrapOnlyLocalEntries; + return (B) this; + } + + /** * Creates a Replication instance with the given node (server) identifier. * diff --git a/src/main/java/net/openhft/chronicle/hash/replication/ReplicationChannel.java b/src/main/java/net/openhft/chronicle/hash/replication/ReplicationChannel.java index 13d883e56..29ae03377 100644 --- a/src/main/java/net/openhft/chronicle/hash/replication/ReplicationChannel.java +++ b/src/main/java/net/openhft/chronicle/hash/replication/ReplicationChannel.java @@ -20,6 +20,8 @@ import net.openhft.chronicle.hash.ChronicleHash; import net.openhft.chronicle.hash.ChronicleHashInstanceConfig; import org.jetbrains.annotations.NotNull; +import java.io.Serializable; + /** * A token which should be created via {@link ReplicationHub#createChannel(int)} call and passed to {@link * ChronicleHashInstanceConfig#replicatedViaChannel(ReplicationChannel)} method, to establish a channel @@ -31,7 +33,7 @@ import org.jetbrains.annotations.NotNull; * @see ReplicationHub * @see ReplicationHub#createChannel(int) */ -public final class ReplicationChannel { +public final class ReplicationChannel implements Serializable{ private ReplicationHub hub; private final int channelId; diff --git a/src/main/java/net/openhft/chronicle/hash/replication/ReplicationHub.java b/src/main/java/net/openhft/chronicle/hash/replication/ReplicationHub.java index d8a7ac3e3..81d1b1a7d 100644 --- a/src/main/java/net/openhft/chronicle/hash/replication/ReplicationHub.java +++ b/src/main/java/net/openhft/chronicle/hash/replication/ReplicationHub.java @@ -73,7 +73,7 @@ public final class ReplicationHub extends AbstractReplication { @Override public String toString() { return "ReplicationHub{" + super.toString() + - "maxEntrySize=" + maxEntrySize + + ", maxEntrySize=" + maxEntrySize + ", channels=" + Arrays.toString(channels) + '}'; } diff --git a/src/main/java/net/openhft/chronicle/hash/replication/SingleChronicleHashReplication.java b/src/main/java/net/openhft/chronicle/hash/replication/SingleChronicleHashReplication.java index 6032edfc2..a4d869c2b 100644 --- a/src/main/java/net/openhft/chronicle/hash/replication/SingleChronicleHashReplication.java +++ b/src/main/java/net/openhft/chronicle/hash/replication/SingleChronicleHashReplication.java @@ -29,6 +29,11 @@ public final class SingleChronicleHashReplication extends AbstractReplication { super(localIdentifier, builder); } + @Override + public String toString() { + return "SingleChronicleHashReplication{" + super.toString() + "}"; + } + public static final class Builder extends AbstractReplication.Builder { private Builder() {} diff --git a/src/main/java/net/openhft/chronicle/hash/replication/TcpInetSocketAddress.java b/src/main/java/net/openhft/chronicle/hash/replication/TcpInetSocketAddress.java index cfd0c7fd5..a128a046d 100644 --- a/src/main/java/net/openhft/chronicle/hash/replication/TcpInetSocketAddress.java +++ b/src/main/java/net/openhft/chronicle/hash/replication/TcpInetSocketAddress.java @@ -2,7 +2,7 @@ package net.openhft.chronicle.hash.replication; import java.net.InetSocketAddress; -public class TcpInetSocketAddress extends InetSocketAddress { +class TcpInetSocketAddress extends InetSocketAddress { private final String toString; public TcpInetSocketAddress(String hostname, int port) { diff --git a/src/main/java/net/openhft/chronicle/hash/replication/TcpTransportAndNetworkConfig.java b/src/main/java/net/openhft/chronicle/hash/replication/TcpTransportAndNetworkConfig.java index 3e8b025d9..8a416d78e 100644 --- a/src/main/java/net/openhft/chronicle/hash/replication/TcpTransportAndNetworkConfig.java +++ b/src/main/java/net/openhft/chronicle/hash/replication/TcpTransportAndNetworkConfig.java @@ -31,13 +31,13 @@ import static java.util.concurrent.TimeUnit.SECONDS; public final class TcpTransportAndNetworkConfig implements Serializable { - private static final int DEFAULT_PACKET_SIZE = 1024 * 8; + private static final int DEFAULT_TCP_BUFFER_SIZE = 1024 * 64; private static final long DEFAULT_HEART_BEAT_INTERVAL = 20; private static final TimeUnit DEFAULT_HEART_BEAT_INTERVAL_UNIT = SECONDS; private final int serverPort; private final Set endpoints; - private final int packetSize; + private final int tcpBufferSize; private final boolean autoReconnectedUponDroppedConnection; private final ThrottlingConfig throttlingConfig; private final long heartBeatInterval; @@ -45,13 +45,13 @@ public final class TcpTransportAndNetworkConfig implements Serializable { private String name = "(unknown)"; private TcpTransportAndNetworkConfig(int serverPort, Set endpoints, - int packetSize, + int tcpBufferSize, boolean autoReconnectedUponDroppedConnection, ThrottlingConfig throttlingConfig, long heartBeatInterval, TimeUnit heartBeatIntervalUnit) { this.serverPort = serverPort; this.endpoints = endpoints; - this.packetSize = packetSize; + this.tcpBufferSize = tcpBufferSize; this.autoReconnectedUponDroppedConnection = autoReconnectedUponDroppedConnection; this.throttlingConfig = throttlingConfig; this.heartBeatInterval = heartBeatInterval; @@ -76,7 +76,7 @@ public final class TcpTransportAndNetworkConfig implements Serializable { } return new TcpTransportAndNetworkConfig( serverPort, unmodifiableSet(s), - DEFAULT_PACKET_SIZE, + DEFAULT_TCP_BUFFER_SIZE, true, // autoReconnectedUponDroppedConnection ThrottlingConfig.noThrottling(), DEFAULT_HEART_BEAT_INTERVAL, @@ -89,7 +89,7 @@ public final class TcpTransportAndNetworkConfig implements Serializable { public TcpTransportAndNetworkConfig autoReconnectedUponDroppedConnection( boolean autoReconnectedUponDroppedConnection) { - return new TcpTransportAndNetworkConfig(serverPort, endpoints, packetSize, + return new TcpTransportAndNetworkConfig(serverPort, endpoints, tcpBufferSize, autoReconnectedUponDroppedConnection, throttlingConfig, heartBeatInterval, heartBeatIntervalUnit); } @@ -100,7 +100,7 @@ public final class TcpTransportAndNetworkConfig implements Serializable { public TcpTransportAndNetworkConfig throttlingConfig(ThrottlingConfig throttlingConfig) { ThrottlingConfig.checkMillisecondBucketInterval(throttlingConfig, "TCP"); - return new TcpTransportAndNetworkConfig(serverPort, endpoints, packetSize, + return new TcpTransportAndNetworkConfig(serverPort, endpoints, tcpBufferSize, autoReconnectedUponDroppedConnection, throttlingConfig, heartBeatInterval, heartBeatIntervalUnit); } @@ -114,7 +114,7 @@ public final class TcpTransportAndNetworkConfig implements Serializable { } public TcpTransportAndNetworkConfig serverPort(int serverPort) { - return new TcpTransportAndNetworkConfig(serverPort, endpoints, packetSize, + return new TcpTransportAndNetworkConfig(serverPort, endpoints, tcpBufferSize, autoReconnectedUponDroppedConnection, throttlingConfig, heartBeatInterval, heartBeatIntervalUnit); } @@ -129,13 +129,13 @@ public final class TcpTransportAndNetworkConfig implements Serializable { throw new IllegalArgumentException("endpoint=" + endpoint + " can not point to the same port as the server"); } - return new TcpTransportAndNetworkConfig(serverPort, endpoints, packetSize, + return new TcpTransportAndNetworkConfig(serverPort, endpoints, tcpBufferSize, autoReconnectedUponDroppedConnection, throttlingConfig, heartBeatInterval, heartBeatIntervalUnit); } - public int packetSize() { - return packetSize; + public int tcpBufferSize() { + return tcpBufferSize; } /** @@ -148,17 +148,17 @@ public final class TcpTransportAndNetworkConfig implements Serializable { return this; } - public TcpTransportAndNetworkConfig packetSize(int packetSize) { - if (packetSize <= 0) + public TcpTransportAndNetworkConfig tcpBufferSize(int tcpBufferSize) { + if (tcpBufferSize <= 0) throw new IllegalArgumentException(); - return new TcpTransportAndNetworkConfig(serverPort, endpoints, packetSize, + return new TcpTransportAndNetworkConfig(serverPort, endpoints, tcpBufferSize, autoReconnectedUponDroppedConnection, throttlingConfig, heartBeatInterval, heartBeatIntervalUnit); } public TcpTransportAndNetworkConfig heartBeatInterval(long heartBeatInterval, TimeUnit heartBeatIntervalUnit) { - return new TcpTransportAndNetworkConfig(serverPort, endpoints, packetSize, + return new TcpTransportAndNetworkConfig(serverPort, endpoints, tcpBufferSize, autoReconnectedUponDroppedConnection, throttlingConfig, heartBeatInterval, heartBeatIntervalUnit); } @@ -173,12 +173,13 @@ public final class TcpTransportAndNetworkConfig implements Serializable { if (autoReconnectedUponDroppedConnection != that.autoReconnectedUponDroppedConnection) return false; if (heartBeatInterval != that.heartBeatInterval) return false; - if (packetSize != that.packetSize) return false; + if (tcpBufferSize != that.tcpBufferSize) return false; if (serverPort != that.serverPort) return false; if (endpoints != null ? !endpoints.equals(that.endpoints) : that.endpoints != null) return false; if (heartBeatIntervalUnit != that.heartBeatIntervalUnit) return false; - if (throttlingConfig != null ? !throttlingConfig.equals(that.throttlingConfig) : that.throttlingConfig != null) + if (throttlingConfig != null ? !throttlingConfig.equals(that.throttlingConfig) : + that.throttlingConfig != null) return false; return true; @@ -188,7 +189,7 @@ public final class TcpTransportAndNetworkConfig implements Serializable { public int hashCode() { int result = serverPort; result = 31 * result + (endpoints != null ? endpoints.hashCode() : 0); - result = 31 * result + packetSize; + result = 31 * result + tcpBufferSize; result = 31 * result + (autoReconnectedUponDroppedConnection ? 1 : 0); result = 31 * result + (throttlingConfig != null ? throttlingConfig.hashCode() : 0); result = 31 * result + (int) (heartBeatInterval ^ (heartBeatInterval >>> 32)); @@ -202,7 +203,7 @@ public final class TcpTransportAndNetworkConfig implements Serializable { "name=" + name() + ", serverPort=" + serverPort + ", endpoints=" + endpoints + - ", packetSize=" + packetSize + + ", tcpBufferSize=" + tcpBufferSize + ", autoReconnectedUponDroppedConnection=" + autoReconnectedUponDroppedConnection + ", throttlingConfig=" + throttlingConfig + ", heartBeatInterval=" + heartBeatInterval + diff --git a/src/main/java/net/openhft/chronicle/hash/serialization/BytesReader.java b/src/main/java/net/openhft/chronicle/hash/serialization/BytesReader.java index 05e9aa391..c05ae3b12 100644 --- a/src/main/java/net/openhft/chronicle/hash/serialization/BytesReader.java +++ b/src/main/java/net/openhft/chronicle/hash/serialization/BytesReader.java @@ -42,6 +42,8 @@ import java.io.Serializable; * * @Override * public LongPair[] read(Bytes bytes, long size, LongPair[] toReuse) { + * if (size > Integer.MAX_VALUE * 16L) + * throw new IllegalStateException("LongPair[] size couldn't be " + (size / 16L)); * int resLen = (int) (size / 16L); * LongPair[] res; * if (toReuse != null) { diff --git a/src/main/java/net/openhft/chronicle/hash/serialization/DeserializationFactoryConfigurableBytesReader.java b/src/main/java/net/openhft/chronicle/hash/serialization/DeserializationFactoryConfigurableBytesReader.java new file mode 100644 index 000000000..1c4444a24 --- /dev/null +++ b/src/main/java/net/openhft/chronicle/hash/serialization/DeserializationFactoryConfigurableBytesReader.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.hash.serialization; + +import net.openhft.lang.io.serialization.ObjectFactory; + +public interface DeserializationFactoryConfigurableBytesReader> + extends BytesReader { + + R withDeserializationFactory(ObjectFactory deserializationFactory); +} diff --git a/src/main/java/net/openhft/chronicle/hash/serialization/Hasher.java b/src/main/java/net/openhft/chronicle/hash/serialization/Hasher.java deleted file mode 100644 index 21eeb1d30..000000000 --- a/src/main/java/net/openhft/chronicle/hash/serialization/Hasher.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.hash.serialization; - -import net.openhft.lang.Maths; -import net.openhft.lang.io.Bytes; -import net.openhft.lang.io.NativeBytes; -import sun.misc.Unsafe; - -import static java.nio.ByteOrder.LITTLE_ENDIAN; -import static java.nio.ByteOrder.nativeOrder; - -public enum Hasher { - ; - - private static final long LONG_LEVEL_PRIME_MULTIPLE = 0x9ddfea08eb382d69L; - private static final short SHORT_LEVEL_PRIME_MULTIPLE = 0x404f; - private static final byte BYTE_LEVEL_PRIME_MULTIPLE = 0x57; - - private static final int INT_HASH_LOW_SHORT_MULTIPLE = - nativeOrder() == LITTLE_ENDIAN ? SHORT_LEVEL_PRIME_MULTIPLE : 1; - private static final int INT_HASH_HIGH_SHORT_MULTIPLE = - nativeOrder() == LITTLE_ENDIAN ? 1 : SHORT_LEVEL_PRIME_MULTIPLE; - - public static long hash(Bytes bytes) { - return hash(bytes, bytes.position(), bytes.limit()); - } - - public static long hash(Bytes bytes, long offset, long limit) { - if (limit - offset == 8) - return Maths.hash(bytes.readLong(offset)); - long h = 0; - long i = offset; - for (; i < limit - 7; i += 8) - h = LONG_LEVEL_PRIME_MULTIPLE * h + bytes.readLong(i); - for (; i < limit - 1; i += 2) - h = SHORT_LEVEL_PRIME_MULTIPLE * h + bytes.readShort(i); - if (i < limit) - h = BYTE_LEVEL_PRIME_MULTIPLE * h + bytes.readByte(i); - return hash(h); - } - - public static long hash(Object array, int length) { - Unsafe unsafe = NativeBytes.UNSAFE; - long base = unsafe.arrayBaseOffset(array.getClass()); - long scale = unsafe.arrayIndexScale(array.getClass()); - long h = 0; - long i = 0; - long limit = ((long) length) * scale; - for (; i < limit - 7; i += 8) - h = LONG_LEVEL_PRIME_MULTIPLE * h + unsafe.getLong(array, base + i); - for (; i < limit - 1; i += 2) - h = SHORT_LEVEL_PRIME_MULTIPLE * h + unsafe.getShort(array, base + i); - if (i < limit) - h = BYTE_LEVEL_PRIME_MULTIPLE * h + unsafe.getByte(array, base + i); - return hash(h); - } - - public static long hash(int value) { - return hash(((long) (((short) (value >>> 16)) * INT_HASH_HIGH_SHORT_MULTIPLE)) + - ((short) value) * INT_HASH_LOW_SHORT_MULTIPLE); - } - - public static long hash(long value) { - return Maths.hash(value); - } -} diff --git a/src/main/java/net/openhft/chronicle/hash/serialization/internal/ByteArrayMarshaller.java b/src/main/java/net/openhft/chronicle/hash/serialization/internal/ByteArrayMarshaller.java index b77e524bb..d92787fbb 100644 --- a/src/main/java/net/openhft/chronicle/hash/serialization/internal/ByteArrayMarshaller.java +++ b/src/main/java/net/openhft/chronicle/hash/serialization/internal/ByteArrayMarshaller.java @@ -20,7 +20,7 @@ package net.openhft.chronicle.hash.serialization.internal; import net.openhft.chronicle.hash.serialization.BytesInterop; import net.openhft.chronicle.hash.serialization.BytesReader; -import net.openhft.chronicle.hash.serialization.Hasher; +import net.openhft.chronicle.hash.hashing.Hasher; import net.openhft.lang.io.Bytes; public enum ByteArrayMarshaller implements BytesInterop, BytesReader { @@ -33,6 +33,8 @@ public enum ByteArrayMarshaller implements BytesInterop, BytesReader, BytesReader, BytesReader (long) Integer.MAX_VALUE) + throw new IllegalArgumentException("byte[] size should be non-negative int, " + + size + " given. Memory corruption?"); + return (int) size; + } } diff --git a/src/main/java/net/openhft/chronicle/hash/serialization/internal/ByteBufferMarshaller.java b/src/main/java/net/openhft/chronicle/hash/serialization/internal/ByteBufferMarshaller.java new file mode 100644 index 000000000..faffb7876 --- /dev/null +++ b/src/main/java/net/openhft/chronicle/hash/serialization/internal/ByteBufferMarshaller.java @@ -0,0 +1,95 @@ +/* + * 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.hash.serialization.internal; + +import net.openhft.chronicle.hash.hashing.Accesses; +import net.openhft.chronicle.hash.hashing.Hasher; +import net.openhft.chronicle.hash.serialization.BytesInterop; +import net.openhft.chronicle.hash.serialization.BytesReader; +import net.openhft.lang.io.Bytes; +import org.jetbrains.annotations.Nullable; + +import java.nio.ByteBuffer; + +public enum ByteBufferMarshaller implements BytesInterop, BytesReader { + INSTANCE; + + @Override + public boolean startsWith(Bytes bytes, ByteBuffer bb) { + int inputRemaining = bb.remaining(); + if(bytes.capacity() - bytes.position() < (long) inputRemaining) + return false; + + long pos = bytes.position(); + int inputPos = bb.position(); + + int i; + for(i = 0; i < inputRemaining - 7; i += 8) { + if (bytes.readLong(pos + (long) i) != bb.getLong(inputPos + i)) + return false; + } + for(; i < inputRemaining - 3; i += 4) { + if (bytes.readInt(pos + (long) i) != bb.getInt(inputPos + i)) + return false; + } + for(; i < inputRemaining; i++) { + if (bytes.readByte(pos + (long) i) != bb.get(inputPos + i)) + return false; + } + + return true; + } + + @Override + public long hash(ByteBuffer bb) { + return Hasher.hash(bb, Accesses.toByteBuffer(), 0L, (long) bb.remaining()); + } + + @Override + public ByteBuffer read(Bytes bytes, long size) { + return read(bytes, size, null); + } + + @Override + public ByteBuffer read(Bytes bytes, long size, @Nullable ByteBuffer toReuse) { + if (size < 0L || size > (long) Integer.MAX_VALUE) + throw new IllegalArgumentException("ByteBuffer size should be non-negative int, " + + size + " given. Memory corruption?"); + int intSize = (int) size; + if (toReuse == null || toReuse.capacity() < intSize) { + toReuse = ByteBuffer.allocate(intSize); + } else { + toReuse.position(0); + toReuse.limit(intSize); + } + bytes.read(toReuse); + toReuse.flip(); + return toReuse; + } + + @Override + public long size(ByteBuffer bb) { + return (long) bb.remaining(); + } + + @Override + public void write(Bytes bytes, ByteBuffer bb) { + int position = bb.position(); + bytes.write(bb); + bb.position(position); + } +} diff --git a/src/main/java/net/openhft/chronicle/hash/serialization/internal/ByteableMarshaller.java b/src/main/java/net/openhft/chronicle/hash/serialization/internal/ByteableMarshaller.java index 7e7dd742c..bb39475c7 100644 --- a/src/main/java/net/openhft/chronicle/hash/serialization/internal/ByteableMarshaller.java +++ b/src/main/java/net/openhft/chronicle/hash/serialization/internal/ByteableMarshaller.java @@ -18,34 +18,29 @@ package net.openhft.chronicle.hash.serialization.internal; -import net.openhft.chronicle.hash.serialization.BytesInterop; -import net.openhft.chronicle.hash.serialization.BytesReader; -import net.openhft.chronicle.hash.serialization.Hasher; -import net.openhft.chronicle.hash.serialization.SizeMarshaller; +import net.openhft.chronicle.hash.hashing.Hasher; +import net.openhft.chronicle.hash.serialization.*; import net.openhft.lang.io.Bytes; import net.openhft.lang.io.MultiStoreBytes; import net.openhft.lang.io.NativeBytes; import net.openhft.lang.io.serialization.*; -import net.openhft.lang.io.serialization.impl.AllocateInstanceObjectFactory; import net.openhft.lang.model.Byteable; import org.jetbrains.annotations.NotNull; -public class ByteableMarshaller - implements BytesInterop, BytesReader, SizeMarshaller { +public abstract class ByteableMarshaller + implements BytesInterop, + DeserializationFactoryConfigurableBytesReader>, + SizeMarshaller { private static final long serialVersionUID = 0L; public static ByteableMarshaller of(@NotNull Class eClass) { - return new ByteableMarshaller(eClass); + return new Default<>(eClass); } - public static ByteableMarshaller of(@NotNull Class eClass, - ObjectFactory factory) { - if (factory instanceof AllocateInstanceObjectFactory) { - Class allocatedClass = ((AllocateInstanceObjectFactory) factory).allocatedClass(); - return new Default(allocatedClass); - } else { - return new WithCustomFactory(eClass, factory); - } + @Override + public ByteableMarshaller withDeserializationFactory( + ObjectFactory deserializationFactory) { + return new WithCustomFactory<>(tClass, deserializationFactory); } @NotNull @@ -81,6 +76,8 @@ public class ByteableMarshaller @Override public boolean startsWith(Bytes bytes, E e) { + if (bytes.capacity() - bytes.position() < size) + return false; Bytes input = e.bytes(); long pos = bytes.position(), inputPos = e.offset(); diff --git a/src/main/java/net/openhft/chronicle/hash/serialization/internal/BytesBytesInterop.java b/src/main/java/net/openhft/chronicle/hash/serialization/internal/BytesBytesInterop.java index ae25852d8..c8cb5ea3d 100644 --- a/src/main/java/net/openhft/chronicle/hash/serialization/internal/BytesBytesInterop.java +++ b/src/main/java/net/openhft/chronicle/hash/serialization/internal/BytesBytesInterop.java @@ -17,7 +17,7 @@ package net.openhft.chronicle.hash.serialization.internal; import net.openhft.chronicle.hash.serialization.BytesInterop; -import net.openhft.chronicle.hash.serialization.Hasher; +import net.openhft.chronicle.hash.hashing.Hasher; import net.openhft.lang.io.Bytes; public enum BytesBytesInterop implements BytesInterop { @@ -25,7 +25,11 @@ public enum BytesBytesInterop implements BytesInterop { @Override public boolean startsWith(Bytes bytes, Bytes e) { - return bytes.startsWith(e); + long limit = bytes.limit(); + bytes.limit(bytes.capacity()); + boolean result = bytes.startsWith(e); + bytes.limit(limit); + return result; } @Override diff --git a/src/main/java/net/openhft/chronicle/hash/serialization/internal/BytesReaders.java b/src/main/java/net/openhft/chronicle/hash/serialization/internal/BytesReaders.java index 8fe37b984..db1c88600 100644 --- a/src/main/java/net/openhft/chronicle/hash/serialization/internal/BytesReaders.java +++ b/src/main/java/net/openhft/chronicle/hash/serialization/internal/BytesReaders.java @@ -41,6 +41,18 @@ public final class BytesReaders { return new SimpleBytesReader(marshaller); } + /** + * Returns {@code BytesMarshaller} {@code m}, if the given reader is the result of {@link + * #fromBytesMarshaller(BytesMarshaller) fromBytesMarshaller(m)} call, {@code null} otherwise. + * + * @param reader reader to extract {@code BytesMarshaller} from + * @param type of the objects marshalled + * @return {@code BytesMarshaller} from which the specified reader was created, or {@code null} + */ + public static BytesMarshaller getBytesMarshaller(BytesReader reader) { + return reader instanceof SimpleBytesReader ? ((SimpleBytesReader) reader).marshaller : null; + } + private static class SimpleBytesReader implements BytesReader { private static final long serialVersionUID = 0L; diff --git a/src/main/java/net/openhft/chronicle/hash/serialization/internal/CharArrayMarshaller.java b/src/main/java/net/openhft/chronicle/hash/serialization/internal/CharArrayMarshaller.java index 1a5d6ea0f..9e646beaf 100644 --- a/src/main/java/net/openhft/chronicle/hash/serialization/internal/CharArrayMarshaller.java +++ b/src/main/java/net/openhft/chronicle/hash/serialization/internal/CharArrayMarshaller.java @@ -20,7 +20,7 @@ package net.openhft.chronicle.hash.serialization.internal; import net.openhft.chronicle.hash.serialization.BytesInterop; import net.openhft.chronicle.hash.serialization.BytesReader; -import net.openhft.chronicle.hash.serialization.Hasher; +import net.openhft.chronicle.hash.hashing.Hasher; import net.openhft.lang.io.Bytes; public enum CharArrayMarshaller implements BytesInterop, BytesReader { @@ -28,6 +28,8 @@ public enum CharArrayMarshaller implements BytesInterop, BytesReader, BytesReader Integer.MAX_VALUE) + throw new IllegalArgumentException("char[] size should be non-negative int, " + + resLen + " given. Memory corruption?"); + return (int) resLen; + } + @Override public long size(char[] chars) { return chars.length * 2L; diff --git a/src/main/java/net/openhft/chronicle/hash/serialization/internal/CharSequenceReader.java b/src/main/java/net/openhft/chronicle/hash/serialization/internal/CharSequenceReader.java index 0fc614406..60eb7b516 100644 --- a/src/main/java/net/openhft/chronicle/hash/serialization/internal/CharSequenceReader.java +++ b/src/main/java/net/openhft/chronicle/hash/serialization/internal/CharSequenceReader.java @@ -33,7 +33,16 @@ public final class CharSequenceReader implements BytesReader, StatefulCopyable> { private static final long serialVersionUID = 0L; - private enum NoInterning implements CharSequenceInterner { + private enum NoInterningIdentity implements CharSequenceInterner { + INSTANCE; + + @Override + public CharSequence intern(CharSequence cs) { + return cs; + } + } + + private enum NoInterningToString implements CharSequenceInterner { INSTANCE; @Override public String intern(CharSequence cs) { @@ -41,10 +50,17 @@ public final class CharSequenceReader } } - private static final CharSequenceReader DEFAULT_READER = - new CharSequenceReader(NoInterning.INSTANCE, NoInterning.INSTANCE); + private static final CharSequenceReader STRING_READER = + new CharSequenceReader(NoInterningToString.INSTANCE, NoInterningToString.INSTANCE); - public static CharSequenceReader of() { + private static final CharSequenceReader DEFAULT_READER = + new CharSequenceReader(NoInterningIdentity.INSTANCE, NoInterningIdentity.INSTANCE); + + public static CharSequenceReader of() { + return STRING_READER; + } + + public static CharSequenceReader ofStringBuilder() { return DEFAULT_READER; } @@ -111,7 +127,9 @@ public final class CharSequenceReader } private Object readResolve() throws ObjectStreamException { - if (interner == NoInterning.INSTANCE) + if (interner == NoInterningToString.INSTANCE) + return STRING_READER; + if (interner == NoInterningIdentity.INSTANCE) return DEFAULT_READER; return this; } diff --git a/src/main/java/net/openhft/chronicle/hash/serialization/internal/CopyingMetaBytesInterop.java b/src/main/java/net/openhft/chronicle/hash/serialization/internal/CopyingMetaBytesInterop.java index 31a897e16..fa631cd04 100644 --- a/src/main/java/net/openhft/chronicle/hash/serialization/internal/CopyingMetaBytesInterop.java +++ b/src/main/java/net/openhft/chronicle/hash/serialization/internal/CopyingMetaBytesInterop.java @@ -19,12 +19,13 @@ package net.openhft.chronicle.hash.serialization.internal; import net.openhft.chronicle.hash.serialization.BytesWriter; -import net.openhft.chronicle.hash.serialization.Hasher; +import net.openhft.chronicle.hash.hashing.Hasher; +import net.openhft.lang.MemoryUnit; import net.openhft.lang.io.Bytes; import net.openhft.lang.io.DirectBytes; import net.openhft.lang.io.DirectStore; import net.openhft.lang.io.serialization.BytesMarshaller; -import net.openhft.lang.io.serialization.JDKZObjectSerializer; +import net.openhft.lang.io.serialization.JDKObjectSerializer; import net.openhft.lang.threadlocal.Provider; import net.openhft.lang.threadlocal.StatefulCopyable; import net.openhft.lang.threadlocal.ThreadLocalCopies; @@ -36,6 +37,19 @@ import java.io.Serializable; public abstract class CopyingMetaBytesInterop implements MetaBytesInterop { private static final long serialVersionUID = 0L; + private static final long MAX_REASONABLE_SERIALIZED_SIZE = MemoryUnit.MEGABYTES.toBytes(512L); + + public static void checkMaxSizeStillReasonable(long maxSize, Exception ex) { + if (maxSize > MAX_REASONABLE_SERIALIZED_SIZE) { + throw new IllegalStateException("We try to figure out size of objects " + + "in serialized form, but it exceeds " + + MAX_REASONABLE_SERIALIZED_SIZE + " bytes. We assume this is " + + "a error and throw exception at this point. If you really " + + "want larger keys/values, use ChronicleMapBuilder." + + "keySize(int)/valueSize(int)/entrySize(int) configurations", ex); + } + } + final DirectBytesBuffer buffer; transient long size; transient long hash; @@ -50,11 +64,19 @@ public abstract class CopyingMetaBytesInterop implements MetaBytesInterop< if (mutable || writer != this.writer || e != cur) { this.writer = writer; cur = e; - Bytes buffer = this.buffer.obtain(maxSize); - innerWrite(writer, buffer, e); - buffer.flip(); - size = buffer.remaining(); - hash = 0L; + while (true) { + try { + Bytes buffer = this.buffer.obtain(maxSize); + innerWrite(writer, buffer, e); + buffer.flip(); + size = buffer.remaining(); + hash = 0L; + return; + } catch (Exception ex) { + checkMaxSizeStillReasonable(maxSize, ex); + maxSize *= 2L; + } + } } } @@ -203,7 +225,7 @@ public abstract class CopyingMetaBytesInterop implements MetaBytesInterop< return buffer = store.bytes(); } } else { - buffer = new DirectStore(JDKZObjectSerializer.INSTANCE, maxSize, false).bytes(); + buffer = new DirectStore(JDKObjectSerializer.INSTANCE, maxSize, false).bytes(); return buffer; } } diff --git a/src/main/java/net/openhft/chronicle/hash/serialization/internal/DataValueBytesMarshallers.java b/src/main/java/net/openhft/chronicle/hash/serialization/internal/DataValueBytesMarshallers.java index 0e71de34d..c12280146 100644 --- a/src/main/java/net/openhft/chronicle/hash/serialization/internal/DataValueBytesMarshallers.java +++ b/src/main/java/net/openhft/chronicle/hash/serialization/internal/DataValueBytesMarshallers.java @@ -18,6 +18,7 @@ package net.openhft.chronicle.hash.serialization.internal; import net.openhft.chronicle.hash.serialization.BytesReader; import net.openhft.chronicle.hash.serialization.BytesWriter; +import net.openhft.chronicle.hash.serialization.DeserializationFactoryConfigurableBytesReader; import net.openhft.compiler.CompilerUtils; import net.openhft.lang.io.Bytes; import net.openhft.lang.io.serialization.ObjectFactory; @@ -56,18 +57,6 @@ public final class DataValueBytesMarshallers { } } - public static BytesReader acquireBytesReader(Class tClass, ObjectFactory factory) { - Class readerClass = acquireReaderWithCustomFactory(tClass); - try { - Constructor constructor = readerClass.getConstructor(ObjectFactory.class); - Object reader = constructor.newInstance(factory); - return (BytesReader) reader; - } catch (IllegalAccessException | NoSuchMethodException | InstantiationException | - InvocationTargetException e) { - throw new AssertionError(e); - } - } - public static BytesWriter acquireBytesWriter(Class tClass) { Class readerClass = acquireWriterClass(tClass); try { @@ -106,6 +95,7 @@ public final class DataValueBytesMarshallers { } } readersClassMap.put(tClass, readerClass); + acquireReaderWithCustomFactory(tClass); return readerClass; } @@ -146,7 +136,7 @@ public final class DataValueBytesMarshallers { if (dumpCode) LoggerFactory.getLogger(DataValueGenerator.class).info(actual); ClassLoader classLoader = tClass.getClassLoader(); - String className = bytesReaderName(tClass, false) + "$WithCustomFactory"; + String className = withCustomFactoryName(tClass); try { c = classLoader.loadClass(className); } catch (ClassNotFoundException ignored) { @@ -161,6 +151,10 @@ public final class DataValueBytesMarshallers { return c; } + private static String withCustomFactoryName(Class tClass) { + return bytesReaderName(tClass, false) + "$WithCustomFactory"; + } + private static String generateWithCustomFactoryClass(Class tClass) { DataValueModel dvModel = DataValueModels.acquireModel(tClass); @@ -217,10 +211,14 @@ public final class DataValueBytesMarshallers { SortedSet imported = newImported(); imported.add(BytesReader.class); + imported.add(DeserializationFactoryConfigurableBytesReader.class); + imported.add(ObjectFactory.class); imported.add(Bytes.class); imported.add(ByteableMarshaller.class); imported.add(Byteable.class); imported.add(dvModel.type()); + imported.add(InvocationTargetException.class); + imported.add(Constructor.class); StringBuilder read = generateReadBody(dvModel, imported); @@ -230,11 +228,13 @@ public final class DataValueBytesMarshallers { String simpleName = tClass.getSimpleName(); String bytesReaderName = bytesReaderName(tClass, false); String simpleReaderName = bytesReaderName(simpleName(tClass)); - sb.append("\npublic class ").append(simpleReaderName) - .append(" implements BytesReader<").append(simpleName).append("> {\n"); + sb.append("\n@SuppressWarnings(\"unchecked\") public class ").append(simpleReaderName) + .append(" implements DeserializationFactoryConfigurableBytesReader<") + .append(simpleName).append(", ").append(simpleReaderName).append("> {\n"); declareSerialVersionUID(sb); declareStaticInstance(sb, tClass); generatePrivateConstructor(sb, simpleReaderName); + generateWithDeserializationFactory(tClass, dvModel, sb, simpleName, simpleReaderName); generateGetInstance(sb, tClass); generateDelegatingRead(sb, simpleName); generateRead(read, sb, simpleName, tClass); @@ -242,6 +242,31 @@ public final class DataValueBytesMarshallers { return sb.toString(); } + private static void generateWithDeserializationFactory(Class tClass, + DataValueModel dvModel, + StringBuilder sb, String simpleName, + String simpleReaderName) { + appendOverride(sb); + sb.append(" public ").append(simpleReaderName) + .append(" withDeserializationFactory(ObjectFactory<").append(simpleName) + .append("> factory) {\n"); + sb.append(" try {\n"); + sb.append(" ") + .append("Class cfc = Class.forName(\"") + .append(getPackage(dvModel)) + .append(".").append(withCustomFactoryName(tClass)).append("\");\n"); + sb.append( + " Constructor constructor = cfc.getConstructor(ObjectFactory.class);\n" + + " Object reader = constructor.newInstance(factory);\n" + + " return (" + simpleReaderName + ") reader;\n" + + " } catch (ClassNotFoundException | IllegalAccessException | " + + "NoSuchMethodException | InstantiationException |\n" + + " InvocationTargetException e) {\n" + + " throw new AssertionError(e);\n" + + " }\n"); + sb.append(" }\n\n"); + } + private static StringBuilder generateReadBody(DataValueModel dvModel, SortedSet imported) { StringBuilder read = new StringBuilder(); @@ -272,7 +297,7 @@ public final class DataValueBytesMarshallers { .append(bytesReaderName(type, false)) .append(".INSTANCE").append(".read(bytes, ") .append(computeNonScalarOffset(dvModel, type)).append(", ") - .append(defaultGetter.getName()).append("(i)));"); + .append("toReuse.").append(defaultGetter.getName()).append("(i)));\n"); read.append(" }\n"); } else { read.append(" toReuse.") @@ -280,24 +305,37 @@ public final class DataValueBytesMarshallers { .append(bytesReaderName(type, false)) .append(".INSTANCE").append(".read(bytes, ") .append(computeNonScalarOffset(dvModel, type)).append(", ") - .append(defaultGetter.getName()).append("()));"); + .append("toReuse.").append(defaultGetter.getName()).append("()));\n"); } } else { if (model.isArray()) { read.append(" for (int i = 0; i < ") .append(model.indexSize().value()).append("; i++) {\n"); + saveCharSequencePosition(read, type); read.append(" toReuse.").append(defaultSetter.getName()) .append("(i, bytes.read").append(bytesType(type)).append("());\n"); + updateCharSequencePosition(read, model, type); read.append(" }\n"); } else { - read.append(" toReuse.").append(defaultSetter.getName()) + read.append(" {\n"); + saveCharSequencePosition(read, type); + read.append(" toReuse.").append(defaultSetter.getName()) .append("(bytes.read").append(bytesType(type)).append("());\n"); + updateCharSequencePosition(read, model, type); + read.append(" }\n"); } } } return read; } + private static void updateCharSequencePosition(StringBuilder read, FieldModel model, + Class type) { + if (CharSequence.class.isAssignableFrom(type)) + read.append(" bytes.position(pos + ") + .append(fieldSize(model)).append(");\n"); + } + private static String generateBytesWriter(Class tClass) { DataValueModel dvModel = DataValueModels.acquireModel(tClass); @@ -390,7 +428,7 @@ public final class DataValueBytesMarshallers { write.append(" for (int i = 0; i < ") .append(model.indexSize().value()).append("; i++) {\n"); write.append(" ") - .append(type.getName()).append(" ").append(" $ = e.") + .append(normalize(type)).append(" $ = e.") .append(defaultGetter.getName()).append("(i);\n"); write.append(" ") .append("if ($ == null) shouldNotBeNull();\n"); @@ -400,7 +438,7 @@ public final class DataValueBytesMarshallers { write.append(" }\n"); } else { write.append(" {") - .append(type.getName()).append(" ").append(" $ = e.") + .append(normalize(type)).append(" $ = e.") .append(defaultGetter.getName()).append("();\n"); write.append(" ") .append("if ($ == null) shouldNotBeNull();\n"); @@ -412,18 +450,39 @@ public final class DataValueBytesMarshallers { if (model.isArray()) { write.append(" for (int i = 0; i < ") .append(model.indexSize().value()).append("; i++) {\n"); + saveCharSequencePosition(write, type); write.append(" bytes.write").append(bytesType(type)) .append("(e.").append(defaultGetter.getName()).append("(i));\n"); + zeroOutRemainingCharSequenceBytesAndUpdatePosition(write, model, type); write.append(" }\n"); } else { - write.append(" bytes.write").append(bytesType(type)) + write.append(" {\n"); + saveCharSequencePosition(write, type); + write.append(" bytes.write").append(bytesType(type)) .append("(e.").append(defaultGetter.getName()).append("());\n"); + zeroOutRemainingCharSequenceBytesAndUpdatePosition(write, model, type); + write.append(" }\n"); } } } return write.toString(); } + private static void saveCharSequencePosition(StringBuilder write, Class type) { + if (CharSequence.class.isAssignableFrom(type)) + write.append(" long pos = bytes.position();\n"); + } + + private static void zeroOutRemainingCharSequenceBytesAndUpdatePosition( + StringBuilder write, FieldModel model, Class type) { + if (CharSequence.class.isAssignableFrom(type)) { + write.append(" long newPos = pos + ").append(fieldSize(model)) + .append(";\n"); + write.append(" bytes.zeroOut(bytes.position(), newPos);\n"); + write.append(" bytes.position(newPos);\n"); + } + } + private static void generateRead(StringBuilder read, StringBuilder sb, String simpleName, Class cl) { appendOverride(sb); @@ -432,8 +491,7 @@ public final class DataValueBytesMarshallers { sb.append(" try {\n"); sb.append(" if (toReuse == null)\n"); sb.append(" toReuse = getInstance();\n"); - sb.append(" if (toReuse instanceof Byteable && ") - .append("((Byteable) toReuse).bytes() == null) {\n"); + sb.append(" if (toReuse instanceof Byteable) {\n"); sb.append(" " + "ByteableMarshaller.setBytesAndOffset(((Byteable) toReuse), bytes);\n"); sb.append(" bytes.skip(size);\n"); diff --git a/src/main/java/net/openhft/chronicle/hash/serialization/internal/DoubleMarshaller.java b/src/main/java/net/openhft/chronicle/hash/serialization/internal/DoubleMarshaller.java index 621a6a8ef..2a13e1577 100644 --- a/src/main/java/net/openhft/chronicle/hash/serialization/internal/DoubleMarshaller.java +++ b/src/main/java/net/openhft/chronicle/hash/serialization/internal/DoubleMarshaller.java @@ -20,7 +20,7 @@ package net.openhft.chronicle.hash.serialization.internal; import net.openhft.chronicle.hash.serialization.BytesInterop; import net.openhft.chronicle.hash.serialization.BytesReader; -import net.openhft.chronicle.hash.serialization.Hasher; +import net.openhft.chronicle.hash.hashing.Hasher; import net.openhft.chronicle.hash.serialization.SizeMarshaller; import net.openhft.lang.io.Bytes; @@ -46,7 +46,7 @@ public enum DoubleMarshaller implements BytesInterop, BytesReader, BytesReader, SizeMarshaller { - private static final long serialVersionUID = 0L; - - public static final LongValueKeyMarshaller INSTANCE = new LongValueKeyMarshaller(); - - private static final Class heapLongValueClass = - DataValueClasses.heapClassFor(LongValue.class); - - LongValueKeyMarshaller() {} - - private Object readResolve() { - return INSTANCE; - } - - @Override - public long size(LongValue e) { - return 8L; - } - - @Override - public int sizeEncodingSize(long size) { - return 0; - } - - @Override - public void writeSize(Bytes bytes, long size) { - // do nothing - } - - @Override - public boolean startsWith(Bytes bytes, LongValue e) { - return e.getValue() == bytes.readLong(0); - } - - @Override - public long hash(LongValue e) { - return Hasher.hash(e.getValue()); - } - - @Override - public void write(Bytes bytes, LongValue e) { - bytes.writeLong(e.getValue()); - } - - @Override - public long readSize(Bytes bytes) { - return 8L; - } - - @Override - public LongValue read(Bytes bytes, long size) { - return read(bytes, size, null); - } - - @Override - public LongValue read(Bytes bytes, long size, LongValue toReuse) { - if (toReuse == null || - (toReuse instanceof Byteable && ((Byteable) toReuse).bytes() == null)) { - try { - toReuse = heapLongValueClass.newInstance(); - } catch (InstantiationException | IllegalAccessException e) { - throw new AssertionError(e); - } - } - toReuse.setValue(bytes.readLong()); - return toReuse; - } -} diff --git a/src/main/java/net/openhft/chronicle/hash/serialization/internal/LongValueValueMarshaller.java b/src/main/java/net/openhft/chronicle/hash/serialization/internal/LongValueValueMarshaller.java deleted file mode 100644 index 3473e584b..000000000 --- a/src/main/java/net/openhft/chronicle/hash/serialization/internal/LongValueValueMarshaller.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.hash.serialization.internal; - -import net.openhft.lang.io.Bytes; -import net.openhft.lang.model.Byteable; -import net.openhft.lang.model.DataValueClasses; -import net.openhft.lang.values.LongValue; - -public final class LongValueValueMarshaller extends LongValueKeyMarshaller { - private static final long serialVersionUID = 0L; - - public static final LongValueValueMarshaller INSTANCE = new LongValueValueMarshaller(); - - private static final Class directLongValueClass = - DataValueClasses.directClassFor(LongValue.class); - - private LongValueValueMarshaller() {} - - private Object readResolve() { - return INSTANCE; - } - - @Override - public LongValue read(Bytes bytes, long size, LongValue toReuse) { - if (!(toReuse instanceof Byteable)) { - try { - toReuse = directLongValueClass.newInstance(); - } catch (InstantiationException | IllegalAccessException e) { - throw new AssertionError(e); - } - } - Byteable value = (Byteable) toReuse; - ByteableMarshaller.setBytesAndOffset(value, bytes); - bytes.skip(8L); - return toReuse; - } -} diff --git a/src/main/java/net/openhft/chronicle/hash/serialization/internal/VoidMarshaller.java b/src/main/java/net/openhft/chronicle/hash/serialization/internal/VoidMarshaller.java index 5edf54d71..092b62fd7 100644 --- a/src/main/java/net/openhft/chronicle/hash/serialization/internal/VoidMarshaller.java +++ b/src/main/java/net/openhft/chronicle/hash/serialization/internal/VoidMarshaller.java @@ -18,6 +18,7 @@ package net.openhft.chronicle.hash.serialization.internal; +import net.openhft.chronicle.hash.hashing.Hasher; import net.openhft.chronicle.hash.serialization.BytesInterop; import net.openhft.chronicle.hash.serialization.BytesReader; import net.openhft.chronicle.hash.serialization.SizeMarshaller; @@ -49,7 +50,7 @@ public enum VoidMarshaller @Override public long hash(Void e) { - return 0; + throw new UnsupportedOperationException(); } @Override diff --git a/src/main/java/net/openhft/chronicle/map/AbstractChannelReplicator.java b/src/main/java/net/openhft/chronicle/map/AbstractChannelReplicator.java index 8f87caed2..94c8ab798 100644 --- a/src/main/java/net/openhft/chronicle/map/AbstractChannelReplicator.java +++ b/src/main/java/net/openhft/chronicle/map/AbstractChannelReplicator.java @@ -32,6 +32,8 @@ import java.io.Closeable; import java.io.IOException; import java.lang.reflect.Field; import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.channels.*; import java.security.AccessController; import java.security.PrivilegedAction; @@ -76,8 +78,7 @@ abstract class AbstractChannelReplicator implements Closeable { ThreadLocalCopies copies; VanillaChronicleMap.SegmentState segmentState; - AbstractChannelReplicator(String name, ThrottlingConfig throttlingConfig, - int maxEntrySizeBytes) + AbstractChannelReplicator(String name, ThrottlingConfig throttlingConfig) throws IOException { executorService = Executors.newSingleThreadExecutor( new NamedThreadFactory(name, true) { @@ -90,7 +91,7 @@ abstract class AbstractChannelReplicator implements Closeable { throttler = throttlingConfig.throttling(DAYS) > 0 ? new Throttler(selector, throttlingConfig.bucketInterval(MILLISECONDS), - maxEntrySizeBytes, throttlingConfig.throttling(DAYS)) : null; + throttlingConfig.throttling(DAYS)) : null; startedHere = new Throwable("Started here"); } @@ -317,12 +318,11 @@ abstract class AbstractChannelReplicator implements Closeable { Throttler(@NotNull Selector selector, long throttleIntervalInMillis, - long serializedEntrySize, long bitsPerDay) { this.selector = selector; this.throttleInterval = throttleIntervalInMillis; double bytesPerMs = ((double) bitsPerDay) / DAYS.toMillis(1) / BITS_IN_A_BYTE; - this.maxBytesInInterval = round(bytesPerMs * throttleInterval) - serializedEntrySize; + this.maxBytesInInterval = round(bytesPerMs * throttleInterval); } public void add(SelectableChannel selectableChannel) { @@ -413,16 +413,78 @@ abstract class AbstractChannelReplicator implements Closeable { static class EntryCallback extends Replica.EntryCallback { private final Replica.EntryExternalizable externalizable; - private final ByteBufferBytes in; + + @NotNull + private ByteBufferBytes in; + + @NotNull + private ByteBuffer out; EntryCallback(@NotNull final Replica.EntryExternalizable externalizable, - @NotNull final ByteBufferBytes in) { + final int tcpBufferSize) { this.externalizable = externalizable; - this.in = in; + out = ByteBuffer.allocateDirect(tcpBufferSize); + in = new ByteBufferBytes(out); } + @NotNull + public ByteBufferBytes in() { + return in; + } + + @NotNull + public ByteBuffer out() { + return out; + } + + + private void resizeBuffer(int size) { + + if (LOG.isDebugEnabled()) + LOG.debug("resizing buffer to size=" + size); + + if (size < out.capacity()) + throw new IllegalStateException("it not possible to resize the buffer smaller"); + + assert size < Integer.MAX_VALUE; + + final ByteBuffer result = ByteBuffer.allocate(size).order(ByteOrder.nativeOrder()); + final long bytesPosition = in.position(); + + in = new ByteBufferBytes(result.slice()); + + out.position(0); + out.limit((int) bytesPosition); + + int numberOfLongs = (int) bytesPosition / 8; + + // chunk in longs first + for (int i = 0; i < numberOfLongs; i++) { + in.writeLong(out.getLong()); + } + + for (int i = numberOfLongs * 8; i < bytesPosition; i++) { + in.writeByte(out.get()); + } + + out = result; + + assert out.capacity() == in.capacity(); + + assert out.capacity() == size; + assert out.capacity() == in.capacity(); + assert in.limit() == in.capacity(); + + } + + public boolean shouldBeIgnored(final Bytes entry, final int chronicleId) { + return !externalizable.identifierCheck(entry, chronicleId); + } + + @Override public boolean onEntry(final Bytes entry, final int chronicleId) { + long pos0 = in.position(); // used to denote that this is not a stateless map event @@ -433,7 +495,22 @@ abstract class AbstractChannelReplicator implements Closeable { // this is where we will store the size of the entry in.skip(SIZE_OF_SIZE); + int entrySize = externalizable.sizeOfEntry(entry, chronicleId); + + if (entrySize > in.remaining()) { + + long newSize = in.position() + entrySize; + + // This can occur when we pack a number of entries into the buffer and the + // last entry is very large. + if (newSize > Integer.MAX_VALUE) + return false; + + resizeBuffer((int) newSize); + } + final long start = in.position(); + externalizable.writeExternalEntry(entry, in, chronicleId); if (in.position() == start) { @@ -443,16 +520,16 @@ abstract class AbstractChannelReplicator implements Closeable { // write the length of the entry, just before the start, so when we read it back // we read the length of the entry first and hence know how many preceding writer to read - final long entrySize = (int) (in.position() - start); - if (entrySize > Integer.MAX_VALUE) + final long bytesWritten = (int) (in.position() - start); + + if (bytesWritten > Integer.MAX_VALUE) throw new IllegalStateException("entry too large, the entry size=" + entrySize + ", " + "entries are limited to a size of " + Integer.MAX_VALUE); - int entrySize1 = (int) entrySize; if (LOG.isDebugEnabled()) - LOG.debug("sending entry of entrySize=" + entrySize1); + LOG.debug("sending entry of entrySize=" + (int) bytesWritten); - in.writeInt(sizeLocation, entrySize1); + in.writeInt(sizeLocation, (int) bytesWritten); return true; } diff --git a/src/main/java/net/openhft/chronicle/map/AbstractChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/AbstractChronicleMapBuilder.java deleted file mode 100755 index bee59229d..000000000 --- a/src/main/java/net/openhft/chronicle/map/AbstractChronicleMapBuilder.java +++ /dev/null @@ -1,1017 +0,0 @@ -/* - * 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.hash.*; -import net.openhft.chronicle.hash.replication.*; -import net.openhft.chronicle.hash.serialization.BytesReader; -import net.openhft.chronicle.hash.serialization.BytesWriter; -import net.openhft.chronicle.hash.serialization.SizeMarshaller; -import net.openhft.chronicle.hash.serialization.internal.MetaBytesInterop; -import net.openhft.chronicle.hash.serialization.internal.MetaBytesWriter; -import net.openhft.chronicle.hash.serialization.internal.MetaProvider; -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 net.openhft.lang.threadlocal.Provider; -import net.openhft.lang.threadlocal.ThreadLocalCopies; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.*; -import java.lang.reflect.Constructor; -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -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 OnHeapUpdatableChronicleMapBuilder} should be used to create maps with ordinary values, - * {@link ChronicleMapBuilder} -- 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 = ChronicleMapOnHeapUpdatableBuilder
    - *     .of(Key.class, Value.class)
    - *     .entries(100500)
    - *     // ... other configurations
    - *     .create();}
    - * one of concrete {@code AbstractChronicleMapBuilder} subclasses, {@link OnHeapUpdatableChronicleMapBuilder} - * or {@link 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 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 - */ -abstract class AbstractChronicleMapBuilder> - implements Cloneable, ChronicleHashBuilder, B>, ChronicleMapBuilderI { - - static final byte UDP_REPLICATION_MODIFICATION_ITERATOR_ID = (byte)128; - 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); - private static final Logger LOG = - LoggerFactory.getLogger(AbstractChronicleMapBuilder.class.getName()); - - SerializationBuilder keyBuilder; - SerializationBuilder valueBuilder; - - private String name; - - // 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 = null; - private long entries = 1 << 20; // 1 million by default - private long lockTimeOut = 20000; - 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 = null; - private BytesMapEventListener bytesEventListener = null; - private V defaultValue = null; - private DefaultValueProvider defaultValueProvider = null; - private PrepareValueBytes prepareValueBytes = null; - - private SingleChronicleHashReplication singleHashReplication = null; - private InetSocketAddress[] pushToAddresses; - public boolean bootstrapOnlyLocalEntries = false; - private boolean disableOversizedEntries = false; - - AbstractChronicleMapBuilder(Class keyClass, Class valueClass) { - keyBuilder = new SerializationBuilder(keyClass, SerializationBuilder.Role.KEY); - valueBuilder = new SerializationBuilder(valueClass, SerializationBuilder.Role.VALUE); - } - - protected static boolean offHeapReference(Class valueClass) { - return Byteable.class.isAssignableFrom(valueClass); - } - - private static long roundUpMapHeaderSize(long headerSize) { - long roundUp = (headerSize + 127L) & ~127L; - if (roundUp - headerSize < 64) - roundUp += 128; - return roundUp; - } - - 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"); - } - - private static long divideUpper(long dividend, long divisor) { - return ((dividend - 1L) / divisor) + 1L; - } - - private static String pretty(int value) { - return value > 0 ? value + "" : "not configured"; - } - - private static String pretty(Object obj) { - return obj != null ? obj + "" : "not configured"; - } - - @Override - public ChronicleMapBuilderI pushTo(InetSocketAddress... addresses) { - this.pushToAddresses = addresses; - return this; - } - - @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 = ChronicleMapBuilder
    -     *     .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); - } - - public B valueSize(int valueSize) { - if (valueSize <= 0) - throw new IllegalArgumentException("Value size must be positive"); - this.valueSize = valueSize; - return self(); - } - - public 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.

    - * - * @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(boolean replicated) { - if (entrySize > 0) - return entryAndValueAlignment().alignSize(entrySize); - int size = metaDataBytes; - int keySize = keySize(); - size += keyBuilder.sizeMarshaller().sizeEncodingSize(keySize); - size += keySize; - if (replicated) - 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); - } - - public B entryAndValueAlignment(Alignment alignment) { - this.alignment = alignment; - return self(); - } - - Alignment entryAndValueAlignment() { - if (alignment != null) - return alignment; - return offHeapReference(valueBuilder.eClass) ? Alignment.OF_4_BYTES : Alignment.NO_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 " + - MultiMapFactory.MAX_CAPACITY + ", " + actualEntriesPerSegment + " given"); - this.actualEntriesPerSegment = actualEntriesPerSegment; - return self(); - } - - private boolean tooManyEntriesPerSegment(long entriesPerSegment) { - return entriesPerSegment > MultiMapFactory.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 " + - MultiMapFactory.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; - return Math.min(entries * segments, - (long) (entries + poorDistEntriesScale * 0.14 + 32)); // 12% 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() { - return (int) Math.min(Maths.nextPower2(entries / 32, 1), estimateSegementBasedOnSize()); - } - - private int estimateSegementBasedOnSize() { - // based on entries with multimap of 100 bytes. - long size = (long) (Math.log10(entrySize + 32) / 2 * entries); - if (size < 10 * 1000) - return 512; - if (size < 100 * 1000) - return 1024; - if (size < 1000 * 1000) - return 2 * 1024; - if (size < 10 * 1000 * 1000) - return 4 * 1024; - if (size < 100 * 1000 * 1000) - return 8 * 1024; - return 16 * 1024; - } - - @Override - public B actualSegments(int actualSegments) { - checkSegments(actualSegments); - this.actualSegments = actualSegments; - return self(); - } - - int actualSegments() { - if (actualSegments > 0) - return actualSegments; - long shortMMapSegments = trySegments(MultiMapFactory.I16_MAX_CAPACITY, - MAX_SEGMENTS_TO_CHAISE_COMPACT_MULTI_MAPS); - if (shortMMapSegments > 0L) - return (int) shortMMapSegments; - long intMMapSegments = trySegments(MultiMapFactory.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; - } - - int segmentHeaderSize() { - int segments = actualSegments(); - // reduce false sharing unless we have a lot of segments. - return segments <= 16 * 1024 ? 64 : 32; - } - - MultiMapFactory multiMapFactory() { - return MultiMapFactory.forCapacity(actualEntriesPerSegment()); - } - - 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{" + - "name=" + name + - ", 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) + - '}'; - } - - @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") - @Override - public boolean equals(Object o) { - return builderEquals(this, o); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - @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 acquireObjectSerializer(ObjectSerializer defaultSerializer) { - return objectSerializer == null ? - BytesMarshallableSerializer.create(bytesMarshallerFactory(), defaultSerializer) : - objectSerializer; - } - - @Override - public B objectSerializer(ObjectSerializer objectSerializer) { - this.objectSerializer = objectSerializer; - return self(); - } - - /** - * {@inheritDoc} - * - *

    Example: - */ - @Override - public B keyMarshaller(@NotNull BytesMarshaller keyMarshaller) { - keyBuilder.marshaller(keyMarshaller); - return self(); - } - - @Override - public B keyMarshallers(@NotNull BytesWriter keyWriter, @NotNull BytesReader keyReader) { - keyBuilder.writer(keyWriter); - keyBuilder.reader(keyReader); - return self(); - } - - @Override - public B keySizeMarshaller(@NotNull SizeMarshaller keySizeMarshaller) { - keyBuilder.sizeMarshaller(keySizeMarshaller); - 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 StatelessClientConfig> statelessClient( - InetSocketAddress remoteAddress) { - return new StatelessMapConfig<>(this.clone(), remoteAddress); - } - - /** - * 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; - } - - public B bytesEventListener(BytesMapEventListener eventListener) { - this.bytesEventListener = eventListener; - return self(); - } - - BytesMapEventListener bytesEventListener() { - return bytesEventListener; - } - - /** - * 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(); - } - - public B prepareValueBytesOnAcquire(@NotNull PrepareValueBytes prepareValueBytes) { - this.prepareValueBytes = prepareValueBytes; - this.defaultValue = null; - this.defaultValueProvider = null; - return self(); - } - - PrepareValueBytesAsWriter prepareValueBytesAsWriter() { - if (prepareValueBytes == null) - return null; - return new PrepareValueBytesAsWriter<>(prepareValueBytes, valueSize()); - } - - /** - * Non-public because should be called only after {@link #preMapConstruction(boolean)} - */ - 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); - } - - @Override - public B replication(SingleChronicleHashReplication replication) { - this.singleHashReplication = replication; - return self(); - } - - @Override - public B replication(byte identifier) { - return replication(SingleChronicleHashReplication.builder().createWithId(identifier)); - } - - @Override - public B replication(byte identifier, TcpTransportAndNetworkConfig tcpTransportAndNetwork) { - return replication(SingleChronicleHashReplication.builder() - .tcpTransportAndNetwork(tcpTransportAndNetwork).createWithId(identifier)); - } - - @Override - public ChronicleHashInstanceConfig> instance() { - return new InstanceConfig<>(this.clone(), singleHashReplication, null, null); - } - - @Override - public ChronicleMap createPersistedTo(File file) throws IOException { - // clone() to make this builder instance thread-safe, because createWithFile() method - // computes some state based on configurations, but doesn't synchronize on configuration - // changes. - return clone().createWithFile(file, singleHashReplication, null); - } - - @Override - public ChronicleMap create() { - // clone() to make this builder instance thread-safe, because createWithoutFile() method - // computes some state based on configurations, but doesn't synchronize on configuration - // changes. - return clone().createWithoutFile(singleHashReplication, null); - } - - ChronicleMap create(InstanceConfig ib) throws IOException { - if (ib.file != null) { - return createWithFile(ib.file, ib.singleHashReplication, ib.channel); - } else { - return createWithoutFile(ib.singleHashReplication, ib.channel); - } - } - - ChronicleMap createWithFile(File file, SingleChronicleHashReplication singleHashReplication, - ReplicationChannel channel) throws IOException { - pushingToMapEventListener(); - for (int i = 0; i < 10; i++) { - if (file.exists() && file.length() > 0) { - try (FileInputStream fis = new FileInputStream(file); - ObjectInputStream ois = new ObjectInputStream(fis)) { - VanillaChronicleMap map = - (VanillaChronicleMap) ois.readObject(); - map.headerSize = roundUpMapHeaderSize(fis.getChannel().position()); - map.createMappedStoreAndSegments(file); - return establishReplication(map, singleHashReplication, channel); - } catch (ClassNotFoundException e) { - throw new IOException(e); - } - } - 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(singleHashReplication, channel); - - try (FileOutputStream fos = new FileOutputStream(file); - ObjectOutputStream oos = new ObjectOutputStream(fos)) { - oos.writeObject(map); - oos.flush(); - map.headerSize = roundUpMapHeaderSize(fos.getChannel().position()); - map.createMappedStoreAndSegments(file); - } - - return establishReplication(map, singleHashReplication, channel); - } - - /** - * @param identifier id - * @return map - */ - public ChronicleMap createReplicated(byte identifier) throws IOException { - pushingToMapEventListener(); - preMapConstruction(true); - VanillaChronicleMap map = - new ReplicatedChronicleMap, - V, Object, MetaBytesInterop>(this, identifier); - BytesStore bytesStore = new DirectStore(JDKZObjectSerializer.INSTANCE, - map.sizeInBytes(), true); - map.createMappedStoreAndSegments(bytesStore); - return map; - } - - ChronicleMap createWithoutFile( - SingleChronicleHashReplication singleHashReplication, ReplicationChannel channel) { - pushingToMapEventListener(); - try { - VanillaChronicleMap map = newMap(singleHashReplication, channel); - BytesStore bytesStore = new DirectStore(JDKZObjectSerializer.INSTANCE, - map.sizeInBytes(), true); - map.createMappedStoreAndSegments(bytesStore); - return establishReplication(map, singleHashReplication, channel); - } catch (IOException e) { - // file-less version should never trigger an IOException. - throw new AssertionError(e); - } - } - - private void pushingToMapEventListener() { - if (pushToAddresses == null || pushToAddresses.length == 0) { - return; - } - try { - Class pmel = Class.forName("com.higherfrequencytrading.chronicle.enterprise.map.PushingMapEventListener"); - Constructor constructor = pmel.getConstructor(ChronicleMap[].class); - // create a stateless client for each address - ChronicleMap[] statelessClients = new ChronicleMap[pushToAddresses.length]; - ChronicleMapBuilderI cmb = clone(); - cmb.pushTo((InetSocketAddress[]) null); - for (int i = 0; i < pushToAddresses.length; i++) { - statelessClients[i] = cmb.statelessClient(pushToAddresses[i]).create(); - } - eventListener = (MapEventListener) constructor.newInstance((Object) statelessClients); - } catch (ClassNotFoundException e) { - LoggerFactory.getLogger(getClass().getName()).warn("Chronicle Enterprise not found in the class path"); - } catch (Exception e) { - LoggerFactory.getLogger(getClass().getName()).error("PushingMapEventListener failed to load", e); - } - } - - ChronicleMap createStatelessMap(StatelessMapConfig statelessBuilder) - throws IOException { - pushingToMapEventListener(); - preMapConstruction(false); - return new StatelessChronicleMap( - statelessBuilder, - this); - } - - private VanillaChronicleMap newMap( - SingleChronicleHashReplication singleHashReplication, ReplicationChannel channel) throws IOException { - boolean replicated = singleHashReplication != null || channel != null; - preMapConstruction(replicated); - if (replicated) { - byte identifier; - if (singleHashReplication != null) { - identifier = singleHashReplication.identifier(); - } else { - identifier = channel.hub().identifier(); - } - return new ReplicatedChronicleMap, - V, Object, MetaBytesInterop>(this, identifier); - } else { - return new VanillaChronicleMap, - V, Object, MetaBytesInterop>(this); - } - } - - void preMapConstruction(boolean replicated) { - keyBuilder.objectSerializer(acquireObjectSerializer(JDKObjectSerializer.INSTANCE)); - valueBuilder.objectSerializer(acquireObjectSerializer(JDKObjectSerializer.INSTANCE)); - - long maxSize = (long) entrySize(replicated) * Math.min(maxEntryOversizeFactor(), figureBufferAllocationFactor()); - keyBuilder.maxSize(maxSize); - valueBuilder.maxSize(maxSize); - - if (sampleKey != null) - keyBuilder.constantSizeBySample(sampleKey); - if (sampleValue != null) - valueBuilder.constantSizeBySample(sampleValue); - } - - private ChronicleMap establishReplication( - VanillaChronicleMap map, SingleChronicleHashReplication singleHashReplication, - ReplicationChannel channel) throws IOException { - if (map instanceof ReplicatedChronicleMap) { - if (singleHashReplication != null && channel != null) { - throw new AssertionError("Only one non-null replication should be passed"); - } - ReplicatedChronicleMap result = (ReplicatedChronicleMap) map; - List replicators = new ArrayList<>(2); - if (singleHashReplication != null) { - if (singleHashReplication.tcpTransportAndNetwork() != null) - replicators.add(Replicators.tcp(singleHashReplication)); - if (singleHashReplication.udpTransport() != null) - replicators.add(Replicators.udp(singleHashReplication.udpTransport())); - } else { - ReplicationHub hub = channel.hub(); - int entrySize = entrySize(true); - if (entrySize > hub.maxEntrySize()) { - throw new IllegalArgumentException("During ChannelProviderBuilder setup, " + - "maxEntrySize=" + hub.maxEntrySize() + - " was specified, but map with " + - "entrySize=" + entrySize + " is attempted to apply to the replicator"); - } - ChannelProvider provider = ChannelProvider.getProvider(hub); - ChannelProvider.ChronicleChannel ch = provider.createChannel(channel.channelId()); - replicators.add(ch); - } - for (Replicator replicator : replicators) { - Closeable token = replicator.applyTo(this, result, result, map); - if (replicators.size() == 1 && token.getClass() == UdpReplicator.class) { - LOG.warn(Replicators.ONLY_UDP_WARN_MESSAGE); - } - 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.max(4L, entries() >> 10); - } - - @Override - public ChronicleMapBuilderI name(String name) { - this.name = name; - return this; - } - - @Override - public ChronicleMapBuilderI bootstrapOnlyLocalEntries(boolean bootstapOnlyLocalEntries) { - this.bootstrapOnlyLocalEntries = bootstapOnlyLocalEntries; - return this; - } - - public String name() { - return this.name; - } - - @Override - public ChronicleMapBuilderI disableOversizedEntries() { - this.disableOversizedEntries = true; - return this; - } - - public int maxEntryOversizeFactor() { - return disableOversizedEntries ? 1 : VanillaChronicleMap.MAX_ENTRY_OVERSIZE_FACTOR; - } -} - diff --git a/src/main/java/net/openhft/chronicle/map/BuildVersion.java b/src/main/java/net/openhft/chronicle/map/BuildVersion.java new file mode 100644 index 000000000..59ec7b9f9 --- /dev/null +++ b/src/main/java/net/openhft/chronicle/map/BuildVersion.java @@ -0,0 +1,89 @@ +/* + * 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 shaded.org.apache.maven.model.Model; +import shaded.org.apache.maven.model.io.xpp3.MavenXpp3Reader; + +import java.io.File; +import java.io.FileReader; +import java.io.Reader; + +/** + * gets the version of the current build + */ +class BuildVersion { + + + /** + * @return version of ChronicleMap being used, or NULL if its not known + */ + public static String readVersion() { + + // the best way to get the version is to read it from the manifest + final String version = getVersionFromManifest(); + + if (version != null) + return version; + + // as a fall back for development, we will read the version from the pom file + return getVersionFromPom(); + } + + /** + * This should be used by everyone that has install chronicle map as a JAR + * + * @return gets the version out of the manifest, or null if it can not be read + */ + private static String getVersionFromManifest() { + return ChronicleMapBuilder.class.getPackage().getImplementationVersion(); + } + + + /** + * reads the pom file to get this version, only to be used for development or within the IDE. + * + * @return gets the version from the pom.xml + */ + private static String getVersionFromPom() { + + final String absolutePath = new File(BuildVersion.class.getResource(BuildVersion.class + .getSimpleName() + ".class").getPath()) + .getParentFile().getParentFile().getParentFile().getParentFile().getParentFile() + .getParentFile().getParentFile().getAbsolutePath(); + + final File file = new File(absolutePath + "/pom.xml"); + + try (Reader reader = new FileReader(file)) { + + final MavenXpp3Reader xpp3Reader = new MavenXpp3Reader(); + Model model = xpp3Reader.read(reader); + return model.getVersion(); + + } catch (NoClassDefFoundError e) { + // if you want to get the version possibly in development add in to your pom + // pax-url-aether.jar + return null; + } catch (Exception e) { + return null; + } + } + + +} \ No newline at end of file diff --git a/src/main/java/net/openhft/chronicle/map/BytesMapEventListener.java b/src/main/java/net/openhft/chronicle/map/BytesMapEventListener.java index 2746dd9fd..745221023 100644 --- a/src/main/java/net/openhft/chronicle/map/BytesMapEventListener.java +++ b/src/main/java/net/openhft/chronicle/map/BytesMapEventListener.java @@ -33,7 +33,7 @@ import java.io.Serializable; * by the serialized key itself. *
  7. From {@code valuePos} offset value area in the given {@code entry}, serialized value size * (using {@linkplain ChronicleMapBuilder#keySizeMarshaller(SizeMarshaller)}), then (optionally) - * alignment (see {@link OffHeapUpdatableChronicleMapBuilder#entryAndValueAlignment(Alignment)} + * alignment (see {@link ChronicleMapBuilder#entryAndValueAlignment(Alignment)} * ), followed by the serialized value itself.
  8. * */ diff --git a/src/main/java/net/openhft/chronicle/map/ChannelProvider.java b/src/main/java/net/openhft/chronicle/map/ChannelProvider.java index ea56231c2..b69f8c3bb 100644 --- a/src/main/java/net/openhft/chronicle/map/ChannelProvider.java +++ b/src/main/java/net/openhft/chronicle/map/ChannelProvider.java @@ -64,7 +64,7 @@ final class ChannelProvider implements Closeable { final TcpReplicator tcpReplicator = new TcpReplicator( channelProvider.asReplica, channelProvider.asEntryExternalizable, - tcpConfig, hub.maxEntrySize(), null, hub.remoteNodeValidator()); + tcpConfig, null, hub.remoteNodeValidator()); channelProvider.add(tcpReplicator); } @@ -85,6 +85,30 @@ final class ChannelProvider implements Closeable { private static final byte BOOTSTRAP_MESSAGE = 'B'; final EntryExternalizable asEntryExternalizable = new EntryExternalizable() { + @Override + public int sizeOfEntry(@NotNull Bytes entry, int chronicleChannel) { + channelDataLock.readLock().lock(); + try { + + return channelEntryExternalizables[chronicleChannel] + .sizeOfEntry(entry, chronicleChannel); + } finally { + channelDataLock.readLock().unlock(); + } + } + + @Override + public boolean identifierCheck(@NotNull Bytes entry, int chronicleChannel) { + channelDataLock.readLock().lock(); + try { + + return channelEntryExternalizables[chronicleChannel] + .identifierCheck(entry, chronicleChannel); + } finally { + channelDataLock.readLock().unlock(); + } + } + /** * writes the entry to the chronicle channel provided * @@ -116,7 +140,7 @@ final class ChannelProvider implements Closeable { final int chronicleId = (int) source.readStopBit(); if (chronicleId < chronicleChannels.length) { // this channel is has not currently been created so it updates will be ignored - if (channelEntryExternalizables[chronicleId] != null) + if (channelEntryExternalizables[chronicleId] != null) channelEntryExternalizables[chronicleId] .readExternalEntry(copies, segmentState, source); } else @@ -398,8 +422,8 @@ final class ChannelProvider implements Closeable { } /** - * used to send system messages such as bootstrap from one remote node to another, it also can be used in - * a broadcast context + * used to send system messages such as bootstrap from one remote node to another, it also can + * be used in a broadcast context */ class SystemQueue { @@ -467,6 +491,16 @@ final class ChannelProvider implements Closeable { } }; final EntryExternalizable asEntryExternalizable = new EntryExternalizable() { + @Override + public int sizeOfEntry(@NotNull Bytes entry, int chronicleId) { + return (int) entry.remaining(); + } + + @Override + public boolean identifierCheck(@NotNull Bytes entry, int chronicleId) { + return true; + } + @Override public void writeExternalEntry(@NotNull Bytes entry, @NotNull Bytes destination, int na) { @@ -506,7 +540,7 @@ final class ChannelProvider implements Closeable { } @Override - protected Closeable applyTo(AbstractChronicleMapBuilder builder, + protected Closeable applyTo(ChronicleMapBuilder builder, Replica map, EntryExternalizable entryExternalizable, final ChronicleMap chronicleMap) { add(chronicleChannel, map, entryExternalizable); diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMap.java b/src/main/java/net/openhft/chronicle/map/ChronicleMap.java index db2acbc35..33f98fa45 100755 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMap.java @@ -36,7 +36,7 @@ import java.util.concurrent.ConcurrentMap; * *

    For information on

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

    Functionally this interface defines some methods supporting garbage-free off-heap programming: * {@link #getUsing(Object, Object)}, {@link #acquireUsing(Object, Object)}. @@ -98,7 +98,7 @@ public interface ChronicleMap extends ConcurrentMap, ChronicleHash { * no mapping for the key * @see #get(Object) * @see #acquireUsing(Object, Object) - * @see OnHeapUpdatableChronicleMapBuilder#valueMarshaller(BytesMarshaller) + * @see ChronicleMapBuilder#valueMarshaller(BytesMarshaller) */ V getUsing(K key, V usingValue); @@ -141,7 +141,7 @@ public interface ChronicleMap extends ConcurrentMap, ChronicleHash { * no mapping for the key * @see #get(Object) * @see #getUsing(Object, Object) - * @see OnHeapUpdatableChronicleMapBuilder#valueMarshaller(BytesMarshaller) + * @see ChronicleMapBuilder#valueMarshaller(BytesMarshaller) */ @NotNull ReadContext getUsingLocked(@NotNull K key, @NotNull V usingValue); @@ -149,8 +149,8 @@ public interface ChronicleMap extends ConcurrentMap, ChronicleHash { /** * Acquire a value for a key, creating if absent. * - *

    If the specified key is absent in the map, {@linkplain AbstractChronicleMapBuilder#defaultValue(Object) - * default value} is taken or {@linkplain AbstractChronicleMapBuilder#defaultValueProvider(DefaultValueProvider) + *

    If the specified key is absent in the map, {@linkplain ChronicleMapBuilder#defaultValue(Object) + * default value} is taken or {@linkplain ChronicleMapBuilder#defaultValueProvider(DefaultValueProvider) * default value provider} is called. Then this object is put to this map for the specified * key. * @@ -169,13 +169,13 @@ public interface ChronicleMap extends ConcurrentMap, ChronicleHash { * }} * * - * Where {@code defaultValue(key)} returns either {@linkplain AbstractChronicleMapBuilder#defaultValue(Object) - * default value} or {@link AbstractChronicleMapBuilder#defaultValueProvider(DefaultValueProvider) + * Where {@code defaultValue(key)} returns either {@linkplain ChronicleMapBuilder#defaultValue(Object) + * default value} or {@link ChronicleMapBuilder#defaultValueProvider(DefaultValueProvider) * defaultValueProvider.} * *

    If the {@code ChronicleMap} is off-heap updatable, i. e. created via {@link * ChronicleMapBuilder} builder (values are {@link Byteable}), there is one more option of what - * to do if the key is absent in the map, see {@link ChronicleMapBuilder#prepareValueBytesOnAcquire(PrepareValueBytes)}. + * to do if the key is absent in the map. * By default, value bytes are just zeroed out, no default value, either provided for key or * constant, is put for the absent key. * diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java index 5dcee5383..4426d3718 100755 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java @@ -1,48 +1,164 @@ +/* + * 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.hash.ChronicleHashBuilder; -import net.openhft.chronicle.hash.ChronicleHashErrorListener; -import net.openhft.chronicle.hash.ChronicleHashInstanceConfig; -import net.openhft.chronicle.hash.StatelessClientConfig; -import net.openhft.chronicle.hash.replication.SingleChronicleHashReplication; -import net.openhft.chronicle.hash.replication.TcpTransportAndNetworkConfig; -import net.openhft.chronicle.hash.replication.TimeProvider; -import net.openhft.chronicle.hash.serialization.BytesReader; -import net.openhft.chronicle.hash.serialization.BytesWriter; -import net.openhft.chronicle.hash.serialization.SizeMarshaller; -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.chronicle.hash.*; +import net.openhft.chronicle.hash.replication.*; +import net.openhft.chronicle.hash.serialization.*; +import net.openhft.chronicle.hash.serialization.internal.MetaBytesInterop; +import net.openhft.chronicle.hash.serialization.internal.MetaBytesWriter; +import net.openhft.chronicle.hash.serialization.internal.MetaProvider; +import net.openhft.chronicle.set.ChronicleSetBuilder; +import net.openhft.lang.Maths; +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 net.openhft.lang.model.DataValueClasses; +import net.openhft.lang.threadlocal.Provider; +import net.openhft.lang.threadlocal.ThreadLocalCopies; import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.IOException; +import java.io.*; +import java.lang.reflect.Constructor; import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import static net.openhft.chronicle.map.Objects.builderEquals; +import static net.openhft.chronicle.map.VanillaChronicleMap.MAX_ENTRY_OVERSIZE_FACTOR; +import static net.openhft.lang.model.DataValueGenerator.firstPrimitiveFieldType; /** - * {@code ChronicleMapBuilder} is intended to be used to create {@link ChronicleMap}s + * {@code ChronicleMapBuilder} manages {@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 = ChronicleMapOnHeapUpdatableBuilder
    + *     .of(Key.class, Value.class)
    + *     .entries(100500)
    + *     // ... other configurations
    + *     .create();}
    + * it 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 mutable, see a note in {@link 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, created by this builder - * @param value type of the maps, created by this builder - * @see AbstractChronicleMapBuilder - * @see net.openhft.chronicle.hash.ChronicleHashBuilder + * @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 final class ChronicleMapBuilder implements ChronicleMapBuilderI, ChronicleHashBuilder, ChronicleMapBuilder> { +public final class ChronicleMapBuilder implements Cloneable, + ChronicleHashBuilder, ChronicleMapBuilder>, Serializable { - final ChronicleMapBuilderI delegate; + static final byte UDP_REPLICATION_MODIFICATION_ITERATOR_ID = (byte) 128; + 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); + private static final Logger LOG = + LoggerFactory.getLogger(ChronicleMapBuilder.class.getName()); + static final long DEFAULT_STATELESS_CLIENT_TIMEOUT = TimeUnit.SECONDS.toMillis(10); + + private static final StringBuilder EMTRY_STRING_BUILDER = new StringBuilder(); + + SerializationBuilder keyBuilder; + SerializationBuilder valueBuilder; + + private String name; + + // 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 int maxEntryOversizeFactor = -1; + private Alignment alignment = null; + private long entries = 1 << 20; // 1 million by default + private long lockTimeOut = 20000; + 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 = null; + private BytesMapEventListener bytesEventListener = null; + private V defaultValue = null; + private DefaultValueProvider defaultValueProvider = null; + private PrepareValueBytes prepareValueBytes = null; + + private SingleChronicleHashReplication singleHashReplication = null; + private InetSocketAddress[] pushToAddresses; + + ChronicleMapBuilder(Class keyClass, Class valueClass) { + keyBuilder = new SerializationBuilder<>(keyClass, SerializationBuilder.Role.KEY); + valueBuilder = new SerializationBuilder<>(valueClass, SerializationBuilder.Role.VALUE); + + if (CharSequence.class == valueClass) + defaultValue = (V) ""; + if (StringBuilder.class == valueClass) + defaultValue = (V) EMTRY_STRING_BUILDER; + } /** * 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 internal builder. - * * @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 @@ -53,159 +169,153 @@ public final class ChronicleMapBuilder implements ChronicleMapBuilderI ChronicleMapBuilder of( @NotNull Class keyClass, @NotNull Class valueClass) { - if (valueClass.isEnum()) - return new ChronicleMapBuilder(OnHeapUpdatableChronicleMapBuilder.of(keyClass, valueClass)); + return new ChronicleMapBuilder<>(keyClass, valueClass); + } - if (keyClass.isInterface() && !builtInType(keyClass)) - keyClass = DataValueClasses.directClassFor(keyClass); + private static long roundUpMapHeaderSize(long headerSize) { + long roundUp = (headerSize + 127L) & ~127L; + if (roundUp - headerSize < 64) + roundUp += 128; + return roundUp; + } - if ((valueClass.isInterface() && !builtInType(valueClass))) { - valueClass = DataValueClasses.directClassFor(valueClass); - } else if (!offHeapReference(valueClass)) { - return new ChronicleMapBuilder(OnHeapUpdatableChronicleMapBuilder.of(keyClass, valueClass)); + 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"); + } + + private static long divideUpper(long dividend, long divisor) { + return ((dividend - 1L) / divisor) + 1L; + } + + private static String pretty(int value) { + return value > 0 ? value + "" : "not configured"; + } + + private static String pretty(Object obj) { + return obj != null ? obj + "" : "not configured"; + } + + public ChronicleMapBuilder pushTo(InetSocketAddress... addresses) { + this.pushToAddresses = addresses; + return this; + } + + @Override + public ChronicleMapBuilder clone() { + try { + @SuppressWarnings("unchecked") + ChronicleMapBuilder result = + (ChronicleMapBuilder) super.clone(); + result.keyBuilder = keyBuilder.clone(); + result.valueBuilder = valueBuilder.clone(); + return result; + } catch (CloneNotSupportedException e) { + throw new AssertionError(e); } - - ChronicleMapBuilderI builder = new OffHeapUpdatableChronicleMapBuilder(keyClass, valueClass); - return new ChronicleMapBuilder(builder); - } - - - - static boolean builtInType(Class clazz) { - return clazz.getClassLoader() == Class.class.getClassLoader(); - } - - ChronicleMapBuilder(ChronicleMapBuilderI delegate) { - this.delegate = delegate; - } - - static boolean offHeapReference(Class valueClass) { - return Byteable.class.isAssignableFrom(valueClass); } /** - * {@inheritDoc} With respect to {@linkplain #entryAndValueAlignment(Alignment) alignment}. + * {@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, 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}.) * - *

    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, and - * entry alignment is set to {@link Alignment#OF_4_BYTES}, the actual entry size will be 32 (30 - * aligned to 4 bytes). - * - * @see #entryAndValueAlignment(Alignment) // * @see #entries(long) + * @throws IllegalStateException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} + * @see #constantKeySizeBySample(Object) + * @see #valueSize(int) + * @see #entrySize(int) */ - @Override - public ChronicleMapBuilder entrySize(int entrySize) { - delegate.entrySize(entrySize); - return this; - } - - /** - * 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} for Byteable values. - * - * @param alignment the new alignment of the maps constructed by this builder - * @return this {@code ChronicleMapOnHeapUpdatableBuilder} back - */ - @Override - public ChronicleMapBuilder entryAndValueAlignment(Alignment alignment) { - delegate.entryAndValueAlignment(alignment); - return this; - } - - /** - * {@inheritDoc} Also, it overrides any previous {@link #prepareValueBytesOnAcquire} - * configuration to this {@code ChronicleMapBuilder}. - * - *

    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 ChronicleMapBuilder defaultValue(V defaultValue) { - delegate.defaultValue(defaultValue); - return this; - } - - /** - * 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. See {@link PrepareValueBytes} for more - * information. - * - *

    The default preparation callback zeroes out the value bytes. - * - * @param prepareValueBytes what to do with the value bytes before assigning them into the - * {@link net.openhft.lang.model.Byteable} value to return from {@code - * acquireUsing()} call - * @return this builder back - * @see PrepareValueBytes - * @see #defaultValue(Object) - * @see #defaultValueProvider(DefaultValueProvider) - */ - @Override - public ChronicleMapBuilder prepareValueBytesOnAcquire(@NotNull PrepareValueBytes prepareValueBytes) { - delegate.prepareValueBytesOnAcquire(prepareValueBytes); - return this; - } - @Override public ChronicleMapBuilder keySize(int keySize) { - delegate.keySize(keySize); + checkSizeIsNotStaticallyKnown(keyBuilder); + if (keySize <= 0) + throw new IllegalArgumentException("Key size must be positive"); + this.keySize = keySize; return this; } + /** + * {@inheritDoc} + *

    + *

    For example, if your keys are Git commit hashes:

    {@code
    +     * Map gitCommitMessagesByHash =
    +     *     ChronicleMapBuilder.of(byte[].class, String.class)
    +     *     .constantKeySizeBySample(new byte[20])
    +     *     .immutableKeys()
    +     *     .create();}
    + * + * @see #keySize(int) + * @see #constantValueSizeBySample(Object) + */ @Override public ChronicleMapBuilder constantKeySizeBySample(K sampleKey) { - delegate.constantKeySizeBySample(sampleKey); + this.sampleKey = sampleKey; return this; } + private int keySize() { + return keyOrValueSize(keySize, keyBuilder); + } + /** * Configures the optimal number of bytes, taken by serialized form of values, put into maps, * 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, i. e. if value size is known statically, it is - * automatically accounted and shouldn't be specified by user. - * + *

    + *

    If values are of 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 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, * it's better to use {@linkplain #entrySize(int) entry size} in "chunk" mode and configure it * directly. * * @param valueSize number of bytes, taken by serialized form of values - * @return this {@code ChronicleMapOnHeapUpdatableBuilder} back + * @return this builder back + * @throws IllegalStateException if value size is known statically and shouldn't be + * configured by user + * @throws IllegalArgumentException if the given {@code valueSize} is non-positive * @see #constantValueSizeBySample(Object) * @see #keySize(int) * @see #entrySize(int) */ - @Override public ChronicleMapBuilder valueSize(int valueSize) { - delegate.valueSize(valueSize); + checkSizeIsNotStaticallyKnown(valueBuilder); + if (valueSize <= 0) + throw new IllegalArgumentException("Value size must be positive"); + this.valueSize = valueSize; return this; } + private static void checkSizeIsNotStaticallyKnown(SerializationBuilder builder) { + if (builder.sizeIsStaticallyKnown) + throw new IllegalStateException("Size of type " + builder.eClass + + " is statically known and shouldn't be specified manually"); + } + /** * Configures the constant number of bytes, taken by serialized form of values, put into maps, * created by this builder. This is done by providing the {@code sampleValue}, all values should * take the same number of bytes in serialized form, as this sample object. - * - *

    If values are of boxed primitive type or {@link net.openhft.lang.model.Byteable} subclass, - * i. e. if value size is known statically, it is automatically accounted and this method - * shouldn't be called. - * + *

    + *

    If values are of boxed primitive type or {@link Byteable} subclass, i. e. if value size is + * known statically, it is automatically accounted and this method shouldn't be called. + *

    *

    If value size varies, method {@link #valueSize(int)} or {@link #entrySize(int)} should be * called instead of this one. * @@ -214,15 +324,481 @@ public final class ChronicleMapBuilder implements ChronicleMapBuilderI constantValueSizeBySample(@NotNull V sampleValue) { - delegate.constantValueSizeBySample(sampleValue); + public ChronicleMapBuilder constantValueSizeBySample(V sampleValue) { + this.sampleValue = sampleValue; return this; } + int valueSize() { + return keyOrValueSize(valueSize, valueBuilder); + } + + private int keyOrValueSize(int configuredSize, SerializationBuilder builder) { + if (configuredSize > 0) + return configuredSize; + if (builder.constantSizeMarshaller()) + return builder.pseudoReadConstantSize(); + 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 by the configured {@linkplain + * #entryAndValueAlignment(Alignment) entry and value alignment}. I. e. if you set entry size to + * 30, and entry alignment is set to {@link Alignment#OF_4_BYTES}, the actual entry size will be + * 32 (30 aligned to 4 bytes). * + * @throws IllegalStateException is sizes of both keys and values of maps created by this + * builder are statically known, hence entry size shouldn't be + * configured by user + * @see #entryAndValueAlignment(Alignment) + * @see #entries(long) + * @see #maxEntryOversizeFactor(int) + */ + @Override + public ChronicleMapBuilder entrySize(int entrySize) { + if (keyBuilder.sizeIsStaticallyKnown && valueBuilder.sizeIsStaticallyKnown) { + throw new IllegalStateException("Sizes of key type: " + keyBuilder.eClass + " and " + + "value type: " + valueBuilder.eClass + " are both statically known, " + + "so entry size shouldn't be specified manually"); + } + if (entrySize <= 0) + throw new IllegalArgumentException("Entry Size must be positive"); + this.entrySize = entrySize; + return this; + } + + int entrySize(boolean replicated) { + if (entrySize > 0) + return entryAndValueAlignment().alignSize(entrySize); + int size = metaDataBytes; + int keySize = keySize(); + size += keyBuilder.sizeMarshaller().sizeEncodingSize(keySize); + size += keySize; + if (replicated) + 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); + } + + @Override + public ChronicleMapBuilder maxEntryOversizeFactor(int maxEntryOversizeFactor) { + if (maxEntryOversizeFactor < 1) + throw new IllegalArgumentException("maxEntryOversizeFactor should be more than 1, " + + maxEntryOversizeFactor + " given"); + this.maxEntryOversizeFactor = maxEntryOversizeFactor; + return this; + } + + private void checkMaxEntryOversizeFactorIfReplicated(boolean replicated) { + if (replicated && maxEntryOversizeFactor > MAX_ENTRY_OVERSIZE_FACTOR) + throw new IllegalStateException("As of now Replicated Chornicle Map doesn't support " + + "more than " + MAX_ENTRY_OVERSIZE_FACTOR + " entry oversize factor, " + + maxEntryOversizeFactor + " is configured"); + } + + int maxEntryOversizeFactor(boolean replicated) { + if (maxEntryOversizeFactor < 0) { + if (keyBuilder.constantSizeMarshaller() && valueBuilder.constantSizeMarshaller()) { + return 1; + } else { + if (replicated) { + return MAX_ENTRY_OVERSIZE_FACTOR; + } else { + return Integer.MAX_VALUE; + } + } + } + return maxEntryOversizeFactor; + } + + /** + * 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). + *

    + *

    If values couldn't reference off-heap memory (i. e. it is not {@link Byteable} or "data + * value generated"), alignment configuration makes no sense and forbidden. + *

    + *

    Default is {@link Alignment#NO_ALIGNMENT} if values couldn't reference off-heap memory, + * otherwise chosen heuristically (configure explicitly for being sure and to compare + * performance in your case). + * + * @param alignment the new alignment of the maps constructed by this builder + * @return this {@code ChronicleMapOnHeapUpdatableBuilder} back + * @throws IllegalStateException if values of maps, created by this builder, couldn't reference + * off-heap memory + */ + public ChronicleMapBuilder entryAndValueAlignment(Alignment alignment) { + this.alignment = alignment; + checkAlignmentOnlyIfValuesPossiblyReferenceOffHeap(); + return this; + } + + private void checkAlignmentOnlyIfValuesPossiblyReferenceOffHeap() { + if (!valueBuilder.possibleOffHeapReferences() && + (alignment == Alignment.OF_4_BYTES || alignment == Alignment.OF_8_BYTES)) + throw new IllegalStateException("Entry and value alignment should be configured only " + + "if values might point to off-heap memory"); + } + + Alignment entryAndValueAlignment() { + if (alignment != null) + return alignment; + Class firstPrimitiveFieldType = firstPrimitiveFieldType(valueBuilder.eClass); + if (firstPrimitiveFieldType == long.class || firstPrimitiveFieldType == double.class) + return Alignment.OF_8_BYTES; + if (firstPrimitiveFieldType == int.class || firstPrimitiveFieldType == float.class) + return Alignment.OF_4_BYTES; + return Alignment.NO_ALIGNMENT; + } + + /** + * Package-private for tests + */ + + + @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 " + + MultiMapFactory.MAX_CAPACITY + ", " + actualEntriesPerSegment + " given"); + this.actualEntriesPerSegment = actualEntriesPerSegment; + return this; + } + + private boolean tooManyEntriesPerSegment(long entriesPerSegment) { + return entriesPerSegment > MultiMapFactory.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 " + + MultiMapFactory.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; + long entries = this.entries; + // check if the number of entries is small compared with the number of segments. + long s3 = (long) Math.min(8, segments) * Math.min(32, segments) * Math.min(128, segments); + if (entries * 4 <= s3) + entries *= 1.8; + else if (entries <= s3) + entries *= 1.45; + else if (entries <= s3 * 4) + entries *= 1.33; + else if (entries <= s3 * 8) + entries *= 1.22; + else if (entries <= s3 * 16) + entries *= 1.15; + else + entries *= 1.1; + return Math.min(segments * this.entries, + entries + 4 * segments + 8); + } + + @Override + public ChronicleMapBuilder minSegments(int minSegments) { + checkSegments(minSegments); + this.minSegments = minSegments; + return this; + } + + int minSegments() { + return Math.max(estimateSegments(), minSegments); + } + + private int estimateSegments() { + return (int) Math.min(Maths.nextPower2(entries / 32, 1), estimateSegmentsBasedOnSize()); + } + + private int estimateSegmentsBasedOnSize() { + // based on entries with multimap of 100 bytes. + int segmentsForEntries = estimateSegmentsForEntries(entries); + return entrySize >= 1000000 + ? segmentsForEntries * 16 + : entrySize >= 100000 + ? segmentsForEntries * 8 + : entrySize >= 10000 + ? segmentsForEntries * 4 + : entrySize >= 1000 + ? segmentsForEntries * 2 + : segmentsForEntries; + } + + private static int estimateSegmentsForEntries(long size) { + if (size > 200 << 20) + return 256; + if (size >= 1 << 20) + return 128; + if (size >= 128 << 10) + return 64; + if (size >= 16 << 10) + return 32; + if (size >= 4 << 10) + return 16; + if (size >= 1 << 10) + return 8; + return 1; + } + + @Override + public ChronicleMapBuilder actualSegments(int actualSegments) { + checkSegments(actualSegments); + this.actualSegments = actualSegments; + return this; + } + + int actualSegments() { + if (actualSegments > 0) + return actualSegments; + long shortMMapSegments = trySegments(MultiMapFactory.I16_MAX_CAPACITY, + MAX_SEGMENTS_TO_CHAISE_COMPACT_MULTI_MAPS); + if (shortMMapSegments > 0L) + return (int) shortMMapSegments; + long intMMapSegments = trySegments(MultiMapFactory.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; + } + + int segmentHeaderSize() { + int segments = actualSegments(); + // reduce false sharing unless we have a lot of segments. + return segments <= 16 * 1024 ? 64 : 32; + } + + MultiMapFactory multiMapFactory() { + return MultiMapFactory.forCapacity(actualEntriesPerSegment()); + } + + 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 this 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. + *

    + *

    By default, of cause, {@code ChronicleMap} conforms the general {@code Map} contract and + * returns the mapped value on {@code remove()} calls. + * + * @param removeReturnsNull {@code true} if you want {@link ChronicleMap#remove(Object) + * ChronicleMap.remove()} to not return the value of the removed entry + * but instead return {@code null} + * @return this 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 < 0 || metaDataBytes > 255) + 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{" + + "name=" + name + + ", 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) + + '}'; + } + + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + @Override + public boolean equals(Object o) { + return builderEquals(this, o); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @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 acquireObjectSerializer(ObjectSerializer defaultSerializer) { + return objectSerializer == null ? + BytesMarshallableSerializer.create(bytesMarshallerFactory(), defaultSerializer) : + objectSerializer; + } + + /** + * {@inheritDoc} + *

    *

    Example:

    {@code Map map =
          *     ChronicleMapBuilder.of(Key.class, Value.class)
          *     .entries(1_000_000)
    @@ -230,37 +806,52 @@ public final class ChronicleMapBuilder implements ChronicleMapBuilderI
    +     * 

    + *

    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 #valueMarshaller(BytesMarshaller) value} marshallers are not configured. */ @Override - public ChronicleMapBuilder objectSerializer(@NotNull ObjectSerializer objectSerializer) { - delegate.objectSerializer(objectSerializer); + public ChronicleMapBuilder objectSerializer(ObjectSerializer objectSerializer) { + this.objectSerializer = objectSerializer; + return this; + } + + @Override + public ChronicleMapBuilder keyMarshaller( + @NotNull BytesMarshaller keyMarshaller) { + keyBuilder.marshaller(keyMarshaller); + return this; + } + + @Override + public ChronicleMapBuilder keyMarshallers( + @NotNull BytesWriter keyWriter, @NotNull BytesReader keyReader) { + keyBuilder.writer(keyWriter); + keyBuilder.reader(keyReader); + return this; + } + + @Override + public ChronicleMapBuilder keySizeMarshaller(@NotNull SizeMarshaller keySizeMarshaller) { + keyBuilder.sizeMarshaller(keySizeMarshaller); return this; } /** - * {@inheritDoc} - * - * @throws IllegalStateException if custom value marshaller is specified or value class is not - * either {@code BytesMarshallable} or {@code Externalizable} + * @throws IllegalStateException {@inheritDoc} + * @see #valueDeserializationFactory(ObjectFactory) */ - @Override - public ChronicleMapBuilder valueDeserializationFactory(@NotNull ObjectFactory valueDeserializationFactory) { - delegate.valueDeserializationFactory(valueDeserializationFactory); + public ChronicleMapBuilder keyDeserializationFactory( + @NotNull ObjectFactory keyDeserializationFactory) { + keyBuilder.factory(keyDeserializationFactory); return this; } - /** - * {@inheritDoc} - * - *

    By default, default value provider is not specified, {@link #defaultValue(Object) default - * value} is specified instead. - * - * @see #defaultValue(Object) - */ @Override - public ChronicleMapBuilder defaultValueProvider(@NotNull DefaultValueProvider defaultValueProvider) { - delegate.defaultValueProvider(defaultValueProvider); + public ChronicleMapBuilder immutableKeys() { + keyBuilder.instancesAreMutable(false); return this; } @@ -271,15 +862,13 @@ public final class ChronicleMapBuilder implements ChronicleMapBuilderI valueMarshaller( @NotNull BytesMarshaller valueMarshaller) { - delegate.valueMarshaller(valueMarshaller); + valueBuilder.marshaller(valueMarshaller); return this; } @@ -287,243 +876,440 @@ public final class ChronicleMapBuilder implements ChronicleMapBuilderIthe * section about serialization in ChronicleMap manual for more information. - * + *

    *

    Configuring marshalling this way results to a little bit more compact in-memory layout of - * the map, comparing to a single interface configuration: {@link #valueMarshaller(BytesMarshaller)}. + * the map, comparing to a single interface configuration: {@link #valueMarshaller( + *BytesMarshaller)}. + *

    + *

    Passing {@link BytesInterop} instead of plain {@link BytesWriter} is, of cause, possible, + * but currently pointless for values. * - *

    Passing {@link net.openhft.chronicle.hash.serialization.BytesInterop} instead of plain - * {@link net.openhft.chronicle.hash.serialization.BytesWriter} is, of cause, possible, but - * currently pointless for values. - * - * @param valueWriter the new value object → {@link net.openhft.lang.io.Bytes} writer - * (interop) strategy - * @param valueReader the new {@link net.openhft.lang.io.Bytes} → value object reader - * strategy + * @param valueWriter the new value object → {@link Bytes} writer (interop) strategy + * @param valueReader the new {@link Bytes} → value object reader strategy * @return this builder back * @see #valueMarshaller(BytesMarshaller) + * @see #valueSizeMarshaller(SizeMarshaller) + * @see #keyMarshallers(BytesWriter, BytesReader) */ - @Override - public ChronicleMapBuilder valueMarshallers(@NotNull BytesWriter valueWriter, @NotNull BytesReader valueReader) { - delegate.valueMarshallers(valueWriter, valueReader); + public ChronicleMapBuilder valueMarshallers( + @NotNull BytesWriter valueWriter, @NotNull BytesReader valueReader) { + valueBuilder.writer(valueWriter); + valueBuilder.reader(valueReader); return this; } /** * Configures the marshaller used to serialize actual value sizes to off-heap memory in maps, * created by this builder. - * - *

    Default value size marshaller is so-called {@linkplain net.openhft.chronicle.hash.serialization.SizeMarshallers#stopBit() - * stop bit encoding marshalling}. If {@linkplain #constantValueSizeBySample(Object) constant - * value size} is configured, or defaulted if the value type is always constant and {@code - * ChronicleHashBuilder} implementation knows about it, this configuration takes no effect, - * because a special {@link net.openhft.chronicle.hash.serialization.SizeMarshaller} - * implementation, which doesn't actually do any marshalling, and just returns the known - * constant size on {@link net.openhft.chronicle.hash.serialization.SizeMarshaller#readSize( - *net.openhft.lang.io.Bytes)} calls, is used instead of any {@code SizeMarshaller} configured - * using this method. + *

    + *

    Default value size marshaller is so-called {@linkplain SizeMarshallers#stopBit() stop bit + * encoding marshalling}, unless {@link #constantValueSizeBySample(Object)} or the builder + * statically knows the value size is constant -- {@link SizeMarshallers#constant(long)} or + * equivalents are used by default in these cases. * * @param valueSizeMarshaller the new marshaller, used to serialize actual value sizes to * off-heap memory * @return this builder back + * @see #keySizeMarshaller(SizeMarshaller) */ - @Override - public ChronicleMapBuilder valueSizeMarshaller(@NotNull SizeMarshaller valueSizeMarshaller) { - delegate.valueSizeMarshaller(valueSizeMarshaller); - return this; - } - - @Override - public ChronicleMapBuilder entries(long l) { - delegate.entries(l); + public ChronicleMapBuilder valueSizeMarshaller( + @NotNull SizeMarshaller valueSizeMarshaller) { + valueBuilder.sizeMarshaller(valueSizeMarshaller); return this; } /** - * each map can be identified by a name + * Configures factory which is used to create a new value instance, if value class is either + * {@link Byteable}, {@link BytesMarshallable} or {@link Externalizable} subclass, or value type + * is eligible for data value generation, or configured custom value reader is an instance of + * {@link DeserializationFactoryConfigurableBytesReader}, 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 name the name of the map - * @return self + * @param valueDeserializationFactory the value factory used to produce instances to deserialize + * data in + * @return this builder back + * @throws IllegalStateException if it is not possible to apply deserialization factory to value + * deserializers, currently configured for this builder + * @see #keyDeserializationFactory(ObjectFactory) */ - @Override - public ChronicleMapBuilder name(String name) { - delegate.name(name); + public ChronicleMapBuilder valueDeserializationFactory( + @NotNull ObjectFactory valueDeserializationFactory) { + valueBuilder.factory(valueDeserializationFactory); return this; } - @Override - public String name() { - return delegate.name(); - } - - @Override - public ChronicleMapBuilder replication(SingleChronicleHashReplication withId) { - delegate.replication(withId); + public ChronicleMapBuilder eventListener(MapEventListener eventListener) { + this.eventListener = eventListener; return this; } - @Override - public ChronicleMapBuilder pushTo(InetSocketAddress... addresses) { - delegate.pushTo(addresses); + MapEventListener eventListener() { + return eventListener; + } + + public ChronicleMapBuilder bytesEventListener(BytesMapEventListener eventListener) { + this.bytesEventListener = eventListener; return this; } - @Override - public ChronicleMap create() { - return delegate.create(); + BytesMapEventListener bytesEventListener() { + return bytesEventListener; } - @Override - public ChronicleMapBuilder actualSegments(int actualSegments) { - delegate.actualSegments(actualSegments); + /** + * 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)} and {@link #prepareDefaultValueBytes(PrepareValueBytes)} + * configurations to this builder. + * + * @param defaultValue the default value to be put to the map for absent keys during {@code + * acquireUsing()} calls + * @return this builder object back + * @see #defaultValueProvider(DefaultValueProvider) + * @see #prepareDefaultValueBytes(PrepareValueBytes) + */ + public ChronicleMapBuilder defaultValue(V defaultValue) { + if (defaultValue == null) + throw new IllegalArgumentException("default ChronicleMap value couldn't be null"); + this.defaultValue = defaultValue; + this.defaultValueProvider = null; + this.prepareValueBytes = null; return this; } - @Override - public ChronicleMapBuilder minSegments(int minSegments) { - delegate.minSegments(minSegments); + /** + * 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)} and {@link + * #prepareDefaultValueBytes(PrepareValueBytes)} configurations to this builder. + * + * @param defaultValueProvider the strategy to obtain a default value by the absent key + * @return this builder object back + * @see #defaultValue(Object) + * @see #prepareDefaultValueBytes(PrepareValueBytes) + */ + public ChronicleMapBuilder defaultValueProvider( + @NotNull DefaultValueProvider defaultValueProvider) { + this.defaultValueProvider = defaultValueProvider; + this.defaultValue = null; + this.prepareValueBytes = null; return this; } - @Override - public ChronicleMapBuilder actualEntriesPerSegment(long actualEntriesPerSegment) { - delegate.actualEntriesPerSegment(actualEntriesPerSegment); + /** + * Configures the procedure which is called on the bytes, which later the returned value is + * pointing to or deserialized from, if the key is absent, on {@link ChronicleMap#acquireUsing + * acquireUsing()} call on maps, created by this builder. See {@link PrepareValueBytes} for more + * information. + *

    + *

    This method of value initialization on {@code acquireUsing()} calls is allowed only if + * value size is constant. Otherwise you should use either {@link #defaultValue(Object)} or + * {@link #defaultValueProvider(DefaultValueProvider)} methods. + *

    + *

    This configuration overrides any previous {@link #defaultValue(Object)} and {@link + * #defaultValueProvider(DefaultValueProvider)} configurations to 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 or deserializing the value from, to return + * from {@code acquireUsing()} call + * @return this builder back + * @throws IllegalStateException is value size is not constant + * @see PrepareValueBytes + * @see #defaultValue(Object) + * @see #defaultValueProvider(DefaultValueProvider) + */ + public ChronicleMapBuilder prepareDefaultValueBytes( + @NotNull PrepareValueBytes prepareValueBytes) { + this.prepareValueBytes = prepareValueBytes; + this.defaultValue = null; + this.defaultValueProvider = null; + checkPrepareValueBytesOnlyIfConstantValueSize(); return this; } + private void checkPrepareValueBytesOnlyIfConstantValueSize() { + if (prepareValueBytes != null && !valueBuilder.constantSizeMarshaller()) + throw new IllegalStateException("Prepare value bytes could be used only if " + + "value size is constant"); + } + + PrepareValueBytesAsWriter prepareValueBytesAsWriter() { + PrepareValueBytes prepareValueBytes = this.prepareValueBytes; + if ((prepareValueBytes == null && defaultValueProvider() != null) || + !valueBuilder.constantSizeMarshaller()) + return null; + if (prepareValueBytes == null) + prepareValueBytes = new ZeroOutValueBytes<>(valueSize()); + return new PrepareValueBytesAsWriter<>(prepareValueBytes, valueSize()); + } + + /** + * Non-public because should be called only after {@link #preMapConstruction(boolean)} + */ + 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); + } + @Override - public ChronicleMapBuilder lockTimeOut(long lockTimeOut, TimeUnit unit) { - delegate.lockTimeOut(lockTimeOut, unit); + public ChronicleMapBuilder replication(SingleChronicleHashReplication replication) { + this.singleHashReplication = replication; return this; } - @Override - public ChronicleMapBuilder errorListener(ChronicleHashErrorListener errorListener) { - delegate.errorListener(errorListener); - return this; - } - - @Override - public ChronicleMapBuilder metaDataBytes(int metaDataBytes) { - delegate.metaDataBytes(metaDataBytes); - return this; - } - - @Override - public ChronicleMapBuilder timeProvider(TimeProvider timeProvider) { - delegate.timeProvider(timeProvider); - return this; - } - - @Override - public ChronicleMapBuilder bytesMarshallerFactory(BytesMarshallerFactory bytesMarshallerFactory) { - delegate.bytesMarshallerFactory(bytesMarshallerFactory); - return this; - } - - @Override - public ChronicleMapBuilder keyMarshaller(BytesMarshaller keyMarshaller) { - delegate.keyMarshaller(keyMarshaller); - return this; - } - - @Override - public ChronicleMapBuilder keyMarshallers(BytesWriter keyWriter, BytesReader keyReader) { - delegate.keyMarshallers(keyWriter, keyReader); - return this; - } - - @Override - public ChronicleMapBuilder keySizeMarshaller(SizeMarshaller keySizeMarshaller) { - delegate.keySizeMarshaller(keySizeMarshaller); - return this; - } - - @Override - public ChronicleMapBuilder keyDeserializationFactory(ObjectFactory keyDeserializationFactory) { - delegate.keyDeserializationFactory(keyDeserializationFactory); - return this; - } - - @Override - public ChronicleMapBuilder immutableKeys() { - delegate.immutableKeys(); - return this; - } - - @Override - public StatelessClientConfig> statelessClient(InetSocketAddress remoteAddress) { - return delegate.statelessClient(remoteAddress); - } - - @Override - public ChronicleHashInstanceConfig> instance() { - return delegate.instance(); - } - @Override public ChronicleMapBuilder replication(byte identifier) { - delegate.replication(identifier); - return this; + return replication(SingleChronicleHashReplication.builder().createWithId(identifier)); } @Override - public ChronicleMapBuilder replication(byte identifier, TcpTransportAndNetworkConfig tcpTransportAndNetwork) { - delegate.replication(identifier, tcpTransportAndNetwork); - return this; + public ChronicleMapBuilder replication( + byte identifier, TcpTransportAndNetworkConfig tcpTransportAndNetwork) { + return replication(SingleChronicleHashReplication.builder() + .tcpTransportAndNetwork(tcpTransportAndNetwork).createWithId(identifier)); } @Override - public ChronicleMapBuilder clone() { - delegate.clone(); - return this; + public ChronicleHashInstanceConfig> instance() { + return new MapInstanceConfig<>(this.clone(), singleHashReplication, null, null, null, + new AtomicBoolean(false)); } @Override - public ChronicleMapBuilder putReturnsNull(boolean b) { - delegate.putReturnsNull(b); - return this; + public ChronicleMap createStatelessClient(InetSocketAddress serverAddress) + throws IOException { + return statelessClient(serverAddress).create(); } - @Override - public ChronicleMapBuilder removeReturnsNull(boolean b) { - delegate.removeReturnsNull(b); - return this; - } - - @Override - public ChronicleMapBuilder eventListener(MapEventListener eventListener) { - delegate.eventListener(eventListener); - return this; - } - - @Override - public ChronicleMapBuilderI bytesEventListener(BytesMapEventListener eventListener) { - delegate.bytesEventListener(eventListener); - return this; + ChronicleMap createStatelessClient(StatelessMapConfig statelessClientConfig) + throws IOException { + pushingToMapEventListener(); + preMapConstruction(false); + return new StatelessChronicleMap<>(statelessClientConfig, this); } @Override public ChronicleMap createPersistedTo(File file) throws IOException { - return delegate.createPersistedTo(file); + // clone() to make this builder instance thread-safe, because createWithFile() method + // computes some state based on configurations, but doesn't synchronize on configuration + // changes. + return clone().createWithFile(file, singleHashReplication, null); } - /** - * @param bootstrapOnlyLocalEntries if set to true - when a new node joins a TCP replication - * grid, the new node will be populated with data, only for the - * nodes that created that data. Otherwise, all the nodes will - * publish all the data they have ( potentially swamping the - * new node with duplicates ) however this does guarantee that - * all the data is replicated over to the new node, and is - * useful especially in the case that the originating node is - * not currently running. - */ - public ChronicleMapBuilder bootstrapOnlyLocalEntries(boolean bootstrapOnlyLocalEntries) { - delegate.bootstrapOnlyLocalEntries(bootstrapOnlyLocalEntries); - return this; + @Override + public StatelessClientConfig> statelessClient( + InetSocketAddress remoteAddress) { + return new StatelessMapConfig<>(clone(), remoteAddress, DEFAULT_STATELESS_CLIENT_TIMEOUT, + null, new AtomicBoolean(false)); } - public ChronicleMapBuilder disableOversizedEntries() { - delegate.disableOversizedEntries(); - return this; + @Override + public ChronicleMap create() { + // clone() to make this builder instance thread-safe, because createWithoutFile() method + // computes some state based on configurations, but doesn't synchronize on configuration + // changes. + return clone().createWithoutFile(singleHashReplication, null); + } + + ChronicleMap create(MapInstanceConfig ib) throws IOException { + if (ib.file != null) { + return createWithFile(ib.file, ib.singleHashReplication, ib.channel); + } else { + return createWithoutFile(ib.singleHashReplication, ib.channel); + } + } + + ChronicleMap createWithFile(File file, SingleChronicleHashReplication singleHashReplication, + ReplicationChannel channel) throws IOException { + pushingToMapEventListener(); + for (int i = 0; i < 10; i++) { + if (file.exists() && file.length() > 0) { + try (FileInputStream fis = new FileInputStream(file); + ObjectInputStream ois = new ObjectInputStream(fis)) { + VanillaChronicleMap map = + (VanillaChronicleMap) ois.readObject(); + map.headerSize = roundUpMapHeaderSize(fis.getChannel().position()); + map.createMappedStoreAndSegments(file); + return establishReplication(map, singleHashReplication, channel); + } catch (ClassNotFoundException e) { + throw new IOException(e); + } + } + 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(singleHashReplication, channel); + + try (FileOutputStream fos = new FileOutputStream(file); + ObjectOutputStream oos = new ObjectOutputStream(fos)) { + oos.writeObject(map); + oos.flush(); + map.headerSize = roundUpMapHeaderSize(fos.getChannel().position()); + map.createMappedStoreAndSegments(file); + } + + return establishReplication(map, singleHashReplication, channel); + } + + ChronicleMap createWithoutFile( + SingleChronicleHashReplication singleHashReplication, ReplicationChannel channel) { + try { + pushingToMapEventListener(); + VanillaChronicleMap map = newMap(singleHashReplication, channel); + map.warnOnWindows(); + BytesStore bytesStore = new DirectStore(JDKObjectSerializer.INSTANCE, + map.sizeInBytes(), true); + map.createMappedStoreAndSegments(bytesStore); + return establishReplication(map, singleHashReplication, channel); + } catch (IOException e) { + // file-less version should never trigger an IOException. + throw new AssertionError(e); + } + } + + private void pushingToMapEventListener() { + if (pushToAddresses == null || pushToAddresses.length == 0) { + return; + } + try { + Class pmel = Class.forName( + "com.higherfrequencytrading.chronicle.enterprise.map.PushingMapEventListener"); + Constructor constructor = pmel.getConstructor(ChronicleMap[].class); + // create a stateless client for each address + ChronicleMap[] statelessClients = new ChronicleMap[pushToAddresses.length]; + ChronicleMapBuilder cmb = clone(); + cmb.pushTo((InetSocketAddress[]) null); + for (int i = 0; i < pushToAddresses.length; i++) { + statelessClients[i] = cmb.createStatelessClient(pushToAddresses[i]); + } + eventListener = + (MapEventListener) constructor.newInstance((Object) statelessClients); + } catch (ClassNotFoundException e) { + LoggerFactory.getLogger(getClass().getName()) + .warn("Chronicle Enterprise not found in the class path"); + } catch (Exception e) { + LoggerFactory.getLogger(getClass().getName()) + .error("PushingMapEventListener failed to load", e); + } + } + + private VanillaChronicleMap newMap( + SingleChronicleHashReplication singleHashReplication, ReplicationChannel channel) + throws IOException { + boolean replicated = singleHashReplication != null || channel != null; + preMapConstruction(replicated); + if (replicated) { + AbstractReplication replication; + if (singleHashReplication != null) { + replication = singleHashReplication; + } else { + replication = channel.hub(); + } + return new ReplicatedChronicleMap, + V, Object, MetaBytesInterop>(this, replication); + } else { + return new VanillaChronicleMap, + V, Object, MetaBytesInterop>(this); + } + } + + void preMapConstruction(boolean replicated) { + keyBuilder.objectSerializer(acquireObjectSerializer(JDKObjectSerializer.INSTANCE)); + valueBuilder.objectSerializer(acquireObjectSerializer(JDKObjectSerializer.INSTANCE)); + + long maxSize = (long) entrySize(replicated) * figureBufferAllocationFactor(replicated); + keyBuilder.maxSize(maxSize); + valueBuilder.maxSize(maxSize); + + if (sampleKey != null) + keyBuilder.constantSizeBySample(sampleKey); + if (sampleValue != null) + valueBuilder.constantSizeBySample(sampleValue); + stateChecks(replicated); + } + + private void stateChecks(boolean replicated) { + checkAlignmentOnlyIfValuesPossiblyReferenceOffHeap(); + checkPrepareValueBytesOnlyIfConstantValueSize(); + checkMaxEntryOversizeFactorIfReplicated(replicated); + } + + private ChronicleMap establishReplication( + VanillaChronicleMap map, + SingleChronicleHashReplication singleHashReplication, + ReplicationChannel channel) throws IOException { + if (map instanceof ReplicatedChronicleMap) { + if (singleHashReplication != null && channel != null) { + throw new AssertionError("Only one non-null replication should be passed"); + } + ReplicatedChronicleMap result = (ReplicatedChronicleMap) map; + List replicators = new ArrayList<>(2); + if (singleHashReplication != null) { + if (singleHashReplication.tcpTransportAndNetwork() != null) + replicators.add(Replicators.tcp(singleHashReplication)); + if (singleHashReplication.udpTransport() != null) + replicators.add(Replicators.udp(singleHashReplication.udpTransport())); + } else { + ReplicationHub hub = channel.hub(); + int entrySize = entrySize(true); + if (entrySize > hub.maxEntrySize()) { + throw new IllegalArgumentException("During ChannelProviderBuilder setup, " + + "maxEntrySize=" + hub.maxEntrySize() + + " was specified, but map with " + + "entrySize=" + entrySize + " is attempted to apply to the replicator"); + } + ChannelProvider provider = ChannelProvider.getProvider(hub); + ChannelProvider.ChronicleChannel ch = provider.createChannel(channel.channelId()); + replicators.add(ch); + } + for (Replicator replicator : replicators) { + Closeable token = replicator.applyTo(this, result, result, map); + if (replicators.size() == 1 && token.getClass() == UdpReplicator.class) { + LOG.warn(Replicators.ONLY_UDP_WARN_MESSAGE); + } + result.addCloseable(token); + } + } + return map; + } + + private int figureBufferAllocationFactor(boolean replicated) { + // 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), + maxEntryOversizeFactor(replicated)); } } + diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilderI.java b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilderI.java deleted file mode 100755 index 99cc3455b..000000000 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilderI.java +++ /dev/null @@ -1,319 +0,0 @@ -package net.openhft.chronicle.map; - -import net.openhft.chronicle.hash.ChronicleHashErrorListener; -import net.openhft.chronicle.hash.ChronicleHashInstanceConfig; -import net.openhft.chronicle.hash.StatelessClientConfig; -import net.openhft.chronicle.hash.replication.SingleChronicleHashReplication; -import net.openhft.chronicle.hash.replication.TcpTransportAndNetworkConfig; -import net.openhft.chronicle.hash.replication.TimeProvider; -import net.openhft.chronicle.hash.serialization.BytesReader; -import net.openhft.chronicle.hash.serialization.BytesWriter; -import net.openhft.chronicle.hash.serialization.SizeMarshaller; -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 org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.io.IOException; -import java.io.Serializable; -import java.net.InetSocketAddress; -import java.util.concurrent.TimeUnit; - -/** - * @author Rob Austin. - */ -public interface ChronicleMapBuilderI extends Serializable { - - /** - * {@inheritDoc} 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, and - * entry alignment is set to {@link Alignment#OF_4_BYTES}, the actual entry size will be 32 (30 - * aligned to 4 bytes). - * - * @see #entryAndValueAlignment(Alignment) // * @see #entries(long) - */ - ChronicleMapBuilderI entrySize(int entrySize); - - /** - * 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} for Byteable values. - * - * @param alignment the new alignment of the maps constructed by this builder - * @return this {@code ChronicleMapOnHeapUpdatableBuilder} back - */ - ChronicleMapBuilderI entryAndValueAlignment(Alignment alignment); - - /** - * {@inheritDoc} Also, it overrides any previous {@link #prepareValueBytesOnAcquire} - * configuration to this {@code ChronicleMapBuilder}. - * - *

    By default, the default value is not specified, default {@linkplain - * #prepareValueBytesOnAcquire prepare value bytes routine} is specified instead. - * - * @see #defaultValueProvider(DefaultValueProvider) - * @see #prepareValueBytesOnAcquire(PrepareValueBytes) - */ - ChronicleMapBuilderI defaultValue(V defaultValue); - - /** - * 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. See {@link PrepareValueBytes} for more - * information. - * - *

    The default preparation callback zeroes out the value bytes. - * - * @param prepareValueBytes what to do with the value bytes before assigning them into the - * {@link net.openhft.lang.model.Byteable} value to return from {@code - * acquireUsing()} call - * @return this builder back - * @see PrepareValueBytes - * @see #defaultValue(Object) - * @see #defaultValueProvider(DefaultValueProvider) - */ - ChronicleMapBuilderI prepareValueBytesOnAcquire( - @NotNull PrepareValueBytes prepareValueBytes); - - /** - * {@inheritDoc} - * - * @see #constantKeySizeBySample(Object) - * @see #valueSize(int) - * @see #entrySize(int) - */ - ChronicleMapBuilderI keySize(int keySize); - - /** - * {@inheritDoc} - * - * @see #keySize(int) - * @see #constantValueSizeBySample(Object) - */ - - ChronicleMapBuilderI constantKeySizeBySample(K sampleKey); - - /** - * Configures the optimal number of bytes, taken by serialized form of values, put into maps, - * 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, 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, - * it's better to use {@linkplain #entrySize(int) entry size} in "chunk" mode and configure it - * directly. - * - * @param valueSize number of bytes, taken by serialized form of values - * @return this {@code ChronicleMapOnHeapUpdatableBuilder} back - * @see #constantValueSizeBySample(Object) - * @see #keySize(int) - * @see #entrySize(int) - */ - - ChronicleMapBuilderI valueSize(int valueSize); - - /** - * Configures the constant number of bytes, taken by serialized form of values, put into maps, - * created by this builder. This is done by providing the {@code sampleValue}, all values should - * take the same number of bytes in serialized form, as this sample object. - * - *

    If values are of boxed primitive type or {@link net.openhft.lang.model.Byteable} subclass, - * i. e. if value size is known statically, it is automatically accounted and this method - * shouldn't be called. - * - *

    If value size varies, method {@link #valueSize(int)} or {@link #entrySize(int)} should be - * called instead of this one. - * - * @param sampleValue the sample value - * @return this builder back - * @see #valueSize(int) - * @see #constantKeySizeBySample(Object) - */ - - ChronicleMapBuilderI constantValueSizeBySample( - @NotNull V sampleValue); - - /** - * {@inheritDoc} - * - *

    Example:

    {@code Map map =
    -     *     ChronicleMapBuilder.of(Key.class, Value.class)
    -     *     .entries(1_000_000)
    -     *     .keySize(50).valueSize(200)
    -     *     // this class hasn't implemented yet, just for example
    -     *     .objectSerializer(new KryoObjectSerializer())
    -     *     .create();}
    - */ - - ChronicleMapBuilderI objectSerializer( - @NotNull ObjectSerializer objectSerializer); - - /** - * {@inheritDoc} - * - * @throws IllegalStateException if custom value marshaller is specified or value class is not - * either {@code BytesMarshallable} or {@code Externalizable} - */ - - ChronicleMapBuilderI valueDeserializationFactory( - @NotNull ObjectFactory valueDeserializationFactory); - - /** - * {@inheritDoc} - * - *

    By default, default value provider is not specified, {@link #defaultValue(Object) default - * value} is specified instead. - * - * @see #defaultValue(Object) - */ - - ChronicleMapBuilderI defaultValueProvider( - @NotNull DefaultValueProvider defaultValueProvider); - - /** - * Configures the {@code BytesMarshaller} used to serialize/deserialize values to/from off-heap - * memory in maps, created by this builder. See the - * section about serialization in ChronicleMap manual for more information. - * - * @param valueMarshaller the marshaller used to serialize values - * @return this builder back - * @see #valueMarshallers(net.openhft.chronicle.hash.serialization.BytesWriter, - * net.openhft.chronicle.hash.serialization.BytesReader) - * @see ChronicleMapBuilder - * @see #objectSerializer(ObjectSerializer) - */ - ChronicleMapBuilderI valueMarshaller( - @NotNull BytesMarshaller valueMarshaller); - - /** - * Configures the marshallers, used to serialize/deserialize values to/from off-heap memory in - * maps, created by this builder. See the - * section about serialization in ChronicleMap manual for more information. - * - *

    Configuring marshalling this way results to a little bit more compact in-memory layout of - * the map, comparing to a single interface configuration: {@link #valueMarshaller(BytesMarshaller)}. - * - *

    Passing {@link net.openhft.chronicle.hash.serialization.BytesInterop} instead of plain - * {@link net.openhft.chronicle.hash.serialization.BytesWriter} is, of cause, possible, but - * currently pointless for values. - * - * @param valueWriter the new value object → {@link net.openhft.lang.io.Bytes} writer - * (interop) strategy - * @param valueReader the new {@link net.openhft.lang.io.Bytes} → value object reader - * strategy - * @return this builder back - * @see #valueMarshaller(BytesMarshaller) - */ - ChronicleMapBuilderI valueMarshallers( - @NotNull BytesWriter valueWriter, @NotNull BytesReader valueReader); - - /** - * Configures the marshaller used to serialize actual value sizes to off-heap memory in maps, - * created by this builder. - * - *

    Default value size marshaller is so-called {@linkplain net.openhft.chronicle.hash.serialization.SizeMarshallers#stopBit() - * stop bit encoding marshalling}. If {@linkplain #constantValueSizeBySample(Object) constant - * value size} is configured, or defaulted if the value type is always constant and {@code - * ChronicleHashBuilder} implementation knows about it, this configuration takes no effect, - * because a special {@link net.openhft.chronicle.hash.serialization.SizeMarshaller} - * implementation, which doesn't actually do any marshalling, and just returns the known - * constant size on {@link net.openhft.chronicle.hash.serialization.SizeMarshaller#readSize( - *net.openhft.lang.io.Bytes)} calls, is used instead of any {@code SizeMarshaller} configured - * using this method. - * - * @param valueSizeMarshaller the new marshaller, used to serialize actual value sizes to - * off-heap memory - * @return this builder back - */ - ChronicleMapBuilderI valueSizeMarshaller( - @NotNull SizeMarshaller valueSizeMarshaller); - - ChronicleMapBuilderI entries(long l); - - ChronicleMapBuilderI replication(SingleChronicleHashReplication withId); - - ChronicleMapBuilderI pushTo(InetSocketAddress... addresses); - - ChronicleMap create(); - - ChronicleMapBuilderI actualSegments(int actualSegments); - - ChronicleMapBuilderI minSegments(int minSegments); - - ChronicleMapBuilderI actualEntriesPerSegment(long actualEntriesPerSegment); - - ChronicleMapBuilderI lockTimeOut(long lockTimeOut, TimeUnit unit); - - ChronicleMapBuilderI errorListener(ChronicleHashErrorListener errorListener); - - ChronicleMapBuilderI metaDataBytes(int metaDataBytes); - - ChronicleMapBuilderI timeProvider(TimeProvider timeProvider); - - ChronicleMapBuilderI bytesMarshallerFactory(BytesMarshallerFactory bytesMarshallerFactory); - - ChronicleMapBuilderI keyMarshaller(BytesMarshaller keyMarshaller); - - ChronicleMapBuilderI keyMarshallers(BytesWriter keyWriter, BytesReader keyReader); - - ChronicleMapBuilderI keySizeMarshaller(SizeMarshaller keySizeMarshaller); - - ChronicleMapBuilderI keyDeserializationFactory(ObjectFactory keyDeserializationFactory); - - ChronicleMapBuilderI immutableKeys(); - - ChronicleMap createPersistedTo(File file) throws IOException; - - StatelessClientConfig> statelessClient(InetSocketAddress remoteAddress); - - ChronicleHashInstanceConfig> instance(); - - ChronicleMapBuilderI replication(byte identifier); - - ChronicleMapBuilderI replication(byte identifier, TcpTransportAndNetworkConfig tcpTransportAndNetwork); - - ChronicleMapBuilderI clone(); - - ChronicleMapBuilderI eventListener(MapEventListener eventListener); - - ChronicleMapBuilderI bytesEventListener(BytesMapEventListener eventListener); - - ChronicleMapBuilderI putReturnsNull(boolean b); - - ChronicleMapBuilderI removeReturnsNull(boolean b); - - ChronicleMapBuilderI name(String name); - - /** - * @param bootstapOnlyLocalEntries if set to true - when a new node joins a TCP replication - * grid, the new node will be populated with data, only for the - * nodes that created that data. Otherwise, all the nodes will - * publish all the data they have ( potentially swamping the new - * node with duplicates ) however this does guarantee that all - * the data is replicated over to the new node, and is useful - * especially in the case that the originating node is not - * currently running. - * @return self - */ - ChronicleMapBuilderI bootstrapOnlyLocalEntries(boolean bootstapOnlyLocalEntries); - - String name(); - - ChronicleMapBuilderI disableOversizedEntries(); -} diff --git a/src/main/java/net/openhft/chronicle/map/CloseablesManager.java b/src/main/java/net/openhft/chronicle/map/CloseablesManager.java index 8022e0ab2..ee8db28f9 100755 --- a/src/main/java/net/openhft/chronicle/map/CloseablesManager.java +++ b/src/main/java/net/openhft/chronicle/map/CloseablesManager.java @@ -58,8 +58,12 @@ final class CloseablesManager implements Closeable { Closeable closeable = closeables.get(i); try { closeable.close(); + } catch (NullPointerException e) { + // at java.nio.channels.spi.AbstractSelectableChannel.removeKey + // (AbstractSelectableChannel.java:129) is throwing a NULL on close + LOG.debug("", e); } catch (IOException e) { - LOG.error("", e); + LOG.debug("", e); ex = e; } } diff --git a/src/main/java/net/openhft/chronicle/map/DefaultValueProvider.java b/src/main/java/net/openhft/chronicle/map/DefaultValueProvider.java index a07c03982..7a2d5ea52 100644 --- a/src/main/java/net/openhft/chronicle/map/DefaultValueProvider.java +++ b/src/main/java/net/openhft/chronicle/map/DefaultValueProvider.java @@ -22,7 +22,7 @@ import java.io.Serializable; /** * Default value computation strategy, used - * in {@link AbstractChronicleMapBuilder#defaultValueProvider(DefaultValueProvider)} configuration. + * in {@link ChronicleMapBuilder#defaultValueProvider(DefaultValueProvider)} configuration. * * @param map key class * @param map value class diff --git a/src/main/java/net/openhft/chronicle/hash/FindByName.java b/src/main/java/net/openhft/chronicle/map/FindByName.java similarity index 86% rename from src/main/java/net/openhft/chronicle/hash/FindByName.java rename to src/main/java/net/openhft/chronicle/map/FindByName.java index 77fd8fcfa..b67da6cf5 100644 --- a/src/main/java/net/openhft/chronicle/hash/FindByName.java +++ b/src/main/java/net/openhft/chronicle/map/FindByName.java @@ -1,4 +1,6 @@ -package net.openhft.chronicle.hash; +package net.openhft.chronicle.map; + +import net.openhft.chronicle.hash.ChronicleHash; import java.io.IOException; import java.util.concurrent.TimeoutException; @@ -6,7 +8,7 @@ import java.util.concurrent.TimeoutException; /** * @author Rob Austin. */ -public interface FindByName { +interface FindByName { /** * @param name the name of the map or set diff --git a/src/main/java/net/openhft/chronicle/map/MapEventListener.java b/src/main/java/net/openhft/chronicle/map/MapEventListener.java index 5cf73f8b9..8535d1b29 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 - * AbstractChronicleMapBuilder#eventListener(MapEventListener)} method. + * ChronicleMapBuilder#eventListener(MapEventListener)} method. * *

    See {@link #logging(String)} implementation. * @@ -45,7 +45,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 - * @see AbstractChronicleMapBuilder#eventListener(MapEventListener) + * @see ChronicleMapBuilder#eventListener(MapEventListener) */ public abstract class MapEventListener implements Serializable { private static final long serialVersionUID = 0L; @@ -107,7 +107,7 @@ public abstract class MapEventListener implements Serializable { * This method is called 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 - * AbstractChronicleMapBuilder#defaultValue(Object) default} for the map or obtained during + * ChronicleMapBuilder#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/InstanceConfig.java b/src/main/java/net/openhft/chronicle/map/MapInstanceConfig.java similarity index 58% rename from src/main/java/net/openhft/chronicle/map/InstanceConfig.java rename to src/main/java/net/openhft/chronicle/map/MapInstanceConfig.java index 5e75a6492..67959f6a8 100644 --- a/src/main/java/net/openhft/chronicle/map/InstanceConfig.java +++ b/src/main/java/net/openhft/chronicle/map/MapInstanceConfig.java @@ -23,61 +23,69 @@ import net.openhft.chronicle.hash.replication.TcpTransportAndNetworkConfig; import java.io.File; import java.io.IOException; +import java.io.Serializable; +import java.util.concurrent.atomic.AtomicBoolean; -final class InstanceConfig - implements ChronicleHashInstanceConfig> { +final class MapInstanceConfig + implements ChronicleHashInstanceConfig>, Serializable{ - final AbstractChronicleMapBuilder mapBuilder; + final ChronicleMapBuilder mapBuilder; final SingleChronicleHashReplication singleHashReplication; final ReplicationChannel channel; final File file; + final String name; - private boolean used = false; + final AtomicBoolean used; - InstanceConfig(AbstractChronicleMapBuilder mapBuilder, - SingleChronicleHashReplication singleHashReplication, - ReplicationChannel channel, - File file) { + MapInstanceConfig(ChronicleMapBuilder mapBuilder, + SingleChronicleHashReplication singleHashReplication, + ReplicationChannel channel, + File file, String name, AtomicBoolean used) { this.mapBuilder = mapBuilder; this.singleHashReplication = singleHashReplication; this.channel = channel; this.file = file; + this.name = name; + this.used = used; } @Override - public ChronicleHashInstanceConfig> replicated( + public MapInstanceConfig replicated( byte identifier, TcpTransportAndNetworkConfig tcpTransportAndNetwork) { return replicated(SingleChronicleHashReplication.builder() .tcpTransportAndNetwork(tcpTransportAndNetwork).createWithId(identifier)); } @Override - public ChronicleHashInstanceConfig> replicated( - SingleChronicleHashReplication replication) { - return new InstanceConfig<>(mapBuilder, replication, null, file); + public MapInstanceConfig replicated(SingleChronicleHashReplication replication) { + return new MapInstanceConfig<>(mapBuilder, replication, null, file, name, used); } @Override - public ChronicleHashInstanceConfig> replicatedViaChannel( - ReplicationChannel channel) { - return new InstanceConfig<>(mapBuilder, null, channel, file); + public MapInstanceConfig replicatedViaChannel(ReplicationChannel channel) { + return new MapInstanceConfig<>(mapBuilder, null, channel, file, name, used); } @Override - public ChronicleHashInstanceConfig> persistedTo(File file) { - return new InstanceConfig<>(mapBuilder, singleHashReplication, channel, file); + public MapInstanceConfig persistedTo(File file) { + return new MapInstanceConfig<>(mapBuilder, singleHashReplication, channel, file, name, used); + } + + @Override + public MapInstanceConfig name(String name) { + return new MapInstanceConfig<>(mapBuilder, singleHashReplication, channel, file, name, used); } @Override public synchronized ChronicleMap create() throws IOException { - if (used) + if (!used.getAndSet(true)) { + return mapBuilder.create(this); + } else { throw new IllegalStateException( - "A ChronicleMap has already been created using this instance config. " + - "Create a new instance config (builder.instance() or call any " + - "configuration method on this instance config) to create a new " + + "A ChronicleMap has already been created using this instance config chain. " + + "Create a new instance config (builder.instance()) to create a new " + "ChronicleMap instance"); - used = true; - return mapBuilder.create(this); + } } } diff --git a/src/main/java/net/openhft/chronicle/map/NodeDiscovery.java b/src/main/java/net/openhft/chronicle/map/NodeDiscovery.java index 04331cfb0..c4c14bc24 100644 --- a/src/main/java/net/openhft/chronicle/map/NodeDiscovery.java +++ b/src/main/java/net/openhft/chronicle/map/NodeDiscovery.java @@ -53,7 +53,7 @@ import static net.openhft.chronicle.map.NodeDiscoveryBroadcaster.LOG; * * This code is in alpha, and still has to undergo refactoring */ -public class NodeDiscovery { +class NodeDiscovery { public static final int DEFAULT_UDP_BROADCAST_PORT = 8129; public static final short DEFAULT_TCP_PORT = (short) 8123; diff --git a/src/main/java/net/openhft/chronicle/map/OffHeapUpdatableChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/OffHeapUpdatableChronicleMapBuilder.java deleted file mode 100644 index e94a9383a..000000000 --- a/src/main/java/net/openhft/chronicle/map/OffHeapUpdatableChronicleMapBuilder.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * 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.hash.serialization.BytesReader; -import net.openhft.chronicle.hash.serialization.BytesWriter; -import net.openhft.chronicle.hash.serialization.SizeMarshaller; -import net.openhft.lang.io.serialization.BytesMarshaller; -import net.openhft.lang.model.Byteable; -import net.openhft.lang.model.DataValueClasses; -import org.jetbrains.annotations.NotNull; - -final class OffHeapUpdatableChronicleMapBuilder - extends AbstractChronicleMapBuilder> { - - public static ChronicleMapBuilderI of( - @NotNull Class keyClass, @NotNull Class valueClass) { - if (valueClass.isEnum()) - return OnHeapUpdatableChronicleMapBuilder.of(keyClass, valueClass); - - if (keyClass.isInterface() && keyClass != CharSequence.class) { - keyClass = DataValueClasses.directClassFor(keyClass); - } - - if ((valueClass.isInterface() && valueClass != CharSequence.class)) { - valueClass = DataValueClasses.directClassFor(valueClass); - } else if (!offHeapReference(valueClass)) { - return OnHeapUpdatableChronicleMapBuilder.of(keyClass, valueClass); - } - return new OffHeapUpdatableChronicleMapBuilder(keyClass, valueClass); - } - - OffHeapUpdatableChronicleMapBuilder(Class keyClass, Class valueClass) { - super(keyClass, valueClass); - prepareValueBytesOnAcquire(new ZeroOutValueBytes(valueSize())); - } - - @Override - OffHeapUpdatableChronicleMapBuilder self() { - return this; - } - - @Override - public OffHeapUpdatableChronicleMapBuilder valueSize(int valueSize) { - throw new UnsupportedOperationException(); - } - - /** - * {@inheritDoc} 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, and entry - * alignment is set to {@link Alignment#OF_4_BYTES}, the actual entry size will be 32 (30 aligned to 4 - * bytes). - * - * @see #entryAndValueAlignment(Alignment) - * @see #entries(long) - */ - @Override - public OffHeapUpdatableChronicleMapBuilder entrySize(int entrySize) { - return super.entrySize(entrySize); - } - - /** - * 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} for Byteable values. - * - * @param alignment the new alignment of the maps constructed by this builder - * @return this {@code ChronicleMapOnHeapUpdatableBuilder} back - */ - @Override - public OffHeapUpdatableChronicleMapBuilder entryAndValueAlignment(Alignment alignment) { - return super.entryAndValueAlignment(alignment); - } - - /** - * {@inheritDoc} Also, it overrides any previous {@link #prepareValueBytesOnAcquire} configuration to this - * {@code ChronicleMapBuilder}. - * - *

    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 ChronicleMapBuilder}. - * - *

    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. See {@link PrepareValueBytes} for more information. - * - *

    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 PrepareValueBytes - * @see #defaultValue(Object) - * @see #defaultValueProvider(DefaultValueProvider) - */ - @Override - public OffHeapUpdatableChronicleMapBuilder prepareValueBytesOnAcquire( - @NotNull PrepareValueBytes prepareValueBytes) { - return super.prepareValueBytesOnAcquire(prepareValueBytes); - } - - public ChronicleMapBuilderI valueMarshaller(@NotNull BytesMarshaller valueMarshaller) { - throw new UnsupportedOperationException("not supported for this combination of key/value type"); - } - - public ChronicleMapBuilderI valueMarshallers(@NotNull BytesWriter valueWriter, @NotNull BytesReader valueReader) { - throw new UnsupportedOperationException("not supported for this combination of key/value type"); - } - - public ChronicleMapBuilderI valueSizeMarshaller(@NotNull SizeMarshaller valueSizeMarshaller) { - throw new UnsupportedOperationException("not supported for this combination of key/value type"); - } - - @Override - public OffHeapUpdatableChronicleMapBuilder constantValueSizeBySample(V sampleValue) { - throw new UnsupportedOperationException("not supported for this combination of key/value type"); - } - -} diff --git a/src/main/java/net/openhft/chronicle/map/OnHeapUpdatableChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/OnHeapUpdatableChronicleMapBuilder.java deleted file mode 100644 index 058dc94cb..000000000 --- a/src/main/java/net/openhft/chronicle/map/OnHeapUpdatableChronicleMapBuilder.java +++ /dev/null @@ -1,282 +0,0 @@ -/* - * 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.hash.ChronicleHashBuilder; -import net.openhft.chronicle.hash.serialization.BytesReader; -import net.openhft.chronicle.hash.serialization.BytesWriter; -import net.openhft.chronicle.hash.serialization.SizeMarshaller; -import net.openhft.lang.io.Bytes; -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 java.io.Externalizable; -import java.io.Serializable; - -/** - * {@code ChronicleMapOnHeapUpdatableBuilder} 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) of(Key.class, Value.class)} method to obtain a {@code ChronicleMapOnHeapUpdatableBuilder} - * instance. - * - * @param key type of the maps, created by this builder - * @param value type of the maps, created by this builder - * @see ChronicleMapBuilder - * @see AbstractChronicleMapBuilder - * @see ChronicleHashBuilder - */ -final class OnHeapUpdatableChronicleMapBuilder - extends AbstractChronicleMapBuilder> { - - /** - * Returns a new {@code ChronicleMapOnHeapUpdatableBuilder} instance which is able to {@linkplain - * #create() create} maps with the specified key and value classes.

    {@code - * ChronicleMapOnHeapUpdatableBuilder} 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 ChronicleMapBuilderI of( - @NotNull Class keyClass, @NotNull Class valueClass) { - return new OnHeapUpdatableChronicleMapBuilder(keyClass, valueClass); - } - - OnHeapUpdatableChronicleMapBuilder(Class keyClass, Class valueClass) { - super(keyClass, valueClass); - } - - @Override - OnHeapUpdatableChronicleMapBuilder self() { - return this; - } - - /** - * {@inheritDoc} - * - * @see #constantKeySizeBySample(Object) - * @see #valueSize(int) - * @see #entrySize(int) - */ - @Override - public OnHeapUpdatableChronicleMapBuilder keySize(int keySize) { - return super.keySize(keySize); - } - - /** - * {@inheritDoc} - * - * @see #keySize(int) - * @see #constantValueSizeBySample(Object) - */ - @Override - public OnHeapUpdatableChronicleMapBuilder constantKeySizeBySample(K sampleKey) { - return super.constantKeySizeBySample(sampleKey); - } - - /** - * Configures the optimal number of bytes, taken by serialized form of values, put into maps, 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, 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, it's better to use {@linkplain - * #entrySize(int) entry size} in "chunk" mode and configure it directly. - * - * @param valueSize number of bytes, taken by serialized form of values - * @return this {@code ChronicleMapOnHeapUpdatableBuilder} back - * @see #constantValueSizeBySample(Object) - * @see #keySize(int) - * @see #entrySize(int) - */ - @Override - public OnHeapUpdatableChronicleMapBuilder valueSize(int valueSize) { - return super.valueSize(valueSize); - } - - /** - * Configures the constant number of bytes, taken by serialized form of values, put into maps, created by - * this builder. This is done by providing the {@code sampleValue}, all values should take the same number - * of bytes in serialized form, as this sample object. If values are of boxed primitive type or - * {@link Byteable} subclass, i. e. if value size is known statically, it is automatically accounted and - * this method shouldn't be called. If value size varies, method {@link #valueSize(int)} or {@link - * #entrySize(int)} should be called instead of this one. - * - * @param sampleValue the sample value - * @return this builder back - * @see #valueSize(int) - * @see #constantKeySizeBySample(Object) - */ - - @Override - public OnHeapUpdatableChronicleMapBuilder constantValueSizeBySample( - @NotNull V sampleValue) { - return super.constantValueSizeBySample(sampleValue); - } - - /** - * {@inheritDoc}

    - *

    Example:

    {@code Map map =
    -     *     ChronicleMapBuilder.of(Key.class, Value.class)
    -     *     .entries(1_000_000)
    -     *     .keySize(50).valueSize(200)
    -     *     // this class hasn't implemented yet, just for example
    -     *     .objectSerializer(new KryoObjectSerializer())
    -     *     .create();}
    - *

    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 - * #valueMarshaller(BytesMarshaller) value} marshallers are not configured. - */ - @Override - public OnHeapUpdatableChronicleMapBuilder objectSerializer( - @NotNull ObjectSerializer objectSerializer) { - return super.objectSerializer(objectSerializer); - } - - /** - * {@inheritDoc} 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 OnHeapUpdatableChronicleMapBuilder valueDeserializationFactory( - @NotNull ObjectFactory valueDeserializationFactory) { - return super.valueDeserializationFactory(valueDeserializationFactory); - } - - /** - * {@inheritDoc}

    By default, the default value is set to {@code null}. - * - * @see #defaultValueProvider(DefaultValueProvider) - */ - @Override - public OnHeapUpdatableChronicleMapBuilder defaultValue(V defaultValue) { - return super.defaultValue(defaultValue); - } - - /** - * {@inheritDoc}

    By default, default value provider is not specified, {@link #defaultValue(Object) - * default value} is specified instead. - * - * @see #defaultValue(Object) - */ - @Override - public OnHeapUpdatableChronicleMapBuilder defaultValueProvider( - @NotNull DefaultValueProvider defaultValueProvider) { - return super.defaultValueProvider(defaultValueProvider); - } - - /** - * Configures the {@code BytesMarshaller} used to serialize/deserialize values to/from off-heap memory in - * maps, created by this builder. See the - * section about serialization in ChronicleMap manual for more information. - * - * @param valueMarshaller the marshaller used to serialize values - * @return this builder back - * @see #valueMarshallers(BytesWriter, BytesReader) - * @see ChronicleMapBuilder - * @see #objectSerializer(ObjectSerializer) - */ - public OnHeapUpdatableChronicleMapBuilder valueMarshaller( - @NotNull BytesMarshaller valueMarshaller) { - valueBuilder.marshaller(valueMarshaller); - return this; - } - - /** - * Configures the marshallers, used to serialize/deserialize values to/from off-heap memory in maps, - * created by this builder. See the - * section about serialization in ChronicleMap manual for more information.

    Configuring - * marshalling this way results to a little bit more compact in-memory layout of the map, comparing to a - * single interface configuration: {@link #valueMarshaller(BytesMarshaller)}.

    Passing {@link - * net.openhft.chronicle.hash.serialization.BytesInterop} instead of plain {@link BytesWriter} is, of - * cause, possible, but currently pointless for values. - * - * @param valueWriter the new value object → {@link Bytes} writer (interop) strategy - * @param valueReader the new {@link Bytes} → value object reader strategy - * @return this builder back - * @see #valueMarshaller(BytesMarshaller) - */ - public OnHeapUpdatableChronicleMapBuilder valueMarshallers( - @NotNull BytesWriter valueWriter, @NotNull BytesReader valueReader) { - valueBuilder.writer(valueWriter); - valueBuilder.reader(valueReader); - return this; - } - - /** - * Configures the marshaller used to serialize actual value sizes to off-heap memory in maps, created by - * this builder.

    Default value size marshaller is so-called {@linkplain - * net.openhft.chronicle.hash.serialization.SizeMarshallers#stopBit() stop bit encoding marshalling}. If - * {@linkplain #constantValueSizeBySample(Object) constant value size} is configured, or defaulted if the - * value type is always constant and {@code ChronicleHashBuilder} implementation knows about it, this - * configuration takes no effect, because a special {@link SizeMarshaller} implementation, which doesn't - * actually do any marshalling, and just returns the known constant size on {@link - * SizeMarshaller#readSize(Bytes)} calls, is used instead of any {@code SizeMarshaller} configured using - * this method. - * - * @param valueSizeMarshaller the new marshaller, used to serialize actual value sizes to off-heap memory - * @return this builder back - */ - public OnHeapUpdatableChronicleMapBuilder valueSizeMarshaller( - @NotNull SizeMarshaller valueSizeMarshaller) { - valueBuilder.sizeMarshaller(valueSizeMarshaller); - return this; - } - - /** - * 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. See {@link PrepareValueBytes} for more information.

    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 PrepareValueBytes - * @see #defaultValue(Object) - * @see #defaultValueProvider(DefaultValueProvider) - */ - @Override - public OnHeapUpdatableChronicleMapBuilder prepareValueBytesOnAcquire( - @NotNull PrepareValueBytes prepareValueBytes) { - throw new UnsupportedOperationException("not supported for this combination of key/value type"); - } - - public OnHeapUpdatableChronicleMapBuilder entryAndValueAlignment(Alignment a) { - throw new UnsupportedOperationException("not supported for this combination of key/value type"); - } - -} diff --git a/src/main/java/net/openhft/chronicle/map/PrepareValueBytes.java b/src/main/java/net/openhft/chronicle/map/PrepareValueBytes.java index 87462f6a8..a450891a8 100644 --- a/src/main/java/net/openhft/chronicle/map/PrepareValueBytes.java +++ b/src/main/java/net/openhft/chronicle/map/PrepareValueBytes.java @@ -26,12 +26,11 @@ import java.io.Serializable; * value during {@link ChronicleMap#acquireUsing(Object, Object) * chronicleMap.acquireUsing()} call, when the given key is absent in the map. Example: if you * want {@code 1L} to be the off-heap updatable ChronicleMap's {@linkplain - * ChronicleMapBuilder#defaultValue(Object) default value}, the most simple way - * to achieve this is to {@linkplain ChronicleMapBuilder#prepareValueBytesOnAcquire( + * ChronicleMapBuilder#defaultValue(Object) default value}. * PrepareValueBytes) configure} custom {@code PrepareValueBytes} strategy in {@link * ChronicleMapBuilder}:

    {@code
      * ChronicleMapBuilder.of(Key.class, LongValue.class)
    - *     .prepareValueBytesOnAcquire((bytes, k) -> bytes.writeLong(1L))
    + *     .prepareDefaultValueBytes((bytes, k) -> bytes.writeLong(1L))
      *     ...
      *     .create();}
    * @@ -41,7 +40,7 @@ import java.io.Serializable; * * @param the key type of {@link ChronicleMap}, on which {@code acquireUsing()} is queried * @param the value type of {@link ChronicleMap}, on which {@code acquireUsing()} is queried - * @see ChronicleMapBuilder#prepareValueBytesOnAcquire(PrepareValueBytes) + */ public interface PrepareValueBytes extends Serializable { diff --git a/src/main/java/net/openhft/chronicle/map/Replica.java b/src/main/java/net/openhft/chronicle/map/Replica.java index 7ceb3b66b..1077c44f7 100644 --- a/src/main/java/net/openhft/chronicle/map/Replica.java +++ b/src/main/java/net/openhft/chronicle/map/Replica.java @@ -30,39 +30,43 @@ import java.io.Closeable; interface Replica extends Closeable { /** - * Provides the unique Identifier associated with this map instance.

    An identifier is used to - * determine which replicating node made the change.

    If two nodes update their map at the same time - * with different values, we have to deterministically resolve which update wins, because of eventual - * consistency both nodes should end up locally holding the same data. Although it is rare two remote - * nodes could receive an update to their maps at exactly the same time for the same key, we have to - * handle this edge case, its therefore important not to rely on timestamps alone to reconcile the - * updates. Typically the update with the newest timestamp should win, but in this example both - * timestamps are the same, and the decision made to one node should be identical to the decision made to - * the other. We resolve this simple dilemma by using a node identifier, each node will have a unique - * identifier, the update from the node with the smallest identifier wins. + * Provides the unique Identifier associated with this map instance.

    An identifier is used + * to determine which replicating node made the change.

    If two nodes update their map at the + * same time with different values, we have to deterministically resolve which update wins, + * because of eventual consistency both nodes should end up locally holding the same data. + * Although it is rare two remote nodes could receive an update to their maps at exactly the + * same time for the same key, we have to handle this edge case, its therefore important not to + * rely on timestamps alone to reconcile the updates. Typically the update with the newest + * timestamp should win, but in this example both timestamps are the same, and the decision + * made to one node should be identical to the decision made to the other. We resolve this + * simple dilemma by using a node identifier, each node will have a unique identifier, the + * update from the node with the smallest identifier wins. * * @return the unique Identifier associated with this map instance */ byte identifier(); /** - * Gets (if it does not exist, creates) an instance of ModificationIterator associated with a remote node, - * this weak associated is bound using the {@code identifier}. + * Gets (if it does not exist, creates) an instance of ModificationIterator associated with a + * remote node, this weak associated is bound using the {@code identifier}. * * @param remoteIdentifier the identifier of the remote node - * @param modificationNotifier called when ever there is a change applied to the modification iterator - * @return the ModificationIterator dedicated for replication to the remote node with the given identifier + * @param modificationNotifier called when ever there is a change applied to the modification + * iterator + * @return the ModificationIterator dedicated for replication to the remote node with the given + * identifier * @see #identifier() */ ModificationIterator acquireModificationIterator(byte remoteIdentifier, ModificationNotifier modificationNotifier); /** - * Returns the timestamp of the last change from the specified remote node, already replicated to this - * Replica.

    Used in conjunction with replication, to back fill data from a remote node. This node - * may have missed updates while it was not been running or connected via TCP. + * Returns the timestamp of the last change from the specified remote node, already replicated + * to this Replica.

    Used in conjunction with replication, to back fill data from a remote + * node. This node may have missed updates while it was not been running or connected via TCP. * - * @param remoteIdentifier the identifier of the remote node to check last replicated update time from + * @param remoteIdentifier the identifier of the remote node to check last replicated update + * time from * @return a timestamp of the last modification to an entry, or 0 if there are no entries. * @see #identifier() */ @@ -97,20 +101,21 @@ interface Replica extends Closeable { boolean hasNext(); /** - * A non-blocking call that provides the entry that has changed to {@code callback.onEntry()}. + * A non-blocking call that provides the entry that has changed to {@code + * callback.onEntry()}. * * @param callback a callback which will be called when a new entry becomes available. * @param chronicleId only assigned when using chronicle channels - * @return {@code true} if the entry was accepted by the {@code callback.onEntry()} method, {@code - * false} if the entry was not accepted or was not available + * @return {@code true} if the entry was accepted by the {@code callback.onEntry()} method, + * {@code false} if the entry was not accepted or was not available */ boolean nextEntry(@NotNull final EntryCallback callback, final int chronicleId); /** - * Dirties all entries with a modification time equal to {@code fromTimeStamp} or newer. It means all - * these entries will be considered as "new" by this ModificationIterator and iterated once again no - * matter if they have already been.

    This functionality is used to publish recently modified - * entries to a new remote node as it connects. + * Dirties all entries with a modification time equal to {@code fromTimeStamp} or newer. It + * means all these entries will be considered as "new" by this ModificationIterator and + * iterated once again no matter if they have already been.

    This functionality is used + * to publish recently modified entries to a new remote node as it connects. * * @param fromTimeStamp the timestamp from which all entries should be dirty */ @@ -122,34 +127,59 @@ interface Replica extends Closeable { */ interface EntryExternalizable { + /** + * The size of entry in bytes + * + * @param entry an entry in the map + * @param chronicleId is the channel id used to identify the canonical map or queue + * @return the size of the entry + */ + int sizeOfEntry(@NotNull Bytes entry, int chronicleId); + + + /** + * check that the identifier in the entry is from this node + * + * @param entry an entry in the map + * @param chronicleId is the channel id used to identify the canonical map or queue + * @return the size of the entry + */ + boolean identifierCheck(@NotNull Bytes entry, int chronicleId); + + /** * The map implements this method to save its contents. * * @param entry the byte location of the entry to be stored - * @param destination a buffer the entry will be written to, the segment may reject this operation and - * add zeroBytes, if the identifier in the entry did not match the maps local + * @param destination a buffer the entry will be written to, the segment may reject this + * operation and add zeroBytes, if the identifier in the entry did not + * match the maps local * @param chronicleId is the channel id used to identify the canonical map or queue */ - void writeExternalEntry(@NotNull Bytes entry, @NotNull Bytes destination, int chronicleId); + void writeExternalEntry(@NotNull Bytes entry, @NotNull Bytes destination, int + chronicleId); /** - * The map implements this method to restore its contents. This method must read the values in the - * same sequence and with the same types as were written by {@code writeExternalEntry()}. This method - * is typically called when we receive a remote replication event, this event could originate from - * either a remote {@code put(K key, V value)} or {@code remove(Object key)} + * The map implements this method to restore its contents. This method must read the values + * in the same sequence and with the same types as were written by {@code + * writeExternalEntry()}. This method is typically called when we receive a remote + * replication event, this event could originate from either a remote {@code put(K key, V + *value)} or {@code remove(Object key)} * * @param copies * @param segmentState - * @param source bytes to read an entry from + * @param source bytes to read an entry from */ void readExternalEntry( @NotNull ThreadLocalCopies copies, @NotNull VanillaChronicleMap.SegmentState segmentState, @NotNull Bytes source); + + } /** - * provides a key and value from NativeBytes, this can be used in conjunction with the modification - * iterator to get the key and value out of the NativeBytes + * provides a key and value from NativeBytes, this can be used in conjunction with the + * modification iterator to get the key and value out of the NativeBytes */ interface EntryResolver { @@ -175,20 +205,20 @@ interface Replica extends Closeable { } /** - * Implemented typically by a replicator, This interface provides the event, which will get called - * whenever a put() or remove() has occurred to the map + * Implemented typically by a replicator, This interface provides the event, which will get + * called whenever a put() or remove() has occurred to the map */ abstract class EntryCallback { /** * Called whenever a put() or remove() has occurred to a replicating map. * - * @param entry the entry you will receive, this does not have to be locked, as locking is - * already provided from the caller. + * @param entry the entry you will receive, this does not have to be locked, as + * locking is already provided from the caller. * @param chronicleId only assigned when clustering - * @return {@code false} if this entry should be ignored because the identifier of the source node is - * not from one of our changes, WARNING even though we check the identifier in the - * ModificationIterator the entry may have been updated. + * @return {@code false} if this entry should be ignored because the identifier of the + * source node is not from one of our changes, WARNING even though we check the identifier + * in the ModificationIterator the entry may have been updated. */ public abstract boolean onEntry(final Bytes entry, final int chronicleId); @@ -203,6 +233,21 @@ interface Replica extends Closeable { */ public void onBeforeEntry() { } + + + /** + * its possible that the entry should now be ignored, for example although rare its + * identifier may have recently been changed by another thread, so its no longer applicable + * to send + * + * @param entry the entry you will receive, this does not have to be locked, as + * locking is already provided from the caller. + * @param chronicleId only assigned when clustering + * @return {@code true} if this entry should be ignored + */ + public abstract boolean shouldBeIgnored(final Bytes entry, final int chronicleId); } + + } diff --git a/src/main/java/net/openhft/chronicle/map/ReplicatedChronicleMap.java b/src/main/java/net/openhft/chronicle/map/ReplicatedChronicleMap.java index 24f960bd5..43fb00602 100644 --- a/src/main/java/net/openhft/chronicle/map/ReplicatedChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/ReplicatedChronicleMap.java @@ -18,6 +18,7 @@ package net.openhft.chronicle.map; +import net.openhft.chronicle.hash.replication.AbstractReplication; import net.openhft.chronicle.hash.replication.TimeProvider; import net.openhft.chronicle.hash.serialization.BytesInterop; import net.openhft.chronicle.hash.serialization.BytesReader; @@ -43,7 +44,7 @@ import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicReferenceArray; -import static net.openhft.chronicle.hash.serialization.Hasher.hash; +import static net.openhft.chronicle.hash.hashing.Hasher.hash; import static net.openhft.lang.MemoryUnit.*; import static net.openhft.lang.collection.DirectBitSet.NOT_FOUND; @@ -105,14 +106,14 @@ final class ReplicatedChronicleMap builder, - byte identifier) + public ReplicatedChronicleMap(@NotNull ChronicleMapBuilder builder, + AbstractReplication replication) throws IOException { super(builder); this.timeProvider = builder.timeProvider(); - this.localIdentifier = identifier; - this.bootstrapOnlyLocalEntries = builder.bootstrapOnlyLocalEntries; + this.localIdentifier = replication.identifier(); + this.bootstrapOnlyLocalEntries = replication.bootstrapOnlyLocalEntries(); if (localIdentifier == -1) { throw new IllegalStateException("localIdentifier should not be -1"); @@ -274,7 +275,7 @@ final class ReplicatedChronicleMap>instance(), @@ -835,7 +887,7 @@ final class ReplicatedChronicleMap readValue, boolean resultUnused, byte identifier, long timeStamp) { - writeLock(null); + writeLock(); try { return putWithoutLock(copies, segmentState, metaKeyInterop, keyInterop, key, keySize, toKey, @@ -1015,7 +1067,7 @@ final class ReplicatedChronicleMap getValueInterops, VB expectedValue, InstanceOrBytesToInstance toValue, long hash2, ReadValue readValue, boolean resultUnused) { - writeLock(null); + writeLock(); try { return removeWithoutLock(copies, segmentState, metaKeyInterop, keyInterop, key, keySize, toKey, @@ -1073,67 +1125,97 @@ final class ReplicatedChronicleMap= 0L; ) { - long offset = offsetFromPos(pos); - if (entry == null) { - if (localSegmentState == null) { - copies = SegmentState.getCopies(copies); // copies is not null now - localSegmentState = SegmentState.get(copies); + returnNothing: + { + for (long pos; (pos = hashLookup.nextPos()) >= 0L; ) { + long offset = offsetFromPos(pos); + if (entry == null) { + if (localSegmentState == null) { + copies = SegmentState.getCopies(copies); // copies is not null now + localSegmentState = SegmentState.get(copies); + } + entry = localSegmentState.tmpBytes; } - entry = localSegmentState.tmpBytes; - } - reuse(entry, offset); - if (!keyEquals(keyInterop, metaKeyInterop, key, keySize, entry)) - continue; - // key is found - entry.skip(keySize); + reuse(entry, offset); + if (!keyEquals(keyInterop, metaKeyInterop, key, keySize, entry)) + continue; + // key is found + entry.skip(keySize); - long timestampPos = entry.position(); - if (shouldIgnore(entry, timestamp, identifier)) { - // the following assert should be enabled, but TimeBasedReplicationTest - // intentionally violates the explained invariant, => assertion fails. - // todo do something with this + long timestampPos = entry.position(); + if (shouldIgnore(entry, timestamp, identifier)) { + // the following assert should be enabled, but TimeBasedReplicationTest + // intentionally violates the explained invariant, => assertion fails. + // todo do something with this + + // we should ignore only remote updates + // which don't use remove, put results + // assert booleanResult || resultUnused; + return booleanResult ? Boolean.FALSE : null; + } + boolean isDeleted = entry.readBoolean(); + if (isDeleted) { + if (expectedValue != null) + return Boolean.FALSE; + entry.position(timestampPos); + entry.writeLong(timestamp); + entry.writeByte(identifier); + onRemoveMaybeRemote(pos, remote); + break returnNothing; + } + + long valueSizePos = entry.position(); + long valueSize = readValueSize(entry); + long valuePos = entry.position(); + + // check the value assigned for the key is that we expect + if (expectedValue != null) { + VBI valueInterop = getValueInterops.getValueInterop(copies); + MVBI metaValueInterop = getValueInterops.getMetaValueInterop( + copies, valueInterop, expectedValue); + if (metaValueInterop.size(valueInterop, expectedValue) != valueSize) + return Boolean.FALSE; + if (!metaValueInterop.startsWith(valueInterop, entry, expectedValue)) + return Boolean.FALSE; + } - // we should ignore only remote updates - // which don't use remove, put results - // assert booleanResult || resultUnused; - return booleanResult ? Boolean.FALSE : null; - } - boolean isDeleted = entry.readBoolean(); - if (isDeleted) { entry.position(timestampPos); entry.writeLong(timestamp); entry.writeByte(identifier); - onRemoveMaybeRemote(pos, remote); - break; // to "key not found" location + entry.writeBoolean(true); + entry.position(valuePos); + + return removeEntry(copies, key, keySize, toKey, toValue, readValue, + resultUnused, hashLookup, entry, pos, valueSizePos, + valueSize, remote, false, booleanResult); } + // key is not found + if (remote) { + long entrySize = entrySize(keySize, 0); + long pos = alloc(inBlocks(entrySize)); + long offset = offsetFromPos(pos); + clearMetaData(offset); + if (entry == null) { + if (localSegmentState == null) { + copies = SegmentState.getCopies(copies); // copies is not null now + localSegmentState = SegmentState.get(copies); + } + entry = localSegmentState.tmpBytes; + } + reuse(entry, offset); - long valueSizePos = entry.position(); - long valueSize = readValueSize(entry); - long valuePos = entry.position(); + keySizeMarshaller.writeSize(entry, keySize); + metaKeyInterop.write(keyInterop, entry, key); - // check the value assigned for the key is that we expect - if (expectedValue != null) { - VBI valueInterop = getValueInterops.getValueInterop(copies); - MVBI metaValueInterop = getValueInterops.getMetaValueInterop( - copies, valueInterop, expectedValue); - if (metaValueInterop.size(valueInterop, expectedValue) != valueSize) - return Boolean.FALSE; - if (!metaValueInterop.startsWith(valueInterop, entry, expectedValue)) - return Boolean.FALSE; + entry.writeLong(timestamp); + entry.writeByte(identifier); + entry.writeBoolean(true); + + hashLookup.putAfterFailedSearch(pos); + hashLookup.removePosition(pos); } - - entry.position(timestampPos); - entry.writeLong(timestamp); - entry.writeByte(identifier); - entry.writeBoolean(true); - entry.position(valuePos); - - return removeEntry(copies, key, keySize, toKey, toValue, readValue, - resultUnused, hashLookup, entry, pos, valueSizePos, - valueSize, remote, false, booleanResult); } - // key is not found + // return nothing if (booleanResult) { return Boolean.FALSE; } else { @@ -1158,7 +1240,7 @@ final class ReplicatedChronicleMap>> segmentIndexShift)); segment.readLock(null); try { - if (changes.clearIfSet(position)) { + if (changes.get(position)) { + entryCallback.onBeforeEntry(); final long segmentPos = position & posMask; @@ -1463,13 +1546,24 @@ final class ReplicatedChronicleMap implements FindByName { public static final int MAP_BY_NAME_CHANNEL = 1; private final AtomicInteger nextFreeChannel = new AtomicInteger(2); - private final Map map; + private final Map map; private final ReplicationHub replicationHub; - public static class ChronicleMapBuilderWithChannelId implements Serializable { - ChronicleHashBuilder, ?> chronicleMapBuilder; - int channelId; - } - /** * @throws IOException */ @@ -61,35 +52,36 @@ class ReplicationHubFindByName implements FindByName { this.replicationHub = replicationHub; ReplicationChannel channel = replicationHub.createChannel((short) MAP_BY_NAME_CHANNEL); - final MapEventListener listener = new - MapEventListener() { + final MapEventListener listener = + new MapEventListener() { // creates a map based on the details that are sent to the map of builders @Override - public void onPut(CharSequence key, ChronicleMapBuilderWithChannelId value, - ChronicleMapBuilderWithChannelId replacedValue) { + public void onPut(CharSequence key, MapInstanceConfig value, + MapInstanceConfig replacedValue) { super.onPut(key, value, replacedValue); boolean added = replacedValue == null; if (!added || value == null) return; // establish the new map based on this channel ID - LOG.info("create new map for name=" + value.chronicleMapBuilder.name() - + ",channelId=" + value.channelId); + LOG.info("create new map for name=" + value.name + + ",channelId=" + value.channel.channelId()); try { - toReplicatedViaChannel(value.chronicleMapBuilder, value.channelId).create(); + toReplicatedViaChannel(value.mapBuilder, value.channel.channelId()).create(); } catch (IllegalStateException e) { // channel is already created - LOG.debug("while creating channel for name=" + value.chronicleMapBuilder.name() - + ",channelId=" + value.channelId, e); + LOG.debug("while creating channel for name=" + value.name + + ",channelId=" + value.channel.channelId(), e); } catch (IOException e) { LOG.error("", e); } } }; - this.map = (Map) of(CharSequence.class, ChronicleMapBuilderWithChannelId.class) - .entrySize(3000) + this.map = (Map) ChronicleMapBuilder + .of(CharSequence.class, MapInstanceConfig.class) + .entrySize(4000) .entries(128) .eventListener(listener) .instance() @@ -99,34 +91,32 @@ class ReplicationHubFindByName implements FindByName { LOG.debug("map=" + map); } - public T create(ChronicleMapBuilder builder) throws IllegalArgumentException, - IOException, TimeoutException, InterruptedException { + public T create( + MapInstanceConfig config) + throws IOException, TimeoutException, InterruptedException { int withChannelId = nextFreeChannel.incrementAndGet(); - ChronicleMapBuilderWithChannelId builderWithChannelId = new - ChronicleMapBuilderWithChannelId(); - builderWithChannelId.channelId = withChannelId; - builderWithChannelId.chronicleMapBuilder = (ChronicleHashBuilder)builder; - map.put(builder.name(), builderWithChannelId); + map.put(config.name, + config.replicatedViaChannel(replicationHub.createChannel(withChannelId))); - return (T) get(builder.name()).chronicleMapBuilder.create(); + return (T) get(config.name).mapBuilder.create(); } public T from(String name) throws IllegalArgumentException, IOException, TimeoutException, InterruptedException { - return (T) replicatedViaChannel(name).create(); + return (T) get(name).create(); } - ChronicleMapBuilderWithChannelId get(String name) throws IllegalArgumentException, + MapInstanceConfig get(String name) throws IllegalArgumentException, TimeoutException, InterruptedException { - ChronicleMapBuilderWithChannelId chronicleMapBuilder = waitTillEntryReceived(5000, name); - if (chronicleMapBuilder == null) + MapInstanceConfig config = waitTillEntryReceived(5000, name); + if (config == null) throw new IllegalArgumentException("A map name=" + name + " can not be found."); - return chronicleMapBuilder; + return config; } /** @@ -135,23 +125,19 @@ class ReplicationHubFindByName implements FindByName { * @param timeOutMs timeout in milliseconds * @throws InterruptedException */ - private ChronicleMapBuilderWithChannelId waitTillEntryReceived(final int timeOutMs, String name) throws TimeoutException, InterruptedException { + private MapInstanceConfig waitTillEntryReceived(final int timeOutMs, String name) + throws TimeoutException, InterruptedException { int t = 0; for (; t < timeOutMs; t++) { - ChronicleMapBuilderWithChannelId builder = map.get(name); + MapInstanceConfig config = map.get(name); - if (builder != null) - return builder; + if (config != null) + return config; Thread.sleep(1); } throw new TimeoutException("timed out wait for map name=" + name); } - private ChronicleHashInstanceConfig replicatedViaChannel(String name) throws TimeoutException, InterruptedException { - ChronicleMapBuilderWithChannelId builder = get(name); - return toReplicatedViaChannel(builder.chronicleMapBuilder, builder.channelId); - } - /** * similar to {@link ChronicleMapBuilder#createPersistedTo(File file) } * @@ -163,9 +149,10 @@ class ReplicationHubFindByName implements FindByName { * @throws IOException * @see ChronicleMapBuilder#createPersistedTo(File file) */ - public T createPersistedTo(String name, File file) throws IllegalArgumentException, + public T createPersistedTo(String name, File file) + throws IllegalArgumentException, IOException, TimeoutException, InterruptedException { - return (T) replicatedViaChannel(name).persistedTo(file).create(); + return (T) get(name).persistedTo(file).create(); } private ChronicleHashInstanceConfig toReplicatedViaChannel(ChronicleMapBuilder builder) { diff --git a/src/main/java/net/openhft/chronicle/map/Replicator.java b/src/main/java/net/openhft/chronicle/map/Replicator.java index 7580d519d..a6307c3f2 100644 --- a/src/main/java/net/openhft/chronicle/map/Replicator.java +++ b/src/main/java/net/openhft/chronicle/map/Replicator.java @@ -47,7 +47,7 @@ abstract class Replicator { * already been applied to a map (or the specified number of * maps) */ - protected abstract Closeable applyTo(AbstractChronicleMapBuilder builder, + protected abstract Closeable applyTo(ChronicleMapBuilder builder, Replica map, Replica.EntryExternalizable entryExternalizable, final ChronicleMap chronicleMap) throws IOException; } diff --git a/src/main/java/net/openhft/chronicle/map/Replicators.java b/src/main/java/net/openhft/chronicle/map/Replicators.java index df38cbc8e..f1c9aa232 100644 --- a/src/main/java/net/openhft/chronicle/map/Replicators.java +++ b/src/main/java/net/openhft/chronicle/map/Replicators.java @@ -40,7 +40,7 @@ final class Replicators { static Replicator tcp(final AbstractReplication replication) { return new Replicator() { @Override - protected Closeable applyTo(@NotNull final AbstractChronicleMapBuilder builder, + protected Closeable applyTo(@NotNull final ChronicleMapBuilder builder, @NotNull final Replica replica, @NotNull final Replica.EntryExternalizable entryExternalizable, final ChronicleMap chronicleMap) @@ -54,7 +54,7 @@ final class Replicators { assert tcpConfig != null; return new TcpReplicator(replica, entryExternalizable, tcpConfig, - builder.entrySize(true), statelessServer, + statelessServer, replication.remoteNodeValidator()); } }; @@ -64,7 +64,7 @@ final class Replicators { final UdpTransportConfig replicationConfig) { return new Replicator() { @Override - protected Closeable applyTo(@NotNull final AbstractChronicleMapBuilder builder, + protected Closeable applyTo(@NotNull final ChronicleMapBuilder builder, @NotNull final Replica map, @NotNull final Replica.EntryExternalizable entryExternalizable, final ChronicleMap chronicleMap) diff --git a/src/main/java/net/openhft/chronicle/map/SerializationBuilder.java b/src/main/java/net/openhft/chronicle/map/SerializationBuilder.java index 7c87d2ee9..cb32fd227 100644 --- a/src/main/java/net/openhft/chronicle/map/SerializationBuilder.java +++ b/src/main/java/net/openhft/chronicle/map/SerializationBuilder.java @@ -20,6 +20,9 @@ package net.openhft.chronicle.map; import net.openhft.chronicle.hash.serialization.*; import net.openhft.chronicle.hash.serialization.internal.*; +import net.openhft.chronicle.hash.serialization.internal.ByteBufferMarshaller; +import net.openhft.lang.MemoryUnit; +import net.openhft.lang.io.ByteBufferBytes; import net.openhft.lang.io.Bytes; import net.openhft.lang.io.serialization.BytesMarshallable; import net.openhft.lang.io.serialization.BytesMarshaller; @@ -32,7 +35,6 @@ import net.openhft.lang.model.DataValueModel; import net.openhft.lang.model.DataValueModels; import net.openhft.lang.threadlocal.Provider; import net.openhft.lang.threadlocal.ThreadLocalCopies; -import net.openhft.lang.values.LongValue; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -43,16 +45,18 @@ import java.lang.reflect.Modifier; import java.math.BigDecimal; import java.math.BigInteger; import java.net.URL; +import java.nio.ByteBuffer; import java.util.Arrays; import java.util.List; import static net.openhft.chronicle.hash.serialization.SizeMarshallers.constant; import static net.openhft.chronicle.hash.serialization.SizeMarshallers.stopBit; import static net.openhft.chronicle.map.Objects.hash; -import static net.openhft.chronicle.map.SerializationBuilder.Role.KEY; final class SerializationBuilder implements Cloneable, Serializable { + private static final Bytes EMPTY_BYTES = new ByteBufferBytes(ByteBuffer.allocate(0)); + private static boolean concreteClass(Class c) { return !c.isInterface() && !Modifier.isAbstract(c.getModifiers()); } @@ -79,7 +83,6 @@ final class SerializationBuilder implements Cloneable, Serializable { private final Role role; final Class eClass; private boolean instancesAreMutable; - private boolean dataValueClass = false; private SizeMarshaller sizeMarshaller = stopBit(); private BytesReader reader; private Object interop; @@ -88,10 +91,17 @@ final class SerializationBuilder implements Cloneable, Serializable { private MetaProvider metaInteropProvider; private long maxSize; + final boolean sizeIsStaticallyKnown; + @SuppressWarnings("unchecked") SerializationBuilder(Class eClass, Role role) { this.role = role; this.eClass = eClass; + configureByDefault(eClass, role); + sizeIsStaticallyKnown = constantSizeMarshaller(); + } + + private void configureByDefault(Class eClass, Role role) { instancesAreMutable = instancesAreMutable(eClass); ObjectFactory factory = concreteClass(eClass) && marshallerUseFactory(eClass) ? @@ -104,7 +114,6 @@ final class SerializationBuilder implements Cloneable, Serializable { BytesWriter writer = DataValueBytesMarshallers.acquireBytesWriter(eClass); DataValueModel model = DataValueModels.acquireModel(eClass); int size = DataValueGenerator.computeNonScalarOffset(model, eClass); - dataValueClass = true; reader(reader); writer(writer); sizeMarshaller(constant((long) size)); @@ -122,6 +131,9 @@ final class SerializationBuilder implements Cloneable, Serializable { } else if (eClass == CharSequence.class || eClass == String.class) { reader((BytesReader) CharSequenceReader.of()); writer((BytesWriter) CharSequenceWriter.instance()); + } else if (eClass == StringBuilder.class) { + reader((BytesReader) CharSequenceReader.ofStringBuilder()); + writer((BytesWriter) CharSequenceWriter.instance()); } else if (eClass == Void.class) { sizeMarshaller(VoidMarshaller.INSTANCE); reader((BytesReader) VoidMarshaller.INSTANCE); @@ -144,12 +156,9 @@ final class SerializationBuilder implements Cloneable, Serializable { } else if (eClass == char[].class) { reader((BytesReader) CharArrayMarshaller.INSTANCE); interop((BytesInterop) CharArrayMarshaller.INSTANCE); - } else if (eClass == LongValue.class) { - LongValueKeyMarshaller marshaller = role == KEY ? - LongValueKeyMarshaller.INSTANCE : LongValueValueMarshaller.INSTANCE; - sizeMarshaller(marshaller); - reader((BytesReader) marshaller); - interop((BytesInterop) marshaller); + } else if (eClass == ByteBuffer.class) { + reader((BytesReader) ByteBufferMarshaller.INSTANCE); + writer((BytesWriter) ByteBufferMarshaller.INSTANCE); } else if (concreteClass(eClass)) { BytesMarshaller marshaller = chooseMarshaller(eClass, eClass); if (marshaller != null) @@ -160,8 +169,23 @@ final class SerializationBuilder implements Cloneable, Serializable { } } - private void clearDefaults() { - dataValueClass = false; + boolean possibleOffHeapReferences() { + if (reader instanceof CharSequenceReader) + return false; + if (reader instanceof VoidMarshaller) + return false; + // exclude some known classes, most notably boxed primitive types + if (!instancesAreMutable(eClass)) + return false; + if (eClass.isArray() && (eClass.getComponentType().isPrimitive() || + instancesAreMutable(eClass.getComponentType()))) + return false; + BytesMarshaller marshallerAsReader = BytesReaders.getBytesMarshaller(reader); + if (marshallerAsReader instanceof SerializableMarshaller || + marshallerAsReader instanceof ExternalizableMarshaller) + return false; + // otherwise reader could possibly keep the given bytes addresses and update off-heap memory + return true; } @SuppressWarnings("unchecked") @@ -179,7 +203,6 @@ final class SerializationBuilder implements Cloneable, Serializable { } public SerializationBuilder interop(BytesInterop interop) { - clearDefaults(); return copyingInterop(null) .setInterop(interop) .metaInterop(DelegatingMetaBytesInterop.>instance()) @@ -188,7 +211,6 @@ final class SerializationBuilder implements Cloneable, Serializable { } public SerializationBuilder writer(BytesWriter writer) { - clearDefaults(); if (writer instanceof BytesInterop) return interop((BytesInterop) writer); return copyingInterop(CopyingInterop.FROM_WRITER) @@ -198,7 +220,6 @@ final class SerializationBuilder implements Cloneable, Serializable { } public SerializationBuilder marshaller(BytesMarshaller marshaller) { - clearDefaults(); return copyingInterop(CopyingInterop.FROM_MARSHALLER) .reader(BytesReaders.fromBytesMarshaller(marshaller)) .setInterop(marshaller) @@ -241,10 +262,7 @@ final class SerializationBuilder implements Cloneable, Serializable { copies, this.metaInterop, interop, sampleObject); break findSufficientSerializationSize; } catch (Exception e) { - // assuming nobody need > 512 mb _constant size_ keys/values - if (maxSize > 512 * 1024 * 1024) { - throw e; - } + CopyingMetaBytesInterop.checkMaxSizeStillReasonable(maxSize, e); maxSize(maxSize * 2); } } @@ -253,6 +271,16 @@ final class SerializationBuilder implements Cloneable, Serializable { metaInterop = metaInteropProvider.get(copies, this.metaInterop, interop, sampleObject); } long constantSize = metaInterop.size(interop, sampleObject); + if (sizeIsStaticallyKnown) { + int expectedConstantSize = pseudoReadConstantSize(); + if (constantSize != expectedConstantSize) { + throw new IllegalStateException("Although configuring constant size by sample " + + "is not forbidden for types which size we already know statically, they " + + "should be the same. For " + eClass + " we know constant size is " + + expectedConstantSize + " statically, configured sample is " + sampleObject + + " which size in serialized form is " + constantSize); + } + } // set max size once more, because current maxSize could be x64 or x2 larger than needed maxSize(constantSize); sizeMarshaller(SizeMarshallers.constant(constantSize)); @@ -275,6 +303,14 @@ final class SerializationBuilder implements Cloneable, Serializable { return sizeMarshaller; } + boolean constantSizeMarshaller() { + return sizeMarshaller().sizeEncodingSize(0L) == 0; + } + + int pseudoReadConstantSize() { + return (int) sizeMarshaller().readSize(EMPTY_BYTES); + } + public SerializationBuilder sizeMarshaller(SizeMarshaller sizeMarshaller) { this.sizeMarshaller = sizeMarshaller; return this; @@ -285,7 +321,6 @@ final class SerializationBuilder implements Cloneable, Serializable { } public SerializationBuilder reader(BytesReader reader) { - clearDefaults(); this.reader = reader; return this; } @@ -321,27 +356,20 @@ final class SerializationBuilder implements Cloneable, Serializable { @SuppressWarnings("unchecked") public SerializationBuilder factory(ObjectFactory factory) { - if (dataValueClass) { - reader(DataValueBytesMarshallers.acquireBytesReader(eClass, factory)); + if (reader instanceof DeserializationFactoryConfigurableBytesReader) { + DeserializationFactoryConfigurableBytesReader newReader = + ((DeserializationFactoryConfigurableBytesReader) reader) + .withDeserializationFactory(factory); + reader(newReader); + if (newReader instanceof BytesInterop) + interop((BytesInterop) newReader); + if (newReader instanceof SizeMarshaller) + sizeMarshaller((SizeMarshaller) newReader); return this; } if (!marshallerUseFactory(eClass)) { throw new IllegalStateException("Default marshaller for " + eClass + " value don't use object factory"); - } else if (interop instanceof ByteableMarshaller) { - if (factory instanceof AllocateInstanceObjectFactory) { - ByteableMarshaller byteableMarshaller = - ByteableMarshaller.of((Class) eClass); - sizeMarshaller(byteableMarshaller) - .reader(byteableMarshaller) - .interop(byteableMarshaller); - } else { - ByteableMarshaller byteableMarshaller = - ByteableMarshaller.of((Class) eClass, (ObjectFactory) factory); - sizeMarshaller(byteableMarshaller) - .reader(byteableMarshaller) - .interop(byteableMarshaller); - } } else if (interop instanceof BytesMarshallableMarshaller) { if (factory instanceof AllocateInstanceObjectFactory) { interop = new BytesMarshallableMarshaller( diff --git a/src/main/java/net/openhft/chronicle/map/SharedSegment.java b/src/main/java/net/openhft/chronicle/map/SharedSegment.java index 496332344..adc5ab3e6 100644 --- a/src/main/java/net/openhft/chronicle/map/SharedSegment.java +++ b/src/main/java/net/openhft/chronicle/map/SharedSegment.java @@ -34,8 +34,6 @@ interface SharedSegment { */ AutoCloseable readLock(@Nullable VanillaChronicleMap.SegmentState segmentState); - AutoCloseable writeLock(@Nullable VanillaChronicleMap.SegmentState segmentState); - Map.Entry getEntry(@NotNull VanillaChronicleMap.SegmentState segmentState, long pos); void readUnlock(); diff --git a/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java b/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java index 23bd41e4a..c3c012792 100644 --- a/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java @@ -24,7 +24,6 @@ import net.openhft.chronicle.hash.serialization.BytesReader; import net.openhft.lang.io.ByteBufferBytes; import net.openhft.lang.io.Bytes; import net.openhft.lang.io.NativeBytes; -import net.openhft.lang.thread.NamedThreadFactory; import net.openhft.lang.threadlocal.ThreadLocalCopies; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -40,9 +39,6 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.SocketChannel; import java.util.*; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.ReentrantLock; @@ -63,14 +59,13 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon private final byte[] connectionByte = new byte[1]; private final ByteBuffer connectionOutBuffer = ByteBuffer.wrap(connectionByte); private final String name; - @NotNull - private final AbstractChronicleMapBuilder chronicleMapBuilder; - private volatile ByteBuffer outBuffer; - private volatile ByteBufferBytes outBytes; - private volatile ByteBuffer inBuffer; - private volatile ByteBufferBytes inBytes; + private ByteBuffer outBuffer; + private ByteBufferBytes outBytes; + + private ByteBuffer inBuffer; + private ByteBufferBytes inBytes; @NotNull private final ReaderWithSize keyReaderWithSize; @@ -80,7 +75,7 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon private final ReaderWithSize valueReaderWithSize; @NotNull private final WriterWithSize valueWriterWithSize; - private volatile SocketChannel clientChannel; + private SocketChannel clientChannel; @Nullable private CloseablesManager closeables; @@ -93,10 +88,9 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon private final boolean removeReturnsNull; - private final ReentrantLock inBytesLock = new ReentrantLock(); - private final ReentrantLock outBytesLock = new ReentrantLock(true); + private final ReentrantLock inBytesLock = new ReentrantLock(true); + private final ReentrantLock outBytesLock = new ReentrantLock(); - private ExecutorService executorService; static enum EventId { HEARTBEAT, @@ -140,9 +134,11 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon private final AtomicLong transactionID = new AtomicLong(0); StatelessChronicleMap(@NotNull final StatelessMapConfig config, - @NotNull final AbstractChronicleMapBuilder chronicleMapBuilder) throws IOException { - this.chronicleMapBuilder = chronicleMapBuilder; + @NotNull final ChronicleMapBuilder chronicleMapBuilder) + throws IOException { + this.config = config; + keyReaderWithSize = new ReaderWithSize<>(chronicleMapBuilder.keyBuilder); keyWriterWithSize = new WriterWithSize<>(chronicleMapBuilder.keyBuilder); valueReaderWithSize = new ReaderWithSize<>(chronicleMapBuilder.valueBuilder); @@ -156,7 +152,7 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon this.vClass = chronicleMapBuilder.valueBuilder.eClass; this.kClass = chronicleMapBuilder.keyBuilder.eClass; - this.name = chronicleMapBuilder.name(); + this.name = config.name(); attemptConnect(config.remoteAddress()); outBuffer = allocateDirect(maxEntrySize).order(ByteOrder.nativeOrder()); @@ -164,7 +160,7 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon inBuffer = allocateDirect(maxEntrySize).order(ByteOrder.nativeOrder()); - inBytes = new ByteBufferBytes(outBuffer.slice()); + inBytes = new ByteBufferBytes(inBuffer.slice()); } @@ -180,6 +176,9 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon @Override public V newValueInstance() { + if (vClass.equals(CharSequence.class) || vClass.equals(StringBuilder.class)) { + return (V) new StringBuilder(); + } return VanillaChronicleMap.newInstance(vClass, false); } @@ -188,24 +187,17 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon return VanillaChronicleMap.newInstance(kClass, true); } - private ExecutorService lazyExecutorService() { - if (executorService == null) { - synchronized (this) { - if (executorService == null) - executorService = Executors.newSingleThreadExecutor(new NamedThreadFactory(chronicleMapBuilder.name() + - "-stateless-client-async-" + name, - true)); - } - } - return executorService; + private void checkTimeout(long timeoutTime) { + if (timeoutTime < System.currentTimeMillis()) + throw new RemoteCallTimeoutException(); } private synchronized SocketChannel lazyConnect(final long timeoutMs, final InetSocketAddress remoteAddress) { if (LOG.isDebugEnabled()) - LOG.debug("attempting to connect to " + remoteAddress); + LOG.debug("attempting to connect to " + remoteAddress + " ,name=" + name); SocketChannel result; @@ -236,14 +228,16 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon } /** - * attempts a single connect without a timeout and eat any IOException used typically in the constructor, its - * possible the server is not running with this instance is created, {@link net.openhft.chronicle.map.StatelessChronicleMap#lazyConnect(long, - * java.net.InetSocketAddress)} will attempt to establish the connection when the client make the first map method - * call. + * attempts a single connect without a timeout and eat any IOException used typically in the + * constructor, its possible the server is not running with this instance is created, {@link + * net.openhft.chronicle.map.StatelessChronicleMap#lazyConnect(long, + * java.net.InetSocketAddress)} will attempt to establish the connection when the client make + * the first map method call. * * @param remoteAddress the Inet Socket Address * @throws java.io.IOException - * @see net.openhft.chronicle.map.StatelessChronicleMap#lazyConnect(long, java.net.InetSocketAddress) + * @see net.openhft.chronicle.map.StatelessChronicleMap#lazyConnect(long, + * java.net.InetSocketAddress) */ private synchronized void attemptConnect(final InetSocketAddress remoteAddress) { @@ -257,6 +251,7 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon doHandShaking(clientChannel); } catch (IOException e) { if (closeables != null) closeables.closeQuietly(); + clientChannel = null; } } @@ -272,8 +267,9 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon } /** - * initiates a very simple level of handshaking with the remote server, we send a special ID of -127 ( when the - * server receives this it knows its dealing with a stateless client, receive back an identifier from the server + * initiates a very simple level of handshaking with the remote server, we send a special ID of + * -127 ( when the server receives this it knows its dealing with a stateless client, receive + * back an identifier from the server * * @param clientChannel clientChannel * @throws java.io.IOException @@ -305,7 +301,8 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon byte remoteIdentifier = connectionByte[0]; if (LOG.isDebugEnabled()) - LOG.debug("Attached to a map with a remote identifier=" + remoteIdentifier); + LOG.debug("Attached to a map with a remote identifier=" + remoteIdentifier + " ,name=" + name); + } @NotNull @@ -313,22 +310,12 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon throw new UnsupportedOperationException(); } - public void close() { + public synchronized void close() { if (closeables != null) closeables.closeQuietly(); closeables = null; - - if (executorService != null) { - executorService.shutdown(); - try { - if (!executorService.awaitTermination(20, TimeUnit.SECONDS)) { - executorService.shutdownNow(); - } - } catch (InterruptedException e) { - LOG.error("", e); - } - } + clientChannel = null; } @@ -391,9 +378,10 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon } /** - * calling this method should be avoided at all cost, as the entire {@code object} is serialized. This equals can be - * used to compare map that extends ChronicleMap. So two Chronicle Maps that contain the same data are considered - * equal, even if the instances of the chronicle maps were of different types + * calling this method should be avoided at all cost, as the entire {@code object} is + * serialized. This equals can be used to compare map that extends ChronicleMap. So two + * Chronicle Maps that contain the same data are considered equal, even if the instances of the + * chronicle maps were of different types * * @param object the object that you are comparing against * @return true if the contain the same data @@ -506,6 +494,8 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon private void resizeIfRequired(int numberOfEntries, int numberOfEntriesReadSoFar, final long start) { + assert outBytesLock.isHeldByCurrentThread(); + assert !inBytesLock.isHeldByCurrentThread(); final long remaining = outBytes.remaining(); if (remaining < maxEntrySize) { @@ -518,14 +508,21 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon * estimates the size based on what been completed so far */ private long estimateSize(int numberOfEntries, int numberOfEntriesReadSoFar) { + assert outBytesLock.isHeldByCurrentThread(); + assert !inBytesLock.isHeldByCurrentThread(); // we will back the size estimate on what we have so far final double percentageComplete = (double) numberOfEntriesReadSoFar / (double) numberOfEntries; return (long) ((double) outBytes.position() / percentageComplete); } private void resizeBufferOutBuffer(int size, long start) { + + + assert outBytesLock.isHeldByCurrentThread(); + assert !inBytesLock.isHeldByCurrentThread(); + if (LOG.isDebugEnabled()) - LOG.debug("resizing buffer to size=" + size); + LOG.debug("resizing buffer to size=" + size + " ,name=" + name); if (size < outBuffer.capacity()) throw new IllegalStateException("it not possible to resize the buffer smaller"); @@ -562,8 +559,11 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon } private void resizeBufferInBuffer(int size, long start) { + assert !outBytesLock.isHeldByCurrentThread(); + assert inBytesLock.isHeldByCurrentThread(); + if (LOG.isDebugEnabled()) - LOG.debug("InBuffer resizing buffer to size=" + size); + LOG.debug("InBuffer resizing buffer to size=" + size + " ,name=" + name); if (size < inBuffer.capacity()) throw new IllegalStateException("it not possible to resize the buffer smaller"); @@ -600,18 +600,26 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon } public void clear() { - fetchVoid(CLEAR); } @NotNull public Collection values() { - final long sizeLocation = writeEventAnSkip(VALUES); - final long startTime = System.currentTimeMillis(); + final long timeoutTime; + final long transactionId; - final long timeoutTime = System.currentTimeMillis() + config.timeoutMs(); - final long transactionId = send(sizeLocation, startTime); + outBytesLock.lock(); + try { + + final long sizeLocation = writeEventAnSkip(VALUES); + final long startTime = System.currentTimeMillis(); + + timeoutTime = System.currentTimeMillis() + config.timeoutMs(); + transactionId = send(sizeLocation, startTime); + } finally { + outBytesLock.unlock(); + } // get the data back from the server final Collection result = new ArrayList(); @@ -644,11 +652,18 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon @NotNull public Set> entrySet() { + final long transactionId; + final long timeoutTime; - final long sizeLocation = writeEventAnSkip(ENTRY_SET); - final long startTime = System.currentTimeMillis(); - final long timeoutTime = System.currentTimeMillis() + config.timeoutMs(); - final long transactionId = send(sizeLocation, startTime); + outBytesLock.lock(); + try { + final long sizeLocation = writeEventAnSkip(ENTRY_SET); + final long startTime = System.currentTimeMillis(); + timeoutTime = System.currentTimeMillis() + config.timeoutMs(); + transactionId = send(sizeLocation, startTime); + } finally { + outBytesLock.unlock(); + } // get the data back from the server ThreadLocalCopies copies = keyReaderWithSize.getCopies(null); @@ -688,10 +703,15 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon public void putAll(@NotNull Map map) { + final long sizeLocation; - final long sizeLocation = putReturnsNull ? writeEventAnSkip(PUT_ALL_WITHOUT_ACC) : - writeEventAnSkip(PUT_ALL); - + outBytesLock.lock(); + try { + sizeLocation = putReturnsNull ? writeEventAnSkip(PUT_ALL_WITHOUT_ACC) : + writeEventAnSkip(PUT_ALL); + } finally { + outBytesLock.unlock(); + } final long startTime = System.currentTimeMillis(); final long timeoutTime = startTime + config.timeoutMs(); final long transactionId; @@ -751,17 +771,31 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon } - if (!putReturnsNull) - blockingFetchReadOnly(timeoutTime, transactionId); + if (!putReturnsNull) { + inBytesLock.lock(); + try { + blockingFetchReadOnly(timeoutTime, transactionId); + } finally { + inBytesLock.unlock(); + } + } } @NotNull public Set keySet() { + final long transactionId; + final long timeoutTime; + + outBytesLock.lock(); + try { + final long sizeLocation = writeEventAnSkip(KEY_SET); + final long startTime = System.currentTimeMillis(); + timeoutTime = startTime + config.timeoutMs(); + transactionId = send(sizeLocation, startTime); + } finally { + outBytesLock.unlock(); + } - final long sizeLocation = writeEventAnSkip(KEY_SET); - final long startTime = System.currentTimeMillis(); - final long timeoutTime = startTime + config.timeoutMs(); - final long transactionId = send(sizeLocation, startTime); final Set result = new HashSet<>(); final BytesReader keyReader = keyReaderWithSize.readerForLoop(null); @@ -793,6 +827,9 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon private long readLong(long transactionId, long startTime) { + + assert !outBytesLock.isHeldByCurrentThread(); + final long timeoutTime = startTime + this.config.timeoutMs(); // receive @@ -806,10 +843,15 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon private long writeEvent(@NotNull StatelessChronicleMap.EventId event) { - outBuffer.clear(); - outBytes.clear(); + assert outBytesLock.isHeldByCurrentThread(); + assert !inBytesLock.isHeldByCurrentThread(); + assert event != HEARTBEAT; + + if (outBytes.remaining() < 128) + resizeBufferOutBuffer((int) outBytes.capacity() + 128, outBytes.position()); + + outBytes.writeByte((byte) event.ordinal()); - outBytes.write((byte) event.ordinal()); return markSizeLocation(); } @@ -818,12 +860,20 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon * skips for the transactionid */ private long writeEventAnSkip(@NotNull EventId event) { + + assert outBytesLock.isHeldByCurrentThread(); + assert !inBytesLock.isHeldByCurrentThread(); + final long sizeLocation = writeEvent(event); + assert outBytes.readByte(sizeLocation - 1) == event.ordinal(); + + assert outBytes.position() > 0; // skips for the transaction id outBytes.skip(SIZE_OF_TRANSACTION_ID); outBytes.writeByte(identifier); outBytes.writeInt(0); + assert outBytes.readByte(sizeLocation - 1) == event.ordinal(); return sizeLocation; } @@ -889,6 +939,10 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon * @return a unique transaction id */ private long send(long sizeLocation, final long startTime) { + + assert outBytesLock.isHeldByCurrentThread(); + assert !inBytesLock.isHeldByCurrentThread(); + long transactionId = nextUniqueTransaction(startTime); final long timeoutTime = startTime + this.config.timeoutMs(); try { @@ -900,14 +954,13 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon try { if (LOG.isDebugEnabled()) - LOG.debug("sending data with transactionId=" + transactionId); + LOG.debug("sending data with transactionId=" + transactionId + " ,name=" + name); - writeSizeAndTransactionIdAt(sizeLocation, transactionId, startTime); + writeSizeAndTransactionIdAt(sizeLocation, transactionId); // send out all the bytes - writeBytesToSocket(outBytes, timeoutTime); - outBytes.clear(); - outBytes.buffer().clear(); + writeBytesToSocket(timeoutTime); + break; } catch (@NotNull java.nio.channels.ClosedChannelException | ClosedConnectionException e) { @@ -926,6 +979,10 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon } private Bytes blockingFetchReadOnly(long timeoutTime, final long transactionId) { + + assert !outBytesLock.isHeldByCurrentThread(); + assert inBytesLock.isHeldByCurrentThread(); + try { return blockingFetchThrowable(timeoutTime, transactionId); } catch (IOException e) { @@ -938,7 +995,7 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon close(); throw new RuntimeException(e); } catch (AssertionError e) { - LOG.error("", e); + LOG.error("name=" + name, e); throw e; } } @@ -951,6 +1008,9 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon private Bytes blockingFetchThrowable(long timeoutTime, long transactionId) throws IOException, InterruptedException { + assert !outBytesLock.isHeldByCurrentThread(); + assert inBytesLock.isHeldByCurrentThread(); + int remainingBytes = nextEntry(timeoutTime, transactionId); if (inBytes.capacity() < remainingBytes) { @@ -962,7 +1022,7 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon inBytes.limit(inBytes.capacity()); // block until we have received all the bytes in this chunk - receive(remainingBytes, timeoutTime); + receiveBytesFromSocket(remainingBytes, timeoutTime); final boolean isException = inBytes.readBoolean(); @@ -993,6 +1053,9 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon } private int nextEntry(long timeoutTime, long transactionId) throws IOException { + assert !outBytesLock.isHeldByCurrentThread(); + assert inBytesLock.isHeldByCurrentThread(); + int remainingBytes; for (; ; ) { @@ -1002,7 +1065,7 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon assert parkedTransactionTimeStamp == 0; assert parkedRemainingBytes == 0; - receive(SIZE_OF_SIZE + SIZE_OF_TRANSACTION_ID, timeoutTime); + receiveBytesFromSocket(SIZE_OF_SIZE + SIZE_OF_TRANSACTION_ID, timeoutTime); final int messageSize = inBytes.readInt(); assert messageSize > 0 : "Invalid message size " + messageSize; @@ -1049,14 +1112,15 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon if (System.currentTimeMillis() - timeoutTime > parkedTransactionTimeStamp) { - LOG.error("", new IllegalStateException("Skipped Message with transaction-id=" + + LOG.error("name=" + name, new IllegalStateException("Skipped Message with " + + "transaction-id=" + parkedTransactionTimeStamp + ", this can occur when you have another thread which has called the " + "stateless client and terminated abruptly before the message has been " + "returned from the server")); // read the the next message - receive(parkedRemainingBytes, timeoutTime); + receiveBytesFromSocket(parkedRemainingBytes, timeoutTime); clearParked(); } @@ -1067,6 +1131,8 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon } private void clearParked() { + assert !outBytesLock.isHeldByCurrentThread(); + assert inBytesLock.isHeldByCurrentThread(); parkedTransactionId = 0; parkedRemainingBytes = 0; parkedTransactionTimeStamp = 0; @@ -1074,6 +1140,9 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon private void pause() { + assert !outBytesLock.isHeldByCurrentThread(); + assert inBytesLock.isHeldByCurrentThread(); + /// don't call inBytesLock.isHeldByCurrentThread() as it not atomic inBytesLock.unlock(); inBytesLock.lock(); @@ -1089,8 +1158,13 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon * @throws IOException socket failed to read data */ @SuppressWarnings("UnusedReturnValue") - private Bytes receive(int requiredNumberOfBytes, long timeoutTime) throws IOException { + private Bytes receiveBytesFromSocket(int requiredNumberOfBytes, long timeoutTime) throws IOException { + assert !outBytesLock.isHeldByCurrentThread(); + assert inBytesLock.isHeldByCurrentThread(); + + inBytes.position(0); + inBytes.limit(requiredNumberOfBytes); inBytes.buffer().position(0); inBytes.buffer().limit(requiredNumberOfBytes); @@ -1100,7 +1174,7 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon int len = clientChannel.read(inBytes.buffer()); if (len == -1) - throw new IORuntimeException("Disconnected to remote server"); + throw new IORuntimeException("Disconnection to server"); checkTimeout(timeoutTime); } @@ -1111,31 +1185,80 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon } - private void writeBytesToSocket(@NotNull final Bytes out, long timeoutTime) throws IOException { + long lagestEntrySoFar = 0; + long limitOfLast = 0; - outBuffer.limit((int) out.position()); + private void writeBytesToSocket(long timeoutTime) throws IOException { + + assert outBytesLock.isHeldByCurrentThread(); + assert !inBytesLock.isHeldByCurrentThread(); + + // if we have other threads waiting to send and the buffer is not full, let the other threads + // write to the buffer + if (outBytesLock.hasQueuedThreads() && + outBytes.position() + lagestEntrySoFar <= config.tcpBufferSize()) { + return; + } + + outBuffer.limit((int) outBytes.position()); outBuffer.position(0); + + int sizeOfThisMessage = (int) (outBuffer.limit() - limitOfLast); + if (lagestEntrySoFar < sizeOfThisMessage) + lagestEntrySoFar = sizeOfThisMessage; + + limitOfLast = outBuffer.limit(); + + + while (outBuffer.remaining() > 0) { + + int len = clientChannel.write(outBuffer); + if (len == -1) + throw new IORuntimeException("Disconnection to server"); + + + // if we have queued threads then we don't have to write all the bytes as the other + // threads will write the remains bytes. + if (outBuffer.remaining() > 0 && outBytesLock.hasQueuedThreads() && + outBuffer.remaining() + lagestEntrySoFar <= config.tcpBufferSize()) { + + + LOG.debug("continuing - without all the data being written to the buffer as " + + "it will be written by the next thread"); + outBuffer.compact(); + outBytes.limit(outBuffer.limit()); + outBytes.position(outBuffer.position()); + return; + } + + checkTimeout(timeoutTime); - while (outBuffer.remaining() > 0) { - clientChannel.write(outBuffer); - checkTimeout(timeoutTime); } - out.clear(); outBuffer.clear(); + outBytes.clear(); + } - private void checkTimeout(long timeoutTime) { - if (timeoutTime < System.currentTimeMillis()) - throw new RemoteCallTimeoutException(); - } - private void writeSizeAndTransactionIdAt(long locationOfSize, final long transactionId, long startTime) { + private void writeSizeAndTransactionIdAt(long locationOfSize, final long transactionId) { + + assert outBytesLock.isHeldByCurrentThread(); + assert !inBytesLock.isHeldByCurrentThread(); + + assert outBytes.readByte(locationOfSize - 1) >= LONG_SIZE.ordinal(); + + final long size = outBytes.position() - locationOfSize; final long pos = outBytes.position(); outBytes.position(locationOfSize); + try { + outBuffer.position((int) locationOfSize); + + } catch (IllegalArgumentException e) { + LOG.error("locationOfSize=" + locationOfSize + ", limit=" + outBuffer.limit(), e); + } int size0 = (int) size - SIZE_OF_SIZE; - assert size0 > 0; outBytes.writeInt(size0); assert transactionId != 0; @@ -1145,8 +1268,11 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon } private long markSizeLocation() { + assert outBytesLock.isHeldByCurrentThread(); + assert !inBytesLock.isHeldByCurrentThread(); + final long position = outBytes.position(); - outBytes.skip(SIZE_OF_SIZE); + outBytes.writeInt(0); // skip the size return position; } @@ -1156,6 +1282,9 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon @SuppressWarnings("SameParameterValue") private ThreadLocalCopies writeKey(K key, ThreadLocalCopies copies) { + assert outBytesLock.isHeldByCurrentThread(); + assert !inBytesLock.isHeldByCurrentThread(); + long start = outBytes.position(); for (; ; ) { @@ -1164,7 +1293,7 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon } catch (IndexOutOfBoundsException e) { resizeBufferOutBuffer((int) (outBytes.capacity() + maxEntrySize), start); } catch (IllegalArgumentException e) { - resizeToMessage(start, e); + resizeToMessageOutBuffer(start, e); } catch (IllegalStateException e) { if (e.getMessage().contains("Not enough available space")) resizeBufferOutBuffer((int) (outBytes.capacity() + maxEntrySize), start); @@ -1176,6 +1305,10 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon @SuppressWarnings("UnusedReturnValue") private ThreadLocalCopies writeKeyInLoop(K key, Object writer, ThreadLocalCopies copies) { + + assert outBytesLock.isHeldByCurrentThread(); + assert !inBytesLock.isHeldByCurrentThread(); + long start = outBytes.position(); for (; ; ) { @@ -1184,7 +1317,7 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon } catch (IndexOutOfBoundsException e) { resizeBufferOutBuffer((int) (outBytes.capacity() + maxEntrySize), start); } catch (IllegalArgumentException e) { - resizeToMessage(start, e); + resizeToMessageOutBuffer(start, e); } catch (IllegalStateException e) { if (e.getMessage().contains("Not enough available space")) resizeBufferOutBuffer((int) (outBytes.capacity() + maxEntrySize), start); @@ -1195,6 +1328,9 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon } private ThreadLocalCopies writeValue(V value, ThreadLocalCopies copies) { + assert outBytesLock.isHeldByCurrentThread(); + assert !inBytesLock.isHeldByCurrentThread(); + long start = outBytes.position(); for (; ; ) { try { @@ -1202,7 +1338,7 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon outBytes.limit(outBytes.capacity()); return valueWriterWithSize.write(outBytes, value, copies); } catch (IllegalArgumentException e) { - resizeToMessage(start, e); + resizeToMessageOutBuffer(start, e); } catch (IllegalStateException e) { if (e.getMessage().contains("Not enough available space")) resizeBufferOutBuffer((int) (outBytes.capacity() + maxEntrySize), start); @@ -1214,6 +1350,8 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon @SuppressWarnings("UnusedReturnValue") private ThreadLocalCopies writeValueInLoop(V value, Object writer, ThreadLocalCopies copies) { + assert outBytesLock.isHeldByCurrentThread(); + assert !inBytesLock.isHeldByCurrentThread(); long start = outBytes.position(); for (; ; ) { try { @@ -1221,7 +1359,7 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon outBytes.limit(outBytes.capacity()); return valueWriterWithSize.writeInLoop(outBytes, value, writer, copies); } catch (IllegalArgumentException e) { - resizeToMessage(start, e); + resizeToMessageOutBuffer(start, e); } catch (IllegalStateException e) { if (e.getMessage().contains("Not enough available space")) resizeBufferOutBuffer((int) (outBytes.capacity() + maxEntrySize), start); @@ -1231,7 +1369,9 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon } } - private void resizeToMessage(long start, @NotNull IllegalArgumentException e) { + private void resizeToMessageOutBuffer(long start, @NotNull IllegalArgumentException e) { + assert outBytesLock.isHeldByCurrentThread(); + assert !inBytesLock.isHeldByCurrentThread(); String message = e.getMessage(); if (message.startsWith(START_OF)) { String substring = message.substring("Attempt to write ".length(), message.length()); @@ -1248,6 +1388,8 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon } private V readValue(long transactionId, long startTime, ThreadLocalCopies copies) { + assert !outBytesLock.isHeldByCurrentThread(); + long timeoutTime = startTime + this.config.timeoutMs(); // receive @@ -1261,6 +1403,8 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon @Nullable private O readObject(long transactionId, long startTime) { + assert !outBytesLock.isHeldByCurrentThread(); + long timeoutTime = startTime + this.config.timeoutMs(); // receive @@ -1273,6 +1417,8 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon } private boolean readBoolean(long transactionId, long startTime) { + assert !outBytesLock.isHeldByCurrentThread(); + long timeoutTime = startTime + this.config.timeoutMs(); // receive @@ -1286,6 +1432,8 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon private int readInt(long transactionId, long startTime) { + assert !outBytesLock.isHeldByCurrentThread(); + assert inBytesLock.isHeldByCurrentThread(); long timeoutTime = startTime + this.config.timeoutMs(); // receive @@ -1298,10 +1446,16 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon } private long send(@NotNull final EventId eventId, final long startTime) { + + assert outBytesLock.isHeldByCurrentThread(); + assert !inBytesLock.isHeldByCurrentThread(); + + assert eventId.ordinal() != 0; // send outBytesLock.lock(); try { final long sizeLocation = writeEventAnSkip(eventId); + assert sizeLocation == 1; return send(sizeLocation, startTime); } finally { outBytesLock.unlock(); @@ -1309,6 +1463,8 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon } private V readValue(ThreadLocalCopies copies, Bytes in) { + assert !outBytesLock.isHeldByCurrentThread(); + assert inBytesLock.isHeldByCurrentThread(); return valueReaderWithSize.readNullable(in, copies); } @@ -1468,7 +1624,14 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon @Nullable private O fetchObject(final Class tClass, @NotNull final EventId eventId) { final long startTime = System.currentTimeMillis(); - long transactionId = send(eventId, startTime); + long transactionId; + + outBytesLock.lock(); + try { + transactionId = send(eventId, startTime); + } finally { + outBytesLock.unlock(); + } long timeoutTime = startTime + this.config.timeoutMs(); @@ -1530,37 +1693,6 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon "supported"); } - @Nullable - private R fetchObject(Class rClass, @NotNull final EventId eventId, K key, V value1, V value2) { - final long startTime = System.currentTimeMillis(); - ThreadLocalCopies copies; - - long transactionId; - - outBytesLock.lock(); - try { - - final long sizeLocation = writeEventAnSkip(eventId); - - copies = writeKey(key); - copies = writeValue(value1, copies); - copies = writeValue(value2, copies); - - transactionId = send(sizeLocation, startTime); - } finally { - outBytesLock.unlock(); - } - - - if (eventReturnsNull(eventId)) - return null; - - if (rClass == vClass) - return (R) readValue(transactionId, startTime, copies); - else - throw new UnsupportedOperationException("class of type class=" + rClass + " is not " + - "supported"); - } @Nullable private R fetchObject(Class rClass, @NotNull final EventId eventId, K key) { @@ -1605,6 +1737,10 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon } private void writeObject(@NotNull Object function) { + + assert outBytesLock.isHeldByCurrentThread(); + assert !inBytesLock.isHeldByCurrentThread(); + long start = outBytes.position(); for (; ; ) { try { @@ -1614,7 +1750,7 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon Throwable cause = e.getCause(); if (cause instanceof IOException && cause.getMessage().contains("Not enough available space")) { - LOG.debug("resizing buffer"); + LOG.debug("resizing buffer, name=" + name); resizeBufferOutBuffer(outBuffer.capacity() + maxEntrySize, start); } else throw e; diff --git a/src/main/java/net/openhft/chronicle/map/StatelessMapConfig.java b/src/main/java/net/openhft/chronicle/map/StatelessMapConfig.java index 2a06b045f..256e9eefa 100644 --- a/src/main/java/net/openhft/chronicle/map/StatelessMapConfig.java +++ b/src/main/java/net/openhft/chronicle/map/StatelessMapConfig.java @@ -23,20 +23,30 @@ import net.openhft.chronicle.hash.StatelessClientConfig; import java.io.IOException; import java.net.InetSocketAddress; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; /** * @author Rob Austin. */ class StatelessMapConfig implements StatelessClientConfig> { - private final AbstractChronicleMapBuilder mapBuilder; + private final ChronicleMapBuilder mapBuilder; private final InetSocketAddress remoteAddress; - private long timeoutMs = TimeUnit.SECONDS.toMillis(10); + private final long timeoutMs; + private final String name; - StatelessMapConfig(AbstractChronicleMapBuilder mapBuilder, - InetSocketAddress remoteAddress) { + private int tcpBufferSize = 1024 * VanillaChronicleMap.MAX_ENTRY_OVERSIZE_FACTOR; + + private final AtomicBoolean used; + + StatelessMapConfig(ChronicleMapBuilder mapBuilder, + InetSocketAddress remoteAddress, long timeoutMs, + String name, AtomicBoolean used) { this.mapBuilder = mapBuilder; this.remoteAddress = remoteAddress; + this.timeoutMs = timeoutMs; + this.name = name; + this.used = used; } public long timeoutMs() { @@ -49,12 +59,37 @@ class StatelessMapConfig implements StatelessClientConfig timeout(long timeout, TimeUnit units) { - this.timeoutMs = units.toMillis(timeout); - return this; + return new StatelessMapConfig<>(mapBuilder, remoteAddress, units.toMillis(timeout), + name, used); + } + + @Override + public StatelessClientConfig> name(String name) { + return new StatelessMapConfig<>(mapBuilder, remoteAddress, timeoutMs, name, used); + } + + String name() { + return name; } @Override public ChronicleMap create() throws IOException { - return mapBuilder.createStatelessMap(this); + if (!used.getAndSet(true)) { + return mapBuilder.createStatelessClient(this); + } else { + throw new IllegalStateException( + "A stateless client has already been created using this config chain. " + + "Create a new StatelessClientConfig (builder.statelessClient()) " + + "to create a new stateless client"); + } + } + + public int tcpBufferSize() { + return tcpBufferSize; + } + + public StatelessMapConfig tcpBufferSize(int packetSize) { + this.tcpBufferSize = packetSize; + return this; } } diff --git a/src/main/java/net/openhft/chronicle/map/TcpReplicator.java b/src/main/java/net/openhft/chronicle/map/TcpReplicator.java index 88fcd6183..66bb147d4 100644 --- a/src/main/java/net/openhft/chronicle/map/TcpReplicator.java +++ b/src/main/java/net/openhft/chronicle/map/TcpReplicator.java @@ -76,11 +76,12 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable private final KeyInterestUpdater opWriteUpdater = new KeyInterestUpdater(OP_WRITE, selectionKeysStore); private final BitSet activeKeys = new BitSet(selectionKeysStore.length); private final long heartBeatIntervalMillis; + private long largestEntrySoFar = 128; @NotNull private final Replica replica; private final byte localIdentifier; - private final int maxEntrySizeBytes; + @NotNull private final Replica.EntryExternalizable externalizable; @NotNull @@ -95,23 +96,17 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable private long selectorTimeout; /** - * @param maxEntrySizeBytes used to check that the last entry will fit into the buffer, - * it can not be smaller than the size of and entry, if it is - * set smaller the buffer will over flow, it can be larger then - * the entry, but setting it too large reduces the workable - * space in the buffer. * @param statelessServerConnector set to NULL if not required * @throws IOException on an io error. */ public TcpReplicator(@NotNull final Replica replica, @NotNull final Replica.EntryExternalizable externalizable, @NotNull final TcpTransportAndNetworkConfig replicationConfig, - final int maxEntrySizeBytes, @Nullable StatelessServerConnector statelessServerConnector, @Nullable RemoteNodeValidator remoteNodeValidator) throws IOException { - super("TcpSocketReplicator-" + replica.identifier(), replicationConfig.throttlingConfig(), - maxEntrySizeBytes); + super("TcpSocketReplicator-" + replica.identifier(), replicationConfig.throttlingConfig() + ); this.statelessServerConnector = statelessServerConnector; final ThrottlingConfig throttlingConfig = replicationConfig.throttlingConfig(); @@ -123,7 +118,7 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable this.replica = replica; this.localIdentifier = replica.identifier(); - this.maxEntrySizeBytes = maxEntrySizeBytes; + this.externalizable = externalizable; this.replicationConfig = replicationConfig; @@ -598,13 +593,18 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable socketChannel.close(); return; } + TcpSocketChannelEntryWriter entryWriter = attached.entryWriter; - if (entryWriter == null) throw new NullPointerException("No entryWriter"); + + if (entryWriter == null) throw + new NullPointerException("No entryWriter"); + if (entryWriter.isWorkIncomplete()) { final boolean completed = entryWriter.doWork(); if (completed) entryWriter.workCompleted(); + } else if (attached.remoteModificationIterator != null) entryWriter.entriesToBuffer(attached.remoteModificationIterator, key); @@ -639,6 +639,12 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable final SocketChannel socketChannel = (SocketChannel) key.channel(); final Attached attached = (Attached) key.attachment(); + if (attached == null) { + LOG.info("Closing connection " + socketChannel + ", nothing attached"); + socketChannel.close(); + return; + } + try { int len = attached.entryReader.readSocketToBuffer(socketChannel); @@ -761,6 +767,7 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable serverChannel.socket().setReceiveBufferSize(BUFFER_SIZE); serverChannel.configureBlocking(false); + serverChannel.register(TcpReplicator.this.selector, 0); ServerSocket serverSocket = null; try { @@ -919,10 +926,7 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable */ private class TcpSocketChannelEntryWriter { - @NotNull - final ByteBufferBytes in; - @NotNull - private final ByteBuffer out; + @NotNull private final EntryCallback entryCallback; // if uncompletedWork is set ( not null ) , this must be completed before any further work @@ -932,9 +936,7 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable private long lastSentTime; private TcpSocketChannelEntryWriter() { - out = ByteBuffer.allocateDirect(replicationConfig.packetSize() + maxEntrySizeBytes); - in = new ByteBufferBytes(out); - entryCallback = new EntryCallback(externalizable, in); + entryCallback = new EntryCallback(externalizable, replicationConfig.tcpBufferSize()); } public boolean isWorkIncomplete() { @@ -951,7 +953,11 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable * @param localIdentifier the current nodes identifier */ void identifierToBuffer(final byte localIdentifier) { - in.writeByte(localIdentifier); + in().writeByte(localIdentifier); + } + + private Bytes in() { + return entryCallback.in(); } /** @@ -960,9 +966,10 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable * @param timeStampOfLastMessage the last timestamp we received a message from that node */ void writeRemoteBootstrapTimestamp(final long timeStampOfLastMessage) { - in.writeLong(timeStampOfLastMessage); + in().writeLong(timeStampOfLastMessage); } + /** * writes all the entries that have changed, to the buffer which will later be written to * TCP/IP @@ -982,20 +989,36 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable int entriesWritten = 0; try { for (; ; entriesWritten++) { - final boolean wasDataRead = modificationIterator.nextEntry(entryCallback, 0); - if (!wasDataRead) { + long start = in().position(); + + boolean success = modificationIterator.nextEntry(entryCallback, 0); + + // if not success this is most likely due to the next entry not fitting into + // the buffer and the buffer can not be re-sized above Integer.max_value, in + // this case success will return false, so we return so that we can send to + // the socket what we have. + if (!success) + return; + + long entrySize = in().position() - start; + + if (entrySize > largestEntrySoFar) + largestEntrySoFar = entrySize; + + + if (!modificationIterator.hasNext()) { // if we have no more data to write to the socket then we will // un-register OP_WRITE on the selector, until more data becomes available - if (in.position() == 0 && handShakingComplete) + if (in().position() == 0 && handShakingComplete) disableWrite(socketChannel, attached); - return; } // we've filled up the buffer lets give another channel a chance to send // some data - if (in.remaining() <= maxEntrySizeBytes) + if (in().remaining() <= largestEntrySoFar || in().position() > + replicationConfig.tcpBufferSize()) return; // if we have space in the buffer to write more data and we just wrote data @@ -1017,6 +1040,9 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable private int writeBufferToSocket(@NotNull final SocketChannel socketChannel, final long approxTime) throws IOException { + final Bytes in = in(); + final ByteBuffer out = out(); + if (in.position() == 0) return 0; @@ -1024,6 +1050,7 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable lastSentTime = approxTime; assert in.position() <= Integer.MAX_VALUE; int size = (int) in.position(); + out.limit(size); final int len = socketChannel.write(out); @@ -1044,20 +1071,24 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable return len; } + private ByteBuffer out() { + return entryCallback.out(); + } + /** * used to send an single zero byte if we have not send any data for up to the * localHeartbeatInterval */ private void writeHeartbeatToBuffer() { // denotes the state - 0 for a heartbeat - in.writeByte(HEARTBEAT.ordinal()); + in().writeByte(HEARTBEAT.ordinal()); // denotes the size in bytes - in.writeInt(0); + in().writeInt(0); } private void writeRemoteHeartbeatInterval(long localHeartbeatInterval) { - in.writeLong(localHeartbeatInterval); + in().writeLong(localHeartbeatInterval); } /** @@ -1086,11 +1117,11 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable } public boolean doWork() { - return uncompletedWork != null && uncompletedWork.doWork(in); + return uncompletedWork != null && uncompletedWork.doWork(in()); } public boolean hasBytesToWrite() { - return in.position() > 0; + return in().position() > 0; } } @@ -1106,7 +1137,7 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable private byte state; private TcpSocketChannelEntryReader() { - in = ByteBuffer.allocateDirect(replicationConfig.packetSize() + maxEntrySizeBytes); + in = ByteBuffer.allocateDirect(replicationConfig.tcpBufferSize()); out = new ByteBufferBytes(in.slice()); out.limit(0); in.clear(); @@ -1215,7 +1246,7 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable final Work futureWork = statelessServerConnector.processStatelessEvent(state, - attached.entryWriter.in, attached.entryReader.out); + attached.entryWriter.in(), attached.entryReader.out); // turn the OP_WRITE on key.interestOps(key.interestOps() | OP_WRITE); @@ -1225,7 +1256,8 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable // the buffer is no longer full, and as such is treated as future work if (futureWork != null) { try { // we will complete what we can for now - boolean isComplete = futureWork.doWork(attached.entryWriter.in); + boolean isComplete = futureWork.doWork(attached.entryWriter + .in()); if (!isComplete) attached.entryWriter.uncompletedWork = futureWork; } catch (Exception e) { @@ -1257,7 +1289,7 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable // the maxEntrySizeBytes used here may not be the maximum size of the entry in its serialized form // however, its only use as an indication that the buffer is becoming full and should be compacted // the buffer can be compacted at any time - if (in.position() == 0 || in.remaining() > maxEntrySizeBytes) + if (in.position() == 0 || in.remaining() > largestEntrySoFar) return; in.limit(in.position()); diff --git a/src/main/java/net/openhft/chronicle/map/UdpChannelReplicator.java b/src/main/java/net/openhft/chronicle/map/UdpChannelReplicator.java index 0ec606d45..fd3f2a247 100644 --- a/src/main/java/net/openhft/chronicle/map/UdpChannelReplicator.java +++ b/src/main/java/net/openhft/chronicle/map/UdpChannelReplicator.java @@ -73,8 +73,8 @@ class UdpChannelReplicator extends AbstractChannelReplicator implements Replica. final byte localIdentifier) throws IOException { - super("UdpReplicator-" + localIdentifier, replicationConfig.throttlingConfig(), - maxPayloadSize); + super("UdpReplicator-" + localIdentifier, replicationConfig.throttlingConfig() + ); this.localIdentifier = localIdentifier; diff --git a/src/main/java/net/openhft/chronicle/map/UdpReplicator.java b/src/main/java/net/openhft/chronicle/map/UdpReplicator.java index 3aa8ef3a3..a4cb64688 100644 --- a/src/main/java/net/openhft/chronicle/map/UdpReplicator.java +++ b/src/main/java/net/openhft/chronicle/map/UdpReplicator.java @@ -30,10 +30,10 @@ import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; /** - * The UdpReplicator attempts to read the data ( but it does not enforce or grantee delivery ), typically, 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. In order to not miss data, UdpReplicator should be used in conjunction with - * the TCP Replicator. + * The UdpReplicator attempts to read the data ( but it does not enforce or grantee delivery ), + * typically, 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. In order to not miss data, UdpReplicator + * should be used in conjunction with the TCP Replicator. */ final class UdpReplicator extends UdpChannelReplicator implements Replica.ModificationNotifier, Closeable { @@ -49,7 +49,7 @@ final class UdpReplicator extends UdpChannelReplicator implements Replica.Modifi super(replicationConfig, serializedEntrySize, replica.identifier()); Replica.ModificationIterator modificationIterator = replica.acquireModificationIterator( - AbstractChronicleMapBuilder.UDP_REPLICATION_MODIFICATION_ITERATOR_ID, this); + ChronicleMapBuilder.UDP_REPLICATION_MODIFICATION_ITERATOR_ID, this); setReader(new UdpSocketChannelEntryReader(serializedEntrySize, entryExternalizable)); @@ -106,7 +106,7 @@ final class UdpReplicator extends UdpChannelReplicator implements Replica.Modifi // check the the first 4 bytes are the inverted len followed by the len // we do this to check that this is a valid start of entry, otherwise we throw it away - if ( ~size != invertedSize) + if (~size != invertedSize) return; if (out.remaining() != size) @@ -118,8 +118,7 @@ final class UdpReplicator extends UdpChannelReplicator implements Replica.Modifi private static class UdpSocketChannelEntryWriter implements EntryWriter { - private final ByteBuffer out; - private final ByteBufferBytes in; + private final EntryCallback entryCallback; private final UdpChannelReplicator udpReplicator; private Replica.ModificationIterator modificationIterator; @@ -131,9 +130,8 @@ final class UdpReplicator extends UdpChannelReplicator implements Replica.Modifi this.udpReplicator = udpReplicator; // we make the buffer twice as large just to give ourselves headroom - out = ByteBuffer.allocateDirect(serializedEntrySize * 2); - in = new ByteBufferBytes(out); - entryCallback = new EntryCallback(externalizable, in); + + entryCallback = new EntryCallback(externalizable, serializedEntrySize); this.modificationIterator = modificationIterator; } @@ -156,6 +154,9 @@ final class UdpReplicator extends UdpChannelReplicator implements Replica.Modifi public int writeAll(@NotNull final DatagramChannel socketChannel) throws InterruptedException, IOException { + final ByteBufferBytes in = entryCallback.in(); + final ByteBuffer out = entryCallback.out(); + out.clear(); in.clear(); in.skip(SIZE_OF_SIZE); diff --git a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java index 48a8219c9..2740a19a5 100755 --- a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java @@ -27,11 +27,12 @@ import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver; import net.openhft.chronicle.hash.ChronicleHashErrorListener; +import net.openhft.chronicle.hash.hashing.Hasher; import net.openhft.chronicle.hash.serialization.BytesInterop; import net.openhft.chronicle.hash.serialization.BytesReader; -import net.openhft.chronicle.hash.serialization.Hasher; import net.openhft.chronicle.hash.serialization.SizeMarshaller; import net.openhft.chronicle.hash.serialization.internal.*; +import net.openhft.lang.Jvm; import net.openhft.lang.collection.DirectBitSet; import net.openhft.lang.collection.SingleThreadedDirectBitSet; import net.openhft.lang.io.*; @@ -43,9 +44,12 @@ import net.openhft.lang.threadlocal.StatefulCopyable; import net.openhft.lang.threadlocal.ThreadLocalCopies; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.*; import java.lang.reflect.Array; +import java.lang.reflect.Method; import java.nio.ByteOrder; import java.nio.channels.FileChannel; import java.util.*; @@ -60,8 +64,7 @@ import static java.lang.Math.max; import static java.lang.Thread.currentThread; import static java.nio.ByteBuffer.allocateDirect; import static net.openhft.chronicle.map.Asserts.assertNotNull; -import static net.openhft.chronicle.map.ChronicleMapBuilder.builtInType; -import static net.openhft.chronicle.map.ChronicleMapBuilder.offHeapReference; +import static net.openhft.chronicle.map.XStreamHelper.to$$Native; import static net.openhft.lang.MemoryUnit.*; class VanillaChronicleMap, @@ -69,14 +72,16 @@ class VanillaChronicleMap, implements ChronicleMap, Serializable, ReadValue, InstanceOrBytesToInstance, GetValueInterops { -// private static final Logger LOG = LoggerFactory.getLogger(VanillaChronicleMap.class); + private static final Logger LOG = LoggerFactory.getLogger(VanillaChronicleMap.class); /** - * Because DirectBitSet implementations couldn't find more than 64 continuous clear or set bits. + * Because DirectBitSet implementations couldn't find more than 64 continuous clear or set + * bits. */ static final int MAX_ENTRY_OVERSIZE_FACTOR = 64; private static final long serialVersionUID = 2L; final Class kClass; + final String mapVersion; final SizeMarshaller keySizeMarshaller; final BytesReader originalKeyReader; final KI originalKeyInterop; @@ -106,7 +111,7 @@ class VanillaChronicleMap, private final ChronicleHashErrorListener errorListener; private final int segmentHeaderSize; private final HashSplitting hashSplitting; - final boolean isNativeValueClass; + final Class nativeValueClass; final MultiMapFactory multiMapFactory; final int maxEntryOversizeFactor; transient Provider> keyReaderProvider; @@ -119,12 +124,13 @@ class VanillaChronicleMap, transient long headerSize; transient Set> entrySet; - public VanillaChronicleMap(AbstractChronicleMapBuilder builder) throws IOException { + public VanillaChronicleMap(ChronicleMapBuilder builder) throws IOException { SerializationBuilder keyBuilder = builder.keyBuilder; kClass = keyBuilder.eClass; keySizeMarshaller = keyBuilder.sizeMarshaller(); originalKeyReader = keyBuilder.reader(); + mapVersion = BuildVersion.readVersion(); originalKeyInterop = (KI) keyBuilder.interop(); @@ -133,7 +139,19 @@ class VanillaChronicleMap, SerializationBuilder valueBuilder = builder.valueBuilder; vClass = valueBuilder.eClass; - isNativeValueClass = vClass.getName().endsWith("$$Native"); + if (vClass.getName().endsWith("$$Native")) { + nativeValueClass = vClass; + } else if (vClass.isInterface()) { + Class nativeValueClass = null; + try { + nativeValueClass = DataValueClasses.directClassFor(vClass); + } catch (Exception e) { + // fall through + } + this.nativeValueClass = nativeValueClass; + } else { + nativeValueClass = null; + } valueSizeMarshaller = valueBuilder.sizeMarshaller(); originalValueReader = valueBuilder.reader(); @@ -160,12 +178,20 @@ class VanillaChronicleMap, this.eventListener = builder.eventListener(); this.bytesEventListener = builder.bytesEventListener(); this.segmentHeaderSize = builder.segmentHeaderSize(); - this.maxEntryOversizeFactor = builder.maxEntryOversizeFactor(); + this.maxEntryOversizeFactor = + builder.maxEntryOversizeFactor(this instanceof ReplicatedChronicleMap); hashSplitting = HashSplitting.Splitting.forSegments(actualSegments); initTransients(); } + /** + * @return the version of Chronicle Map this instance was built with + */ + String version() { + return mapVersion; + } + final long segmentHash(long hash) { return hashSplitting.segmentHash(hash); } @@ -223,7 +249,37 @@ class VanillaChronicleMap, return offset; } + void warnOnWindows() { + if (!Jvm.isWindows()) + return; + long offHeapMapSize = sizeInBytes(); + long oneGb = GIGABYTES.toBytes(1L); + double offHeapMapSizeInGb = offHeapMapSize * 1.0 / oneGb; + if (offHeapMapSize > GIGABYTES.toBytes(4L)) { + System.out.printf( + "WARNING: On Windows, you probably cannot create a ChronicleMap\n" + + "of more than 4 GB. The configured map requires %.2f GB of off-heap memory.\n", + offHeapMapSizeInGb); + } + try { + long freePhysicalMemory = Jvm.freePhysicalMemoryOnWindowsInBytes(); + if (offHeapMapSize > freePhysicalMemory * 0.9) { + double freePhysicalMemoryInGb = freePhysicalMemory * 1.0 / oneGb; + System.out.printf( + "WARNING: On Windows, you probably cannot create a ChronicleMap\n" + + "of more than 90%% of available free memory in the system.\n" + + "The configured map requires %.2f GB of off-heap memory.\n" + + "There is only %.2f GB of free physical memory in the system.\n", + offHeapMapSizeInGb, freePhysicalMemoryInGb); + + } + } catch (IOException e) { + // ignore -- anyway we just warn the user + } + } + final long createMappedStoreAndSegments(File file) throws IOException { + warnOnWindows(); return createMappedStoreAndSegments(new MappedStore(file, FileChannel.MapMode.READ_WRITE, sizeInBytes(), JDKObjectSerializer.INSTANCE)); } @@ -318,18 +374,18 @@ class VanillaChronicleMap, } final void checkKey(Object key) { - // if (!kClass.isInstance(key)) { - // // key.getClass will cause NPE exactly as needed - // throw new ClassCastException("Key must be a " + kClass.getName() + - // " but was a " + key.getClass()); - //} + if (!kClass.isInstance(key)) { + // key.getClass will cause NPE exactly as needed + throw new ClassCastException("Key must be a " + kClass.getName() + + " but was a " + key.getClass()); + } } final void checkValue(Object value) { - // if (vClass != Void.class && !vClass.isInstance(value)) { - // throw new ClassCastException("Value must be a " + vClass.getName() + - // " but was a " + value.getClass()); - // } + if (vClass != Void.class && !vClass.isInstance(value)) { + throw new ClassCastException("Value must be a " + vClass.getName() + + " but was a " + value.getClass()); + } } final void put(Bytes entry, Bytes output) { @@ -609,7 +665,6 @@ class VanillaChronicleMap, final XStream xstream = new XStream(new JettisonMappedXmlDriver()); xstream.setMode(XStream.NO_REFERENCES); xstream.alias("chronicle-entries", VanillaChronicleMap.EntrySet.class); - // ideally we will use java serialization if we can lazyXStreamConverter().registerConverter(xstream); OutputStream outputStream = new FileOutputStream(toFile); @@ -639,6 +694,10 @@ class VanillaChronicleMap, @Override public V newValueInstance() { + if (vClass == CharSequence.class || vClass == StringBuilder.class) { + return (V) new StringBuilder(); + } + return newInstance(vClass, false); } @@ -648,27 +707,41 @@ class VanillaChronicleMap, } - static T newInstance(Class interfaceClass, boolean isKey) { + static T newInstance(Class aClass, boolean isKey) { + try { + return isKey ? DataValueClasses.newInstance(aClass) : + DataValueClasses.newDirectInstance(aClass); + } catch (Exception e) { + if (aClass.isInterface()) { + + throw new IllegalStateException("It not possible to create a instance from " + + "interface=" + aClass.getSimpleName() + " we recommend you create an " + + "instance in the usual way.", e); - if (interfaceClass.isEnum()) - return isKey ? DataValueClasses.newDirectReference(interfaceClass) - : DataValueClasses.newDirectInstance(interfaceClass); - - else if (!offHeapReference(interfaceClass) && !interfaceClass.isInterface() && - !builtInType(interfaceClass)) { - - // if (()) - // interfaceClass = DataValueClasses.directClassFor(interfaceClass); - return DataValueClasses.newInstance(interfaceClass); + } + try { + return (T) aClass.newInstance(); + } catch (Exception e1) { + throw new IllegalStateException("It has not been possible to create a instance " + + "of class=" + aClass.getSimpleName() + + ", Note : its more efficient if your chronicle map is configured with " + + "interface key " + + "and value types rather than classes, as this method is able to use " + + "interfaces to generate off heap proxies that point straight at your data. " + + "In this case you have used a class and chronicle is unable to create an " + + "instance of this class has it does not have a default constructor. " + + "If your class is mutable, we " + + "recommend you create and instance of your class=" + aClass.getSimpleName() + + " in the usual way, rather than using this method.", e); + } } - return DataValueClasses.newDirectInstance(interfaceClass); - } private XStreamConverter xStreamConverter; + /** * @return a lazily created XStreamConverter */ @@ -691,18 +764,17 @@ class VanillaChronicleMap, @Override public boolean canConvert(Class aClass) { + if (EntrySet.class.isAssignableFrom(aClass) || WriteThroughEntry.class.isAssignableFrom(aClass)) return true; - if (Serializable.class - .isAssignableFrom(aClass)) - return false; + String canonicalName = aClass.getCanonicalName(); + + if (canonicalName.startsWith("net.openhft.lang.values")) { + return true; + } - if (vClass.equals(Map.class)) - return false; - // todo add this back in when we do native - // return kClass.isAssignableFrom(aClass) || vClass.isAssignableFrom(aClass); return false; } @@ -715,16 +787,14 @@ class VanillaChronicleMap, writer.startNode("chronicle-key"); Object key = e.getKey(); - if (!kClass.equals(key.getClass())) - writer.addAttribute("type", key.getClass().getCanonicalName()); + writeType(writer, key, kClass); marshallingContext.convertAnother(key); writer.endNode(); writer.startNode("chronicle-value"); Object value = e.getValue(); - if (!vClass.equals(value.getClass())) - writer.addAttribute("type", value.getClass().getCanonicalName()); + writeType(writer, value, vClass); marshallingContext.convertAnother(value); writer.endNode(); } else if (EntrySet.class @@ -735,6 +805,23 @@ class VanillaChronicleMap, writer.endNode(); } } + if (o.getClass().getCanonicalName().startsWith("net.openhft.lang.values") + && (o.getClass().getCanonicalName().endsWith("$$Native") || + o.getClass().getCanonicalName().endsWith("$$Heap"))) { + Method[] methods = o.getClass().getMethods(); + for (Method method : methods) { + if (method.getName().equals("getValue") && method.getParameterTypes() + .length == 0) { + try { + Object result = method.invoke(o); + writer.setValue(result.toString()); + return; + } catch (Exception e) { + throw new RuntimeException("class=" + o.getClass().getCanonicalName(), e); + } + } + } + } } @Override @@ -774,24 +861,40 @@ class VanillaChronicleMap, } break; case "chronicle-key": - long keySize = keySizeMarshaller.readSize(buffer); - ThreadLocalCopies copies = keyReaderProvider.getCopies(null); - BytesReader keyReader = keyReaderProvider.get(copies, originalKeyReader); - return keyReader.read(buffer, keySize); + + if (kClass.getCanonicalName().startsWith("net.openhft.lang.values")) { + return to$$Native(reader, vClass, true, VanillaChronicleMap.this); + } else { + long keySize = keySizeMarshaller.readSize(buffer); + ThreadLocalCopies copies = keyReaderProvider.getCopies(null); + BytesReader keyReader = keyReaderProvider.get(copies, originalKeyReader); + return keyReader.read(buffer, keySize); + } case "chronicle-value": - long valueSize = valueSizeMarshaller.readSize(buffer); - copies = valueReaderProvider.getCopies(null); - BytesReader valueReader = - valueReaderProvider.get(copies, originalValueReader); - return valueReader.read(buffer, valueSize); + + if (vClass.getCanonicalName().startsWith("net.openhft.lang.values")) { + return to$$Native(reader, vClass, false, VanillaChronicleMap.this); + } else { + long valueSize = valueSizeMarshaller.readSize(buffer); + ThreadLocalCopies copies = valueReaderProvider.getCopies(null); + BytesReader valueReader = + valueReaderProvider.get(copies, originalValueReader); + return valueReader.read(buffer, valueSize); + } + } + return null; } private E get(HierarchicalStreamReader reader, UnmarshallingContext unmarshallingContext, Class clazz) { + + if (clazz == CharSequence.class) + return (E) unmarshallingContext.convertAnother(null, String.class); + if (reader.getAttributeCount() > 0) { final String type = reader.getAttribute("type"); try { @@ -801,10 +904,26 @@ class VanillaChronicleMap, } } else return (E) unmarshallingContext.convertAnother(null, clazz); } + + }; xstream.registerConverter(converter); } + + private void writeType(HierarchicalStreamWriter writer, Object o, final Class clazz) { + if (clazz == CharSequence.class) + return; + + String simpleName = o.getClass().getCanonicalName(); + if (simpleName.endsWith("$$Native")) { + String nodeName = simpleName.substring(0, simpleName.length() - + "$$Native".length()); + if (!nodeName.equals(clazz.getCanonicalName())) + writer.addAttribute("type", nodeName); + } else if (!clazz.equals(o.getClass())) + writer.addAttribute("type", o.getClass().getCanonicalName()); + } } @NotNull @@ -835,8 +954,10 @@ class VanillaChronicleMap, long segmentHash = segmentHash(hash); Segment segment = segments[segmentNum]; + boolean nativeValueClass = usingValue != null && + usingValue.getClass() == this.nativeValueClass; MutableLockedEntry lock = (lockType == LockType.WRITE_LOCK) - ? segment.writeLock(segmentState) + ? segment.writeLock(segmentState, nativeValueClass) : segment.readLock(segmentState); V v = segment.acquireWithoutLock(copies, segmentState, @@ -852,7 +973,7 @@ class VanillaChronicleMap, lock.keySize = keySize; lock.segmentHash = segmentHash; - if (!isNativeValueClass && lockType == LockType.WRITE_LOCK && v == null) + if (!nativeValueClass && lockType == LockType.WRITE_LOCK && v == null) v = usingValue; lock.value(v); @@ -1058,9 +1179,9 @@ class VanillaChronicleMap, } /** - * removes ( if there exists ) an entry from the map, if the {@param key} and {@param expectedValue} match that of a - * maps.entry. If the {@param expectedValue} equals null then ( if there exists ) an entry whose key equals {@param - * key} this is removed. + * removes ( if there exists ) an entry from the map, if the {@param key} and {@param + * expectedValue} match that of a maps.entry. If the {@param expectedValue} equals null then ( + * if there exists ) an entry whose key equals {@param key} this is removed. * * @param k the key of the entry to remove * @param expectedValue null if not required @@ -1192,8 +1313,8 @@ class VanillaChronicleMap, * replace the value in a map, only if the existing entry equals {@param expectedValue} * * @param key the key into the map - * @param expectedValue the expected existing value in the map ( could be null when we don't wish to do this check - * ) + * @param expectedValue the expected existing value in the map ( could be null when we don't + * wish to do this check ) * @param newValue the new value you wish to store in the map * @return the value that was replaced */ @@ -1558,7 +1679,7 @@ class VanillaChronicleMap, /** * reads the the number of entries in this segment */ - private long size() { + long size() { // any negative value is in error state. return max(0L, this.segmentHeader.readVolatileLong(SIZE_OFFSET)); } @@ -1596,9 +1717,12 @@ class VanillaChronicleMap, long startWriteLock = 0; - @Override - public final WriteLocked writeLock( - @Nullable SegmentState segmentState) { + final WriteLocked writeLock() { + return writeLock(null, false); + } + + final WriteLocked writeLock( + SegmentState segmentState, boolean nativeValueClass) { startWriteLock = System.nanoTime(); while (true) { final boolean success = segmentHeader.tryRWWriteLock(LOCK_OFFSET, lockTimeOutNS); @@ -1612,7 +1736,7 @@ class VanillaChronicleMap, */ if (success) { if (segmentState != null) { - if (isNativeValueClass) { + if (nativeValueClass) { return segmentState.nativeWriteLocked(this); } else { return segmentState.heapWriteLocked(this); @@ -1839,7 +1963,7 @@ class VanillaChronicleMap, long hash2, boolean replaceIfPresent, ReadValue readValue, boolean resultUnused) { shouldNotBeCalledFromReplicatedChronicleMap("Segment.put"); - writeLock(null); + writeLock(); try { return putWithoutLock(copies, segmentState, metaKeyInterop, keyInterop, key, keySize, toKey, @@ -1968,9 +2092,9 @@ class VanillaChronicleMap, } /** - * Returns value size, writes the entry (key, value, sizes) to the entry, after this method call entry - * positioned after value bytes written (i. e. at the end of entry), sets entry position (in segment) and value - * size position in the given segmentState + * Returns value size, writes the entry (key, value, sizes) to the entry, after this method + * call entry positioned after value bytes written (i. e. at the end of entry), sets entry + * position (in segment) and value size position in the given segmentState */ final , E, EW> long putEntry(SegmentState segmentState, @@ -2006,13 +2130,13 @@ class VanillaChronicleMap, // do nothing } - private void clearMetaData(long offset) { + final void clearMetaData(long offset) { if (metaDataBytes > 0) bytes.zeroOut(offset, offset + metaDataBytes); } //TODO refactor/optimize - private long alloc(int blocks) { + final long alloc(int blocks) { if (blocks > maxEntryOversizeFactor) throw new IllegalArgumentException("Entry is too large: requires " + blocks + " entry size chucks, " + maxEntryOversizeFactor + " is maximum."); @@ -2080,7 +2204,7 @@ class VanillaChronicleMap, GetValueInterops getValueInterops, VB expectedValue, InstanceOrBytesToInstance toValue, long hash2, ReadValue readValue, boolean resultUnused) { - writeLock(null); + writeLock(); try { return removeWithoutLock(copies, segmentState, metaKeyInterop, keyInterop, key, keySize, toKey, @@ -2094,9 +2218,10 @@ class VanillaChronicleMap, } /** - * - if expectedValue is not null, returns Boolean.TRUE (removed) or Boolean.FALSE (entry not found), regardless - * the expectedValue object is Bytes instance (RPC call) or the value instance - if expectedValue is null: - if - * resultUnused is false, null or removed value is returned - if resultUnused is true, null is always returned + * - if expectedValue is not null, returns Boolean.TRUE (removed) or Boolean.FALSE (entry + * not found), regardless the expectedValue object is Bytes instance (RPC call) or the value + * instance - if expectedValue is null: - if resultUnused is false, null or removed value is + * returned - if resultUnused is true, null is always returned */ , RV, VB extends RV, VBI, MVBI extends MetaBytesInterop> @@ -2240,8 +2365,9 @@ class VanillaChronicleMap, } /** - * Replaces the specified value for the key with the given value. {@code newValue} is set only if the existing - * value corresponding to the specified key is equal to {@code expectedValue} or {@code expectedValue == null}. + * Replaces the specified value for the key with the given value. {@code newValue} is set + * only if the existing value corresponding to the specified key is equal to {@code + * expectedValue} or {@code expectedValue == null}. * * @param hash2 a hash code related to the {@code keyBytes} * @return the replaced value or {@code null} if the value was not replaced @@ -2257,7 +2383,7 @@ class VanillaChronicleMap, ReadValue readValue, InstanceOrBytesToInstance toValue, long hash2) { segmentStateNotNullImpliesCopiesNotNull(copies, segmentState); - writeLock(null); + writeLock(); try { hashLookup.startSearch(hash2); MultiStoreBytes entry = null; @@ -2339,15 +2465,17 @@ class VanillaChronicleMap, } /** - * Replaces value in existing entry. May cause entry relocation, because there may be not enough space for new - * value in location already allocated for this entry. + * Replaces value in existing entry. May cause entry relocation, because there may be not + * enough space for new value in location already allocated for this entry. * * @param pos index of the first block occupied by the entry - * @param offset relative offset of the entry in Segment bytes (before, i. e. including metaData) + * @param offset relative offset of the entry in Segment bytes (before, i. e. + * including metaData) * @param entry relative pointer in Segment bytes * @param valueSizePos relative position of value size in entry * @param entryEndAddr absolute address of the entry end - * @return relative offset of the entry in Segment bytes after putting value (that may cause entry relocation) + * @return relative offset of the entry in Segment bytes after putting value (that may cause + * entry relocation) */ final long putValue( long pos, long offset, MultiStoreBytes entry, long valueSizePos, long entryEndAddr, @@ -2409,7 +2537,7 @@ class VanillaChronicleMap, private void clear() { shouldNotBeCalledFromReplicatedChronicleMap("Segment.clear"); - writeLock(null); + writeLock(); try { hashLookup.clear(); freeList.clear(); @@ -2558,7 +2686,7 @@ class VanillaChronicleMap, final Segment segment = segments[segIndex]; final long pos = returnedPos; try { - segment.writeLock(null); + segment.writeLock(); if (segment.hashLookup().getPositions().isClear(pos)) { // The case: // 1. iterator.next() - thread 1 @@ -2567,7 +2695,7 @@ class VanillaChronicleMap, // 3. iterator.remove() - thread 1 segment.writeUnlock(); // not re-entrant. VanillaChronicleMap.this.remove(returnedEntry.getKey()); - segment.writeLock(null); + segment.writeLock(); } else { removePresent(segment, pos); } @@ -2779,6 +2907,14 @@ class VanillaChronicleMap, @Override public void close() { + // TODO optimize -- keep replication bytes offset in SegmentState to jump directly + long pos = segmentState.pos; + long offset = segment.offsetFromPos(pos); + MultiStoreBytes entry = segment.reuse(segmentState.tmpBytes, offset); + long keySize = map.keySizeMarshaller.readSize(entry); + entry.skip(keySize); + segment.manageReplicationBytes(entry, true, false); + map.onPut(segment, pos); segmentState.close(); segment.writeUnlock(); } @@ -2810,9 +2946,25 @@ class VanillaChronicleMap, @Override public void close() { if (putOnClose) { - segment.putWithoutLock(copies, segmentState, - metaKeyInterop, keyInterop, key(), keySize, map.keyIdentity(), - map, value(), map.valueIdentity(), segmentHash, true, map, true); + // TODO optimize -- keep keySize, valueSizePos, entryEndAddr, etc. inside + // segmentState + long pos = segmentState.pos; + long offset = segment.offsetFromPos(pos); + MultiStoreBytes entry = segment.reuse(segmentState.tmpBytes, offset); + long keySize = map.keySizeMarshaller.readSize(entry); + entry.skip(keySize); + segment.manageReplicationBytes(entry, true, false); + long valueSizePos = entry.position(); + long valueSize = map.readValueSize(entry); + long entryEndAddr = entry.positionAddr() + valueSize; + VI valueInterop = map.valueInteropProvider.get(copies, map.originalValueInterop); + V value = value(); + MVI metaValueInterop = map.metaValueInteropProvider + .get(copies, map.originalMetaValueInterop, valueInterop, value); + long newValueSize = metaValueInterop.size(valueInterop, value); + segment.putValue(pos, offset, entry, valueSizePos, entryEndAddr, segmentState, + metaValueInterop, valueInterop, value, newValueSize, segment.hashLookup()); + map.onPut(segment, segmentState.pos); } putOnClose = true; segmentState.close(); @@ -2833,4 +2985,5 @@ class VanillaChronicleMap, map, null, map.valueIdentity(), segmentHash, map, true); } } + } diff --git a/src/main/java/net/openhft/chronicle/map/XStreamHelper.java b/src/main/java/net/openhft/chronicle/map/XStreamHelper.java new file mode 100644 index 000000000..db6b3fe8f --- /dev/null +++ b/src/main/java/net/openhft/chronicle/map/XStreamHelper.java @@ -0,0 +1,75 @@ +/* + * 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 com.thoughtworks.xstream.converters.ConversionException; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; + +import java.lang.reflect.Method; + +/** + * Created by Rob on 18/12/14. + */ +class XStreamHelper { + + static Object to$$Native(HierarchicalStreamReader reader, final Class aClass, final boolean isKey, ChronicleMap map) { + + + final Object o = isKey ? map.newKeyInstance() : map.newValueInstance(); + try { + for (Method $$nativeMethod : o.getClass().getMethods()) { + if ($$nativeMethod.getName().equals("setValue") && $$nativeMethod.getParameterTypes() + .length == 1) { + + Class parameterType = $$nativeMethod.getParameterTypes()[0]; + String value = reader.getValue(); + + + if (parameterType.isPrimitive()) { + + // convert the primitive to their boxed type + + if (parameterType == int.class) + parameterType = Integer.class; + else if (parameterType == char.class) + parameterType = Character.class; + else { + final String name = parameterType.getSimpleName(); + + final String properName = "java.lang." + Character.toString + (name.charAt(0)).toUpperCase() + + name.substring(1); + parameterType = Class.forName(properName); + } + } + + + final Method valueOf = parameterType.getMethod("valueOf", String.class); + final Object invoke = valueOf.invoke(null, value); + $$nativeMethod.invoke(o, invoke); + return o; + + } + } + } catch (Exception e) { + throw new ConversionException("class=" + aClass.getCanonicalName(), e); + } + throw new ConversionException("setValue(..) method not found in class=" + aClass + .getCanonicalName + ()); + } +} diff --git a/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java b/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java index d669d3bec..6ecb96ac3 100755 --- a/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java +++ b/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java @@ -52,7 +52,7 @@ import java.util.concurrent.TimeUnit; * @see ChronicleSet * @see net.openhft.chronicle.map.ChronicleMapBuilder */ -public class ChronicleSetBuilder +public final class ChronicleSetBuilder implements ChronicleHashBuilder, ChronicleSetBuilder> { private ChronicleMapBuilder chronicleMapBuilder; @@ -64,7 +64,7 @@ public class ChronicleSetBuilder } public static ChronicleSetBuilder of(Class keyClass) { - return new ChronicleSetBuilder(keyClass); + return new ChronicleSetBuilder<>(keyClass); } @Override @@ -153,6 +153,12 @@ public class ChronicleSetBuilder return this; } + @Override + public ChronicleSetBuilder maxEntryOversizeFactor(int maxEntryOversizeFactor) { + chronicleMapBuilder.maxEntryOversizeFactor(maxEntryOversizeFactor); + return this; + } + @Override public ChronicleSetBuilder entries(long entries) { chronicleMapBuilder.entries(entries); @@ -227,7 +233,7 @@ public class ChronicleSetBuilder } @Override - public ChronicleSetBuilder keyMarshaller(@NotNull BytesMarshaller keyMarshaller) { + public ChronicleSetBuilder keyMarshaller(@NotNull BytesMarshaller keyMarshaller) { chronicleMapBuilder.keyMarshaller(keyMarshaller); return this; } @@ -293,31 +299,26 @@ public class ChronicleSetBuilder } @Override - public ChronicleHashInstanceConfig> instance() { - return new InstanceConfig<>(chronicleMapBuilder.instance()); + public ChronicleSet createStatelessClient(InetSocketAddress serverAddress) + throws IOException { + return statelessClient(serverAddress).create(); } @Override - public ChronicleSet create() throws IOException { + public ChronicleHashInstanceConfig> instance() { + return new SetInstanceConfig<>(chronicleMapBuilder.instance()); + } + + @Override + public ChronicleSet create() { final ChronicleMap map = chronicleMapBuilder.create(); - return new SetFromMap(map); + return new SetFromMap<>(map); } @Override public ChronicleSet createPersistedTo(File file) throws IOException { ChronicleMap map = chronicleMapBuilder.createPersistedTo(file); - return new SetFromMap(map); + return new SetFromMap<>(map); } - - @Override - public String name() { - return chronicleMapBuilder.name(); - } - - public ChronicleSetBuilder name(String name) { - chronicleMapBuilder.name(name); - return this; - } - } diff --git a/src/main/java/net/openhft/chronicle/set/InstanceConfig.java b/src/main/java/net/openhft/chronicle/set/SetInstanceConfig.java similarity index 57% rename from src/main/java/net/openhft/chronicle/set/InstanceConfig.java rename to src/main/java/net/openhft/chronicle/set/SetInstanceConfig.java index 9d3f43f6c..337168998 100644 --- a/src/main/java/net/openhft/chronicle/set/InstanceConfig.java +++ b/src/main/java/net/openhft/chronicle/set/SetInstanceConfig.java @@ -25,47 +25,55 @@ import net.openhft.chronicle.map.ChronicleMap; import java.io.File; import java.io.IOException; -final class InstanceConfig implements ChronicleHashInstanceConfig> { +final class SetInstanceConfig implements ChronicleHashInstanceConfig> { private final ChronicleHashInstanceConfig> mapConfig; - private boolean used; - - InstanceConfig(ChronicleHashInstanceConfig> mapConfig) { + SetInstanceConfig(ChronicleHashInstanceConfig> mapConfig) { this.mapConfig = mapConfig; } @Override public ChronicleHashInstanceConfig> replicated( byte identifier, TcpTransportAndNetworkConfig tcpTransportAndNetwork) { - return new InstanceConfig<>(mapConfig.replicated(identifier, tcpTransportAndNetwork)); + return new SetInstanceConfig<>(mapConfig.replicated(identifier, tcpTransportAndNetwork)); } @Override - public ChronicleHashInstanceConfig> replicated(SingleChronicleHashReplication replication) { - return new InstanceConfig<>(mapConfig.replicated(replication)); + public ChronicleHashInstanceConfig> replicated( + SingleChronicleHashReplication replication) { + return new SetInstanceConfig<>(mapConfig.replicated(replication)); } @Override public ChronicleHashInstanceConfig> replicatedViaChannel( ReplicationChannel channel) { - return new InstanceConfig<>(mapConfig.replicatedViaChannel(channel)); + return new SetInstanceConfig<>(mapConfig.replicatedViaChannel(channel)); } @Override public ChronicleHashInstanceConfig> persistedTo(File file) { - return new InstanceConfig<>(mapConfig.persistedTo(file)); + return new SetInstanceConfig<>(mapConfig.persistedTo(file)); + } + + @Override + public ChronicleHashInstanceConfig> name(String name) { + return new SetInstanceConfig<>(mapConfig.name(name)); } @Override public synchronized ChronicleSet create() throws IOException { - if (used) - throw new IllegalStateException( - "A ChronicleSet has already been created using this instance config. " + - "Create a new instance config (builder.instance() or call any " + - "configuration method on this instance config) to create a new " + - "ChronicleSet instance"); - used = true; - return new SetFromMap<>(mapConfig.create()); + try { + return new SetFromMap<>(mapConfig.create()); + } catch (IllegalStateException e) { + if (e.getMessage() != null && e.getMessage().startsWith("A ChronicleMap")) { + throw new IllegalStateException( + "A ChronicleSet has already been created using this instance config. " + + "Create a new instance config (builder.instance()) to create " + + "a new ChronicleSet instance"); + } else { + throw e; + } + } } } diff --git a/src/main/java/net/openhft/chronicle/set/StatelessSetConfig.java b/src/main/java/net/openhft/chronicle/set/StatelessSetConfig.java index 983d68604..c5ab66a84 100644 --- a/src/main/java/net/openhft/chronicle/set/StatelessSetConfig.java +++ b/src/main/java/net/openhft/chronicle/set/StatelessSetConfig.java @@ -35,6 +35,11 @@ final class StatelessSetConfig implements StatelessClientConfig(mapClientBuilder.timeout(timeout, units)); } + @Override + public StatelessClientConfig> name(String name) { + return new StatelessSetConfig<>(mapClientBuilder.name(name)); + } + @Override public ChronicleSet create() throws IOException { return new SetFromMap<>(mapClientBuilder.create()); diff --git a/src/test/java/eg/BGChronicleTest.java b/src/test/java/eg/BGChronicleTest.java index 1b65540ef..fbef4bf3e 100644 --- a/src/test/java/eg/BGChronicleTest.java +++ b/src/test/java/eg/BGChronicleTest.java @@ -375,7 +375,7 @@ public class BGChronicleTest { /** * for loop back args = [] - *

    + *

    * for server - eg.BGChronicleTest server for client - eg.BGChronicleTest client * * @param args diff --git a/src/test/java/eg/TestIntrumentVOInterface.java b/src/test/java/eg/TestInstrumentVOInterface.java similarity index 95% rename from src/test/java/eg/TestIntrumentVOInterface.java rename to src/test/java/eg/TestInstrumentVOInterface.java index 915080bac..7b8359a4c 100644 --- a/src/test/java/eg/TestIntrumentVOInterface.java +++ b/src/test/java/eg/TestInstrumentVOInterface.java @@ -5,7 +5,7 @@ import net.openhft.lang.model.constraints.MaxSize; /** * Created by Vanitha on 12/5/2014. */ -public interface TestIntrumentVOInterface { +public interface TestInstrumentVOInterface { public int getSizeOfInstrumentIDArray(); diff --git a/src/test/java/eg/TestNesterInterfaceMarshal.java b/src/test/java/eg/TestNesterInterfaceMarshal.java index cf36ad7cc..e03d8d69f 100644 --- a/src/test/java/eg/TestNesterInterfaceMarshal.java +++ b/src/test/java/eg/TestNesterInterfaceMarshal.java @@ -3,7 +3,7 @@ package eg; import net.openhft.chronicle.hash.replication.TcpTransportAndNetworkConfig; import net.openhft.chronicle.map.ChronicleMap; import net.openhft.chronicle.map.ChronicleMapBuilder; -import net.openhft.lang.model.DataValueClasses; +import net.openhft.chronicle.map.WriteContext; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; @@ -14,65 +14,113 @@ import java.util.concurrent.TimeUnit; /** * Created by Vanitha on 12/5/2014. */ +@Ignore("a test written by someone on the google groups") public class TestNesterInterfaceMarshal { + /** + * Test that both maps are created first and then populated the first map so that second will + * get the contents + * + * @throws Exception + */ + @Test + + public void testReplicatedMap() throws Exception { + + ChronicleMap chronicleMap = null; + TcpTransportAndNetworkConfig tcpTransportAndNetworkConfig = + TcpTransportAndNetworkConfig.of(8076).heartBeatInterval(1L, TimeUnit.SECONDS); + try { + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(CharSequence.class, TestInstrumentVOInterface.class) + .entries(5000L) + .keySize(10) + .replication((byte) 1, tcpTransportAndNetworkConfig); - /** - * Test that both maps are created first and then populated the first map so that second will get the contents - * @throws Exception - */ - @Test - public void testReplicatedMap() throws Exception { - ChronicleMap map =createReplicatedMap1(); - ChronicleMap replicatedMap=createReplicatedMap2(); + chronicleMap = builder.create(); + + } catch (Exception e) { + System.out.println("Error(s) creating instrument cache: " + e); + } + ChronicleMap map = chronicleMap; + + ChronicleMap chronicleMap1 = null; + TcpTransportAndNetworkConfig tcpTransportAndNetworkConfig1 = + TcpTransportAndNetworkConfig.of(8077, new InetSocketAddress("127.0.0.1", 8076)) + .heartBeatInterval(1L, TimeUnit.SECONDS); + + try { + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(CharSequence.class, TestInstrumentVOInterface.class) + .putReturnsNull(true) + .removeReturnsNull(true) + .entries(5000L).keySize(10) + .replication((byte) 2, tcpTransportAndNetworkConfig1); - //Store some data into MAP1 - TestIntrumentVOInterface intrumentVOInterface= DataValueClasses.newDirectInstance(TestIntrumentVOInterface.class); - intrumentVOInterface.setSymbol("FIXFlyer"); + chronicleMap1 = builder.create(); + + } catch (Exception e) { + System.out.println("*********************Error(s) creating launcher " + + "instrument cache: " + e); + } + ChronicleMap replicatedMap = chronicleMap1; + + + //Store some data into MAP1 + TestInstrumentVOInterface intrumentVOInterface = map.newValueInstance(); + + + try (WriteContext wc = map.acquireUsingLocked + ("KEY1", intrumentVOInterface)) { + intrumentVOInterface.setSymbol("Flyer"); intrumentVOInterface.setCurrencyCode("USA"); intrumentVOInterface.setSizeOfInstrumentIDArray(2); - TestIntrumentVOInterface.TestInstrumentIDVOInterface instrumentIDVOInterface= intrumentVOInterface.getInstrumentIDAt(0); - instrumentIDVOInterface.setIdSource("CUSIP"); - instrumentIDVOInterface.setSecurityId("TEST"); - intrumentVOInterface.setInstrumentIDAt(0,instrumentIDVOInterface); - map.put("KEY1",intrumentVOInterface); + // TestIntrumentVOInterface.TestInstrumentIDVOInterface instrumentIDVOInterface = + intrumentVOInterface.getInstrumentIDAt(0); + // instrumentIDVOInterface.setIdSource("CUSIP"); + // instrumentIDVOInterface.setSecurityId("TEST"); + + // intrumentVOInterface.setInstrumentIDAt(0, instrumentIDVOInterface); - - - Assert.assertNotNull(map); - Assert.assertNotNull(replicatedMap); - - - int t = 0; - for (; t < 5000; t++) { - if (map.equals(replicatedMap)) - break; - Thread.sleep(1); - } - - Assert.assertEquals(map, replicatedMap); - Assert.assertTrue(!replicatedMap.isEmpty()); - - System.out.println(map.get("KEY1")); - System.out.println(replicatedMap.get("KEY1")); - - map.close(); - replicatedMap.close(); - + // Assert.assertNotNull(map); + // Assert.assertNotNull(replicatedMap); } + + int t = 0; + for (; t < 5000; t++) { + if (map.equals(replicatedMap)) + break; + Thread.sleep(1); + } + + Assert.assertEquals(map, replicatedMap); + Assert.assertTrue(!replicatedMap.isEmpty()); + + System.out.println(((TestInstrumentVOInterface.TestInstrumentIDVOInterface) map.get + ("KEY1")).getIdSource()); + System.out.println(replicatedMap.get("KEY1")); + + map.close(); + replicatedMap.close(); + + } + protected ChronicleMap createReplicatedMap1() { ChronicleMap chronicleMap = null; - TcpTransportAndNetworkConfig tcpTransportAndNetworkConfig=TcpTransportAndNetworkConfig.of(8076).heartBeatInterval(1L, TimeUnit.SECONDS); + TcpTransportAndNetworkConfig tcpTransportAndNetworkConfig = + TcpTransportAndNetworkConfig.of(8076).heartBeatInterval(1L, TimeUnit.SECONDS); try { - ChronicleMapBuilder builder = ChronicleMapBuilder.of(CharSequence.class, TestIntrumentVOInterface.class) - .entries(5000L).entrySize(2000) + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(CharSequence.class, TestInstrumentVOInterface.class) + .entries(5000L) + .keySize(10) .replication((byte) 1, tcpTransportAndNetworkConfig); @@ -87,20 +135,25 @@ public class TestNesterInterfaceMarshal { protected ChronicleMap createReplicatedMap2() { ChronicleMap chronicleMap = null; - TcpTransportAndNetworkConfig tcpTransportAndNetworkConfig=TcpTransportAndNetworkConfig.of(8077,new InetSocketAddress("127.0.0.1",8076)).heartBeatInterval(1L, TimeUnit.SECONDS); + TcpTransportAndNetworkConfig tcpTransportAndNetworkConfig = + TcpTransportAndNetworkConfig.of(8077, new InetSocketAddress("127.0.0.1", 8076)) + .heartBeatInterval(1L, TimeUnit.SECONDS); try { - ChronicleMapBuilder builder = ChronicleMapBuilder.of(CharSequence.class, TestIntrumentVOInterface.class) + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(CharSequence.class, TestInstrumentVOInterface.class) .putReturnsNull(true) .removeReturnsNull(true) - .entries(5000L).entrySize(2000) - .replication((byte)2,tcpTransportAndNetworkConfig) ; + .entries(5000L) + .keySize(10) + .replication((byte) 2, tcpTransportAndNetworkConfig); chronicleMap = builder.create(); } catch (Exception e) { - System.out.println("*********************Error(s) creating launcher instrument cache: " + e); + System.out.println("*********************Error(s) creating launcher " + + "instrument cache: " + e); } return chronicleMap; } diff --git a/src/test/java/net/openhft/chronicle/map/BuildVersionTest.java b/src/test/java/net/openhft/chronicle/map/BuildVersionTest.java new file mode 100644 index 000000000..3c20c9f17 --- /dev/null +++ b/src/test/java/net/openhft/chronicle/map/BuildVersionTest.java @@ -0,0 +1,59 @@ +/* + * 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 org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; + +/** + * @author Rob Austin. + */ +public class BuildVersionTest { + + + @Test + public void test() throws IOException, InterruptedException { + // checks that we always get a version + Assert.assertNotNull(BuildVersion.readVersion()); + } + + + /** + * check that the map records the version + * + * @throws IOException + * @throws InterruptedException + */ + @Test + public void testVersion() throws IOException, InterruptedException { + + try (ChronicleMap expected = ChronicleMapBuilder.of(Integer.class, Double + .class) + .create()) { + expected.put(1, 1.0); + + String version = ((VanillaChronicleMap) expected).version(); + Assert.assertNotNull(BuildVersion.readVersion(), version); + + } + } + +} diff --git a/src/test/java/net/openhft/chronicle/map/Builder.java b/src/test/java/net/openhft/chronicle/map/Builder.java index 79d4ffc3c..68ac0c64a 100644 --- a/src/test/java/net/openhft/chronicle/map/Builder.java +++ b/src/test/java/net/openhft/chronicle/map/Builder.java @@ -32,7 +32,7 @@ import java.util.concurrent.TimeUnit; */ public class Builder { - public static final int SIZE = 1000; + public static final int SIZE = 10_000; // added to ensure uniqueness static int count; static String WIN_OS = "WINDOWS"; diff --git a/src/test/java/net/openhft/chronicle/map/CHMLatencyTestMain.java b/src/test/java/net/openhft/chronicle/map/CHMLatencyTestMain.java index 8e6863439..980155837 100644 --- a/src/test/java/net/openhft/chronicle/map/CHMLatencyTestMain.java +++ b/src/test/java/net/openhft/chronicle/map/CHMLatencyTestMain.java @@ -78,7 +78,6 @@ public class CHMLatencyTestMain { ChronicleMap countersMap = ChronicleMapBuilder.of(LongValue.class, LongValue.class) .entries(KEYS) - .entrySize(16) .createPersistedTo(file); // add keys diff --git a/src/test/java/net/openhft/chronicle/map/CHMTest5.java b/src/test/java/net/openhft/chronicle/map/CHMTest5.java index 27fb7abeb..4f94f21bc 100644 --- a/src/test/java/net/openhft/chronicle/map/CHMTest5.java +++ b/src/test/java/net/openhft/chronicle/map/CHMTest5.java @@ -39,7 +39,7 @@ public class CHMTest5 { .entries(1000); //// don't include this, just to check it is as expected. - assertEquals(8, ((AbstractChronicleMapBuilder) builder.delegate).minSegments()); + assertEquals(8, builder.minSegments()); //// end of test String chmPath = System.getProperty("java.io.tmpdir") + System.getProperty("file.separator") + "CHMTest5"; diff --git a/src/test/java/net/openhft/chronicle/map/CHMUseCasesTest.java b/src/test/java/net/openhft/chronicle/map/CHMUseCasesTest.java index 4860e2903..21df87417 100644 --- a/src/test/java/net/openhft/chronicle/map/CHMUseCasesTest.java +++ b/src/test/java/net/openhft/chronicle/map/CHMUseCasesTest.java @@ -1,96 +1,315 @@ package net.openhft.chronicle.map; +import com.google.common.primitives.Chars; + import org.junit.Assert; +import net.openhft.chronicle.hash.replication.TcpTransportAndNetworkConfig; +import net.openhft.lang.io.ByteBufferBytes; import net.openhft.lang.io.serialization.impl.*; +import net.openhft.lang.model.constraints.NotNull; import net.openhft.lang.values.*; +import org.junit.After; import org.junit.Ignore; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import java.io.Closeable; +import java.io.IOException; +import java.io.Serializable; +import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.*; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import static java.util.Arrays.asList; import static org.junit.Assert.*; /** - * This test enumerates common usecases for keys and values. + * This test enumerates common use cases for keys and values. */ -// TODO Test for persisted map. -// TODO Test for stateless map. - +@RunWith(value = Parameterized.class) public class CHMUseCasesTest { - /** - * String is not as efficient as CharSequence as a key or value but easier to use The key can only be on heap and - * variable length serialised. - */ + + + enum TypeOfMap {SIMPLE, REPLICATED, STATELESS} + + private final TypeOfMap typeOfMap; + + @Parameterized.Parameters + public static Collection data() { + return asList(new Object[][]{ + { + TypeOfMap.SIMPLE + }, + + { + TypeOfMap.REPLICATED + }, + + // it pointless to run these test as the Function and UnaryOperator are not + // serializable as inner classes adn the getUsingLock is not supported by the + // stateless client + { + TypeOfMap.STATELESS + } + }); + + + } + + public CHMUseCasesTest(TypeOfMap typeOfMap) { + this.typeOfMap = typeOfMap; + } + + + static class PrefixStringFunction implements Function { + private final String prefix; + + public PrefixStringFunction(@NotNull String prefix) { + this.prefix = prefix; + } + + @Override + public String apply(String s) { + return prefix + s; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof PrefixStringFunction && + prefix.equals(((PrefixStringFunction) obj).prefix); + } + + @Override + public String toString() { + return prefix; + } + } + + @Test - public void testStringStringMap() throws ExecutionException, InterruptedException { -/* TODO run the same test for multiple types of map stores - for(ChronicleMapBuilder chmb : Arrays.asList( - ChronicleMapBuilder.of(String.class, String.class), - ChronicleMapBuilder.of(String.class, String.class).file(TMP_FILE), - ChronicleMapBuilder.of(String.class, String.class).statelessClient(CONFIG) - )) { - try (ChronicleMap map = chmb.create()) { -*/ - try (ChronicleMap map = ChronicleMapBuilder - .of(String.class, String.class) // for testing purposes only - .create()) { + public void testCharArrayValue() throws ExecutionException, InterruptedException, IOException { + + int valueSize = 10; + + char[] expected = new char[valueSize]; + Arrays.fill(expected, 'X'); + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(CharSequence.class, char[].class); + + try (ChronicleMap map = newInstance(builder)) { + map.put("Key", expected); + + char[] actual = map.get("Key"); + System.out.println(actual); + assertEquals(Chars.asList(expected), Chars.asList(map.get("Key"))); + } + } + + + @Test + public void testStringStringMap() throws ExecutionException, InterruptedException, + IOException { + + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(String.class, String.class); + + try (ChronicleMap map = newInstance(builder)) { map.put("Hello", "World"); assertEquals("World", map.get("Hello")); - assertEquals("New World", map.getMapped("Hello", new Function() { - @Override - public String apply(String s) { - return "New " + s; - } - })); - assertEquals(null, map.getMapped("No key", new Function() { - @Override - public String apply(String s) { - return "New " + s; - } - })); + assertEquals("New World", map.getMapped("Hello", new PrefixStringFunction("New "))); + assertEquals(null, map.getMapped("No key", new PrefixStringFunction("New "))); + + } + } + + @Test + public void testStringSerialization() throws ExecutionException, + InterruptedException, IOException { + + ByteBufferBytes b = new ByteBufferBytes(ByteBuffer.allocate(512)); + + String expected = "Hello"; + b.writeObject(expected); + + b.clear(); + + String actual = (String) b.readObject(); + assertEquals(expected, actual); + + } + Collection closeables = new ArrayList(); + + @After + public void after() { + for (Closeable c : closeables) { + + try { + c.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + closeables.clear(); + } + + + private ChronicleMap newInstance(ChronicleMapBuilder builder) throws + IOException { + + switch (typeOfMap) { + + case SIMPLE: + return builder.create(); + + case REPLICATED: { + + + { + final TcpTransportAndNetworkConfig tcpConfig1 = TcpTransportAndNetworkConfig + .of(8086).name("server") + .heartBeatInterval(1, TimeUnit.SECONDS) + .tcpBufferSize(1024 * 64); + + + closeables.add(builder + .replication((byte) 1, tcpConfig1) + .instance() + .name("server") + .create()); + + } + { + final TcpTransportAndNetworkConfig tcpConfig2 = TcpTransportAndNetworkConfig.of + (8087, new InetSocketAddress("localhost", 8086)).name("map2") + .heartBeatInterval(1, TimeUnit.SECONDS) + .tcpBufferSize(1024 * 64); + + ChronicleMap map2 = builder + .replication((byte) 2, tcpConfig2) + .instance() + .name("map2") + .create(); + closeables.add(map2); + return map2; + + } + + + } + + + case STATELESS: { + { + final TcpTransportAndNetworkConfig tcpConfig1 = TcpTransportAndNetworkConfig + .of(8086).name("server") + .heartBeatInterval(1, TimeUnit.SECONDS) + .tcpBufferSize(1024 * 64); + + + closeables.add(builder + .replication((byte) 1, tcpConfig1) + .instance() + .name("server") + .create()); + + } + { + ChronicleMap map2 = builder + .statelessClient(new InetSocketAddress("localhost", 8086)) + .create(); + + closeables.add(map2); + return map2; + + } + } + + default: + throw new IllegalStateException(); + } + } + + + private static class StringPrefixUnaryOperator implements UnaryOperator, Serializable { + + private String prefix; + + StringPrefixUnaryOperator(final String prefix1) { + prefix = prefix1; + } + + @Override + public String update(String s) { + return prefix + s; } } @Test - public void testStringStringMapMutableValue() throws ExecutionException, InterruptedException { -/* TODO run the same test for multiple types of map stores - for(ChronicleMapBuilder chmb : Arrays.asList( - ChronicleMapBuilder.of(String.class, String.class), - ChronicleMapBuilder.of(String.class, String.class).file(TMP_FILE), - ChronicleMapBuilder.of(String.class, String.class).statelessClient(CONFIG) - )) { - try (ChronicleMap map = chmb.create()) { -*/ - try (ChronicleMap map = ChronicleMapBuilder - .of(String.class, String.class) // for testing purposes only - .create()) { + public void testStringStringMapMutableValue() throws ExecutionException, InterruptedException, IOException { + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(String.class, String.class); + + try (ChronicleMap map = newInstance(builder)) { map.put("Hello", "World"); - - - map.putMapped("Hello", new UnaryOperator() { - @Override - public String update(String s) { - return "New " + s; - } - }); + map.putMapped("Hello", new StringPrefixUnaryOperator("New ")); } } + @Test + public void testCharSequenceMixingKeyTypes() throws ExecutionException, InterruptedException, IOException { + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(CharSequence.class, CharSequence.class); + + try (ChronicleMap map = newInstance(builder)) { + + map.put("Hello", "World"); + map.put(new StringBuilder("Hello"), "World2"); + + Assert.assertEquals("World2", map.get("Hello")); + } + } + + @Test + public void testCharSequenceMixingValueTypes() throws ExecutionException, InterruptedException, IOException { + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(CharSequence.class, CharSequence.class); + + try (ChronicleMap map = newInstance(builder)) { + map.put("Hello", "World"); + map.put("Hello2", new StringBuilder("World2")); + + Assert.assertEquals("World2", map.get("Hello2")); + Assert.assertEquals("World", map.get("Hello")); + } + } + + /** - * CharSequence is more efficient when object creation is avoided. The key can only be on heap and variable length - * serialised. + * CharSequence is more efficient when object creation is avoided. The key can only be on heap + * and variable length serialised. */ @Test - public void testCharSequenceCharSequenceMap() throws ExecutionException, InterruptedException { - try (ChronicleMap map = ChronicleMapBuilder - .of(CharSequence.class, CharSequence.class) // for testing purposes only - .defaultValue("") - .create()) { + public void testCharSequenceCharSequenceMap() throws ExecutionException, InterruptedException, IOException { + + if (typeOfMap == TypeOfMap.STATELESS) + return; // Function supported by the STATELESS client + + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(CharSequence.class, CharSequence.class); + + try (ChronicleMap map = newInstance(builder)) { map.put("Hello", "World"); StringBuilder key = new StringBuilder(); key.append("key-").append(1); @@ -144,14 +363,268 @@ public class CHMUseCasesTest { } } + + @Test + public void testAcquireUsingWithCharSequence() throws IOException { + + if (typeOfMap == TypeOfMap.STATELESS) + return; // getUsingLocked supported by the STATELESS client + + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(CharSequence.class, CharSequence.class); + + try (ChronicleMap map = newInstance(builder)) { + + CharSequence using = map.newValueInstance(); + + try (WriteContext wc = map.acquireUsingLocked("1", using)) { + assertTrue(using instanceof StringBuilder); + ((StringBuilder) using).append("Hello World"); + } + + assertEquals("Hello World", map.get("1")); + } + } + + @Test + public void testGetUsingWithCharSequenceNoValue() throws IOException { + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(CharSequence.class, CharSequence.class); + + if (typeOfMap == TypeOfMap.STATELESS) + return; // getUsingLocked supported by the STATELESS client + + try (ChronicleMap map = newInstance(builder)) { + + CharSequence using = map.newValueInstance(); + + try (ReadContext rc = map.getUsingLocked("1", using)) { + assertTrue(using instanceof StringBuilder); + ((StringBuilder) using).append("Hello World"); + } + + assertEquals(null, map.get("1")); + } + } + + + @Test + public void testGetUsingWithIntValueNoValue() throws IOException { + + if (typeOfMap == TypeOfMap.STATELESS) + return; // getUsingLocked supported by the STATELESS client + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(CharSequence.class, IntValue.class); + + try (ChronicleMap map = newInstance(builder)) { + + IntValue using = map.newValueInstance(); + + try (ReadContext rc = map.getUsingLocked("1", using)) { + assertTrue(using instanceof IntValue); + using.setValue(1); + } + + assertEquals(null, map.get("1")); + } + } + + + @Test(expected = IllegalStateException.class) + public void testAcquireUsingWithIntValueNoValue() throws IOException { + + if (typeOfMap == TypeOfMap.STATELESS) + throw new IllegalStateException(); // acquireUsingLocked supported by the STATELESS + + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(CharSequence.class, IntValue.class); + + try (ChronicleMap map = newInstance(builder)) { + + IntValue using = map.newValueInstance(); + + try (WriteContext rc = map.acquireUsingLocked("1", using)) { + assertTrue(using instanceof IntValue); + using.setValue(1); + rc.dontPutOnClose(); + } + + assertEquals(null, map.get("1")); + } + } + + + @Test(expected = IllegalArgumentException.class) + public void testAcquireUsingImmutableUsing() throws IOException { + + if (typeOfMap == TypeOfMap.STATELESS) + throw new IllegalArgumentException(); // acquireUsingLocked supported by the STATELESS + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(IntValue.class, CharSequence.class); + + try (ChronicleMap map = newInstance(builder)) { + + + IntValue using = map.newKeyInstance(); + using.setValue(1); + + try (WriteContext rc = map.acquireUsingLocked(using, "")) { + assertTrue(using instanceof IntValue); + using.setValue(1); + rc.dontPutOnClose(); + } + + assertEquals(null, map.get("1")); + } + } + + @Test + public void testAcquireUsingWithIntValueKeyStringBuilderValue() throws IOException { + + if (typeOfMap == TypeOfMap.STATELESS) + return; // acquireUsingLocked supported by the STATELESS client + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(IntValue.class, StringBuilder.class); + + try (ChronicleMap map = newInstance(builder)) { + + + IntValue key = map.newKeyInstance(); + key.setValue(1); + + StringBuilder using = map.newValueInstance(); + + try (WriteContext rc = map.acquireUsingLocked(key, using)) { + using.append("Hello"); + } + + assertEquals("Hello", map.get(key).toString()); + } + } + + @Test + public void testAcquireUsingWithIntValueKey() throws IOException { + + if (typeOfMap == TypeOfMap.STATELESS) + return; // getUsingLocked supported by the STATELESS client + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(IntValue.class, CharSequence.class); + + try (ChronicleMap map = newInstance(builder)) { + + IntValue key = map.newKeyInstance(); + key.setValue(1); + + CharSequence using = map.newValueInstance(); + + try (WriteContext rc = map.acquireUsingLocked(key, using)) { + key.setValue(3); + ((StringBuilder) using).append("Hello"); + } + + key.setValue(2); + try (WriteContext rc = map.acquireUsingLocked(key, using)) { + ((StringBuilder) using).append("World"); + } + + key.setValue(1); + assertEquals("Hello", map.get(key)); + } + } + + @Test + public void testAcquireUsingWithByteBufferBytesValue() throws IOException { + + if (typeOfMap == TypeOfMap.STATELESS) + return; + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(IntValue.class, CharSequence.class); + + try (ChronicleMap map = newInstance(builder)) { + + IntValue key = map.newKeyInstance(); + key.setValue(1); + + ByteBufferBytes value = new ByteBufferBytes(ByteBuffer.allocate(10)); + value.limit(0); + + try (WriteContext rc = map.acquireUsingLocked(key, value)) { + assertTrue(key instanceof IntValue); + assertTrue(value instanceof CharSequence); + } + + assertTrue(map.get(key).length() == 0); + } + } + + + @Test(expected = IllegalStateException.class) + public void testLongNewKeyValueInstance() { + try (ChronicleMap map = ChronicleMapBuilder + .of(Long.class, Long.class) + .create()) { + + map.newKeyInstance(); + map.newValueInstance(); + } + } + + + @Test(expected = IllegalStateException.class) + public void testNewKeyValueInstanceWithMapType() { + try (ChronicleMap map = ChronicleMapBuilder + .of(Map.class, Map.class) + .create()) { + + map.newKeyInstance(); + map.newValueInstance(); + } + } + + @Test + public void testNewKeyValueInstanceWithHahMapType() { + try (ChronicleMap map = ChronicleMapBuilder + .of(HashMap.class, HashMap.class) + .create()) { + + map.newKeyInstance(); + map.newValueInstance(); + } + } + + + @Test(expected = IllegalStateException.class) + public void testNewKeyValueInstanceWithByteArray() { + try (ChronicleMap map = ChronicleMapBuilder + .of(byte[].class, byte[].class) + .create()) { + + map.newKeyInstance(); + map.newValueInstance(); + } + } + + /** * StringValue represents any bean which contains a String Value */ @Test - public void testStringValueStringValueMap() { - try (ChronicleMap map = ChronicleMapBuilder - .of(StringValue.class, StringValue.class) // for testing purposes only - .create()) { + public void testStringValueStringValueMap() throws IOException { + + if (typeOfMap == TypeOfMap.STATELESS) + return; // getUsingLocked supported by the STATELESS client + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(StringValue.class, StringValue.class); + + try (ChronicleMap map = newInstance(builder)) { StringValue key1 = map.newKeyInstance(); StringValue key2 = map.newKeyInstance(); StringValue value1 = map.newValueInstance(); @@ -238,12 +711,18 @@ public class CHMUseCasesTest { } @Test - public void testIntegerIntegerMap() throws ExecutionException, InterruptedException { - try (ChronicleMap map = ChronicleMapBuilder - .of(Integer.class, Integer.class) - .entrySize(8) - .disableOversizedEntries() // disabled for testing purposes only. - .create()) { + public void testIntegerIntegerMap() throws ExecutionException, InterruptedException, IOException { + + + if (typeOfMap == TypeOfMap.STATELESS) + return; // Function supported by the STATELESS client + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(Integer.class, Integer.class); + + try (ChronicleMap map = newInstance(builder)) { + assertEquals(8, entrySize(map)); + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); Integer key1; Integer key2; Integer value1; @@ -292,12 +771,19 @@ public class CHMUseCasesTest { } @Test - public void testLongLongMap() throws ExecutionException, InterruptedException { - try (ChronicleMap map = ChronicleMapBuilder - .of(Long.class, Long.class) - .entrySize(16) - .disableOversizedEntries() // disabled for testing purposes only. - .create()) { + public void testLongLongMap() throws ExecutionException, InterruptedException, IOException { + + + if (typeOfMap == TypeOfMap.STATELESS) + return; + + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(Long.class, Long.class); + + try (ChronicleMap map = newInstance(builder)) { +// assertEquals(16, entrySize(map)); +// assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); map.put(1L, 11L); assertEquals((Long) 11L, map.get(1L)); @@ -335,12 +821,19 @@ public class CHMUseCasesTest { } @Test - public void testDoubleDoubleMap() throws ExecutionException, InterruptedException { - try (ChronicleMap map = ChronicleMapBuilder - .of(Double.class, Double.class) - .disableOversizedEntries() // disabled for testing purposes only. - .entrySize(16) - .create()) { + public void testDoubleDoubleMap() throws ExecutionException, InterruptedException, IOException { + + + if (typeOfMap == TypeOfMap.STATELESS) + return; + + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(Double.class, Double.class); + + try (ChronicleMap map = newInstance(builder)) { + assertEquals(16, entrySize(map)); + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); map.put(1.0, 11.0); assertEquals((Double) 11.0, map.get(1.0)); @@ -379,12 +872,17 @@ public class CHMUseCasesTest { } @Test - public void testByteArrayByteArrayMap() throws ExecutionException, InterruptedException { - try (ChronicleMap map = ChronicleMapBuilder - .of(byte[].class, byte[].class) - .disableOversizedEntries() // disabled for testing purposes only. - .entrySize(12) - .create()) { + public void testByteArrayByteArrayMap() throws ExecutionException, InterruptedException, IOException { + + + if (typeOfMap == TypeOfMap.STATELESS) + return; + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(byte[].class, byte[].class).keySize(4).valueSize(4) + .maxEntryOversizeFactor(1); + + try (ChronicleMap map = newInstance(builder)) { byte[] key1 = {1, 1, 1, 1}; byte[] key2 = {2, 2, 2, 2}; byte[] value1 = {11, 11, 11, 11}; @@ -427,15 +925,49 @@ public class CHMUseCasesTest { } @Test - public void testByteBufferByteBufferMap() throws ExecutionException, InterruptedException { - try (ChronicleMap map = ChronicleMapBuilder + public void testByteBufferByteBufferDefaultKeyValueMarshaller() throws ExecutionException, + InterruptedException, IOException { + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(ByteBuffer.class, ByteBuffer.class) + .keySize(8) + .valueSize(8) + .maxEntryOversizeFactor(1); + + try (ChronicleMap map = newInstance(builder)) { + + + ByteBuffer key1 = ByteBuffer.wrap(new byte[]{1, 1, 1, 1}); + ByteBuffer key2 = ByteBuffer.wrap(new byte[]{2, 2, 2, 2}); + ByteBuffer value1 = ByteBuffer.wrap(new byte[]{11, 11, 11, 11}); + ByteBuffer value2 = ByteBuffer.wrap(new byte[]{22, 22, 22, 22}); + assertNull(map.put(key1, value1)); + assertBBEquals(value1, map.put(key1, value2)); + assertBBEquals(value2, map.get(key1)); + assertNull(map.get(key2)); + + + map.put(key1, value1); + } + } + + @Test + public void testByteBufferByteBufferMap() throws ExecutionException, InterruptedException, IOException { + + if (typeOfMap == TypeOfMap.STATELESS) + return; // getUsingLocked supported by the STATELESS client + + ChronicleMapBuilder builder = ChronicleMapBuilder .of(ByteBuffer.class, ByteBuffer.class) .keyMarshaller(ByteBufferMarshaller.INSTANCE) .valueMarshaller(ByteBufferMarshaller.INSTANCE) - .disableOversizedEntries() // disabled for testing purposes only. - // TODO does this have to be 14 bytes? - .entrySize(14) - .create()) { + .keySize(8) + .valueSize(8) + .maxEntryOversizeFactor(1); + + try (ChronicleMap map = newInstance(builder)) { + + ByteBuffer key1 = ByteBuffer.wrap(new byte[]{1, 1, 1, 1}); ByteBuffer key2 = ByteBuffer.wrap(new byte[]{2, 2, 2, 2}); ByteBuffer value1 = ByteBuffer.wrap(new byte[]{11, 11, 11, 11}); @@ -534,14 +1066,21 @@ public class CHMUseCasesTest { @Test - public void testByteBufferDirectByteBufferMap() throws ExecutionException, InterruptedException { - try (ChronicleMap map = ChronicleMapBuilder + public void testByteBufferDirectByteBufferMap() throws ExecutionException, InterruptedException, IOException { + + if (typeOfMap == TypeOfMap.STATELESS) + return; // not supported by the STATELESS client + + ChronicleMapBuilder builder = ChronicleMapBuilder .of(ByteBuffer.class, ByteBuffer.class) .valueMarshaller(ByteBufferMarshaller.INSTANCE) // we should not have to to this ! .keyMarshaller(ByteBufferMarshaller.INSTANCE) // we should not have to to this ! - .disableOversizedEntries() // disabled for testing purposes only. - .entrySize(12) - .create()) { + .keySize(5).valueSize(5) + .maxEntryOversizeFactor(1); + + try (ChronicleMap map = newInstance(builder)) { + + ByteBuffer key1 = ByteBuffer.wrap(new byte[]{1, 1, 1, 1}); ByteBuffer key2 = ByteBuffer.wrap(new byte[]{2, 2, 2, 2}); ByteBuffer value1 = ByteBuffer.allocateDirect(4); @@ -588,22 +1127,23 @@ public class CHMUseCasesTest { } @Test - public void testIntValueIntValueMap() { - try (ChronicleMap map = ChronicleMapBuilder - .of(IntValue.class, IntValue.class) - .disableOversizedEntries() // disabled for testing purposes only. - .entrySize(8) - .create()) { + public void testIntValueIntValueMap() throws IOException { + if (typeOfMap == TypeOfMap.STATELESS) + return; // getUsingLocked supported by the STATELESS client + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(IntValue.class, IntValue.class); + + try (ChronicleMap map = newInstance(builder)) { + // this may change due to alignment +// assertEquals(8, entrySize(map)); + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); IntValue key1 = map.newKeyInstance(); IntValue key2 = map.newKeyInstance(); IntValue value1 = map.newValueInstance(); IntValue value2 = map.newValueInstance(); - - - - key1.setValue(1); value1.setValue(11); map.put(key1, value1); @@ -672,14 +1212,18 @@ public class CHMUseCasesTest { } @Test - @Ignore("Generated code creates a field too large ie. it ignores the @Range") - public void testUnsignedIntValueUnsignedIntValueMapEntrySize() { + @Ignore("HCOLL-241 Generated code creates a field too large ie. it ignores the @Range") + public void testUnsignedIntValueUnsignedIntValueMapEntrySize() throws IOException { + // TODO once this is working, merge the next test. - try (ChronicleMap map = ChronicleMapBuilder - .of(UnsignedIntValue.class, UnsignedIntValue.class) - .disableOversizedEntries() // disabled for testing purposes only. - .entrySize(8) - .create()) { + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(UnsignedIntValue.class, UnsignedIntValue.class); + + try (ChronicleMap map = newInstance(builder)) { + + // this may change due to alignment + //assertEquals(8, entrySize(map)); + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); UnsignedIntValue key1 = map.newKeyInstance(); UnsignedIntValue value1 = map.newValueInstance(); @@ -691,21 +1235,26 @@ public class CHMUseCasesTest { } } + /** * For unsigned int -> unsigned int entries, the key can be on heap or off heap. */ @Test - public void testUnsignedIntValueUnsignedIntValueMap() { - try (ChronicleMap map = ChronicleMapBuilder - .of(UnsignedIntValue.class, UnsignedIntValue.class) - .create()) { + public void testUnsignedIntValueUnsignedIntValueMap() throws IOException { + if (typeOfMap == TypeOfMap.STATELESS) + return; // getUsingLocked supported by the STATELESS client + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(UnsignedIntValue.class, UnsignedIntValue.class); + + try (ChronicleMap map = newInstance(builder)) { UnsignedIntValue key1 = map.newKeyInstance(); UnsignedIntValue key2 = map.newKeyInstance(); - UnsignedIntValue value1 = map.newValueInstance(); - UnsignedIntValue value2 = map.newValueInstance(); + UnsignedIntValue value1 = map.newValueInstance(); + UnsignedIntValue value2 = map.newValueInstance(); key1.setValue(1); value1.setValue(11); @@ -778,16 +1327,25 @@ public class CHMUseCasesTest { * For int values, the key can be on heap or off heap. */ @Test - public void testIntValueShortValueMap() { - try (ChronicleMap map = ChronicleMapBuilder - .of(IntValue.class, ShortValue.class) - .disableOversizedEntries() // disabled for testing purposes only. - .entrySize(6) - .create()) { + public void testIntValueShortValueMap() throws IOException { + + if (typeOfMap == TypeOfMap.STATELESS) + return; // getUsingLocked supported by the STATELESS client + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(IntValue.class, ShortValue.class); + + try (ChronicleMap map = newInstance(builder)) { + + // this may change due to alignment + // assertEquals(6, entrySize(map)); + + + // assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); IntValue key1 = map.newKeyInstance(); - IntValue key2 = map.newKeyInstance(); + IntValue key2 = map.newKeyInstance(); ShortValue value1 = map.newValueInstance(); - ShortValue value2 = map.newValueInstance(); + ShortValue value2 = map.newValueInstance(); key1.setValue(1); value1.setValue((short) 11); @@ -860,17 +1418,25 @@ public class CHMUseCasesTest { * For int -> unsigned short values, the key can be on heap or off heap. */ @Test - public void testIntValueUnsignedShortValueMap() { - try (ChronicleMap map = ChronicleMapBuilder - .of(IntValue.class, UnsignedShortValue.class) - .entrySize(6) - .disableOversizedEntries() // disabled for testing purposes only. - .create()) { + public void testIntValueUnsignedShortValueMap() throws IOException { + + if (typeOfMap == TypeOfMap.STATELESS) + return; + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(IntValue.class, UnsignedShortValue.class); + + try (ChronicleMap map = newInstance(builder)) { + + + // this may change due to alignment + // assertEquals(8, entrySize(map)); + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); IntValue key1 = map.newKeyInstance(); - IntValue key2 = map.newKeyInstance(); + IntValue key2 = map.newKeyInstance(); UnsignedShortValue value1 = map.newValueInstance(); - UnsignedShortValue value2 = map.newValueInstance(); + UnsignedShortValue value2 = map.newValueInstance(); key1.setValue(1); value1.setValue(11); @@ -943,17 +1509,23 @@ public class CHMUseCasesTest { * For int values, the key can be on heap or off heap. */ @Test - public void testIntValueCharValueMap() { - try (ChronicleMap map = ChronicleMapBuilder - .of(IntValue.class, CharValue.class) - .disableOversizedEntries() // disabled for testing purposes only. - .entrySize(6) - .create()) { + public void testIntValueCharValueMap() throws IOException { + if (typeOfMap == TypeOfMap.STATELESS) + return; // getUsingLocked supported by the STATELESS client + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(IntValue.class, CharValue.class); + + try (ChronicleMap map = newInstance(builder)) { + + + assertEquals(6, entrySize(map)); + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); IntValue key1 = map.newKeyInstance(); - IntValue key2 = map.newKeyInstance(); + IntValue key2 = map.newKeyInstance(); CharValue value1 = map.newValueInstance(); - CharValue value2 = map.newValueInstance(); + CharValue value2 = map.newValueInstance(); key1.setValue(1); value1.setValue((char) 11); @@ -1026,19 +1598,26 @@ public class CHMUseCasesTest { * For int-> byte entries, the key can be on heap or off heap. */ @Test - public void testIntValueUnsignedByteMap() { - try (ChronicleMap map = ChronicleMapBuilder - .of(IntValue.class, UnsignedByteValue.class) - .entrySize(5) - .disableOversizedEntries() // disabled for testing purposes only. - .create()) { + public void testIntValueUnsignedByteMap() throws IOException { + if (typeOfMap == TypeOfMap.STATELESS) + return; // getUsingLocked supported by the STATELESS client + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(IntValue.class, UnsignedByteValue.class); + + try (ChronicleMap map = newInstance(builder)) { + + + // TODO should be 5, but shorter fields based on range doesn't seem to be implemented + // on data value generation level yet + //assertEquals(8, entrySize(map)); this may change due to alignmented + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); IntValue key1 = map.newKeyInstance(); - IntValue key2 = map.newKeyInstance(); + IntValue key2 = map.newKeyInstance(); UnsignedByteValue value1 = map.newValueInstance(); - UnsignedByteValue value2 = map.newValueInstance(); - + UnsignedByteValue value2 = map.newValueInstance(); key1.setValue(1); @@ -1112,19 +1691,23 @@ public class CHMUseCasesTest { * For int values, the key can be on heap or off heap. */ @Test - public void testIntValueBooleanValueMap() { - try (ChronicleMap map = ChronicleMapBuilder - .of(IntValue.class, BooleanValue.class) - .disableOversizedEntries() // disabled for testing purposes only. - .entrySize(5) - .create()) { + public void testIntValueBooleanValueMap() throws IOException { + if (typeOfMap == TypeOfMap.STATELESS) + return; // getUsingLocked supported by the STATELESS client + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(IntValue.class, BooleanValue.class); + + try (ChronicleMap map = newInstance(builder)) { + + assertEquals(5, entrySize(map)); + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); IntValue key1 = map.newKeyInstance(); - IntValue key2 = map.newKeyInstance(); + IntValue key2 = map.newKeyInstance(); BooleanValue value1 = map.newValueInstance(); - BooleanValue value2 = map.newValueInstance(); + BooleanValue value2 = map.newValueInstance(); key1.setValue(1); @@ -1198,17 +1781,26 @@ public class CHMUseCasesTest { * For float values, the key can be on heap or off heap. */ @Test - public void testFloatValueFloatValueMap() { - try (ChronicleMap map = ChronicleMapBuilder - .of(FloatValue.class, FloatValue.class) - .entrySize(8) - .disableOversizedEntries() // disabled for testing purposes only. - .create()) { + public void testFloatValueFloatValueMap() throws IOException { + + + if (typeOfMap == TypeOfMap.STATELESS) + return; // getUsingLocked supported by the STATELESS client + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(FloatValue.class, FloatValue.class); + + try (ChronicleMap map = newInstance(builder)) { + + +// this may change due to alignment + //assertEquals(8, entrySize(map)); + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); FloatValue key1 = map.newKeyInstance(); - FloatValue key2 = map.newKeyInstance(); + FloatValue key2 = map.newKeyInstance(); FloatValue value1 = map.newValueInstance(); - FloatValue value2 = map.newValueInstance(); + FloatValue value2 = map.newValueInstance(); key1.setValue(1); @@ -1278,20 +1870,40 @@ public class CHMUseCasesTest { } } + private long entrySize(ChronicleMap map) { + + if (map instanceof VanillaChronicleMap) { + if (typeOfMap == TypeOfMap.SIMPLE) + return ((VanillaChronicleMap) map).entrySize; + else + return ((VanillaChronicleMap) map).entrySize - ReplicatedChronicleMap.ADDITIONAL_ENTRY_BYTES; + } else + return 0; + } + /** * For double values, the key can be on heap or off heap. */ @Test - public void testDoubleValueDoubleValueMap() { - try (ChronicleMap map = ChronicleMapBuilder - .of(DoubleValue.class, DoubleValue.class) - .entrySize(16) - .create()) { + public void testDoubleValueDoubleValueMap() throws IOException { + + if (typeOfMap == TypeOfMap.STATELESS) + return; // getUsingLocked supported by the STATELESS client + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(DoubleValue.class, DoubleValue.class); + + try (ChronicleMap map = newInstance(builder)) { + + // this may change due to alignment + //assertEquals(16, entrySize(map)); + + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); DoubleValue key1 = map.newKeyInstance(); - DoubleValue key2 = map.newKeyInstance(); + DoubleValue key2 = map.newKeyInstance(); DoubleValue value1 = map.newValueInstance(); - DoubleValue value2 = map.newValueInstance(); + DoubleValue value2 = map.newValueInstance(); key1.setValue(1); @@ -1367,18 +1979,25 @@ public class CHMUseCasesTest { * For long values, the key can be on heap or off heap. */ @Test - public void testLongValueLongValueMap() { - try (ChronicleMap map = ChronicleMapBuilder - .of(LongValue.class, LongValue.class) - .entrySize(16) - .disableOversizedEntries() // disabled for testing purposes only. - .create()) { + public void testLongValueLongValueMap() throws IOException { + + + if (typeOfMap == TypeOfMap.STATELESS) + return; + + ChronicleMapBuilder builder = ChronicleMapBuilder + .of(LongValue.class, LongValue.class); + + try (ChronicleMap map = newInstance(builder)) { + + // this may change due to alignment + // assertEquals(16, entrySize(map)); + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); LongValue key1 = map.newKeyInstance(); - LongValue key2 = map.newKeyInstance(); + LongValue key2 = map.newKeyInstance(); LongValue value1 = map.newValueInstance(); - LongValue value2 = map.newValueInstance(); - + LongValue value2 = map.newValueInstance(); key1.setValue(1); @@ -1456,79 +2075,98 @@ public class CHMUseCasesTest { } @Test - public void testListValue() { - try (ChronicleMap> map = ChronicleMapBuilder + public void testListValue() throws IOException { + + if (typeOfMap == TypeOfMap.STATELESS) + return; // getUsingLocked supported by the STATELESS client + + ChronicleMapBuilder> builder = ChronicleMapBuilder .of(String.class, (Class>) (Class) List.class) - .valueMarshaller(ListMarshaller.of(new StringMarshaller(8))) - .create()) { + .valueMarshaller(ListMarshaller.of(new StringMarshaller(8))); + + + try (ChronicleMap> map = newInstance(builder)) { map.put("1", Collections.emptyList()); - map.put("2", Arrays.asList("two-A")); + map.put("2", asList("two-A")); List list1 = new ArrayList<>(); try (WriteContext wc = map.acquireUsingLocked("1", list1)) { list1.add("one"); - assertEquals(Arrays.asList("one"), list1); + assertEquals(asList("one"), list1); } List list2 = new ArrayList<>(); try (ReadContext rc = map.getUsingLocked("1", list2)) { assertTrue(rc.present()); - assertEquals(Arrays.asList("one"), list2); + assertEquals(asList("one"), list2); } try (ReadContext rc = map.getUsingLocked("2", list2)) { assertTrue(rc.present()); list2.add("two-B"); // this is not written as it only a read context - assertEquals(Arrays.asList("two-A", "two-B"), list2); + assertEquals(asList("two-A", "two-B"), list2); } try (WriteContext wc = map.acquireUsingLocked("2", list1)) { list1.add("two-C"); - assertEquals(Arrays.asList("two-A", "two-C"), list1); + assertEquals(asList("two-A", "two-C"), list1); } try (ReadContext rc = map.getUsingLocked("2", list2)) { assertTrue(rc.present()); - assertEquals(Arrays.asList("two-A", "two-C"), list2); + assertEquals(asList("two-A", "two-C"), list2); } } } @Test - public void testSetValue() { - try (ChronicleMap> map = ChronicleMapBuilder + public void testSetValue() throws IOException { + + if (typeOfMap == TypeOfMap.STATELESS) + return; // getUsingLocked supported by the STATELESS client + + ChronicleMapBuilder> builder = ChronicleMapBuilder .of(String.class, (Class>) (Class) Set.class) - .valueMarshaller(SetMarshaller.of(new StringMarshaller(8))) - .create()) { + .valueMarshaller(SetMarshaller.of(new StringMarshaller(8))); + + + try (ChronicleMap> map = newInstance(builder)) { map.put("1", Collections.emptySet()); - map.put("2", new LinkedHashSet(Arrays.asList("one"))); + map.put("2", new LinkedHashSet(asList("one"))); Set list1 = new LinkedHashSet<>(); try (WriteContext wc = map.acquireUsingLocked("1", list1)) { list1.add("two"); - assertEquals(new LinkedHashSet(Arrays.asList("two")), list1); + assertEquals(new LinkedHashSet(asList("two")), list1); } Set list2 = new LinkedHashSet<>(); try (ReadContext rc = map.getUsingLocked("1", list2)) { assertTrue(rc.present()); - assertEquals(new LinkedHashSet(Arrays.asList("two")), list2); + assertEquals(new LinkedHashSet(asList("two")), list2); } try (WriteContext wc = map.acquireUsingLocked("2", list1)) { list1.add("three"); - assertEquals(new LinkedHashSet(Arrays.asList("one", "three")), list1); + assertEquals(new LinkedHashSet(asList("one", "three")), list1); } try (ReadContext rc = map.getUsingLocked("2", list2)) { assertTrue(rc.present()); - assertEquals(new LinkedHashSet(Arrays.asList("one", "three")), list2); + assertEquals(new LinkedHashSet(asList("one", "three")), list2); } } } @Test - public void testMapStringStringValue() { - try (ChronicleMap> map = ChronicleMapBuilder + public void testMapStringStringValue() throws IOException { + + if (typeOfMap == TypeOfMap.STATELESS) + return; // getUsingLocked supported by the STATELESS client + + ChronicleMapBuilder> builder = ChronicleMapBuilder .of(String.class, (Class>) (Class) Map.class) - .valueMarshaller(MapMarshaller.of(new StringMarshaller(16), new StringMarshaller(16))) - .create()) { + .valueMarshaller(MapMarshaller.of(new StringMarshaller(16), new StringMarshaller + (16))); + + + try (ChronicleMap> map = newInstance(builder)) { map.put("1", Collections.emptyMap()); map.put("2", mapOf("one", "uni")); @@ -1554,11 +2192,18 @@ public class CHMUseCasesTest { } @Test - public void testMapStringIntegerValue() { - try (ChronicleMap> map = ChronicleMapBuilder + public void testMapStringIntegerValue() throws IOException { + + + if (typeOfMap == TypeOfMap.STATELESS) + return; // getUsingLocked supported by the STATELESS client + + ChronicleMapBuilder> builder = ChronicleMapBuilder .of(String.class, (Class>) (Class) Map.class) - .valueMarshaller(MapMarshaller.of(new StringMarshaller(16), new GenericEnumMarshaller(Integer.class, 16))) - .create()) { + .valueMarshaller(MapMarshaller.of(new StringMarshaller(16), new + GenericEnumMarshaller(Integer.class, 16))); + + try (ChronicleMap> map = newInstance(builder)) { map.put("1", Collections.emptyMap()); map.put("2", mapOf("one", 1)); @@ -1583,6 +2228,18 @@ public class CHMUseCasesTest { } } + @Test + public void testMapStringIntegerValueWithoutListMarshallers() { + try (ChronicleMap> map = ChronicleMapBuilder + .of(String.class, (Class>) (Class) Map.class) + .create()) { + map.put("1", Collections.emptyMap()); + map.put("2", mapOf("two", 2)); + + assertEquals(mapOf("two", 2), map.get("2")); + } + } + public static Map mapOf(K k, V v, Object... keysAndValues) { Map ret = new LinkedHashMap<>(); ret.put(k, v); diff --git a/src/test/java/net/openhft/chronicle/map/ChannelReplicationTest.java b/src/test/java/net/openhft/chronicle/map/ChannelReplicationTest.java index cc1d5db98..f82596b37 100644 --- a/src/test/java/net/openhft/chronicle/map/ChannelReplicationTest.java +++ b/src/test/java/net/openhft/chronicle/map/ChannelReplicationTest.java @@ -62,7 +62,7 @@ public class ChannelReplicationTest { map1a = ChronicleMapBuilder.of(Integer.class, CharSequence.class) .entries(2) - .entrySize(100) + .constantValueSizeBySample("EXAMPLE-1") .instance().replicatedViaChannel(hubA.createChannel((short) 1)).create(); } @@ -76,7 +76,7 @@ public class ChannelReplicationTest { map1b = ChronicleMapBuilder.of(Integer.class, CharSequence.class) .entries(2) - .entrySize(100) + .constantValueSizeBySample("EXAMPLE-1") .instance().replicatedViaChannel(hubB.createChannel((short) 1)).create(); } } @@ -111,12 +111,12 @@ public class ChannelReplicationTest { map2b = ChronicleMapBuilder.of(Integer.class, CharSequence.class) .entries(2) - .entrySize(100) + .constantValueSizeBySample("EXAMPLE-1") .instance().replicatedViaChannel(hubB.createChannel((short) 2)).create(); map2a = ChronicleMapBuilder.of(Integer.class, CharSequence.class) .entries(2) - .entrySize(100) + .constantValueSizeBySample("EXAMPLE-1") .instance().replicatedViaChannel(hubA.createChannel((short) 2)).create(); map2a.put(1, "EXAMPLE-2"); diff --git a/src/test/java/net/openhft/chronicle/map/ChronicleMapImportExportTest.java b/src/test/java/net/openhft/chronicle/map/ChronicleMapImportExportTest.java index 1ddb86d9a..bd1cb7ef3 100644 --- a/src/test/java/net/openhft/chronicle/map/ChronicleMapImportExportTest.java +++ b/src/test/java/net/openhft/chronicle/map/ChronicleMapImportExportTest.java @@ -1,13 +1,15 @@ package net.openhft.chronicle.map; -import junit.framework.Assert; + import net.openhft.chronicle.hash.replication.TcpTransportAndNetworkConfig; import net.openhft.lang.values.LongValue; import net.openhft.lang.values.LongValue$$Native; +import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import java.io.File; import java.io.IOException; +import java.net.InetSocketAddress; import java.util.HashMap; import java.util.Map; @@ -152,7 +154,34 @@ public class ChronicleMapImportExportTest { } } - @Ignore("this type of off heap reference is not currently supported") + + @Ignore("HCOLL-239 - The JSON to Map import/export should work on the stateless client.") + @Test + public void testToJsonWithStatlessClient() throws IOException, InterruptedException { + File file = new File(TMP + "/chronicle-map-" + System.nanoTime() + ".json"); + file.deleteOnExit(); + try (ChronicleMap expected = ChronicleMapBuilder.of(CharSequence.class, CharSequence + .class) + .create()) { + try (ChronicleMap serverMap = ChronicleMapBuilder.of(Integer.class, CharSequence.class) + .replication((byte) 2, TcpTransportAndNetworkConfig.of(8056)).create()) { + try (ChronicleMap actual = ChronicleMapBuilder.of(Integer + .class, CharSequence.class) + .statelessClient(new InetSocketAddress("localhost", 8056)).create()) { + expected.put("hello", "world"); + + expected.getAll(file); + + actual.putAll(file); + + Assert.assertEquals(expected, actual); + + } + } + } + } + + @Test public void testWithLongValue() throws IOException, InterruptedException { @@ -178,6 +207,7 @@ public class ChronicleMapImportExportTest { try (ChronicleMap actual = ChronicleMapBuilder.of(CharSequence.class, LongValue .class) .create()) { + actual.putAll(file); Assert.assertEquals(expected, actual); @@ -187,4 +217,5 @@ public class ChronicleMapImportExportTest { } } + } diff --git a/src/test/java/net/openhft/chronicle/map/ChronicleMapTest.java b/src/test/java/net/openhft/chronicle/map/ChronicleMapTest.java index f9cf71bff..cb5a50c0b 100755 --- a/src/test/java/net/openhft/chronicle/map/ChronicleMapTest.java +++ b/src/test/java/net/openhft/chronicle/map/ChronicleMapTest.java @@ -103,17 +103,12 @@ public class ChronicleMapTest { } private static ChronicleMap getSharedMap( - long entries, int segments, int entrySize) throws IOException { - return getSharedMap(entries, segments, entrySize, OF_4_BYTES); - } - - private static ChronicleMap getSharedMap( - long entries, int segments, int entrySize, Alignment alignment) + long entries, int segments, Alignment alignment, int keySize) throws IOException { return ChronicleMapBuilder.of(CharSequence.class, LongValue.class) .entries(entries) .minSegments(segments) - .entrySize(entrySize) + .keySize(keySize) .entryAndValueAlignment(alignment) .create(); } @@ -204,7 +199,7 @@ public class ChronicleMapTest { int count = 300; final ChronicleMap map = ChronicleMapBuilder.of(Object.class, Object.class) - .entrySize(count) + .entries(count) .minSegments(2).create(); for (int i = 1; i < count; i++) { @@ -414,10 +409,13 @@ public class ChronicleMapTest { @Test public void testAcquireWithNullContainer() throws Exception { - ChronicleMap map = getSharedMap(10 * 1000, 128, 24); + ChronicleMap map = + ChronicleMapBuilder.of(CharSequence.class, LongValue.class) + .entries(1000) + .entryAndValueAlignment(OF_4_BYTES) + .create(); map.acquireUsing("key", new LongValue$$Native()); assertEquals(0, map.acquireUsing("key", null).getValue()); - map.close(); } @@ -454,7 +452,11 @@ public class ChronicleMapTest { @Test public void testGetWithNullContainer() throws Exception { - ChronicleMap map = getSharedMap(10 * 1000, 128, 24); + ChronicleMap map = + ChronicleMapBuilder.of(CharSequence.class, LongValue.class) + .entries(10) + .entryAndValueAlignment(OF_4_BYTES) + .create(); map.acquireUsing("key", new LongValue$$Native()); assertEquals(0, map.getUsing("key", null).getValue()); @@ -463,7 +465,11 @@ public class ChronicleMapTest { @Test public void testGetWithoutAcquireFirst() throws Exception { - ChronicleMap map = getSharedMap(10, 1, 24); + ChronicleMap map = + ChronicleMapBuilder.of(CharSequence.class, LongValue.class) + .entries(10) + .entryAndValueAlignment(OF_4_BYTES) + .create(); assertNull(map.getUsing("key", DataValueClasses.newDirectReference(LongValue.class))); map.close(); @@ -485,9 +491,9 @@ public class ChronicleMapTest { public void testAcquireAndGet() throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException { int entries = 3/*00 * 1000*/; - testAcquireAndGet(getSharedMap(entries, 1, 24, OF_4_BYTES), entries); - testAcquireAndGet(getSharedMap(entries, 1, 24, NO_ALIGNMENT), entries); - testAcquireAndGet(getSharedMap(entries, 1, 24, OF_8_BYTES), entries); + testAcquireAndGet(getSharedMap(entries, 1, OF_4_BYTES, 10), entries); + testAcquireAndGet(getSharedMap(entries, 1, NO_ALIGNMENT, 10), entries); + testAcquireAndGet(getSharedMap(entries, 1, OF_8_BYTES, 10), entries); } public void testAcquireAndGet(ChronicleMap map, int entries) @@ -526,9 +532,9 @@ public class ChronicleMapTest { @Test public void testAcquireFromMultipleThreads() throws Exception { int entries = 1000 * 1000; - testAcquireFromMultipleThreads(getSharedMap(entries, 128, 24, NO_ALIGNMENT)); - testAcquireFromMultipleThreads(getSharedMap(entries, 128, 24, OF_4_BYTES)); - testAcquireFromMultipleThreads(getSharedMap(entries, 128, 24, OF_8_BYTES)); + testAcquireFromMultipleThreads(getSharedMap(entries, 128, NO_ALIGNMENT, 10)); + testAcquireFromMultipleThreads(getSharedMap(entries, 128, OF_4_BYTES, 10)); + testAcquireFromMultipleThreads(getSharedMap(entries, 128, OF_8_BYTES, 10)); } public void testAcquireFromMultipleThreads(ChronicleMap map) @@ -565,7 +571,7 @@ public class ChronicleMapTest { } @Test - @Ignore + @Ignore("Performance test") public void testAcquirePerf() throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, InterruptedException, ExecutionException { @@ -582,7 +588,7 @@ public class ChronicleMapTest { .entries(entries) .actualSegments(8 * 1024) .entryAndValueAlignment(OF_8_BYTES) - .entrySize(24); + .keySize(13); File tmpFile = File.createTempFile("testAcquirePerf", ".deleteme"); tmpFile.deleteOnExit(); @@ -603,8 +609,10 @@ public class ChronicleMapTest { StringBuilder sb = new StringBuilder(); long next = 50 * 1000 * 1000; // use a factor to give up to 10 digit numbers. - int factor = Math.max(1, (int) ((10 * 1000 * 1000 * 1000L - 1) / entries)); - for (long j = t % independence; j < entries + independence - 1; j += independence) { + int factor = Math.max(1, + (int) ((10 * 1000 * 1000 * 1000L - 1) / entries)); + for (long j = t % independence; j < entries + independence - 1; + j += independence) { sb.setLength(0); sb.append("us:"); sb.append(j * factor); @@ -625,7 +633,8 @@ public class ChronicleMapTest { future.get(); } long time = System.currentTimeMillis() - start; - System.out.printf("Throughput %.1f M ops/sec%n", threads * entries / independence / 1000.0 / time); + System.out.printf("Throughput %.1f M ops/sec%n", + threads * entries / independence / 1000.0 / time); } printStatus(); File file = map.file(); @@ -637,30 +646,30 @@ public class ChronicleMapTest { } @Test - @Ignore + @Ignore("Performance test") public void testAcquireLockedPerf() throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, InterruptedException, ExecutionException { // int runs = Integer.getInteger("runs", 10); int procs = Runtime.getRuntime().availableProcessors(); - int threads = procs * 3; // runs > 100 ? procs / 2 : procs; + if (procs > 8) procs--; + int threads = procs * 3; ExecutorService es = Executors.newFixedThreadPool(procs); - for (int runs : new int[]{10, 50, 250, 500, 1000, 2500}) { - // JAVA 8 produces more garbage than previous versions for internal work. -// System.gc(); + for (int runs : new int[]{1, 2, 5, 10, 25, 50, 100, 500, 1000, 2500}) { + final long entries = runs * 1000 * 1000L; ChronicleMapBuilder builder = ChronicleMapBuilder .of(CharSequence.class, LongValue.class) .entries(entries) .entryAndValueAlignment(OF_8_BYTES) - .actualSegments(8 * 1024) - .entrySize(24); + .actualSegments(256) + .keySize(13); File tmpFile = File.createTempFile("testAcquirePerf", ".deleteme"); tmpFile.deleteOnExit(); final ChronicleMap map = builder.createPersistedTo(tmpFile); - int count = runs > 500 ? runs > 1200 ? 3 : 5 : 5; + int count = runs >= 5 ? 2 : 3; final int independence = Math.min(procs, runs > 500 ? 8 : 4); System.out.println("\nKey size: " + runs + " Million entries. " + builder); for (int j = 0; j < count; j++) { @@ -674,15 +683,25 @@ public class ChronicleMapTest { LongValue value = nativeLongValue(); StringBuilder sb = new StringBuilder(); long next = 50 * 1000 * 1000; + Random rand = new Random(); // use a factor to give up to 10 digit numbers. - int factor = Math.max(1, (int) ((10 * 1000 * 1000 * 1000L - 1) / entries)); - for (long j = t % independence; j < entries + independence - 1; j += independence) { + int factor = Math.max(1, + (int) ((10 * 1000 * 1000 * 1000L - 1) / entries)); + for (long j = t % independence; j < entries + independence - 1; + j += independence) { sb.setLength(0); sb.append("us:"); sb.append(j * factor); long n; - try (WriteContext wc = map.acquireUsingLocked(sb, value)) { - n = value.addValue(1); + // 75% read + if (rand.nextBoolean() || rand.nextBoolean()) { + try (ReadContext rc = map.getUsingLocked(sb, value)) { + n = rc.present() ? value.getValue() + 1 : 1; + } + } else { + try (WriteContext wc = map.acquireUsingLocked(sb, value)) { + n = value.addValue(1); + } } assert n > 0 && n < 1000 : "Counter corrupted " + n; if (t == 0 && j >= next) { @@ -699,7 +718,8 @@ public class ChronicleMapTest { future.get(); } long time = System.currentTimeMillis() - start; - System.out.printf("Throughput %.1f M ops/sec%n", threads * entries / independence / 1000.0 / time); + System.out.printf("Throughput %.1f M ops/sec%n", + threads * entries / independence / 1000.0 / time); } printStatus(); File file = map.file(); @@ -711,7 +731,7 @@ public class ChronicleMapTest { } @Test - @Ignore + @Ignore("Performance test") public void testAcquireLockedLLPerf() throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, InterruptedException, ExecutionException { @@ -727,8 +747,7 @@ public class ChronicleMapTest { .of(LongValue.class, LongValue.class) .entries(entries) .entryAndValueAlignment(OF_8_BYTES) - .actualSegments(8 * 1024) - .entrySize(16); + .actualSegments(8 * 1024); File tmpFile = File.createTempFile("testAcquirePerf", ".deleteme"); tmpFile.deleteOnExit(); @@ -787,7 +806,7 @@ public class ChronicleMapTest { } @Test - @Ignore + @Ignore("Performance test") public void testCHMAcquirePerf() throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, InterruptedException { for (int runs : new int[]{10, 50, 250, 500, 1000, 2500}) { System.out.println("Testing " + runs + " million entries"); @@ -846,14 +865,16 @@ public class ChronicleMapTest { } @Test - public void testPutAndRemove() throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException { + public void testPutAndRemove() throws IOException, ClassNotFoundException, + IllegalAccessException, InstantiationException { String TMP = System.getProperty("java.io.tmpdir"); int entries = 100 * 1000; ChronicleMap map = ChronicleMapBuilder.of(CharSequence.class, CharSequence.class) .entries(entries) .minSegments(16) - .entrySize(32) + .keySize("user:".length() + 6) + .valueSize("value:".length() + 6) .putReturnsNull(true) .removeReturnsNull(true).create(); StringBuilder key = new StringBuilder(); @@ -1398,12 +1419,14 @@ public class ChronicleMapTest { } } - @Test(expected = NullPointerException.class) + + + @Test(expected = IllegalStateException.class) public void testPutLongValue() throws IOException { final ChronicleMapBuilder builder = ChronicleMapBuilder .of(CharSequence.class, LongValue.class) .entries(1000) - .entrySize(16); + .keySize("x".length()); final ChronicleMap map = builder.create(); @@ -1416,7 +1439,7 @@ public class ChronicleMapTest { ChronicleMapBuilder builder = ChronicleMapBuilder .of(CharSequence.class, LongValue.class) .entries(1000) - .entrySize(16); + .keySize("one".length()); final ChronicleMap map = builder.create(); @@ -1474,8 +1497,7 @@ public class ChronicleMapTest { ChronicleMapBuilder builder = ChronicleMapBuilder .of(CharSequence.class, String.class) .entries(1000) - .defaultValue("") - .entrySize(16); + .defaultValue(""); try (final ChronicleMap map = builder.create()) { // this will add the entry @@ -1491,7 +1513,8 @@ public class ChronicleMapTest { try (final ChronicleMap map = ChronicleMapBuilder .of(CharSequence.class, CharSequence.class) .entries(1000) - .entrySize(40) + .keySize("one".length()) + .valueSize("Hello World".length()) .defaultValue("") .create()) { StringBuilder value = new StringBuilder(); @@ -1509,7 +1532,7 @@ public class ChronicleMapTest { ChronicleMapBuilder builder = ChronicleMapBuilder .of(CharSequence.class, LongValue.class) .entries(1000) - .entrySize(40); + .keySize("one".length()); File tmpFile = File.createTempFile("testAcquireUsingLocked", ".deleteme"); tmpFile.deleteOnExit(); final ChronicleMap map = builder.createPersistedTo(tmpFile); diff --git a/src/test/java/net/openhft/chronicle/map/DataKeyValueTest.java b/src/test/java/net/openhft/chronicle/map/DataKeyValueTest.java index 7d2cb72e1..dd3aef559 100644 --- a/src/test/java/net/openhft/chronicle/map/DataKeyValueTest.java +++ b/src/test/java/net/openhft/chronicle/map/DataKeyValueTest.java @@ -20,7 +20,6 @@ import net.openhft.lang.model.Byteable; import net.openhft.lang.model.DataValueClasses; import net.openhft.lang.values.IntValue; import net.openhft.lang.values.LongValue; -import org.junit.Assert; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -30,7 +29,7 @@ public class DataKeyValueTest { @Test public void dataKeyValueTest() { ChronicleMap map = - OnHeapUpdatableChronicleMapBuilder.of(IntValue.class, LongValue.class) + ChronicleMapBuilder.of(IntValue.class, LongValue.class) .entries(1000).create(); IntValue heapKey = DataValueClasses.newInstance(IntValue.class); IntValue directKey = DataValueClasses.newDirectInstance(IntValue.class); diff --git a/src/test/java/net/openhft/chronicle/map/DefaultValueTest.java b/src/test/java/net/openhft/chronicle/map/DefaultValueTest.java index d0b773361..7e47e4bf0 100644 --- a/src/test/java/net/openhft/chronicle/map/DefaultValueTest.java +++ b/src/test/java/net/openhft/chronicle/map/DefaultValueTest.java @@ -37,7 +37,7 @@ public class DefaultValueTest { ArrayList defaultValue = new ArrayList(); defaultValue.add(42); - try (ChronicleMap> map = OnHeapUpdatableChronicleMapBuilder + try (ChronicleMap> map = ChronicleMapBuilder .of(String.class, (Class>) ((Class) List.class)) .defaultValue(defaultValue).createPersistedTo(file)) { ArrayList using = new ArrayList(); @@ -49,7 +49,7 @@ public class DefaultValueTest { } ArrayList using = new ArrayList(); - try (ChronicleMap> map = OnHeapUpdatableChronicleMapBuilder + try (ChronicleMap> map = ChronicleMapBuilder .of(String.class, (Class>) ((Class) List.class)) .createPersistedTo(file)) { assertEquals(defaultValue, map.acquireUsing("c", using)); diff --git a/src/test/java/net/openhft/chronicle/map/EntryCountMapTest.java b/src/test/java/net/openhft/chronicle/map/EntryCountMapTest.java index 0129f5048..fc53b204a 100644 --- a/src/test/java/net/openhft/chronicle/map/EntryCountMapTest.java +++ b/src/test/java/net/openhft/chronicle/map/EntryCountMapTest.java @@ -24,9 +24,18 @@ import org.junit.Test; import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Random; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicLong; +import static java.lang.Math.log10; +import static java.lang.Math.round; import static net.openhft.chronicle.map.Alignment.OF_4_BYTES; import static net.openhft.lang.model.DataValueClasses.newDirectReference; import static org.junit.Assert.assertTrue; @@ -35,131 +44,185 @@ import static org.junit.Assert.fail; public class EntryCountMapTest { double score = 0; int scoreCount = 0; + static final String TMP = System.getProperty("java.io.tmpdir"); - static File getPersistenceFile() { - String TMP = System.getProperty("java.io.tmpdir"); - File file = new File(TMP + "/small-chm-test" + System.nanoTime()); + static File getPersistenceFile() throws IOException { + File file = File.createTempFile("ecm-chm-test", ".deleteme"); file.deleteOnExit(); return file; } private static ChronicleMap getSharedMap( - long entries, int segments, int entrySize) throws IOException { - return getSharedMap(entries, segments, entrySize, OF_4_BYTES); - } - - private static ChronicleMap getSharedMap( - long entries, int segments, int entrySize, Alignment alignment) - throws IOException { - ChronicleMapBuilder mapBuilder = ChronicleMapBuilder.of(CharSequence - .class, LongValue.class) + long entries, int segments, int keySize) throws IOException { + ChronicleMapBuilder mapBuilder = + ChronicleMapBuilder.of(CharSequence.class, LongValue.class) .entries(entries) .actualSegments(segments) - .entrySize(entrySize) - .entryAndValueAlignment(alignment); + .keySize(keySize) + .maxEntryOversizeFactor(1); return mapBuilder.createPersistedTo(getPersistenceFile()); } + static final int ecmTests = Integer.getInteger("ecm.tests", 5); + @Test public void testVerySmall() throws Exception { - int s = 1, i = 1; - // regression test. - testEntriesMaxSize(s, 1, 1, i); - testEntriesMaxSize(s, 2, 4, i); - testEntriesMaxSize(s, 4, 6, i); - testEntriesMaxSize(s, 8, 13, i); - testEntriesMaxSize(s, 16, 55, i); // todo fix the max. - testEntriesMaxSize(s, 32, 82, i); // todo fix the max. - testEntriesMaxSize(s, 64, 128, i); - testEntriesMaxSize(s, 128, 256, i); + System.out.print("testVerySmall seeds"); + for (int t = 0; t < ecmTests; t++) { + System.out.print("."); + int s = 1; + // regression test. + testEntriesMaxSize(s, 1, 1, t); + testEntriesMaxSize(s, 2, 2, t); + testEntriesMaxSize(s, 4, 4, t); + testEntriesMaxSize(s, 8, 8, t); + testEntriesMaxSize(s, 16, 16, t); + testEntriesMaxSize(s, 32, 32, t); + testEntriesMaxSize(s, 64, 64, t); + testEntriesMaxSize(s, 128, 128, t); + testEntriesMaxSize(s, 256, 256, t); + testEntriesMaxSize(s, 512, 512, t); + + s = 2; + // regression test. + testEntriesMaxSize(s, 8, 16, t); + testEntriesMaxSize(s, 16, 32, t); + testEntriesMaxSize(s, 32, 60, t); + testEntriesMaxSize(s, 64, 95, t); + testEntriesMaxSize(s, 128, 168, t); + testEntriesMaxSize(s, 256, 322, t); + testEntriesMaxSize(s, 512, 640, t); + + s = 4; + // regression test. + testEntriesMaxSize(s, 32, 72, t); + testEntriesMaxSize(s, 64, 120, t); + testEntriesMaxSize(s, 128, 200, t); + testEntriesMaxSize(s, 256, 380, t); + testEntriesMaxSize(s, 512, 650, t); + + for (int s2 : new int[]{1, 2, 4}) { + testEntriesMaxSize(s2, 1000, 1300, t); + testEntriesMaxSize(s2, 2000, 2500, t); + testEntriesMaxSize(s2, 4000, 5000, t); + testEntriesMaxSize(s2, 5000, 6200, t); + testEntriesMaxSize(s2, 8000, 9900, t); + testEntriesMaxSize(s2, 12000, 15000, t); + testEntriesMaxSize(s2, 16000, 20000, t); + } + } + // hyperbolic average gives more weight to small numbers. + System.out.printf(" Score: %.2f%n", scoreCount / score); } @Test public void testSmall() throws Exception { - for (int i = 0; i < 5; i++) { + System.out.print("testSmall seeds"); + int procs = Runtime.getRuntime().availableProcessors(); + ExecutorService es = Executors.newFixedThreadPool(procs); + for (int t = 0; t < ecmTests; t++) { + List> futures = new ArrayList<>(); + { + int s = 8; + futures.add(testEntriesMaxSize(es, s, 250, 400, t)); + futures.add(testEntriesMaxSize(es, s, 500, 770, t)); + futures.add(testEntriesMaxSize(es, s, 1000, 1400, t)); + futures.add(testEntriesMaxSize(es, s, 2000, 2800, t)); + } // regression test. - for (int s : new int[]{1, 2, 4, 8}) { - testEntriesMaxSize(s, 256, 510, i); - testEntriesMaxSize(s, 512, 1010, i); - testEntriesMaxSize(s, 1000, 1730, i); - testEntriesMaxSize(s, 2000, 2800, i); - testEntriesMaxSize(s, 4000, 5300, i); - testEntriesMaxSize(s, 5000, 6800, i); - testEntriesMaxSize(s, 8000, 10600, i); - testEntriesMaxSize(s, 12000, 16000, i); - testEntriesMaxSize(s, 16000, 21300, i); + for (int s : new int[]{8, 16}) { + futures.add(testEntriesMaxSize(es, s, 3000, 4200, t)); + futures.add(testEntriesMaxSize(es, s, 5000, 6900, t)); + futures.add(testEntriesMaxSize(es, s, 10000, 13000, t)); + } + + for(int s : new int[] { 32, 64, 128}) + futures.add(testEntriesMaxSize(es, s, 250 * s, 250 * s * 3 / 2, t)); + + int s = 32; + for (int e : new int[]{ 16000, 25000, 50000}) + futures.add(testEntriesMaxSize(es, s, e, e * 4 / 3, t)); + s = 64; + for (int e : new int[]{ 25000, 50000, 100000}) + futures.add(testEntriesMaxSize(es, s, e, e * 4 / 3, t)); + s = 128; + for (int e : new int[]{ 50000, 100000, 200000}) + futures.add(testEntriesMaxSize(es, s, e, e * 4 / 3, t)); + for (Future future : futures) { + System.out.print("."); + future.get(); } } + es.shutdown(); // hyperbolic average gives more weight to small numbers. - System.out.printf("Score: %.2f%n", scoreCount / score); + System.out.printf(" Score: %.2f%n", scoreCount / score); } + @Ignore("Long running, large tests test") @Test public void testMedium() throws Exception { - for (int i = 0; i < 2; i++) { + System.out.print("testMedium seeds"); + int procs = Runtime.getRuntime().availableProcessors(); + ExecutorService es = Executors.newFixedThreadPool(procs); + for (int t = 0; t < ecmTests; t++) { // regression test. - int s = 16; - testEntriesMaxSize(s, 512, 1000, i); - testEntriesMaxSize(s, 1000, 2100, i); - testEntriesMaxSize(s, 2000, 3100, i); - testEntriesMaxSize(s, 4000, 5100, i); - testEntriesMaxSize(s, 5000, 7100, i); - testEntriesMaxSize(s, 8000, 11000, i); - testEntriesMaxSize(s, 12000, 17000, i); - testEntriesMaxSize(s, 16000, 22000, i); - s = 32; - testEntriesMaxSize(s, 2000, 3900, i); - testEntriesMaxSize(s, 4000, 5900, i); - testEntriesMaxSize(s, 5000, 7900, i); - testEntriesMaxSize(s, 8000, 12000, i); - testEntriesMaxSize(s, 12000, 17000, i); - testEntriesMaxSize(s, 16000, 23000, i); - testEntriesMaxSize(s, 32000, 46000, i); - s = 64; - testEntriesMaxSize(s, 5000, 7550, i); - testEntriesMaxSize(s, 8000, 11400, i); - testEntriesMaxSize(s, 12000, 18000, i); - testEntriesMaxSize(s, 16000, 24000, i); - testEntriesMaxSize(s, 32000, 47000, i); - testEntriesMaxSize(s, 40000, 60000, i); - s = 128; - testEntriesMaxSize(s, 16000, 24000, i); - testEntriesMaxSize(s, 32000, 50000, i); - testEntriesMaxSize(s, 40000, 62000, i); - testEntriesMaxSize(s, 64000, 98000, i); - testEntriesMaxSize(s, 129000, 204000, i); - s = 256; - testEntriesMaxSize(s, 64000, 100000, i); - testEntriesMaxSize(s, 129000, 213000, i); + List> futures = new ArrayList<>(); + for (int s : new int[]{512, 256, 128, 64}) { + int s3 = s * s * 128; + futures.add(testEntriesMaxSize(es, s, s3 * 2, s3 * 24 / 10, t)); + if (s < 512) { + futures.add(testEntriesMaxSize(es, s, s3 * 3 / 2, s3 * 9 / 5, t)); + futures.add(testEntriesMaxSize(es, s, s3, s3 * 12 / 10, t)); + } + } + for (Future future : futures) { + System.out.print("."); + future.get(); + } } + es.shutdown(); // hyperbolic average gives more weight to small numbers. System.out.printf("Score: %.2f%n", scoreCount / score); } - @Test - @Ignore - public void testLarge() throws Exception { - int s = 8 * 1024; - for (int i = 0; i < 10; i++) { - System.out.println("i:" + i); - int entries = s * s; - testEntriesMaxSize(s, entries * 4 / 5, entries * 4 / 3, i); -// testEntriesMaxSize(s, entries + 100, entries * 3 / 2, i); - entries *= 2; - testEntriesMaxSize(s, entries * 4 / 5, entries * 4 / 3, i); -// testEntriesMaxSize(s, entries + 100, entries * 3 / 2, i); - } - // hyperbolic average gives more weight to small numbers. - System.out.printf("Score: %.2f%n", scoreCount / score); + private Future testEntriesMaxSize(ExecutorService es, final int segments, + final int minSize, final int maxSize, final int seed) + throws IOException { + assert minSize <= maxSize; + Random random = new Random(seed); + int counter = minSize + random.nextInt(9999 + maxSize); + final int stride = 1 + random.nextInt(100); + int maxKeySize = "key:".length() + + (int) round(log10(moreThanMaxSize(maxSize) * stride + counter)) + 1; + final ChronicleMap map = + getSharedMap(minSize, segments, maxKeySize); + return es.submit(new Callable() { + @Override + public Void call() throws Exception { + testEntriesMaxSize0(segments, minSize, maxSize, seed, stride, map); + return null; + } + }); } - 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); + void testEntriesMaxSize(int segments, int minSize, int maxSize, int seed) throws IOException { + assert minSize <= maxSize; + Random random = new Random(seed); + int counter = minSize + random.nextInt(9999 + maxSize); + int stride = 1 + random.nextInt(100); + int maxKeySize = "key:".length() + + (int) round(log10(moreThanMaxSize(maxSize) * stride + counter)) + 1; + ChronicleMap map = getSharedMap(minSize, segments, maxKeySize); + testEntriesMaxSize0(segments, minSize, maxSize, counter, stride, map); + } + + void testEntriesMaxSize0(int segments, int minSize, int maxSize, int counter, int stride, + ChronicleMap map) { LongValue longValue = newDirectReference(LongValue.class); try { - for (int j = 0; j < maxSize * 12 / 10 + 300; j++) { - String key = "key:" + counter++; + for (int j = 0; j < moreThanMaxSize(maxSize); j++) { + String key = "key:" + counter; + counter += stride; // give a biased hashcode distribution. if ((Integer.bitCount(key.hashCode()) & 7) != 0) { j--; @@ -168,10 +231,7 @@ public class EntryCountMapTest { map.acquireUsing(key, longValue); longValue.setValue(1); } - long[] a = ((VanillaChronicleMap) map).segmentSizes(); - System.out.println("segs: " + segments + " min: " + minSize + " was " + map.size() + " " - + Arrays.toString(a) - + " sum: " + sum(a)); + dumpMapStats(segments, minSize, map); fail("Expected the map to be full."); } catch (IllegalStateException e) { // calculate the hyperbolic average. @@ -179,17 +239,27 @@ public class EntryCountMapTest { scoreCount++; boolean condition = minSize <= map.size() && map.size() <= maxSize; if (!condition) { - long[] a = ((VanillaChronicleMap) map).segmentSizes(); - System.out.println("segs: " + segments + " min: " + minSize + " was " + map.size() + " " - + Arrays.toString(a) - + " sum: " + sum(a)); - assertTrue("seg: " + segments + ", min: " + minSize + ", size: " + map.size(), condition); + dumpMapStats(segments, minSize, map); + assertTrue("stride: " + stride + ", seg: " + segments + ", min: " + minSize + + ", size: " + map.size(), condition); } } finally { map.close(); } } + private static int moreThanMaxSize(int maxSize) { + return maxSize * 14 / 10 + 300; + } + + private void dumpMapStats(int segments, int minSize, + ChronicleMap map) { + long[] a = ((VanillaChronicleMap) map).segmentSizes(); + System.out.println("segs: " + segments + " min: " + minSize + " was " + map.size() + " " + + Arrays.toString(a) + + " sum: " + sum(a)); + } + private long sum(long[] longs) { long sum = 0; for (long i : longs) { diff --git a/src/test/java/net/openhft/chronicle/map/EventListenerWithTCPSocketReplicationTest.java b/src/test/java/net/openhft/chronicle/map/EventListenerWithTCPSocketReplicationTest.java index 8eb4afd4d..f1ead09b4 100644 --- a/src/test/java/net/openhft/chronicle/map/EventListenerWithTCPSocketReplicationTest.java +++ b/src/test/java/net/openhft/chronicle/map/EventListenerWithTCPSocketReplicationTest.java @@ -1,14 +1,14 @@ /* * 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. @@ -21,17 +21,18 @@ package net.openhft.chronicle.map; import net.openhft.chronicle.hash.replication.TcpTransportAndNetworkConfig; import org.junit.After; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; import java.io.Closeable; import java.io.IOException; import java.net.InetSocketAddress; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; /** - * Test ReplicatedChronicleMap where the Replicated is over a TCP Socket + * Tests ReplicatedChronicleMap when the Replication is over a TCP Socket * * @author Rob Austin. */ @@ -109,7 +110,7 @@ public class EventListenerWithTCPSocketReplicationTest { // we will stores some data into one map here map1.put(5, "EXAMPLE"); - waitTillReplicated(); + waitTillReplicated(5000); Assert.assertEquals("Maps should be equal", map1, map2); Assert.assertTrue("Map 1 should not be empty", !map1.isEmpty()); @@ -142,7 +143,7 @@ public class EventListenerWithTCPSocketReplicationTest { // we will stores some data into one map here map1.put(5, "EXAMPLE"); - waitTillReplicated(); + waitTillReplicated(5000); Assert.assertTrue("The eventListener.onPut should have been called", putWasCalled.get()); Assert.assertEquals(Integer.valueOf(5), keyRef.get()); @@ -171,7 +172,7 @@ public class EventListenerWithTCPSocketReplicationTest { // we will stores some data into one map here map1.put(5, "EXAMPLE"); - waitTillReplicated(); + waitTillReplicated(5000); Assert.assertTrue("The eventListener.onPut should have been called", putWasCalled.get()); Assert.assertEquals(Integer.valueOf(5), keyRef.get()); @@ -200,7 +201,7 @@ public class EventListenerWithTCPSocketReplicationTest { // its we have to wait here for this entry to be removed - as removing an entry that you dont have // on map 1 will have no effect - waitTillReplicated(); + waitTillReplicated(5000); // no more puts after this point putWasCalled.set(false); @@ -208,7 +209,7 @@ public class EventListenerWithTCPSocketReplicationTest { // we will stores some data into one map here map1.remove(5); - waitTillReplicated(); + waitTillReplicated(5000); Assert.assertTrue(!putWasCalled.get()); Assert.assertTrue(wasRemoved.get()); @@ -225,11 +226,26 @@ public class EventListenerWithTCPSocketReplicationTest { .eventListener(eventListener); } - private void waitTillReplicated() throws InterruptedException { - int t = 0; - for (; t < 5000; t++) { + private void waitTillReplicated(final int timeOutMs) throws InterruptedException { + + Map map1UnChanged = new HashMap(); + Map map2UnChanged = new HashMap(); + + int numberOfTimesTheSame = 0; + for (int t = 0; t < timeOutMs + 100; t++) { if (map1.equals(map2)) { - break; + if (map1.equals(map1UnChanged) && map2.equals(map2UnChanged)) { + numberOfTimesTheSame++; + } else { + numberOfTimesTheSame = 0; + map1UnChanged = new HashMap(map1); + map2UnChanged = new HashMap(map2); + } + Thread.sleep(1); + if (numberOfTimesTheSame == 100) { + break; + } + } Thread.sleep(1); } diff --git a/src/test/java/net/openhft/chronicle/map/FirstPrimitiveFieldTest.java b/src/test/java/net/openhft/chronicle/map/FirstPrimitiveFieldTest.java new file mode 100644 index 000000000..d3e24dd12 --- /dev/null +++ b/src/test/java/net/openhft/chronicle/map/FirstPrimitiveFieldTest.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.chronicle.map.fromdocs.BondVOInterface; +import net.openhft.lang.model.DataValueGenerator; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; + +import static org.hamcrest.CoreMatchers.hasItem; + +public class FirstPrimitiveFieldTest { + @Test + public void firstPrimitiveFieldTest() { + Assert.assertThat(Arrays.asList(long.class, double.class), + hasItem(DataValueGenerator.firstPrimitiveFieldType(BondVOInterface.class))); + } +} diff --git a/src/test/java/net/openhft/chronicle/map/IntValueMapTest.java b/src/test/java/net/openhft/chronicle/map/IntValueMapTest.java index b92da7ed3..76e3ea92a 100644 --- a/src/test/java/net/openhft/chronicle/map/IntValueMapTest.java +++ b/src/test/java/net/openhft/chronicle/map/IntValueMapTest.java @@ -36,7 +36,7 @@ public class IntValueMapTest { @Test public void test() throws IOException { - try (final ChronicleMap map = OnHeapUpdatableChronicleMapBuilder + try (final ChronicleMap map = ChronicleMapBuilder .of(IntValue.class, CharSequence.class) .entries(20000) .keyMarshaller(ByteableIntValueMarshaller.INSTANCE).create()) { diff --git a/src/test/java/net/openhft/chronicle/map/Issue42Test.java b/src/test/java/net/openhft/chronicle/map/Issue42Test.java index 0bb6ff305..16cd331b8 100755 --- a/src/test/java/net/openhft/chronicle/map/Issue42Test.java +++ b/src/test/java/net/openhft/chronicle/map/Issue42Test.java @@ -18,7 +18,7 @@ package net.openhft.chronicle.map; -import junit.framework.Assert; + import org.junit.Assert; import net.openhft.lang.Jvm; import org.junit.Test; @@ -32,7 +32,7 @@ public class Issue42Test { if (!Jvm.isWindows()) return; - try (final ChronicleMap map = OnHeapUpdatableChronicleMapBuilder + try (final ChronicleMap map = ChronicleMapBuilder .of(CharSequence.class, CharSequence.class) .entrySize(18) .entries(1500000) diff --git a/src/test/java/net/openhft/chronicle/map/LargeEntriesTest.java b/src/test/java/net/openhft/chronicle/map/LargeEntriesTest.java index 20421483f..a732624a5 100644 --- a/src/test/java/net/openhft/chronicle/map/LargeEntriesTest.java +++ b/src/test/java/net/openhft/chronicle/map/LargeEntriesTest.java @@ -41,23 +41,23 @@ import static org.junit.Assert.*; */ public class LargeEntriesTest { - static final int ENTRIES = 6400; - static final int ENTRY_SIZE = 102400; - private static final boolean TESTING = Boolean.getBoolean("testing"); - @Test - @Ignore public void testLargeStrings() throws ExecutionException, InterruptedException, IOException { + final int ENTRIES = 250; + final int ENTRY_SIZE = 100 * 1024; + File file = File.createTempFile("largeEntries", ".deleteme"); + file.deleteOnExit(); final ChronicleMap map = ChronicleMapBuilder .of(String.class, String.class) .valueMarshaller(SnappyStringMarshaller.INSTANCE) .actualSegments(1) // to force an error. - .entries(ENTRIES * 2) - .entrySize(ENTRY_SIZE / 4) + .entries(ENTRIES) + .entrySize(ENTRY_SIZE / 6) .putReturnsNull(true) .createPersistedTo(file); { + warmUpCompression(ENTRY_SIZE); int threads = 4; //Runtime.getRuntime().availableProcessors(); ExecutorService es = Executors.newFixedThreadPool(threads); final int block = ENTRIES / threads; @@ -66,11 +66,10 @@ public class LargeEntriesTest { List> futureList = new ArrayList<>(); for (int t = 0; t < threads; t++) { final int finalT = t; - final int finalI = i; futureList.add(es.submit(new Runnable() { @Override public void run() { - exerciseLargeStrings(map, finalI, finalT * block, finalT * block + block, ENTRY_SIZE); + exerciseLargeStrings(map, finalT * block, finalT * block + block, ENTRY_SIZE); } })); } @@ -87,7 +86,71 @@ public class LargeEntriesTest { } } - void exerciseLargeStrings(ChronicleMap map, int run, int start, int finish, int entrySize) { + private void warmUpCompression(int entrySize) { + String value = generateValue(entrySize); + DirectBytes bytes = DirectStore.allocate(entrySize / 6).bytes(); + for (int i = 0; i < 5; i++) { + // warmup to compression. + bytes.clear(); + SnappyStringMarshaller.INSTANCE.write(bytes, value); + bytes.flip(); + SnappyStringMarshaller.INSTANCE.read(bytes); + } + bytes.release(); + } + + @Test + @Ignore("Performance Test") + public void testLargeStringsPerf() throws ExecutionException, InterruptedException, IOException { + doLargeEntryPerf(10000, 100 * 1024); + doLargeEntryPerf(1000000, 1024); + doLargeEntryPerf(100000, 10 * 1024); + doLargeEntryPerf(10000, 100 * 1024); + doLargeEntryPerf(3000, 1024 * 1024); + } + + private void doLargeEntryPerf(int ENTRIES, final int ENTRY_SIZE) throws IOException, InterruptedException, ExecutionException { + System.out.printf("Testing %,d entries of %,d KB%n", ENTRIES, ENTRY_SIZE / 1024); + File file = File.createTempFile("largeEntries", ".deleteme"); + file.deleteOnExit(); + final ChronicleMap map = ChronicleMapBuilder + .of(String.class, String.class) + .valueMarshaller(SnappyStringMarshaller.INSTANCE) + .entries(ENTRIES) + .entrySize(ENTRY_SIZE / 7 + 200) + .putReturnsNull(true) + .createPersistedTo(file); + { +// warmUpCompression(ENTRY_SIZE); + int threads = Runtime.getRuntime().availableProcessors(); + ExecutorService es = Executors.newFixedThreadPool(threads); + final int block = ENTRIES / threads; + for (int i = 0; i < 3; i++) { + long start = System.currentTimeMillis(); + List> futureList = new ArrayList<>(); + for (int t = 0; t < threads; t++) { + final int finalT = t; + futureList.add(es.submit(new Runnable() { + @Override + public void run() { + exerciseLargeStrings(map, finalT * block, finalT * block + block, ENTRY_SIZE); + } + })); + } + for (Future future : futureList) { + future.get(); + } + long time = System.currentTimeMillis() - start; + long operations = 3; + System.out.printf("Put/Get rate was %.1f MB/s%n", operations * ENTRIES * ENTRY_SIZE / 1e6 / (time / 1e3)); + } + es.shutdown(); + if (es.isTerminated()) + map.close(); + } + } + + void exerciseLargeStrings(ChronicleMap map, int start, int finish, int entrySize) { /* final Thread thisThread = Thread.currentThread(); Thread monitor = new Thread(new Runnable() { @@ -110,23 +173,7 @@ public class LargeEntriesTest { }); monitor.start(); */ - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - XMLEncoder xml = new XMLEncoder(baos); - Map map2 = new HashMap<>(); - Random rand = new Random(1); - for (int i = 0; i < entrySize / 80; i++) - map2.put("key-" + i, "value-" + rand.nextInt(1000)); - xml.writeObject(map2); - xml.close(); - String value = baos.toString().substring(0, entrySize); - // warmup to compression. - if (run == 0) { - DirectBytes bytes = DirectStore.allocate(entrySize).bytes(); - SnappyStringMarshaller.INSTANCE.write(bytes, value); - bytes.flip(); - SnappyStringMarshaller.INSTANCE.read(bytes); - bytes.release(); - } + String value = generateValue(entrySize); for (int i = start; i < finish; i++) { String key = "key-" + i; @@ -151,4 +198,16 @@ public class LargeEntriesTest { assertEquals(key, entrySize, object.length()); } } + + private String generateValue(int entrySize) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + XMLEncoder xml = new XMLEncoder(baos); + Map map2 = new HashMap<>(); + Random rand = new Random(1); + for (int i = 0; i < entrySize / 80; i++) + map2.put("key-" + i, "value-" + rand.nextInt(1000)); + xml.writeObject(map2); + xml.close(); + return baos.toString().substring(0, entrySize); + } } diff --git a/src/test/java/net/openhft/chronicle/map/MapByNameTest.java b/src/test/java/net/openhft/chronicle/map/MapByNameTest.java index 6fe67ea66..ab39ca043 100644 --- a/src/test/java/net/openhft/chronicle/map/MapByNameTest.java +++ b/src/test/java/net/openhft/chronicle/map/MapByNameTest.java @@ -47,14 +47,17 @@ public class MapByNameTest { @Test public void testSerializingBuilder() throws TimeoutException, InterruptedException, IOException { - final ChronicleMapBuilder builder = ChronicleMapBuilder.of(CharSequence.class, CharSequence.class) - .entries(2) - .minSegments(2) - .name("map1") - .replication((byte) 1, TcpTransportAndNetworkConfig.of(8244)) - .removeReturnsNull(true); + final MapInstanceConfig config = + (MapInstanceConfig) + ChronicleMapBuilder.of(CharSequence.class, CharSequence.class) + .keySize("hello".length()).valueSize("world".length()) + .entries(2) + .minSegments(2) + .replication((byte) 1, TcpTransportAndNetworkConfig.of(8244)) + .removeReturnsNull(true) + .instance().name("map1"); - final ChronicleMap map = findMapByName.create(builder); + final ChronicleMap map = findMapByName.create(config); map.put("hello", "world"); assertEquals(map.get("hello"), "world"); @@ -71,14 +74,16 @@ public class MapByNameTest { NodeDiscovery nodeDiscovery = new NodeDiscovery(); - ChronicleMapBuilder builder = ChronicleMapBuilder.of(CharSequence - .class, CharSequence.class) - .minSegments(2) - .name("myMap") - .removeReturnsNull(true); + MapInstanceConfig config = + (MapInstanceConfig) + ChronicleMapBuilder.of(CharSequence.class, CharSequence.class) + .minSegments(2) + .removeReturnsNull(true) + .instance() + .name("myMap"); ReplicationHubFindByName mapByName = nodeDiscovery.mapByName(); - // mapByName.add(builder); + // mapByName.add(config); ChronicleMap myMap2 = mapByName.from("myMap"); } @@ -89,14 +94,16 @@ public class MapByNameTest { final ReplicationHubFindByName mapByName = nodeDiscovery.mapByName(); Thread.sleep(2000); - ChronicleMapBuilder builder = ChronicleMapBuilder.of(CharSequence - .class, CharSequence.class) - .minSegments(2) - .entries(2) - .name("myMap6") - .removeReturnsNull(true); + MapInstanceConfig config = + (MapInstanceConfig) + ChronicleMapBuilder.of(CharSequence.class, CharSequence.class) + .minSegments(2) + .entries(2) + .removeReturnsNull(true) + .instance() + .name("myMap6"); - ChronicleMap map = mapByName.create(builder); + ChronicleMap map = mapByName.create(config); map.put("hello", "world"); // final ChronicleMap map = mapByName.from("myMap5"); diff --git a/src/test/java/net/openhft/chronicle/map/MarkTest.java b/src/test/java/net/openhft/chronicle/map/MarkTest.java new file mode 100644 index 000000000..9ae684c44 --- /dev/null +++ b/src/test/java/net/openhft/chronicle/map/MarkTest.java @@ -0,0 +1,88 @@ +/* + * 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.MemoryUnit; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +import static org.junit.Assert.assertTrue; + +public class MarkTest { + + static int ENTRIES = 25_000_000; + + @Test(timeout = 25000) + public void inMemoryTest() { + test(new Function, ChronicleMap>() { + @Override + public ChronicleMap apply( + ChronicleMapBuilder builder) { + return builder.create(); + } + }); + } + + @Test(timeout = 25000) + public void persistedTest() { + final File db = Paths.get(System.getProperty("java.io.tmpdir"), "mark").toFile(); + try { + test(new Function, + ChronicleMap>() { + @Override + public ChronicleMap apply( + ChronicleMapBuilder builder) { + try { + return builder.createPersistedTo(db); + } catch (IOException e) { + throw new AssertionError(e); + } + } + }); + System.out.println(MemoryUnit.BYTES.toMegabytes(db.length()) + " MB"); + assertTrue("ChronicleMap of 25 million int-int entries should be lesser than 400MB", + db.length() < MemoryUnit.MEGABYTES.toBytes(400)); + } finally { + db.delete(); + } + } + + private static void test(Function, + ChronicleMap> createMap) { + long ms = System.currentTimeMillis(); + double entriesWithMargin = ENTRIES * 1.1; + long actualEntriesPerSegment = (1L << 17) / 3L; + try (ChronicleMap map = createMap.apply(ChronicleMapBuilder + .of(Integer.class, Integer.class) + .actualSegments((int) (entriesWithMargin / actualEntriesPerSegment)) + .actualEntriesPerSegment(actualEntriesPerSegment) + .putReturnsNull(true) + .removeReturnsNull(true))) { + + Random r = ThreadLocalRandom.current(); + for (int i = 0; i < ENTRIES; i++) { + map.put(r.nextInt(), r.nextInt()); + } + } + System.out.println(System.currentTimeMillis() - ms); + } +} diff --git a/src/test/java/net/openhft/chronicle/map/NoEntryOversizeFactorTest.java b/src/test/java/net/openhft/chronicle/map/NoEntryOversizeFactorTest.java new file mode 100644 index 000000000..7c7671f94 --- /dev/null +++ b/src/test/java/net/openhft/chronicle/map/NoEntryOversizeFactorTest.java @@ -0,0 +1,36 @@ +/* + * 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 org.junit.Assert; +import org.junit.Test; + +public class NoEntryOversizeFactorTest { + + @Test + public void noOversizeFactorTest() { + ChronicleMap map = + ChronicleMapBuilder.of(Integer.class, CharSequence.class) + .valueSize(1).entries(10000L).actualSegments(1).create(); + String ultraLargeValue = ""; + for (int i = 0; i < 100; i++) { + ultraLargeValue += "Hello"; + } + map.put(1, ultraLargeValue); + Assert.assertEquals(ultraLargeValue, map.get(1)); + } +} diff --git a/src/test/java/net/openhft/chronicle/map/NoTcpReplicationSoakTest.java b/src/test/java/net/openhft/chronicle/map/NoTcpReplicationSoakTest.java index b1003d1de..cc33273d2 100644 --- a/src/test/java/net/openhft/chronicle/map/NoTcpReplicationSoakTest.java +++ b/src/test/java/net/openhft/chronicle/map/NoTcpReplicationSoakTest.java @@ -44,17 +44,19 @@ public class NoTcpReplicationSoakTest { map1 = (ReplicatedChronicleMap) ChronicleMapBuilder.of(Integer.class, CharSequence.class) - .name("map1") .replication((byte) 1) .timeProvider(timeProvider) + .instance() + .name("map1") .create(); } { map2 = (ReplicatedChronicleMap) ChronicleMapBuilder.of(Integer.class, CharSequence.class) - .name("map2") .replication((byte) 2) .timeProvider(timeProvider) + .instance() + .name("map2") .create(); } @@ -93,7 +95,6 @@ public class NoTcpReplicationSoakTest { AtomicInteger task = new AtomicInteger(); @Test - @Ignore public void testSoakTestWithRandomData() throws IOException, InterruptedException { ExecutorService executorService = Executors.newSingleThreadExecutor(); System.out.print("SoakTesting "); @@ -117,26 +118,24 @@ public class NoTcpReplicationSoakTest { if (rnd.nextBoolean()) { final CharSequence value = "test" + j; if (rnd.nextBoolean()) { - map1.put(key, value); + map1.put(key, value, (byte) 1, t); task.incrementAndGet(); executorService.submit(new Runnable() { @Override public void run() { - map2.put0(key, value, true, (byte) 1, t); task.decrementAndGet(); } }); } else { - map2.put(key, value); + map2.put(key, value, (byte) 2, t); task.incrementAndGet(); executorService.submit(new Runnable() { @Override public void run() { - map1.put0(key, value, true, (byte) 2, t); task.decrementAndGet(); } @@ -146,26 +145,24 @@ public class NoTcpReplicationSoakTest { } else { if (rnd.nextBoolean()) { - map1.remove(key); + map1.remoteRemove(key, (byte) 1, t); task.incrementAndGet(); executorService.submit(new Runnable() { @Override public void run() { - map2.remoteRemove(key, (byte) 1, t); task.decrementAndGet(); } }); } else { - map2.remove(key); + map2.remoteRemove(key, (byte) 2, t); task.incrementAndGet(); executorService.submit(new Runnable() { @Override public void run() { - map1.remoteRemove(key, (byte) 2, t); task.decrementAndGet(); } @@ -177,10 +174,7 @@ public class NoTcpReplicationSoakTest { while (task.get() > 0) { } - - Assert.assertEquals(new TreeMap(map1), new TreeMap(map2)); - } private void addRandomDelay(ExecutorService executorService, Random rnd) { diff --git a/src/test/java/net/openhft/chronicle/map/OSResizesMain.java b/src/test/java/net/openhft/chronicle/map/OSResizesMain.java index 7d3c986b5..758041c7f 100755 --- a/src/test/java/net/openhft/chronicle/map/OSResizesMain.java +++ b/src/test/java/net/openhft/chronicle/map/OSResizesMain.java @@ -33,13 +33,13 @@ public class OSResizesMain { public static void main(String[] args) throws IOException, InterruptedException { File file = File.createTempFile("over-sized", "deleteme"); int valueSize = 1000 * 1000; - ChronicleMap map = ChronicleMapBuilder.of(String.class, ByteArray.class) - .entrySize(50 * 1024 * 1024) - .valueSize(valueSize + 16) - .createPersistedTo(file); byte[] chars = new byte[valueSize]; - Arrays.fill(chars, (byte) '+'); ByteArray ba = new ByteArray(chars); + ChronicleMap map = ChronicleMapBuilder.of(String.class, ByteArray.class) + .keySize("key-".length() + 4) + .constantValueSizeBySample(ba) + .createPersistedTo(file); + Arrays.fill(chars, (byte) '+'); for (int i = 0; i < 1000; i++) { map.put("key-" + i, ba); } diff --git a/src/test/java/net/openhft/chronicle/map/SerializerTest.java b/src/test/java/net/openhft/chronicle/map/SerializerTest.java index ca7bf70a0..ce7d73b28 100644 --- a/src/test/java/net/openhft/chronicle/map/SerializerTest.java +++ b/src/test/java/net/openhft/chronicle/map/SerializerTest.java @@ -29,8 +29,24 @@ import java.io.*; import java.nio.ByteBuffer; import java.util.Collections; +import static org.junit.Assert.assertEquals; + public class SerializerTest { + @Test + public void testPrefixStringFunctionSerialization() throws Exception { + + ByteBufferBytes b = new ByteBufferBytes(ByteBuffer.allocate(512)); + + CHMUseCasesTest.PrefixStringFunction expected = new CHMUseCasesTest.PrefixStringFunction("New "); + b.writeObject(expected); + + b.clear(); + CHMUseCasesTest.PrefixStringFunction actual = b.readObject(CHMUseCasesTest.PrefixStringFunction.class); + assertEquals(expected, actual); + + } + @Test public void testValueMarshallable() throws Exception { testReadWriteValue("Test"); @@ -70,9 +86,8 @@ public class SerializerTest { final ByteBufferBytes out = new ByteBufferBytes(ByteBuffer.allocateDirect(1024)); ByteBufferBytes in = out.slice(); - ChronicleMapBuilder cBuilder = ChronicleMapBuilder.of(Integer.class, valueClass); - - OnHeapUpdatableChronicleMapBuilder builder = (OnHeapUpdatableChronicleMapBuilder) cBuilder.delegate; + ChronicleMapBuilder builder = + ChronicleMapBuilder.of(Integer.class, valueClass); builder.preMapConstruction(false); @@ -95,9 +110,7 @@ public class SerializerTest { final ByteBufferBytes out = new ByteBufferBytes(ByteBuffer.allocateDirect(1024)); ByteBufferBytes in = out.slice(); - ChronicleMapBuilder cBuilder = ChronicleMapBuilder.of(clazz, Integer.class); - - OnHeapUpdatableChronicleMapBuilder builder = (OnHeapUpdatableChronicleMapBuilder) cBuilder.delegate; + ChronicleMapBuilder builder = ChronicleMapBuilder.of(clazz, Integer.class); builder.preMapConstruction(false); { diff --git a/src/test/java/net/openhft/chronicle/map/StatelessClientTest.java b/src/test/java/net/openhft/chronicle/map/StatelessClientTest.java index a169e7060..81ad75cd2 100644 --- a/src/test/java/net/openhft/chronicle/map/StatelessClientTest.java +++ b/src/test/java/net/openhft/chronicle/map/StatelessClientTest.java @@ -19,12 +19,18 @@ package net.openhft.chronicle.map; import net.openhft.chronicle.hash.replication.TcpTransportAndNetworkConfig; +import net.openhft.lang.io.ByteBufferBytes; +import net.openhft.lang.io.Bytes; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.InetSocketAddress; +import java.nio.ByteBuffer; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; @@ -36,6 +42,7 @@ import static org.junit.Assert.assertTrue; * @author Rob Austin. */ public class StatelessClientTest { + private static final Logger LOG = LoggerFactory.getLogger(StatelessClientTest.class); public static final int SIZE = 2500; static int s_port = 9070; @@ -159,7 +166,7 @@ public class StatelessClientTest { statelessMap.putAll(payload); assertEquals(SIZE, serverMap.size()); - Set> entries = statelessMap.entrySet(); + Set> entries = statelessMap.entrySet(); Map.Entry next = entries.iterator().next(); assertEquals("some value=" + next.getKey(), next.getValue()); @@ -454,6 +461,40 @@ public class StatelessClientTest { } } + @Ignore("HCOLL-245 Stateless Client to support large entries") + @Test(timeout = 10000) + public void testLargeEntries() throws IOException, + InterruptedException { + int valueSize = 1000000; + + char[] value = new char[valueSize]; + + Arrays.fill(value, 'X'); + + String sampleValue = new String(value); + try (ChronicleMap serverMap = + ChronicleMapBuilder.of(Integer.class, CharSequence.class) + .constantValueSizeBySample(sampleValue) + .replication((byte) 2, TcpTransportAndNetworkConfig.of(8056)).create()) { + try (ChronicleMap statelessMap = ChronicleMapBuilder.of(Integer + .class, CharSequence.class) + .constantValueSizeBySample(sampleValue) + .statelessClient(new InetSocketAddress("localhost", 8056)).create()) { + + // for (int i = 0; i < 128; i++) { + + statelessMap.put(1, new String(value)); + + assertEquals("some value", statelessMap.get(1)); + assertEquals(1, statelessMap.size()); + + // } + + assertEquals(null, statelessMap.get(1)); + assertEquals(0, statelessMap.size()); + } + } + } @Test(timeout = 10000) @@ -513,13 +554,13 @@ public class StatelessClientTest { } - @Test + @Test public void testThreadSafeness() throws IOException, InterruptedException { - int nThreads = 1; + int nThreads = 2; final ExecutorService executorService = Executors.newFixedThreadPool(nThreads); - int count = 10000; + int count = 50000; final CountDownLatch latch = new CountDownLatch(count * 2); final AtomicInteger got = new AtomicInteger(); @@ -527,31 +568,33 @@ public class StatelessClientTest { // server try (ChronicleMap server = ChronicleMapBuilder.of(Integer.class, Integer.class) .putReturnsNull(true) - .replication((byte) 1, TcpTransportAndNetworkConfig.of(8059)).create()) { + .replication((byte) 1, TcpTransportAndNetworkConfig.of(8047)).create()) { // stateless client try (ChronicleMap client = ChronicleMapBuilder.of(Integer.class, Integer.class) - .statelessClient(new InetSocketAddress("localhost", 8059)).create()) { + .statelessClient(new InetSocketAddress("localhost", 8047)).create()) { for (int i = 0; i < count; i++) { - final int j = i; executorService.submit(new Runnable() { @Override public void run() { try { + // System.out.print("put("+j+")"); client.put(j, j); latch.countDown(); } catch (Error | Exception e) { - e.printStackTrace(); - System.exit(0); + LOG.error("", e); + //executorService.shutdown(); + } } }); } + for (int i = 0; i < count; i++) { final int j = i; @@ -562,8 +605,8 @@ public class StatelessClientTest { Integer result = client.get(j); - if (result == null) { + System.out.print("entry not found so re-submitting"); executorService.submit(this); return; } @@ -577,7 +620,8 @@ public class StatelessClientTest { latch.countDown(); } catch (Error | Exception e) { e.printStackTrace(); - System.exit(0); + LOG.error("", e); + // executorService.shutdown(); } } }); @@ -585,7 +629,7 @@ public class StatelessClientTest { } - latch.await(); + latch.await(25, TimeUnit.SECONDS); System.out.println("" + count + " messages took " + TimeUnit.MILLISECONDS.toSeconds(System .currentTimeMillis() - startTime) + " seconds, using " + nThreads + "" + @@ -596,14 +640,46 @@ public class StatelessClientTest { } } finally { executorService.shutdownNow(); - executorService.awaitTermination(100, TimeUnit.SECONDS); + executorService.awaitTermination(1000, TimeUnit.SECONDS); } } + @Test(timeout = 10000) + public void testCreateWithByteArrayKeyValue() throws IOException, InterruptedException { + + byte[] key = new byte[4]; + Bytes keyBuffer = new ByteBufferBytes(ByteBuffer.wrap(key)); + + try (ChronicleMap serverMap = ChronicleMapBuilder + .of(byte[].class, byte[].class) + .keySize(4) + .valueSize(4) + .replication((byte) 2, TcpTransportAndNetworkConfig.of(8056)) + .create()) { + + try (ChronicleMap statelessMap = ChronicleMapBuilder + .of(byte[].class, byte[].class) + .keySize(4) + .valueSize(4) + .statelessClient(new InetSocketAddress("localhost", 8056)) + .create()) { + + for (int i = 0; i < SIZE; i++) { + keyBuffer.clear(); + keyBuffer.writeInt(i); + statelessMap.put(key, key); + } + + assertEquals(SIZE, statelessMap.size()); + } + } + } } + + diff --git a/src/test/java/net/openhft/chronicle/map/TCPSocketReplication4WayMapTest.java b/src/test/java/net/openhft/chronicle/map/TCPSocketReplication4WayMapTest.java index 0891b2908..7ace69cb2 100644 --- a/src/test/java/net/openhft/chronicle/map/TCPSocketReplication4WayMapTest.java +++ b/src/test/java/net/openhft/chronicle/map/TCPSocketReplication4WayMapTest.java @@ -89,7 +89,7 @@ public class TCPSocketReplication4WayMapTest { @Test public void test() throws IOException, InterruptedException { - Thread.sleep(1000); + map1.put(1, "EXAMPLE-1"); map2.put(2, "EXAMPLE-1"); map3.put(3, "EXAMPLE-1"); @@ -106,7 +106,7 @@ public class TCPSocketReplication4WayMapTest { @Test public void testBufferOverflow() throws IOException, InterruptedException { - Thread.sleep(1000); + for (int i = 0; i < 50; i++) { map1.put(i, "EXAMPLE-1"); } @@ -122,7 +122,7 @@ public class TCPSocketReplication4WayMapTest { @Test public void testBufferOverflowPutIfAbsent() throws IOException, InterruptedException { - Thread.sleep(1000); + for (int i = 0; i < 1024; i++) { map1.putIfAbsent(i, "EXAMPLE-1"); diff --git a/src/test/java/net/openhft/chronicle/map/TCPSocketReplicationIntValueTest.java b/src/test/java/net/openhft/chronicle/map/TCPSocketReplicationIntValueTest.java index fc45a19ed..a5eb45618 100644 --- a/src/test/java/net/openhft/chronicle/map/TCPSocketReplicationIntValueTest.java +++ b/src/test/java/net/openhft/chronicle/map/TCPSocketReplicationIntValueTest.java @@ -166,7 +166,7 @@ public class TCPSocketReplicationIntValueTest { assertEquals(map1, map2); } - // TODO test this with larger sizes. + @Ignore("HCOLL-244 TCP Replication fails under heavy load") @Test(timeout = 12000) public void testSoakTestWithRandomData() throws IOException, InterruptedException { final long start = System.currentTimeMillis(); diff --git a/src/test/java/net/openhft/chronicle/map/TcpReplicationLargeEntryTest.java b/src/test/java/net/openhft/chronicle/map/TcpReplicationLargeEntryTest.java new file mode 100644 index 000000000..b000d7eba --- /dev/null +++ b/src/test/java/net/openhft/chronicle/map/TcpReplicationLargeEntryTest.java @@ -0,0 +1,148 @@ +package net.openhft.chronicle.map; + +import net.openhft.chronicle.hash.replication.TcpTransportAndNetworkConfig; +import net.openhft.lang.MemoryUnit; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.Closeable; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * @author Rob Austin. + */ +public class TcpReplicationLargeEntryTest { + + private ChronicleMap map1; + private ChronicleMap map2; + + static int s_port = 8010; + int valueSize = (int) MemoryUnit.MEGABYTES.toBytes(1); + + char[] valueX = new char[valueSize - 100]; + char[] valueY = new char[valueSize - 100]; + + @Before + public void setup() throws IOException { + Arrays.fill(valueX, 'X'); + Arrays.fill(valueX, 'Y'); + + String sampleValue = new String(valueX); + final InetSocketAddress endpoint = new InetSocketAddress("localhost", s_port + 1); + + { + final TcpTransportAndNetworkConfig tcpConfig1 = TcpTransportAndNetworkConfig.of(s_port, + endpoint).autoReconnectedUponDroppedConnection(true).name(" map1") + .heartBeatInterval(1, TimeUnit.SECONDS) + .tcpBufferSize(1024 * 64); + + + map1 = ChronicleMapBuilder.of(Integer.class, CharSequence.class) + .constantValueSizeBySample(sampleValue) + .entries(2) + .actualSegments(1) + .replication((byte) 1, tcpConfig1) + .instance() + .name("map1") + .create(); + } + { + final TcpTransportAndNetworkConfig tcpConfig2 = TcpTransportAndNetworkConfig.of + (s_port + 1).autoReconnectedUponDroppedConnection(true).name("map2") + .heartBeatInterval(1, TimeUnit.SECONDS) + .tcpBufferSize(1024 * 64); + + map2 = ChronicleMapBuilder.of(Integer.class, CharSequence.class) + .constantValueSizeBySample(sampleValue) + .entries(2) + .replication((byte) 2, tcpConfig2) + .instance() + .name("map2") + .create(); + + } + s_port += 2; + } + + + @After + public void tearDown() throws InterruptedException { + + for (final Closeable closeable : new Closeable[]{map1, map2}) { + try { + closeable.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + System.gc(); + } + + Set threads; + + @Before + public void sampleThreads() { + threads = Thread.getAllStackTraces().keySet(); + } + + @After + public void checkThreadsShutdown() { + StatelessClientTest.checkThreadsShutdown(threads); + } + + + @Test + public void testLargeValues() throws IOException, InterruptedException { + + String xString = new String(valueX); + String yString = new String(valueY); + + map1.put(1, xString); + map1.put(2, yString); + + System.out.println("\nwaiting till equal"); + + waitTillEqual(10000); + + Assert.assertEquals(xString, map2.get(1)); + Assert.assertEquals(yString, map2.get(2)); + + } + + + private void waitTillEqual(final int timeOutMs) throws InterruptedException { + + Map map1UnChanged = new HashMap(); + Map map2UnChanged = new HashMap(); + + int numberOfTimesTheSame = 0; + for (int t = 0; t < timeOutMs + 100; t++) { + if (map1.equals(map2)) { + if (map1.equals(map1UnChanged) && map2.equals(map2UnChanged)) { + numberOfTimesTheSame++; + } else { + numberOfTimesTheSame = 0; + map1UnChanged = new HashMap(map1); + map2UnChanged = new HashMap(map2); + } + Thread.sleep(1); + if (numberOfTimesTheSame == 100) { + System.out.println("same"); + break; + } + + } + Thread.sleep(1); + } + } +} + diff --git a/src/test/java/net/openhft/chronicle/map/TcpReplicationSoakTest.java b/src/test/java/net/openhft/chronicle/map/TcpReplicationSoakTest.java index 3d17c22d3..cf2365f1f 100644 --- a/src/test/java/net/openhft/chronicle/map/TcpReplicationSoakTest.java +++ b/src/test/java/net/openhft/chronicle/map/TcpReplicationSoakTest.java @@ -14,7 +14,10 @@ import java.io.Closeable; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; -import java.util.*; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.Set; import java.util.concurrent.TimeUnit; /** @@ -38,25 +41,29 @@ public class TcpReplicationSoakTest { { final TcpTransportAndNetworkConfig tcpConfig1 = TcpTransportAndNetworkConfig.of(s_port, endpoint).autoReconnectedUponDroppedConnection(true).name(" map1") - .heartBeatInterval(1, TimeUnit.SECONDS); + .heartBeatInterval(1, TimeUnit.SECONDS) + .tcpBufferSize(1024 * 64); map1 = ChronicleMapBuilder.of(Integer.class, CharSequence.class) .entries(Builder.SIZE + Builder.SIZE) .actualSegments(1) - .name("map1") .replication((byte) 1, tcpConfig1) + .instance() + .name("map1") .create(); } { final TcpTransportAndNetworkConfig tcpConfig2 = TcpTransportAndNetworkConfig.of (s_port + 1).autoReconnectedUponDroppedConnection(true).name("map2") - .heartBeatInterval(1, TimeUnit.SECONDS); + .heartBeatInterval(1, TimeUnit.SECONDS) + .tcpBufferSize(1024 * 64); map2 = ChronicleMapBuilder.of(Integer.class, CharSequence.class) .entries(Builder.SIZE + Builder.SIZE) - .name("map2") .replication((byte) 2, tcpConfig2) + .instance() + .name("map2") .create(); } @@ -96,7 +103,7 @@ public class TcpReplicationSoakTest { System.out.print("SoakTesting "); for (int j = 1; j < 2 * Builder.SIZE; j++) { - if (j % 100 == 0) + if (j % 1000 == 0) System.out.print("."); Random rnd = new Random(j); for (int i = 1; i < 10; i++) { @@ -110,10 +117,10 @@ public class TcpReplicationSoakTest { } } } - Thread.sleep(1000); + System.out.println("\nwaiting till equal"); - waitTillEqual(10000); + waitTillEqual(15000); Assert.assertEquals(map1, map2); diff --git a/src/test/java/net/openhft/chronicle/map/TcpReplicationWithAcquireUsingLockedTest.java b/src/test/java/net/openhft/chronicle/map/TcpReplicationWithAcquireUsingLockedTest.java new file mode 100644 index 000000000..5a8bbeea7 --- /dev/null +++ b/src/test/java/net/openhft/chronicle/map/TcpReplicationWithAcquireUsingLockedTest.java @@ -0,0 +1,176 @@ +package net.openhft.chronicle.map; + +import eg.TestInstrumentVOInterface; +import net.openhft.chronicle.hash.replication.TcpTransportAndNetworkConfig; +import org.junit.Assert; +import org.junit.Test; + +import java.net.InetSocketAddress; + +/** + * @author Rob Austin. + */ +public class TcpReplicationWithAcquireUsingLockedTest { + + + @Test + public void testReplicationWithAcquireUsingLocked() throws Exception { + + + TcpTransportAndNetworkConfig config1 = TcpTransportAndNetworkConfig.of(8076); + + ChronicleMap map2a = null; + ChronicleMap map1a = null; + + try (ChronicleMap map1 = ChronicleMapBuilder.of + (CharSequence.class, TestInstrumentVOInterface.class) + .replication((byte) 1, config1) + .create()) { + + map1a = map1; + + TcpTransportAndNetworkConfig config2 = + TcpTransportAndNetworkConfig.of(8077, new InetSocketAddress("127.0.0.1", 8076)); + + + try (ChronicleMap map2 = + ChronicleMapBuilder.of(CharSequence.class, + TestInstrumentVOInterface.class) + .replication((byte) 2, config2) + .create()) { + + map2a = map2; + + //Store some data into MAP1 + TestInstrumentVOInterface instrumentVOInterface = map1.newValueInstance(); + + + try (WriteContext wc = + map1.acquireUsingLocked("KEY1", instrumentVOInterface)) { + instrumentVOInterface.setSymbol("Flyer"); + instrumentVOInterface.setCurrencyCode("USA"); + } + + + int t = 0; + for (; t < 5000; t++) { + if (map1.equals(map2)) + break; + Thread.sleep(1); + } + + Assert.assertEquals(map1a, map2a); + } + } + + + } + + + @Test + public void testReplicationWithEmptyOffHeapObject() throws Exception { + + + TcpTransportAndNetworkConfig config1 = TcpTransportAndNetworkConfig.of(8076); + + ChronicleMap map2a = null; + ChronicleMap map1a = null; + + try (ChronicleMap map1 = ChronicleMapBuilder.of + (CharSequence.class, + TestInstrumentVOInterface + .class) + .entries(5000L).keySize("hello".length()) + .replication((byte) 1, config1).create()) { + + map1a = map1; + + TcpTransportAndNetworkConfig config2 = TcpTransportAndNetworkConfig + .of(8077, new InetSocketAddress("127.0.0.1", 8076)); + + + try (ChronicleMap map2 = + ChronicleMapBuilder.of(CharSequence.class, + TestInstrumentVOInterface.class) + .putReturnsNull(true) + .removeReturnsNull(true) + .replication((byte) 2, config2) + .entries(5000L).keySize("hello".length()).create()) { + + map2a = map2; + + //Store some data into MAP1 + TestInstrumentVOInterface instrumentVOInterface = map1.newValueInstance(); + + map2a.put("hello", instrumentVOInterface); + + + int t = 0; + for (; t < 5000; t++) { + if (map1.equals(map2)) + break; + Thread.sleep(1); + } + + Assert.assertEquals(map1a, map2a); + } + } + + + } + + + @Test + public void testReplicationWithOffHeapObject() throws Exception { + + + TcpTransportAndNetworkConfig config1 = TcpTransportAndNetworkConfig.of(8076); + + ChronicleMap map2a = null; + ChronicleMap map1a = null; + + try (ChronicleMap map1 = ChronicleMapBuilder.of + (CharSequence.class, + TestInstrumentVOInterface + .class) + .entries(5000L).keySize("hello".length()) + .replication((byte) 1, config1).create()) { + + map1a = map1; + + TcpTransportAndNetworkConfig config2 = + TcpTransportAndNetworkConfig.of(8077, new InetSocketAddress("127.0.0.1", 8076)); + + + try (ChronicleMap map2 = + ChronicleMapBuilder.of(CharSequence.class, + TestInstrumentVOInterface.class) + .putReturnsNull(true) + .removeReturnsNull(true) + .replication((byte) 2, config2) + .entries(5000L).keySize("hello".length()).create()) { + + map2a = map2; + + //Store some data into MAP1 + TestInstrumentVOInterface instrumentVOInterface = map1.newValueInstance(); + instrumentVOInterface.setSymbol("Flyer"); + + map2a.put("hello", instrumentVOInterface); + + + int t = 0; + for (; t < 5000; t++) { + if (map1.equals(map2)) + break; + Thread.sleep(1); + } + + Assert.assertEquals(map1a, map2a); + } + } + + + } + +} diff --git a/src/test/java/net/openhft/chronicle/map/TcpTimeBasedReplicationSoakTest.java b/src/test/java/net/openhft/chronicle/map/TcpTimeBasedReplicationSoakTest.java index d2e5b5720..216903eb2 100644 --- a/src/test/java/net/openhft/chronicle/map/TcpTimeBasedReplicationSoakTest.java +++ b/src/test/java/net/openhft/chronicle/map/TcpTimeBasedReplicationSoakTest.java @@ -102,11 +102,8 @@ public class TcpTimeBasedReplicationSoakTest { @Test - @Ignore public void testSoakTestWithRandomData() throws IOException, InterruptedException { - Thread.sleep(100); - // for (int i = 30; i < 50; i++) { System.out.print("SoakTesting "); int max = 1000000; diff --git a/src/main/java/net/openhft/chronicle/map/NullValueProvider.java b/src/test/java/net/openhft/chronicle/map/WarnOnWindowsTest.java similarity index 59% rename from src/main/java/net/openhft/chronicle/map/NullValueProvider.java rename to src/test/java/net/openhft/chronicle/map/WarnOnWindowsTest.java index 9e79889e8..c3c0e36bc 100644 --- a/src/main/java/net/openhft/chronicle/map/NullValueProvider.java +++ b/src/test/java/net/openhft/chronicle/map/WarnOnWindowsTest.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,11 +16,15 @@ package net.openhft.chronicle.map; -enum NullValueProvider implements DefaultValueProvider { - INSTANCE; +import org.junit.Ignore; +import org.junit.Test; - @Override - public Object get(Object key) { - return null; +public class WarnOnWindowsTest { + + @Test + @Ignore("This test results to OOM/jvm crash, run manually to verify warning output") + public void warnOnWindowsTest() { + ChronicleMapBuilder.of(Long.class, Long.class) + .entries(1_000_000_000).create(); } } diff --git a/src/test/java/net/openhft/chronicle/map/example/ServersMapMain.java b/src/test/java/net/openhft/chronicle/map/example/ServersMapMain.java index 57706e3c0..b4fdd57b7 100644 --- a/src/test/java/net/openhft/chronicle/map/example/ServersMapMain.java +++ b/src/test/java/net/openhft/chronicle/map/example/ServersMapMain.java @@ -39,9 +39,11 @@ public class ServersMapMain { public static void startServer() throws IOException { File file = File.createTempFile("testServersMapMain", ".deleteme"); file.deleteOnExit(); - final ChronicleMap serverMap = ChronicleMapBuilder.of(byte[].class, byte[].class) + final ChronicleMap serverMap = + ChronicleMapBuilder.of(byte[].class, byte[].class) .putReturnsNull(true) - .entrySize(50) + .constantKeySizeBySample(new byte[8]) + .constantValueSizeBySample(new byte[32]) .replication((byte) 2, TcpTransportAndNetworkConfig.of(port)) .createPersistedTo(file); System.out.println("Server started"); @@ -59,7 +61,8 @@ public class ServersMapMain { } else { File file = File.createTempFile("testServersMapMain", ".deleteme"); file.deleteOnExit(); - TcpTransportAndNetworkConfig tcpConfig = TcpTransportAndNetworkConfig.of(port, new InetSocketAddress(hostname, port)); + TcpTransportAndNetworkConfig tcpConfig = + TcpTransportAndNetworkConfig.of(port, new InetSocketAddress(hostname, port)); map = ChronicleMapBuilder.of( byte[].class, byte[].class) @@ -83,11 +86,13 @@ public class ServersMapMain { puts += t2 - t1; gets += t3 - t2; if (t2 - t1 > lastAveragePut * 100 || t3 - t2 > lastAverageGet * 100) - System.out.printf("Took put/get took %.1f/%.1f us%n", (t2 - t1) / 1e3, (t3 - t2) / 1e3); + System.out.printf("Took put/get took %.1f/%.1f us%n", + (t2 - t1) / 1e3, (t3 - t2) / 1e3); } lastAveragePut = puts / entries; lastAverageGet = gets / entries; - System.out.printf("Average took put/get took %.1f/%.1f us%n", lastAveragePut / 1e3, lastAverageGet / 1e3); + System.out.printf("Average took put/get took %.1f/%.1f us%n", + lastAveragePut / 1e3, lastAverageGet / 1e3); } } diff --git a/src/test/java/net/openhft/chronicle/map/example/StringsMapMain.java b/src/test/java/net/openhft/chronicle/map/example/StringsMapMain.java index 08b700c13..4d55a5bfc 100644 --- a/src/test/java/net/openhft/chronicle/map/example/StringsMapMain.java +++ b/src/test/java/net/openhft/chronicle/map/example/StringsMapMain.java @@ -38,10 +38,12 @@ public class StringsMapMain { public static void startServer() throws IOException { File file = File.createTempFile("testServersMapMain", ".deleteme"); file.deleteOnExit(); + int keyAndValueSize = (int) Math.round(Math.log10(entries)); final ChronicleMap serverMap = ChronicleMapBuilder .of(CharSequence.class, CharSequence.class) .putReturnsNull(true) - .entrySize(50) + .keySize(keyAndValueSize) + .valueSize(keyAndValueSize) .replication((byte) 2, TcpTransportAndNetworkConfig.of(port)) .createPersistedTo(file); System.out.println("Server started"); @@ -58,7 +60,8 @@ public class StringsMapMain { } else { File file = File.createTempFile("testServersMapMain", ".deleteme"); file.deleteOnExit(); - TcpTransportAndNetworkConfig tcpConfig = TcpTransportAndNetworkConfig.of(port, new InetSocketAddress(hostname, port)); + TcpTransportAndNetworkConfig tcpConfig = + TcpTransportAndNetworkConfig.of(port, new InetSocketAddress(hostname, port)); map = ChronicleMapBuilder .of(CharSequence.class, CharSequence.class) @@ -90,11 +93,13 @@ public class StringsMapMain { puts += t2 - t1; gets += t3 - t2; // if (t2 - t1 > lastAveragePut * 100 || t3 - t2 > lastAverageGet * 100) -// System.out.printf("Took put/get took %.1f/%.1f us%n", (t2 - t1) / 1e3, (t3 - t2) / 1e3); +// System.out.printf("Took put/get took %.1f/%.1f us%n", +// (t2 - t1) / 1e3, (t3 - t2) / 1e3); } lastAveragePut = puts / entries; lastAverageGet = gets / entries; - System.out.printf("Average took put/get took %.1f/%.1f us%n", lastAveragePut / 1e3, lastAverageGet / 1e3); + System.out.printf("Average took put/get took %.1f/%.1f us%n", + lastAveragePut / 1e3, lastAverageGet / 1e3); } } diff --git a/src/test/java/net/openhft/chronicle/map/fromdocs/LongPairArrayReader.java b/src/test/java/net/openhft/chronicle/map/fromdocs/LongPairArrayReader.java index 198ef89e5..25173b946 100644 --- a/src/test/java/net/openhft/chronicle/map/fromdocs/LongPairArrayReader.java +++ b/src/test/java/net/openhft/chronicle/map/fromdocs/LongPairArrayReader.java @@ -31,6 +31,8 @@ enum LongPairArrayReader implements BytesReader { @Override public LongPair[] read(Bytes bytes, long size, LongPair[] toReuse) { + if (size > Integer.MAX_VALUE * 16L) + throw new IllegalStateException("LongPair[] size couldn't be " + (size / 16L)); int resLen = (int) (size / 16L); LongPair[] res; if (toReuse != null) { 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 8c7bea72d..a7f4f48bf 100644 --- a/src/test/java/net/openhft/chronicle/map/fromdocs/OpenJDKAndHashMapExamplesTest.java +++ b/src/test/java/net/openhft/chronicle/map/fromdocs/OpenJDKAndHashMapExamplesTest.java @@ -56,7 +56,7 @@ public class OpenJDKAndHashMapExamplesTest { file.deleteOnExit(); ChronicleMap chm = ChronicleMapBuilder - .of(String.class, BondVOInterface.class) + .of(String.class, DataValueClasses.directClassFor(BondVOInterface.class)) .keySize(10) .createPersistedTo(file); diff --git a/src/test/java/net/openhft/chronicle/map/fromdocs/pingpong_latency/PingPongCASLeft.java b/src/test/java/net/openhft/chronicle/map/fromdocs/pingpong_latency/PingPongCASLeft.java index cd1a98c5e..1b02f5a6a 100644 --- a/src/test/java/net/openhft/chronicle/map/fromdocs/pingpong_latency/PingPongCASLeft.java +++ b/src/test/java/net/openhft/chronicle/map/fromdocs/pingpong_latency/PingPongCASLeft.java @@ -50,7 +50,8 @@ public class PingPongCASLeft { playPingPong(chm, 4, 5, true, "PingPongCASLEFT"); } - static void playPingPong(ChronicleMap chm, double _coupon, double _coupon2, boolean setFirst, final String desc) { + static void playPingPong(ChronicleMap chm, double _coupon, + double _coupon2, boolean setFirst, final String desc) { BondVOInterface bond1 = newDirectReference(BondVOInterface.class); BondVOInterface bond2 = newDirectReference(BondVOInterface.class); BondVOInterface bond3 = newDirectReference(BondVOInterface.class); @@ -91,6 +92,6 @@ public class PingPongCASLeft { AffinitySupport.setThreadId(); return ChronicleMapBuilder.of(String.class, BondVOInterface.class) .entries(16) - .entrySize(64).create(); + .keySize("369604101".length()).create(); } } \ No newline at end of file diff --git a/src/test/java/net/openhft/chronicle/map/utility/ProcessInstanceLimiter.java b/src/test/java/net/openhft/chronicle/map/utility/ProcessInstanceLimiter.java index cecae189c..3a861300d 100644 --- a/src/test/java/net/openhft/chronicle/map/utility/ProcessInstanceLimiter.java +++ b/src/test/java/net/openhft/chronicle/map/utility/ProcessInstanceLimiter.java @@ -137,7 +137,7 @@ public class ProcessInstanceLimiter implements Runnable { ChronicleMapBuilder builder = ChronicleMapBuilder.of(String.class, Data.class); builder.entries(1000); - builder.entrySize(1024); + builder.keySize((DEFAULT_PROCESS_NAME + "#").length()); this.theSharedMap = builder.createPersistedTo(new File(sharedMapPath)); Thread t = new Thread(this, "ProcessInstanceLimiter updater"); t.setDaemon(true);