diff --git a/pom.xml b/pom.xml index f846e93d8..55df114de 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ 4.0.0 chronicle-map - 2.0.11b-SNAPSHOT + 2.0.12b-SNAPSHOT OpenHFT/Chronicle-Map Chronicle-Map @@ -48,13 +48,13 @@ net.openhft lang - 6.4.15-SNAPSHOT + 6.4.16-SNAPSHOT net.openhft affinity - 2.1.3 + 2.1.4 @@ -167,7 +167,7 @@ maven-javadoc-plugin - http://openhft.github.io/Chronicle-Map/apidocs/ + http://openhft.github.io/Java-Lang/apidocs/ net.openhft.chronicle.hash.serialization.internal diff --git a/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java b/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java index 8cec3130c..7a12a592d 100755 --- a/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java +++ b/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java @@ -46,7 +46,7 @@ import java.util.concurrent.TimeUnit; /** * This interface defines the meaning of configurations, common to {@link - * net.openhft.chronicle.map.OnHeapUpdatableChronicleMapBuilder} and {@link ChronicleSetBuilder}, i. + * net.openhft.chronicle.map.ChronicleMapBuilder} and {@link ChronicleSetBuilder}, i. * e. Chronicle hash container configurations. * *

{@code ChronicleHashBuilder} is mutable. Configuration methods mutate the builder and return @@ -131,7 +131,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.OnHeapUpdatableChronicleMapBuilder#constantValueSizeBySample(Object)} + * #constantKeySizeBySample(Object)} and {@link net.openhft.chronicle.map.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 @@ -184,7 +184,7 @@ public interface ChronicleHashBuilder
  3. 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.OnHeapUpdatableChronicleMapBuilder#valueSize(int) + * net.openhft.chronicle.map.ChronicleMapBuilder#valueSize(int) * value}/{@linkplain #entrySize(int) entry} sizes.
  4. 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 diff --git a/src/main/java/net/openhft/chronicle/hash/FindByName.java b/src/main/java/net/openhft/chronicle/hash/FindByName.java index dcfc27564..77fd8fcfa 100644 --- a/src/main/java/net/openhft/chronicle/hash/FindByName.java +++ b/src/main/java/net/openhft/chronicle/hash/FindByName.java @@ -10,10 +10,12 @@ public interface FindByName { /** * @param name the name of the map or set - * @param + * @param the type returned * @return a chronicle map or set * @throws IllegalArgumentException if a map with this name can not be found * @throws IOException if it not possible to create the map or set + * @throws TimeoutException if the call times out + * @throws InterruptedException if interrupted by another thread */ T from(String name) throws IllegalArgumentException, IOException, TimeoutException, InterruptedException; 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 ccd51fb49..f75804f6d 100644 --- a/src/main/java/net/openhft/chronicle/hash/replication/TcpTransportAndNetworkConfig.java +++ b/src/main/java/net/openhft/chronicle/hash/replication/TcpTransportAndNetworkConfig.java @@ -135,7 +135,9 @@ public final class TcpTransportAndNetworkConfig implements Serializable { } /** - * @param name the name used here is reflected in the Tcp Replicator and is typically used when debugging + * @param name the name used here is reflected in the Tcp Replicator and is typically used when + * debugging + * @return self */ public TcpTransportAndNetworkConfig name(String name) { this.name = name; @@ -164,11 +166,13 @@ public final class TcpTransportAndNetworkConfig implements Serializable { TcpTransportAndNetworkConfig that = (TcpTransportAndNetworkConfig) o; - if (autoReconnectedUponDroppedConnection != that.autoReconnectedUponDroppedConnection) return false; + if (autoReconnectedUponDroppedConnection != that.autoReconnectedUponDroppedConnection) + return false; if (heartBeatInterval != that.heartBeatInterval) return false; if (packetSize != that.packetSize) return false; if (serverPort != that.serverPort) return false; - if (endpoints != null ? !endpoints.equals(that.endpoints) : that.endpoints != null) 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) return false; 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 2e7f415ce..7e7dd742c 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 @@ -103,7 +103,14 @@ public class ByteableMarshaller @Override public void write(Bytes bytes, E e) { - bytes.write(e.bytes(), e.offset(), size); + Bytes eBytes = e.bytes(); + if (eBytes != null) { + bytes.write(eBytes, e.offset(), size); + } else { + throw new NullPointerException("You are trying to write a byteable object of " + + e.getClass() + ", " + + "which bytes are not assigned. I. e. most likely the object is uninitialized."); + } } @Override diff --git a/src/main/java/net/openhft/chronicle/map/AbstractChannelReplicator.java b/src/main/java/net/openhft/chronicle/map/AbstractChannelReplicator.java index 37b6b2e93..80267adbf 100644 --- a/src/main/java/net/openhft/chronicle/map/AbstractChannelReplicator.java +++ b/src/main/java/net/openhft/chronicle/map/AbstractChannelReplicator.java @@ -53,6 +53,7 @@ abstract class AbstractChannelReplicator implements Closeable { public static final int SIZE_OF_SIZE = 4; public static final int SIZE_OF_TRANSACTION_ID = 8; + public static final int SIZE_OF_IS_EXCEPTION = 1; private static final Logger LOG = LoggerFactory.getLogger(AbstractChannelReplicator.class); diff --git a/src/main/java/net/openhft/chronicle/map/BytesMapEventListener.java b/src/main/java/net/openhft/chronicle/map/BytesMapEventListener.java index 5843ae8bc..2746dd9fd 100644 --- a/src/main/java/net/openhft/chronicle/map/BytesMapEventListener.java +++ b/src/main/java/net/openhft/chronicle/map/BytesMapEventListener.java @@ -18,8 +18,6 @@ package net.openhft.chronicle.map; import net.openhft.chronicle.hash.serialization.SizeMarshaller; import net.openhft.lang.io.Bytes; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.Serializable; @@ -29,7 +27,7 @@ import java.io.Serializable; * {@linkplain Bytes#limit() limit}).
  5. *
  6. Methods must not update entry bytes, except in metadata area
  7. *
  8. From {@code metaDataPos} offset metadata area starts in the given {@code entry}, listener - * should be itself aware of it's length (see {@link ChronicleMapBuilder#metaDataBytes()}).
  9. + * should be itself aware of it's length (see {@link ChronicleMapBuilder#metaDataBytes(int)}). *
  10. From {@code keyPos} offset key area in the given {@code entry}, serialized key size * (using {@linkplain ChronicleMapBuilder#keySizeMarshaller(SizeMarshaller)}), directly followed * by the serialized key itself.
  11. diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMap.java b/src/main/java/net/openhft/chronicle/map/ChronicleMap.java index fb5cd53e3..e0cef2c72 100755 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMap.java @@ -220,6 +220,7 @@ public interface ChronicleMap extends ConcurrentMap, ChronicleHash { * Calling this method may or may not block until the operating is complete depending on the * implementation. * + * @param key the key whose associated value is to be returned in the future * @return a future containing the value to which the specified key is mapped after this method * call, or {@code null} if no value is mapped * @see java.util.Map#get(java.lang.Object) @@ -236,10 +237,14 @@ public interface ChronicleMap extends ConcurrentMap, ChronicleHash { * Calling this method may or may not block until the operating is complete depending on the * implementation. * - * @return a future containing the previous value associated with key, or null - * if there was no mapping for key. (A null return can also indicate that the - * map previously associated null with key, if the implementation supports - * null values.) + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key * @return a future containing the + * previous value associated with key, or null if there was no + * mapping for key. (A null return can also indicate that the map + * previously associated null with key, if the implementation + * supports null values.) + * @return a future containing either, the previous value associated with key, or + * null if there was no mapping for key. * @see java.util.Map#put(java.lang.Object, java.lang.Object) * @see java.util.concurrent.Future */ @@ -279,8 +284,8 @@ public interface ChronicleMap extends ConcurrentMap, ChronicleHash { * differ. This method is not performant and as such we recommend it is not used in performance * sensitive code. * - * MAPS CONTAINING KEYS/VALUES GENERATED WITH OFF-HEAP INTERFACES ARE NOT CURRENTLY - * SUPPORTED, CURRENTLY ONLY KEYS/VALUES THAT IMPLEMENT SERIALIZABLE ARE SUPPORTED + * MAPS CONTAINING KEYS/VALUES GENERATED WITH OFF-HEAP INTERFACES ARE NOT CURRENTLY SUPPORTED, + * CURRENTLY ONLY KEYS/VALUES THAT IMPLEMENT SERIALIZABLE ARE SUPPORTED * * @param toFile the file to store all the entries to, the entries will be stored in JSON * format @@ -296,8 +301,8 @@ public interface ChronicleMap extends ConcurrentMap, ChronicleHash { * entries are overwritten. A write lock is only held while each individual entry is inserted * into the map, not over all the entries in the {@link java.io.File} * - * MAPS CONTAINING KEYS/VALUES GENERATED WITH OFF-HEAP INTERFACES ARE NOT CURRENTLY - * SUPPORTED, CURRENTLY ONLY KEYS/VALUES THAT IMPLEMENT SERIALIZABLE ARE SUPPORTED + * MAPS CONTAINING KEYS/VALUES GENERATED WITH OFF-HEAP INTERFACES ARE NOT CURRENTLY SUPPORTED, + * CURRENTLY ONLY KEYS/VALUES THAT IMPLEMENT SERIALIZABLE ARE SUPPORTED * * @param fromFile the file containing entries ( in JSON format ) which will be deserialized and * {@link java.util.Map#put(java.lang.Object, java.lang.Object)} into the map diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilderI.java b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilderI.java index ac81e54bb..ed4dd05d5 100755 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilderI.java +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilderI.java @@ -30,25 +30,25 @@ 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). + * #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. + * 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. + *

    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). + *

    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. * @@ -58,11 +58,11 @@ public interface ChronicleMapBuilderI extends Serializable { ChronicleMapBuilderI entryAndValueAlignment(Alignment alignment); /** - * {@inheritDoc} Also, it overrides any previous {@link #prepareValueBytesOnAcquire} configuration to this - * {@code ChronicleMapBuilder}. + * {@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. + *

    By default, the default value is not specified, default {@linkplain + * #prepareValueBytesOnAcquire prepare value bytes routine} is specified instead. * * @see #defaultValueProvider(DefaultValueProvider) * @see #prepareValueBytesOnAcquire(PrepareValueBytes) @@ -70,15 +70,16 @@ public interface ChronicleMapBuilderI extends Serializable { 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. + * 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 + * @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) @@ -106,16 +107,17 @@ public interface ChronicleMapBuilderI extends Serializable { 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. + * 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 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. + *

    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 @@ -127,15 +129,16 @@ public interface ChronicleMapBuilderI extends Serializable { 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. + * 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 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. + *

    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 @@ -164,8 +167,8 @@ public interface ChronicleMapBuilderI extends Serializable { /** * {@inheritDoc} * - * @throws IllegalStateException if custom value marshaller is specified or value class is not either - * {@code BytesMarshallable} or {@code Externalizable} + * @throws IllegalStateException if custom value marshaller is specified or value class is not + * either {@code BytesMarshallable} or {@code Externalizable} */ ChronicleMapBuilderI valueDeserializationFactory( @@ -174,8 +177,8 @@ public interface ChronicleMapBuilderI extends Serializable { /** * {@inheritDoc} * - *

    By default, default value provider is not specified, {@link #defaultValue(Object) default value} is - * specified instead. + *

    By default, default value provider is not specified, {@link #defaultValue(Object) default + * value} is specified instead. * * @see #defaultValue(Object) */ @@ -184,8 +187,8 @@ public interface ChronicleMapBuilderI extends Serializable { @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 + * 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 @@ -199,20 +202,21 @@ public interface ChronicleMapBuilderI extends Serializable { @NotNull BytesMarshaller valueMarshaller); /** - * Configures the marshallers, used to serialize/deserialize values to/from off-heap memory in maps, - * created by this builder. See the + * 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)}. + *

    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. + *

    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) + * @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 valueReader the new {@link net.openhft.lang.io.Bytes} → value object reader strategy * @return this builder back * @see #valueMarshaller(BytesMarshaller) */ @@ -220,19 +224,21 @@ public interface ChronicleMapBuilderI extends Serializable { @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. + * 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. + * 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 + * @param valueSizeMarshaller the new marshaller, used to serialize actual value sizes to + * off-heap memory * @return this builder back */ ChronicleMapBuilderI valueSizeMarshaller( @@ -295,13 +301,15 @@ public interface ChronicleMapBuilderI extends Serializable { 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 + * @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); diff --git a/src/main/java/net/openhft/chronicle/map/MapEventListener.java b/src/main/java/net/openhft/chronicle/map/MapEventListener.java index 72b8e2679..5cf73f8b9 100644 --- a/src/main/java/net/openhft/chronicle/map/MapEventListener.java +++ b/src/main/java/net/openhft/chronicle/map/MapEventListener.java @@ -26,22 +26,22 @@ import org.slf4j.LoggerFactory; import java.io.Serializable; /** - * Contains methods which are called when {@link ChronicleMap} key events occur. Typical use cases:

      - *
    • Map data backup / replication.
    • Logging, monitoring, debugging.
    + * Contains methods which are called when {@link ChronicleMap} key events occur. Typical use cases: + *
    • Map data backup / replication.
    • Logging, monitoring, debugging.
    * - *

    This is an adapter class - all methods have default implementations as no-ops. Extend this class and - * override only methods corresponding the events you are interested in. + *

    This is an adapter class - all methods have default implementations as no-ops. Extend this + * 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. * *

    See {@link #logging(String)} implementation. * - *

    All these calls are synchronous while a {@code ChronicleMap} lock is held so make them as quick as - * possible. + *

    All these calls are synchronous while a {@code ChronicleMap} lock is held so make them as + * quick as possible. * - *

    The {@code entry} passed to the methods is {@code Bytes} instance positioned at meta data area. See - * {@link ChronicleHashBuilder#metaDataBytes(int)} for more information. + *

    The {@code entry} passed to the methods is {@code Bytes} instance positioned at meta data + * area. See {@link ChronicleHashBuilder#metaDataBytes(int)} for more information. * * @param key type of the maps, trackable by this event listener * @param value type of the maps, trackable by this event listener @@ -51,15 +51,17 @@ public abstract class MapEventListener implements Serializable { private static final long serialVersionUID = 0L; /** - * Returns the map event listener, which logs strings like - * "{@linkplain ChronicleMap#file() map file} opType key value", where opType is either "get", - * "put" or "remove", to the logger provided by SLF4J. + * Returns the map event listener, which logs strings like "{@linkplain ChronicleMap#file() map + * file} opType key value", where opType is either "get", "put" or "remove", to the logger + * provided by SLF4J. * - * @param the map key type - * @param the map value type + * @param the map key type + * @param the map value type + * @param prefix logging prefix * @return the logging event listener */ public static MapEventListener logging(String prefix) { + //noinspection unchecked return new LoggingMapEventListener(prefix); } @@ -90,40 +92,42 @@ public abstract class MapEventListener implements Serializable { } /** - * This method is called if the key is found in the map during {@link ChronicleMap#get get}, {@link - * ChronicleMap#getUsing getUsing} or {@link ChronicleMap#acquireUsing acquireUsing} method call. + * This method is called if the key is found in the map during {@link ChronicleMap#get get}, + * {@link ChronicleMap#getUsing getUsing} or {@link ChronicleMap#acquireUsing acquireUsing} + * method call. * - * @param key the key looked up - * @param foundValue the value found for the key + * @param key the key looked up + * @param foundValue the value found for the key */ public void onGetFound(K key, V foundValue) { // do nothing } /** - * 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 + * 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 {@link - * ChronicleMap#acquireUsing acquireUsing} call is put for the key, this method is called as well. + * AbstractChronicleMapBuilder#defaultValue(Object) default} for the map or obtained during + * {@link ChronicleMap#acquireUsing acquireUsing} call is put for the key, this method is called + * as well. * *

    This method is called when put is already happened. * * @param key the key the given value is put for * @param newValue the value which is now associated with the given key - * @param replacedValue the value which was replaced by {@code newValue}, {@code null} - * if the key was absent in the map before current {@code ChronicleMap} + * @param replacedValue the value which was replaced by {@code newValue}, {@code null} if the + * key was absent in the map before current {@code ChronicleMap} */ public void onPut(K key, V newValue, @Nullable V replacedValue) { // do nothing } /** - * This is called when an entry is removed. Misses, i. e. when {@code map.remove(key)} - * is called, but key is already absent in the map, are not notified. + * This is called when an entry is removed. Misses, i. e. when {@code map.remove(key)} is + * called, but key is already absent in the map, are not notified. * - * @param key the key removed from the map - * @param value the value which was associated with the given key + * @param key the key removed from the map + * @param value the value which was associated with the given key */ public void onRemove(K key, V value) { // do nothing diff --git a/src/main/java/net/openhft/chronicle/map/OnHeapUpdatableChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/OnHeapUpdatableChronicleMapBuilder.java index 3b07010f0..eaed43fdf 100644 --- a/src/main/java/net/openhft/chronicle/map/OnHeapUpdatableChronicleMapBuilder.java +++ b/src/main/java/net/openhft/chronicle/map/OnHeapUpdatableChronicleMapBuilder.java @@ -36,7 +36,7 @@ import java.io.Serializable; * 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 + * {@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. * @@ -103,8 +103,8 @@ final class OnHeapUpdatableChronicleMapBuilder /** * 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 + * 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. @@ -123,9 +123,9 @@ final class OnHeapUpdatableChronicleMapBuilder /** * 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 + * 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 + * 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 @@ -149,7 +149,7 @@ final class OnHeapUpdatableChronicleMapBuilder * // 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 + *

    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. */ @@ -160,7 +160,7 @@ final class OnHeapUpdatableChronicleMapBuilder } /** - * {@inheritDoc}

    If {@linkplain #valueMarshaller(BytesMarshaller) custom value marshaller} is + * {@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 diff --git a/src/main/java/net/openhft/chronicle/map/ReadValue.java b/src/main/java/net/openhft/chronicle/map/ReadValue.java index 8d925ce37..2b90e13d7 100644 --- a/src/main/java/net/openhft/chronicle/map/ReadValue.java +++ b/src/main/java/net/openhft/chronicle/map/ReadValue.java @@ -24,6 +24,9 @@ import org.jetbrains.annotations.NotNull; * @param result type, map value type or Bytes */ interface ReadValue { + /** + * Updates the entry position by valueSize. + */ RV readValue(@NotNull ThreadLocalCopies copies, Bytes entry, RV usingValue, long valueSize); RV readNull(); } diff --git a/src/main/java/net/openhft/chronicle/map/ReplicatedChronicleMap.java b/src/main/java/net/openhft/chronicle/map/ReplicatedChronicleMap.java index 9b517eb0c..eebe5cc7a 100644 --- a/src/main/java/net/openhft/chronicle/map/ReplicatedChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/ReplicatedChronicleMap.java @@ -48,36 +48,39 @@ import static net.openhft.lang.MemoryUnit.*; import static net.openhft.lang.collection.DirectBitSet.NOT_FOUND; /** - *

    A Replicating Multi Master HashMap

    Each remote hash map, mirrors its changes over to another - * remote hash map, neither hash map is considered the master store of data, each hash map uses timestamps to - * reconcile changes. We refer to an instance of a remote hash-map as a node. A node will be connected to any - * number of other nodes, for the first implementation the maximum number of nodes will be fixed. The data - * that is stored locally in each node will become eventually consistent. So changes made to one node, for - * example by calling put() will be replicated over to the other node. To achieve a high level of performance - * and throughput, the call to put() won’t block, with concurrentHashMap, It is typical to check the return - * code of some methods to obtain the old value for example remove(). Due to the loose coupling and lock free - * nature of this multi master implementation, this return value will only be the old value on the nodes - * local data store. In other words the nodes are only concurrent locally. Its worth realising that another - * node performing exactly the same operation may return a different value. However reconciliation will ensure - * the maps themselves become eventually consistent.

    Reconciliation

    If two ( or more nodes ) - * were to receive a change to their maps for the same key but different values, say by a user of the maps, - * calling the put(key, value). Then, initially each node will update its local store and each local store - * will hold a different value, but the aim of multi master replication is to provide eventual consistency - * across the nodes. So, with multi master when ever a node is changed it will notify the other nodes of its - * change. We will refer to this notification as an event. The event will hold a timestamp indicating the time - * the change occurred, it will also hold the state transition, in this case it was a put with a key and - * value. Eventual consistency is achieved by looking at the timestamp from the remote node, if for a given - * key, the remote nodes timestamp is newer than the local nodes timestamp, then the event from the remote - * node will be applied to the local node, otherwise the event will be ignored.

    However there is an - * edge case that we have to concern ourselves with, 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.

    + *

    A Replicating Multi Master HashMap

    Each remote hash map, mirrors its changes over to + * another remote hash map, neither hash map is considered the master store of data, each hash map + * uses timestamps to reconcile changes. We refer to an instance of a remote hash-map as a node. A + * node will be connected to any number of other nodes, for the first implementation the maximum + * number of nodes will be fixed. The data that is stored locally in each node will become + * eventually consistent. So changes made to one node, for example by calling put() will be + * replicated over to the other node. To achieve a high level of performance and throughput, the + * call to put() won’t block, with concurrentHashMap, It is typical to check the return code of some + * methods to obtain the old value for example remove(). Due to the loose coupling and lock free + * nature of this multi master implementation, this return value will only be the old value on the + * nodes local data store. In other words the nodes are only concurrent locally. Its worth realising + * that another node performing exactly the same operation may return a different value. However + * reconciliation will ensure the maps themselves become eventually consistent.

    + *

    Reconciliation

    If two ( or more nodes ) were to receive a change to their maps for + * the same key but different values, say by a user of the maps, calling the put(key, value). Then, + * initially each node will update its local store and each local store will hold a different value, + * but the aim of multi master replication is to provide eventual consistency across the nodes. So, + * with multi master when ever a node is changed it will notify the other nodes of its change. We + * will refer to this notification as an event. The event will hold a timestamp indicating the time + * the change occurred, it will also hold the state transition, in this case it was a put with a key + * and value. Eventual consistency is achieved by looking at the timestamp from the remote node, if + * for a given key, the remote nodes timestamp is newer than the local nodes timestamp, then the + * event from the remote node will be applied to the local node, otherwise the event will be + * ignored.

    However there is an edge case that we have to concern ourselves with, 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.

    * * @param the entries key type * @param the entries value type @@ -207,7 +210,8 @@ final class ReplicatedChronicleMap>instance(), BytesBytesInterop.INSTANCE, key, keySize, keyBytesToInstance, getRemoteBytesValueInterops, value, valueBytesToInstance, - replaceIfPresent, readValue, false, localIdentifier, currentTime());; + replaceIfPresent, readValue, false, localIdentifier, currentTime()); + ; } /** @@ -239,8 +243,8 @@ final class ReplicatedChronicleMap, RV, VB extends RV, VBI, MVBI extends MetaBytesInterop> @@ -355,6 +387,11 @@ final class ReplicatedChronicleMap.Segment segment, long pos) { + onRelocation(segment, pos); // erase modification bits + } + @Override void onRemove(VanillaChronicleMap.Segment segment, long pos) { for (long next = modIterSet.nextSetBit(0L); next > 0L; @@ -367,6 +404,11 @@ final class ReplicatedChronicleMap.Segment segment, long pos) { + onRelocation(segment, pos); // erase modification bits + } + @Override void onRelocation(VanillaChronicleMap.Segment segment, long pos) { for (long next = modIterSet.nextSetBit(0L); next > 0L; @@ -380,8 +422,8 @@ final class ReplicatedChronicleMap, RV, VB extends RV, VBI, MVBI extends MetaBytesInterop> @@ -1057,6 +1095,7 @@ final class ReplicatedChronicleMapOnce a change occurs to a map, map replication requires that these changes are picked up by another - * thread, this class provides an iterator like interface to poll for such changes.

    In most cases - * the thread that adds data to the node is unlikely to be the same thread that replicates the data over - * to the other nodes, so data will have to be marshaled between the main thread storing data to the map, - * and the thread running the replication.

    One way to perform this marshalling, would be to pipe - * the data into a queue. However, This class takes another approach. It uses a bit set, and marks bits - * which correspond to the indexes of the entries that have changed. It then provides an iterator like - * interface to poll for such changes.

    + *

    Once a change occurs to a map, map replication requires that these changes are picked up + * by another thread, this class provides an iterator like interface to poll for such changes. + *

    In most cases the thread that adds data to the node is unlikely to be the same thread + * that replicates the data over to the other nodes, so data will have to be marshaled between + * the main thread storing data to the map, and the thread running the replication.

    One + * way to perform this marshalling, would be to pipe the data into a queue. However, This class + * takes another approach. It uses a bit set, and marks bits which correspond to the indexes of + * the entries that have changed. It then provides an iterator like interface to poll for such + * changes.

    * * @author Rob Austin. */ @@ -1344,7 +1384,8 @@ final class ReplicatedChronicleMap implements ChronicleMap, Closeable, Clon this.connectionOutBuffer.clear(); - // read a single byte back + if (!clientChannel.finishConnect() || !clientChannel.socket().isBound()) + return; + + // read a single byte back while (this.connectionOutBuffer.position() <= 0) { - clientChannel.read(this.connectionOutBuffer); + clientChannel.read(this.connectionOutBuffer); // the remote identifier checkTimeout(timeoutTime); } @@ -938,9 +941,6 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon // send out all the bytes send(bytes, timeoutTime); - bytes.clear(); - bytes.buffer().clear(); - return blockingFetch(timeoutTime, transactionId); } catch (java.nio.channels.ClosedChannelException | ClosedConnectionException e) { checkTimeout(timeoutTime); @@ -963,6 +963,9 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon private Bytes blockingFetch(long timeoutTime, long transactionId) throws IOException { + bytes.clear(); + bytes.buffer().clear(); + // the number of bytes in the response final int size = receive(SIZE_OF_SIZE, timeoutTime).readInt(); @@ -976,15 +979,22 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon } else bytes.limit(bytes.capacity()); - // block until we have received all the byte in this chunk + // block until we have received all the bytes in this chunk receive(size, timeoutTime); - final boolean isException = bytes.readBoolean(); - final long inTransactionId = bytes.readLong(); - if (inTransactionId != transactionId) - throw new IllegalStateException("the received transaction-id=" + inTransactionId + - ", does not match the expected transaction-id=" + transactionId); + final long inTransactionId = bytes.readLong(); + final boolean isException = bytes.readBoolean(); + + if (inTransactionId != transactionId) { + LOG.error("", new IllegalStateException("Skipped Message with transaction-id=" + + inTransactionId + + ", 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")); + + blockingFetch(timeoutTime, transactionId); + } if (isException) { Throwable throwable = (Throwable) bytes.readObject(); diff --git a/src/main/java/net/openhft/chronicle/map/TcpReplicator.java b/src/main/java/net/openhft/chronicle/map/TcpReplicator.java index 8201f6cfb..9d78cb591 100644 --- a/src/main/java/net/openhft/chronicle/map/TcpReplicator.java +++ b/src/main/java/net/openhft/chronicle/map/TcpReplicator.java @@ -203,6 +203,9 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable LOG.debug("", e); } catch (Exception e) { LOG.error("", e); + } catch (Throwable e) { + LOG.error("", e); + throw e; } finally { if (LOG.isDebugEnabled()) @@ -628,11 +631,14 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable attached.entryWriter.entriesToBuffer(attached.remoteModificationIterator, key); try { - final int bytesJustWritten = attached.entryWriter.writeBufferToSocket(socketChannel, + final int len = attached.entryWriter.writeBufferToSocket(socketChannel, approxTime); - if (bytesJustWritten > 0) - contemplateThrottleWrites(bytesJustWritten); + if (len == -1) + socketChannel.close(); + + if (len > 0) + contemplateThrottleWrites(len); if (!attached.entryWriter.hasBytesToWrite() && !attached.entryWriter.isWorkIncomplete()) // TURN OP_WRITE_OFF @@ -659,6 +665,7 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable int len = attached.entryReader.readSocketToBuffer(socketChannel); if (len == -1) { + socketChannel.register(selector, 0); if (replicationConfig.autoReconnectedUponDroppedConnection()) { AbstractConnector connector = attached.connector; if (connector != null) @@ -1018,6 +1025,7 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable // if we still have some unwritten writer from last time lastSentTime = approxTime; + assert in.position() <= Integer.MAX_VALUE; int size = (int) in.position(); out.limit(size); @@ -1027,7 +1035,6 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable LOG.debug("bytes-written=" + len); if (len == size) { - // assert len == size; out.clear(); in.clear(); } else { @@ -1195,9 +1202,9 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable final long limit = out.limit(); out.limit(nextEntryPos); - boolean isStateless = (state != 1); + boolean isStatelessClient = (state != 1); - if (isStateless) { + if (isStatelessClient) { if (statelessServerConnector == null) { LOG.error("", new IllegalArgumentException("received an event " + "from a stateless map, stateless maps are not " + @@ -1247,6 +1254,7 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable return; in.limit(in.position()); + assert out.position() < Integer.MAX_VALUE; in.position((int) out.position()); in.compact(); @@ -1268,8 +1276,6 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable return out.readLong(); else return Long.MIN_VALUE; - - // return (out.remaining() >= 8) ? out.readLong() : Long.MIN_VALUE; } public long remoteHeartbeatIntervalFromBuffer() { @@ -1514,7 +1520,7 @@ class StatelessServerConnector { writeException(writer, e); - writeSizeAndFlags(sizeLocation, true, writer); + writeSizeAndFlags(sizeLocation+SIZE_OF_TRANSACTION_ID, true, writer); return null; } @@ -1839,17 +1845,19 @@ class StatelessServerConnector { } private long reflectTransactionId(Bytes reader, Bytes writer) { - final long sizeLocation = writer.position(); - writer.skip(TRANSACTION_ID_OFFSET); // isException + SIZE_OF_SIZE final long transactionId = reader.readLong(); + final long sizeLocation = writer.position(); + writer.skip(SIZE_OF_SIZE); writer.writeLong(transactionId); + writer.writeBoolean(false); // isException return sizeLocation; } private void writeSizeAndFlags(long locationOfSize, boolean isException, Bytes out) { final long size = out.position() - locationOfSize; out.writeInt(locationOfSize, (int) size); // size - out.writeBoolean(locationOfSize + SIZE_OF_SIZE, isException); // isException + out.skip(SIZE_OF_TRANSACTION_ID); + out.writeBoolean(isException); // isException } private void writeException(Bytes out, Throwable e) { @@ -1881,10 +1889,11 @@ class StatelessServerConnector { private long header(Bytes writer, final long transactionId) { final long sizeLocation = writer.position(); - writer.skip(TRANSACTION_ID_OFFSET); + writer.skip(SIZE_OF_SIZE); + writer.writeLong(transactionId); // exception - writer.writeLong(transactionId); + writer.skip(1); // hasAnotherChunk writer.skip(1); @@ -1903,12 +1912,12 @@ class StatelessServerConnector { // size in bytes writer.writeInt(size); - // is exception - writer.writeBoolean(false); - //transaction id; writer.skip(8); + // is exception + writer.writeBoolean(false); + writer.writeBoolean(hasAnotherChunk); // count diff --git a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java index 4ba59a3e7..94e93d4d7 100755 --- a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java @@ -316,18 +316,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) { @@ -384,7 +384,7 @@ class VanillaChronicleMap, } static abstract class ReadValueToBytes implements ReadValue { - private SizeMarshaller valueSizeMarshaller; + SizeMarshaller valueSizeMarshaller; abstract Bytes bytes(long valueSize); @@ -398,6 +398,7 @@ class VanillaChronicleMap, usingBytes = bytes(valueSize); valueSizeMarshaller.writeSize(usingBytes, valueSize); usingBytes.write(entry, entry.position(), valueSize); + entry.skip(valueSize); return usingBytes; } } @@ -434,6 +435,7 @@ class VanillaChronicleMap, @Override Bytes bytes(long valueSize) { + valueSize = valueSizeMarshaller.sizeEncodingSize(valueSize) + valueSize; if (lazyBytes != null) { if (lazyBytes.capacity() < valueSize) { DirectStore store = (DirectStore) lazyBytes.store(); @@ -1174,6 +1176,10 @@ class VanillaChronicleMap, public final boolean replace(@net.openhft.lang.model.constraints.NotNull K key, @net.openhft.lang.model.constraints.NotNull V oldValue, @net.openhft.lang.model.constraints.NotNull V newValue) { + + if (key == null || oldValue == null || newValue == null) + throw new NullPointerException(); + checkValue(oldValue); return (Boolean) replaceIfValueIs(key, oldValue, newValue); } @@ -1185,6 +1191,9 @@ class VanillaChronicleMap, @SuppressWarnings("unchecked") public final V replace(@net.openhft.lang.model.constraints.NotNull final K key, @net.openhft.lang.model.constraints.NotNull final V value) { + if (key == null || value == null) + throw new NullPointerException(); + return (V) replaceIfValueIs(key, null, value); } @@ -1359,10 +1368,18 @@ class VanillaChronicleMap, // do nothing } + void onRemoteRemove(Segment segment, long pos) { + // do nothing + } + void onPut(Segment segment, long pos) { // do nothing } + void onRemotePut(Segment segment, long pos) { + // do nothing + } + void onRelocation(Segment segment, long pos) { // do nothing } @@ -1595,6 +1612,7 @@ class VanillaChronicleMap, } long startWriteLock = 0; + @Override public final WriteLocked writeLock( @Nullable SegmentState segmentState) { @@ -1930,8 +1948,7 @@ class VanillaChronicleMap, metaValueInterop, valueInterop, value, valueSize, searchedHashLookup); // put callbacks - if (!remote) - onPut(this, segmentState.pos); + onPutMaybeRemote(segmentState.pos, remote); if (bytesEventListener != null) bytesEventListener.onPut(entry, 0L, metaDataBytes, valueSizePos, false); if (eventListener != null) { @@ -1942,6 +1959,14 @@ class VanillaChronicleMap, return resultUnused ? null : prevValue; } + final void onPutMaybeRemote(long pos, boolean remote) { + if (remote) { + onRemotePut(this, pos); + } else { + onPut(this, pos); + } + } + /** * 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 @@ -2021,10 +2046,6 @@ class VanillaChronicleMap, return ret; } - private void setBlocks(long pos, int blocks) { - freeList.set(pos, pos + blocks); - } - private void updateNextPosToSearchFrom(long allocated, int blocks) { if ((nextPosToSearchFrom = allocated + blocks) >= entriesPerSegment) nextPosToSearchFrom = 0L; @@ -2155,16 +2176,15 @@ class VanillaChronicleMap, // update segment state if (removeFromMultiMap) { hashLookup.removePrevPos(); + long entrySizeInBytes = entry.positionAddr() + valueSize - entry.startAddr(); + free(pos, inBlocks(entrySizeInBytes)); } else { hashLookup.removePosition(pos); } decrementSize(); - long entrySizeInBytes = entry.positionAddr() + valueSize - entry.startAddr(); - free(pos, inBlocks(entrySizeInBytes)); // remove callbacks - if (!remote) - onRemove(this, pos); + onRemoveMaybeRemote(pos, remote); if (bytesEventListener != null) bytesEventListener.onRemove(entry, 0L, metaDataBytes, valueSizePos); if (eventListener != null) { @@ -2177,6 +2197,14 @@ class VanillaChronicleMap, return booleanResult ? Boolean.TRUE : removedValue; } + final void onRemoveMaybeRemote(long pos, boolean remote) { + if (remote) { + onRemoteRemove(this, pos); + } else { + onRemove(this, pos); + } + } + private > boolean containsKey(ThreadLocalCopies copies, MKBI metaKeyInterop, KBI keyInterop, KB key, long keySize, long hash2) { @@ -2331,15 +2359,15 @@ class VanillaChronicleMap, MetaBytesWriter metaElemWriter, EW elemWriter, E newElem, long newElemSize, MultiMap searchedHashLookup) { - long valueSizeAddr = entry.address() + valueSizePos; + long entryStartAddr = entry.address(); + long valueSizeAddr = entryStartAddr + valueSizePos; long newValueAddr = alignment.alignAddr( valueSizeAddr + valueSizeMarshaller.sizeEncodingSize(newElemSize)); long newEntryEndAddr = newValueAddr + newElemSize; - long entryStartAddr = entry.address(); - long oldEntrySize = entryEndAddr - entryStartAddr; - int oldSizeInBlocks = inBlocks(oldEntrySize); newValueDoesNotFit: if (newEntryEndAddr != entryEndAddr) { + long oldEntrySize = entryEndAddr - entryStartAddr; + int oldSizeInBlocks = inBlocks(oldEntrySize); int newSizeInBlocks = inBlocks(newEntryEndAddr - entryStartAddr); if (newSizeInBlocks > oldSizeInBlocks) { if (newSizeInBlocks > MAX_ENTRY_OVERSIZE_FACTOR) { @@ -2373,9 +2401,6 @@ class VanillaChronicleMap, // But if these blocks will be taken by that time, // this entry will need to be relocated. } - } else { - // todo investivate when we are able to avoid this assignment - setBlocks(pos, oldSizeInBlocks); } // Common code for all cases entry.position(valueSizePos); @@ -2585,11 +2610,11 @@ class VanillaChronicleMap, final long entryEndAddr = entry.positionAddr() + valueSize; if (removeFromMultiMap) { segment.hashLookup().remove(segmentHash, pos); + segment.free(pos, segment.inBlocks(entryEndAddr - entry.address())); } else { segment.hashLookup().removePosition(pos); } segment.decrementSize(); - segment.free(pos, segment.inBlocks(entryEndAddr - entry.address())); // remove callbacks onRemove(segment, pos); diff --git a/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java b/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java index 7430fe1c9..d669d3bec 100755 --- a/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java +++ b/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java @@ -45,12 +45,12 @@ import java.util.concurrent.TimeUnit; * {@code ChronicleSetBuilder} manages the whole set of {@link ChronicleSet} configurations, could * be used as a classic builder and/or factory. * - *

    {@code ChronicleMapOnHeapUpdatableBuilder} is mutable, see a note in {@link + *

    {@code ChronicleMapBuilder} is mutable, see a note in {@link * ChronicleHashBuilder} interface documentation. * * @param element type of the sets, created by this builder * @see ChronicleSet - * @see net.openhft.chronicle.map.OnHeapUpdatableChronicleMapBuilder + * @see net.openhft.chronicle.map.ChronicleMapBuilder */ public class ChronicleSetBuilder implements ChronicleHashBuilder, ChronicleSetBuilder> { diff --git a/src/test/java/eg/BGChronicleTest.java b/src/test/java/eg/BGChronicleTest.java new file mode 100644 index 000000000..229382dfe --- /dev/null +++ b/src/test/java/eg/BGChronicleTest.java @@ -0,0 +1,242 @@ +package eg; + +import net.openhft.chronicle.hash.replication.TcpTransportAndNetworkConfig; +import net.openhft.chronicle.map.ChronicleMap; +import net.openhft.chronicle.map.ChronicleMapBuilder; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +public class BGChronicleTest { + + private static final int CHRONICLE_HEARTBEAT_SECONDS = 5; + public static final long MAX_NUMBER_OF_EATERIES = 3000000L; + public static final String DEFAULT_HOST = "localhost"; + public static final int DEFAULT_PORT = 9050; + + Random rnd = new Random(System.currentTimeMillis()); + byte[] key = new byte[17]; + byte[] value = new byte[11]; + + + public ChronicleMap startChronicleMapServer(int port) throws InterruptedException, IOException { + + + TcpTransportAndNetworkConfig serverConfig = TcpTransportAndNetworkConfig.of(port) + .heartBeatInterval(CHRONICLE_HEARTBEAT_SECONDS, TimeUnit.SECONDS).name("serverMap") + .autoReconnectedUponDroppedConnection(true); + + + return ChronicleMapBuilder.of(byte[].class, byte[].class) + .entries(MAX_NUMBER_OF_EATERIES) + .keySize(17) + .valueSize(11) + .putReturnsNull(true) + .removeReturnsNull(true) + .replication((byte) 1, serverConfig) + .create(); + } + + /** + * over loop-back on my mac I get around 50,000 messages per second of my local network ( LAN ), + * I get around 730 messages per second + * + * @param hostNameOrIpAddress + * @param port + * @param numberOfMessages + * @throws IOException + */ + public void startChronicleMapClient(String hostNameOrIpAddress, final int port, final long + numberOfMessages) throws IOException { + + System.out.println("client starting"); + + + try (ChronicleMap map = ChronicleMapBuilder.of(byte[].class, byte[].class) + .keySize(17) + .valueSize(11) + .putReturnsNull(true) + .removeReturnsNull(true) + .statelessClient(new InetSocketAddress(hostNameOrIpAddress, port)).create()) { + + System.out.println("client started"); + + long start = System.nanoTime(); + for (long count = 0; count < numberOfMessages; count++) { + if (rnd.nextBoolean()) { + // puts + rnd.nextBytes(key); + rnd.nextBytes(value); + map.put(key, value); + + } else { + //gets + rnd.nextBytes(key); + map.get(key); + } + + if (count % 50000 == 0) { + logMessagesPerSecond(count, start); + } + + } + + logMessagesPerSecond(numberOfMessages, start); + + } + } + + + /** + * over loop-back on my mac I get around 50,000 messages per second of my local network ( LAN ), + * I get around 730 messages per second + * + * @param hostNameOrIpAddress + * @param port + * @param numberOfMessages + * @throws IOException + */ + public void startReplicatingChronicleMapClient(String hostNameOrIpAddress, final int port, + final long numberOfMessages) throws + IOException { + + System.out.println("client starting"); + + final InetSocketAddress serverHostPort = new InetSocketAddress(hostNameOrIpAddress, port); + + TcpTransportAndNetworkConfig clientConfig = TcpTransportAndNetworkConfig.of(port + 1, serverHostPort) + .heartBeatInterval(CHRONICLE_HEARTBEAT_SECONDS, TimeUnit.SECONDS).name("clientMap") + .autoReconnectedUponDroppedConnection(true); + + try (ChronicleMap map = ChronicleMapBuilder.of(byte[].class, byte[].class) + .entries(MAX_NUMBER_OF_EATERIES) + .keySize(17) + .valueSize(11) + .putReturnsNull(true) + .removeReturnsNull(true) + .replication((byte) 2, clientConfig) + .create()) { + + System.out.println("client started"); + + long start = System.nanoTime(); + for (long count = 0; count < numberOfMessages; count++) { + if (rnd.nextBoolean()) { + // puts + rnd.nextBytes(key); + rnd.nextBytes(value); + map.put(key, value); + + } else { + //gets + rnd.nextBytes(key); + map.get(key); + } + + if (count % 50000 == 0) { + logMessagesPerSecond(count, start); + } + + } + + logMessagesPerSecond(numberOfMessages, start); + + } + } + + + private void logMessagesPerSecond(double numberOfMessages, long start) { + double messagesPerSecond = numberOfMessages / (double) TimeUnit.NANOSECONDS + .toSeconds(System.nanoTime() - start); + System.out.println("messages per seconds " + messagesPerSecond); + } + + + /** + * for loop back args = [] + * + * for server - eg.BGChronicleTest server for client - eg.BGChronicleTest client + * + * @param args + * @throws IOException + * @throws InterruptedException + */ + public static void main(String... args) throws IOException, InterruptedException { + + final BGChronicleTest o = new BGChronicleTest(); + ChronicleMap serverMap = null; + try { + + // loop-back + if (args.length == 0) { + serverMap = o.startChronicleMapServer(DEFAULT_PORT); + o.startChronicleMapClient(DEFAULT_HOST, DEFAULT_PORT, MAX_NUMBER_OF_EATERIES); + + } else { + + // server + if (args.length > 0 && args[0].equalsIgnoreCase("server")) { + int port = getPort(args, 1); + + serverMap = o.startChronicleMapServer(port); + System.out.println("server ON"); + } else + System.out.println("server if OFF"); + + // client + if (args.length == 0 || (args.length > 1 && args[0].equalsIgnoreCase("client"))) { + + // stateless replicating client + String host = getHost(args, 1); + int port = getPort(args, 2); + System.out.println("client ON...."); + o.startChronicleMapClient(host, port, MAX_NUMBER_OF_EATERIES); + + // replicating client + } else if (args.length > 1 && args[0].equalsIgnoreCase("replicatingClient")) { + String host = getHost(args, 1); + int port = getPort(args, 2); + System.out.println("client ON...."); + o.startReplicatingChronicleMapClient(host, port, MAX_NUMBER_OF_EATERIES); + + } else { + System.out.println("client if OFF"); + for (; ; ) { + Thread.sleep(1000); + } + } + + System.out.println("client if OFF"); + for (; ; ) { + Thread.sleep(1000); + } + + } + + } finally { + if (serverMap != null) + serverMap.close(); + } + + } + + private static String getHost(String[] args, int index) { + String host = args.length > index && args[index] != null ? args[index] : DEFAULT_HOST; + System.out.println(host); + return host; + } + + private static int getPort(String[] args, int index) { + int port = args.length > index && args[index] != null ? Integer.valueOf(args[index]) : + DEFAULT_PORT; + System.out.println(port); + return port; + } + + public void test() throws IOException, InterruptedException { + main(); + } + +} diff --git a/src/test/java/net/openhft/chronicle/map/TestBooleanValues.java b/src/test/java/net/openhft/chronicle/map/BooleanValuesTest.java similarity index 97% rename from src/test/java/net/openhft/chronicle/map/TestBooleanValues.java rename to src/test/java/net/openhft/chronicle/map/BooleanValuesTest.java index 20f10db07..2f8a71154 100644 --- a/src/test/java/net/openhft/chronicle/map/TestBooleanValues.java +++ b/src/test/java/net/openhft/chronicle/map/BooleanValuesTest.java @@ -26,7 +26,7 @@ import java.io.IOException; /** * @author Rob Austin. */ -public class TestBooleanValues { +public class BooleanValuesTest { /** * see issue http://stackoverflow.com/questions/26219313/strange-npe-from-chronicle-map-toy-code diff --git a/src/test/java/net/openhft/chronicle/map/CHMUseCasesTest.java b/src/test/java/net/openhft/chronicle/map/CHMUseCasesTest.java index 831575868..6c98eb444 100644 --- a/src/test/java/net/openhft/chronicle/map/CHMUseCasesTest.java +++ b/src/test/java/net/openhft/chronicle/map/CHMUseCasesTest.java @@ -20,10 +20,11 @@ import static org.junit.Assert.*; @Ignore 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. + * 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. */ @Test + @Ignore("Not yet implemented") public void testStringStringMap() throws ExecutionException, InterruptedException { /* TODO run the same test for multiple types of map stores for(ChronicleMapBuilder chmb : Arrays.asList( @@ -72,10 +73,11 @@ public class CHMUseCasesTest { } /** - * 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 + @Ignore("Not yet implemented") public void testCharSequenceCharSequenceMap() throws ExecutionException, InterruptedException { try (ChronicleMap map = ChronicleMapBuilder .of(CharSequence.class, CharSequence.class) @@ -138,6 +140,7 @@ public class CHMUseCasesTest { * StringValue represents any bean which contains a String Value */ @Test + @Ignore("Not yet implemented") public void testStringValueStringValueMap() { try (ChronicleMap map = ChronicleMapBuilder .of(StringValue.class, StringValue.class) @@ -229,6 +232,7 @@ public class CHMUseCasesTest { } @Test + @Ignore("HCOLL-226 implement disableOversizedEntries()") public void testIntegerIntegerMap() throws ExecutionException, InterruptedException { try (ChronicleMap map = ChronicleMapBuilder .of(Integer.class, Integer.class) @@ -287,6 +291,7 @@ public class CHMUseCasesTest { } @Test + @Ignore("HCOLL-226 implement disableOversizedEntries()") public void testLongLongMap() throws ExecutionException, InterruptedException { try (ChronicleMap map = ChronicleMapBuilder .of(Long.class, Long.class) @@ -334,6 +339,7 @@ public class CHMUseCasesTest { } @Test + @Ignore("Not yet implemented") public void testDoubleDoubleMap() throws ExecutionException, InterruptedException { try (ChronicleMap map = ChronicleMapBuilder .of(Double.class, Double.class) @@ -381,6 +387,7 @@ public class CHMUseCasesTest { } @Test + @Ignore("HCOLL-226 implement disableOversizedEntries()") public void testByteArrayByteArrayMap() throws ExecutionException, InterruptedException { try (ChronicleMap map = ChronicleMapBuilder .of(byte[].class, byte[].class) @@ -431,6 +438,7 @@ public class CHMUseCasesTest { } @Test + @Ignore("HCOLL-226 implement disableOversizedEntries()") public void testByteBufferByteBufferMap() throws ExecutionException, InterruptedException { try (ChronicleMap map = ChronicleMapBuilder .of(ByteBuffer.class, ByteBuffer.class) @@ -529,6 +537,7 @@ public class CHMUseCasesTest { } @Test + @Ignore("HCOLL-226 implement disableOversizedEntries()") public void testByteBufferDirectByteBufferMap() throws ExecutionException, InterruptedException { try (ChronicleMap map = ChronicleMapBuilder .of(ByteBuffer.class, ByteBuffer.class) @@ -589,6 +598,7 @@ public class CHMUseCasesTest { } @Test + @Ignore("HCOLL-226 implement disableOversizedEntries()") public void testIntValueIntValueMap() { try (ChronicleMap map = ChronicleMapBuilder .of(IntValue.class, IntValue.class) @@ -671,6 +681,7 @@ public class CHMUseCasesTest { * For unsigned int -> unsigned int entries, the key can be on heap or off heap. */ @Test + @Ignore("HCOLL-226 implement disableOversizedEntries()") public void testUnsignedIntValueUnsignedIntValueMap() { try (ChronicleMap map = ChronicleMapBuilder .of(UnsignedIntValue.class, UnsignedIntValue.class) @@ -753,6 +764,7 @@ public class CHMUseCasesTest { * For int values, the key can be on heap or off heap. */ @Test + @Ignore("HCOLL-226 implement disableOversizedEntries()") public void testIntValueShortValueMap() { try (ChronicleMap map = ChronicleMapBuilder .of(IntValue.class, ShortValue.class) @@ -835,6 +847,7 @@ public class CHMUseCasesTest { * For int -> unsigned short values, the key can be on heap or off heap. */ @Test + @Ignore("HCOLL-226 implement disableOversizedEntries()") public void testIntValueUnsignedShortValueMap() { try (ChronicleMap map = ChronicleMapBuilder .of(IntValue.class, UnsignedShortValue.class) @@ -917,6 +930,7 @@ public class CHMUseCasesTest { * For int values, the key can be on heap or off heap. */ @Test + @Ignore("HCOLL-226 implement disableOversizedEntries()") public void testIntValueCharValueMap() { try (ChronicleMap map = ChronicleMapBuilder .of(IntValue.class, CharValue.class) @@ -999,6 +1013,7 @@ public class CHMUseCasesTest { * For int-> byte entries, the key can be on heap or off heap. */ @Test + @Ignore("HCOLL-226 implement disableOversizedEntries()") public void testIntValueUnsignedByteMap() { try (ChronicleMap map = ChronicleMapBuilder .of(IntValue.class, UnsignedByteValue.class) @@ -1081,6 +1096,7 @@ public class CHMUseCasesTest { * For int values, the key can be on heap or off heap. */ @Test + @Ignore("HCOLL-226 implement disableOversizedEntries()") public void testIntValueBooleanValueMap() { try (ChronicleMap map = ChronicleMapBuilder .of(IntValue.class, BooleanValue.class) @@ -1163,6 +1179,7 @@ public class CHMUseCasesTest { * For float values, the key can be on heap or off heap. */ @Test + @Ignore("HCOLL-226 implement disableOversizedEntries()") public void testFloatValueFloatValueMap() { try (ChronicleMap map = ChronicleMapBuilder .of(FloatValue.class, FloatValue.class) @@ -1245,18 +1262,24 @@ public class CHMUseCasesTest { * For double values, the key can be on heap or off heap. */ @Test + @Ignore("HCOLL-225 Add support for heap and native object in the same map") public void testDoubleValueDoubleValueMap() { try (ChronicleMap map = ChronicleMapBuilder .of(DoubleValue.class, DoubleValue.class) .entrySize(16) .create()) { + DoubleValue key1 = DataValueClasses.newDirectInstance(DoubleValue.class); DoubleValue key2 = DataValueClasses.newInstance(DoubleValue.class); DoubleValue value1 = DataValueClasses.newDirectInstance(DoubleValue.class); DoubleValue value2 = DataValueClasses.newInstance(DoubleValue.class); + key1.setValue(1); value1.setValue(11); + assertEquals(null, map.get(key1)); + + map.put(key1, value1); assertEquals(value1, map.get(key1)); key2.setValue(2); @@ -1325,6 +1348,7 @@ public class CHMUseCasesTest { * For long values, the key can be on heap or off heap. */ @Test + @Ignore("HCOLL-226 implement disableOversizedEntries()") public void testLongValueLongValueMap() { try (ChronicleMap map = ChronicleMapBuilder .of(LongValue.class, LongValue.class) @@ -1416,30 +1440,29 @@ public class CHMUseCasesTest { .valueMarshaller(ListMarshaller.of(new StringMarshaller(8))) .create()) { map.put("1", Collections.emptyList()); - map.put("2", Arrays.asList("one")); + map.put("2", Arrays.asList("two-A")); List list1 = new ArrayList<>(); try (WriteContext wc = map.acquireUsingLocked("1", list1)) { - list1.add("two"); - assertEquals(Arrays.asList("two"), list1); + assertEquals(Collections.EMPTY_LIST, list1); } + List list2 = new ArrayList<>(); - try (ReadContext rc = map.getUsingLocked("1", list2)) { - assertTrue(rc.present()); - assertEquals(Arrays.asList("two"), list2); - } - try (WriteContext wc = map.acquireUsingLocked("2", list1)) { - list1.add("three"); - assertEquals(Arrays.asList("three"), list1); - } try (ReadContext rc = map.getUsingLocked("2", list2)) { assertTrue(rc.present()); - assertEquals(Arrays.asList("one", "three"), list2); + list2.add("two-B"); // this is not written as it only a read context + assertEquals(Arrays.asList("two-A", "two-B"), list2); + } + + try (ReadContext rc = map.getUsingLocked("2", list2)) { + assertTrue(rc.present()); + assertEquals(Arrays.asList("two-A"), list2); } } } @Test + @Ignore("Fix LinkedHashSet Marshaller") public void testSetValue() { try (ChronicleMap> map = ChronicleMapBuilder .of(String.class, (Class>) (Class) Set.class) diff --git a/src/test/java/net/openhft/chronicle/map/ChronicleMapTest.java b/src/test/java/net/openhft/chronicle/map/ChronicleMapTest.java index 54906f545..ea9a9ba7d 100755 --- a/src/test/java/net/openhft/chronicle/map/ChronicleMapTest.java +++ b/src/test/java/net/openhft/chronicle/map/ChronicleMapTest.java @@ -551,7 +551,8 @@ public class ChronicleMapTest { threads[t].join(); } - assertEquals(noOfThreads * iterations, map.acquireUsing(key, new LongValue$$Native()).getValue()); + assertEquals(noOfThreads * iterations, + map.acquireUsing(key, new LongValue$$Native()).getValue()); map.close(); } @@ -1398,8 +1399,7 @@ public class ChronicleMapTest { } } - @Ignore("JIRA raised for failing test HCOLL-222") - @Test + @Test(expected = NullPointerException.class) public void testPutLongValue() throws IOException { final ChronicleMapBuilder builder = ChronicleMapBuilder .of(CharSequence.class, LongValue.class) diff --git a/src/test/java/net/openhft/chronicle/map/ConstantSizeBySampleTest.java b/src/test/java/net/openhft/chronicle/map/ConstantSizeBySampleTest.java index 0e5481e40..a570ecd07 100644 --- a/src/test/java/net/openhft/chronicle/map/ConstantSizeBySampleTest.java +++ b/src/test/java/net/openhft/chronicle/map/ConstantSizeBySampleTest.java @@ -106,14 +106,13 @@ public class ConstantSizeBySampleTest { @Override public boolean equals(Object obj) { - if (!(obj instanceof ExternalizableData)) + if (!(obj instanceof SerializableData)) return false; - return Arrays.equals(((ExternalizableData) obj).data, data); + return Arrays.equals(((SerializableData) obj).data, data); } } @Test - @Ignore public void testUnexpectedlyLongConstantSerializableValues() throws IOException { try ( ChronicleMap map = ChronicleMapBuilder.of(Long.class, SerializableData.class) diff --git a/src/test/java/net/openhft/chronicle/map/EventListenerTestWithTCPSocketReplication.java b/src/test/java/net/openhft/chronicle/map/EventListenerWithTCPSocketReplicationTest.java similarity index 98% rename from src/test/java/net/openhft/chronicle/map/EventListenerTestWithTCPSocketReplication.java rename to src/test/java/net/openhft/chronicle/map/EventListenerWithTCPSocketReplicationTest.java index 893b96a30..8eb4afd4d 100644 --- a/src/test/java/net/openhft/chronicle/map/EventListenerTestWithTCPSocketReplication.java +++ b/src/test/java/net/openhft/chronicle/map/EventListenerWithTCPSocketReplicationTest.java @@ -35,7 +35,7 @@ import java.util.concurrent.atomic.AtomicReference; * * @author Rob Austin. */ -public class EventListenerTestWithTCPSocketReplication { +public class EventListenerWithTCPSocketReplicationTest { ChronicleMap map1; ChronicleMap map2; @@ -180,7 +180,6 @@ public class EventListenerTestWithTCPSocketReplication { } @Test - @Ignore("TODO Failing test") public void testRemoteNotifyRemove() throws IOException, InterruptedException { // ---------- SERVER1 1 ---------- diff --git a/src/test/java/net/openhft/chronicle/map/NoTcpReplicationSoakTest.java b/src/test/java/net/openhft/chronicle/map/NoTcpReplicationSoakTest.java new file mode 100644 index 000000000..b1003d1de --- /dev/null +++ b/src/test/java/net/openhft/chronicle/map/NoTcpReplicationSoakTest.java @@ -0,0 +1,208 @@ +package net.openhft.chronicle.map; + +import net.openhft.chronicle.hash.replication.TimeProvider; +import net.openhft.lang.io.ByteBufferBytes; +import net.openhft.lang.model.Byteable; +import net.openhft.lang.model.DataValueClasses; +import net.openhft.lang.values.IntValue; +import org.junit.*; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author Rob Austin. + */ +public class NoTcpReplicationSoakTest { + + private ReplicatedChronicleMap map1; + private ReplicatedChronicleMap map2; + private IntValue value; + static int s_port = 8010; + private long time; + + TimeProvider timeProvider = new TimeProvider() { + @Override + public long currentTimeMillis() { + return time; + } + }; + + + @Before + public void setup() throws IOException { + value = DataValueClasses.newDirectReference(IntValue.class); + ((Byteable) value).bytes(new ByteBufferBytes(ByteBuffer.allocateDirect(4)), 0); + + + { + + + map1 = (ReplicatedChronicleMap) ChronicleMapBuilder.of(Integer.class, CharSequence.class) + .name("map1") + .replication((byte) 1) + .timeProvider(timeProvider) + .create(); + } + { + + map2 = (ReplicatedChronicleMap) ChronicleMapBuilder.of(Integer.class, CharSequence.class) + .name("map2") + .replication((byte) 2) + .timeProvider(timeProvider) + .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); + } + + AtomicInteger task = new AtomicInteger(); + + @Test + @Ignore + public void testSoakTestWithRandomData() throws IOException, InterruptedException { + ExecutorService executorService = Executors.newSingleThreadExecutor(); + System.out.print("SoakTesting "); + for (int j = 1; j < 10000; j++) { + if (j % 100 == 0) + System.out.print("."); + + Random rnd = new Random(j); + + + if (rnd.nextInt(10) < 2) + time++; + + final long t = time; + + final int key = rnd.nextInt(1000); + + // add random delay + addRandomDelay(executorService, rnd); + + if (rnd.nextBoolean()) { + final CharSequence value = "test" + j; + if (rnd.nextBoolean()) { + map1.put(key, value); + + 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); + + task.incrementAndGet(); + executorService.submit(new Runnable() { + @Override + public void run() { + + map1.put0(key, value, true, (byte) 2, t); + task.decrementAndGet(); + } + }); + } + + } else { + + if (rnd.nextBoolean()) { + map1.remove(key); + + task.incrementAndGet(); + executorService.submit(new Runnable() { + @Override + public void run() { + + map2.remoteRemove(key, (byte) 1, t); + task.decrementAndGet(); + } + }); + + } else { + map2.remove(key); + + task.incrementAndGet(); + executorService.submit(new Runnable() { + @Override + public void run() { + + map1.remoteRemove(key, (byte) 2, t); + task.decrementAndGet(); + } + }); + } + } + } + + while (task.get() > 0) { + + } + + + Assert.assertEquals(new TreeMap(map1), new TreeMap(map2)); + + } + + private void addRandomDelay(ExecutorService executorService, Random rnd) { + if (rnd.nextInt(30) < 2) { + + task.incrementAndGet(); + executorService.submit(new Runnable() { + @Override + public void run() { + task.decrementAndGet(); + try { + Thread.sleep(1); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }); + } + } + + + + +} + diff --git a/src/test/java/net/openhft/chronicle/map/TCPSocketReplicationIntValueTest.java b/src/test/java/net/openhft/chronicle/map/TCPSocketReplicationIntValueTest.java index 6a907c631..fc45a19ed 100644 --- a/src/test/java/net/openhft/chronicle/map/TCPSocketReplicationIntValueTest.java +++ b/src/test/java/net/openhft/chronicle/map/TCPSocketReplicationIntValueTest.java @@ -161,14 +161,13 @@ public class TCPSocketReplicationIntValueTest { for (; t < timeOutMs; t++) { if (map1.equals(map2)) break; - Thread.sleep(25); + Thread.sleep(1); } assertEquals(map1, map2); } // TODO test this with larger sizes. @Test(timeout = 12000) - @Ignore("Doesn't work, maps need to check equality.") public void testSoakTestWithRandomData() throws IOException, InterruptedException { final long start = System.currentTimeMillis(); System.out.print("SoakTesting "); diff --git a/src/test/java/net/openhft/chronicle/map/TCPSocketReplicationPostConnectionTest.java b/src/test/java/net/openhft/chronicle/map/TCPSocketReplicationPostConnectionTest.java index b4c0b63df..a389e53b2 100755 --- a/src/test/java/net/openhft/chronicle/map/TCPSocketReplicationPostConnectionTest.java +++ b/src/test/java/net/openhft/chronicle/map/TCPSocketReplicationPostConnectionTest.java @@ -61,12 +61,12 @@ public class TCPSocketReplicationPostConnectionTest { s_port += 2; map1 = TCPSocketReplication4WayMapTest.newTcpSocketShmIntString((byte) 1, port); map1.put(5, "EXAMPLE-2"); - Thread.sleep(1); + Thread.sleep(10); map2 = TCPSocketReplication4WayMapTest.newTcpSocketShmIntString((byte) 2, port + 1, new InetSocketAddress("localhost", port)); // allow time for the recompilation to resolve - waitTillEqual(500); + waitTillEqual(15000); assertEquals(map1, map2); assertTrue(!map1.isEmpty()); diff --git a/src/test/java/net/openhft/chronicle/map/TCPSocketReplicationTest.java b/src/test/java/net/openhft/chronicle/map/TCPSocketReplicationTest.java index fff7574f5..32df4b0bd 100644 --- a/src/test/java/net/openhft/chronicle/map/TCPSocketReplicationTest.java +++ b/src/test/java/net/openhft/chronicle/map/TCPSocketReplicationTest.java @@ -28,7 +28,6 @@ import java.net.InetSocketAddress; import java.util.Random; import java.util.Set; -import static net.openhft.chronicle.map.Builder.getPersistenceFile; import static net.openhft.chronicle.map.Builder.newTcpSocketShmBuilder; import static net.openhft.chronicle.map.TCPSocketReplication4WayMapTest.newTcpSocketShmIntString; import static org.junit.Assert.assertEquals; @@ -52,7 +51,7 @@ public class TCPSocketReplicationTest { ChronicleMapBuilder map1Builder = newTcpSocketShmBuilder(Integer.class, CharSequence.class, (byte) 1, port, new InetSocketAddress("localhost", port + 1)); - map1 = map1Builder.createPersistedTo(getPersistenceFile()); + map1 = map1Builder.create(); map2 = newTcpSocketShmIntString((byte) 2, port + 1); s_port += 2; } @@ -116,6 +115,46 @@ public class TCPSocketReplicationTest { assertTrue(!map1.isEmpty()); } + + @Test + public void test2() throws IOException, InterruptedException { + + map2.put(1, "EXAMPLE-1"); + map2.remove(1); + + map1.put(1, "EXAMPLE-2"); + map1.remove(1); + + map2.put(1, "EXAMPLE-1"); + + + // allow time for the recompilation to resolve + waitTillEqual(5000); + + assertEquals(map1, map2); + assertTrue(!map1.isEmpty()); + } + + @Test + public void test4() throws IOException, InterruptedException { + + map2.put(1, "EXAMPLE-1"); + map2.remove(1); + + map1.put(1, "EXAMPLE-2"); + map1.remove(1); + + map2.put(1, "EXAMPLE-1"); + map2.remove(1); + + // allow time for the recompilation to resolve + waitTillEqual(5000); + + assertEquals(map1, map2); + assertTrue(map1.isEmpty()); + } + + @Test public void testBufferOverflow() throws IOException, InterruptedException { @@ -168,7 +207,7 @@ public class TCPSocketReplicationTest { } } System.out.println(""); - waitTillEqual(10000); + waitTillEqual(1000); } } diff --git a/src/test/java/net/openhft/chronicle/map/TcpReplicationSoakTest.java b/src/test/java/net/openhft/chronicle/map/TcpReplicationSoakTest.java new file mode 100644 index 000000000..14dd1dcfe --- /dev/null +++ b/src/test/java/net/openhft/chronicle/map/TcpReplicationSoakTest.java @@ -0,0 +1,146 @@ +package net.openhft.chronicle.map; + +import net.openhft.chronicle.hash.replication.TcpTransportAndNetworkConfig; +import net.openhft.lang.io.ByteBufferBytes; +import net.openhft.lang.model.Byteable; +import net.openhft.lang.model.DataValueClasses; +import net.openhft.lang.values.IntValue; +import org.junit.*; + +import java.io.Closeable; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.util.*; +import java.util.concurrent.TimeUnit; + +/** + * @author Rob Austin. + */ +public class TcpReplicationSoakTest { + + + private ChronicleMap map1; + private ChronicleMap map2; + private IntValue value; + static int s_port = 8010; + + @Before + public void setup() throws IOException { + value = DataValueClasses.newDirectReference(IntValue.class); + ((Byteable) value).bytes(new ByteBufferBytes(ByteBuffer.allocateDirect(4)), 0); + + 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); + + + map1 = ChronicleMapBuilder.of(Integer.class, CharSequence.class) + .entries(Builder.SIZE + Builder.SIZE) + .actualSegments(1) + .name("map1") + .replication((byte) 1, tcpConfig1) + .create(); + } + { + final TcpTransportAndNetworkConfig tcpConfig2 = TcpTransportAndNetworkConfig.of + (s_port + 1).autoReconnectedUponDroppedConnection(true).name("map2") + .heartBeatInterval(1, TimeUnit.SECONDS); + + map2 = ChronicleMapBuilder.of(Integer.class, CharSequence.class) + .entries(Builder.SIZE + Builder.SIZE) + .name("map2") + .replication((byte) 2, tcpConfig2) + .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 testSoakTestWithRandomData() throws IOException, InterruptedException { + + System.out.print("SoakTesting "); + for (int j = 1; j < 2 * Builder.SIZE; j++) { + if (j % 100 == 0) + System.out.print("."); + Random rnd = new Random(j); + for (int i = 1; i < 10; i++) { + final int select = rnd.nextInt(2); + final ChronicleMap map = select > 0 ? map1 : map2; + + if (rnd.nextBoolean()) { + map.put(rnd.nextInt(Builder.SIZE), "test" + j); + } else { + map.remove(rnd.nextInt(Builder.SIZE)); + } + } + } + Thread.sleep(1000); + System.out.println("\nwaiting till equal"); + + waitTillEqual(10000); + + Assert.assertEquals(new TreeMap(map1), new TreeMap(map2)); + + } + + + 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/TcpTimeBasedReplicationSoakTest.java b/src/test/java/net/openhft/chronicle/map/TcpTimeBasedReplicationSoakTest.java new file mode 100644 index 000000000..d2e5b5720 --- /dev/null +++ b/src/test/java/net/openhft/chronicle/map/TcpTimeBasedReplicationSoakTest.java @@ -0,0 +1,166 @@ +package net.openhft.chronicle.map; + +import net.openhft.chronicle.hash.replication.TcpTransportAndNetworkConfig; +import net.openhft.chronicle.hash.replication.TimeProvider; +import net.openhft.lang.io.ByteBufferBytes; +import net.openhft.lang.model.Byteable; +import net.openhft.lang.model.DataValueClasses; +import net.openhft.lang.values.IntValue; +import org.junit.*; +import org.mockito.Mockito; + +import java.io.Closeable; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.util.*; +import java.util.concurrent.TimeUnit; + +/** + * @author Rob Austin. + */ +public class TcpTimeBasedReplicationSoakTest { + + + private ChronicleMap map1; + private ChronicleMap map2; + private IntValue value; + static int s_port = 8010; + private TimeProvider timeProvider; + int t = 0; + + @Before + public void setup() throws IOException { + value = DataValueClasses.newDirectReference(IntValue.class); + ((Byteable) value).bytes(new ByteBufferBytes(ByteBuffer.allocateDirect(4)), 0); + + final InetSocketAddress endpoint = new InetSocketAddress("localhost", s_port + 1); + timeProvider = new TimeProvider() { + + Random rnd = new Random(4); + + @Override + public long currentTimeMillis() { + + if (rnd.nextBoolean()) + return t++; + else + return t; + } + }; + + { + final TcpTransportAndNetworkConfig tcpConfig1 = TcpTransportAndNetworkConfig.of(s_port, + endpoint); + + + map1 = ChronicleMapBuilder.of(Integer.class, CharSequence.class) + .entries(Builder.SIZE) + .timeProvider(timeProvider) + .replication((byte) 1, tcpConfig1) + .create(); + } + { + final TcpTransportAndNetworkConfig tcpConfig2 = TcpTransportAndNetworkConfig.of(s_port + 1); + + map2 = ChronicleMapBuilder.of(Integer.class, CharSequence.class) + .entries(Builder.SIZE) + .timeProvider(timeProvider) + .replication((byte) 2, tcpConfig2) + .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 + @Ignore + public void testSoakTestWithRandomData() throws IOException, InterruptedException { + + Thread.sleep(100); + + // for (int i = 30; i < 50; i++) { + System.out.print("SoakTesting "); + int max = 1000000; + for (int j = 1; j < max; j++) { + if (j % 100 == 0) + System.out.print("."); + Random rnd = new Random(System.currentTimeMillis()); + + final ChronicleMap map = rnd.nextBoolean() ? map1 : map2; + + if (rnd.nextBoolean()) { + map.put((int) rnd.nextInt(100), "test" + j); + } else { + map.remove((int) rnd.nextInt(100)); + } + + } + + System.out.println("\nwaiting till equal"); + + waitTillUnchanged(1000); + System.out.println("time t=" + t); + Assert.assertEquals(new TreeMap(map1), new TreeMap(map2)); + + } + + + private void waitTillUnchanged(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(map1UnChanged) && map2.equals(map2UnChanged)) { + numberOfTimesTheSame++; + } else { + numberOfTimesTheSame = 0; + map1UnChanged = new HashMap(map1); + map2UnChanged = new HashMap(map2); + } + Thread.sleep(50); + if (numberOfTimesTheSame == 100) { + + break; + } + + + } + } + + private void late(TimeProvider timeProvider) { + Mockito.when(timeProvider.currentTimeMillis()).thenReturn(System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(5)); + } +} + diff --git a/src/test/java/net/openhft/chronicle/map/UDPSocketReplicationTest.java b/src/test/java/net/openhft/chronicle/map/UDPSocketReplicationTest.java index 85d572a40..2ef6178ef 100644 --- a/src/test/java/net/openhft/chronicle/map/UDPSocketReplicationTest.java +++ b/src/test/java/net/openhft/chronicle/map/UDPSocketReplicationTest.java @@ -22,6 +22,8 @@ import net.openhft.chronicle.hash.replication.SingleChronicleHashReplication; import net.openhft.chronicle.hash.replication.UdpTransportConfig; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; import java.io.Closeable; import java.io.IOException; @@ -75,8 +77,8 @@ public class UDPSocketReplicationTest { System.gc(); } - /* @Test - @Ignore + @Test + @Ignore public void testBufferOverflow() throws IOException, InterruptedException { for (int i = 0; i < 1024; i++) { @@ -84,7 +86,7 @@ public class UDPSocketReplicationTest { map2.put(i * 2, "E"); System.out.println("" + map2); } - }*/ + } } diff --git a/src/test/java/net/openhft/chronicle/map/jrs166/map/ChronicleMapTest.java b/src/test/java/net/openhft/chronicle/map/jrs166/map/ChronicleMapTest.java index 3e30638d2..5024f490b 100644 --- a/src/test/java/net/openhft/chronicle/map/jrs166/map/ChronicleMapTest.java +++ b/src/test/java/net/openhft/chronicle/map/jrs166/map/ChronicleMapTest.java @@ -23,7 +23,6 @@ import net.openhft.chronicle.map.ChronicleMapBuilder; import net.openhft.chronicle.map.jrs166.JSR166TestCase; import org.junit.Test; -import java.io.File; import java.io.IOException; import java.util.*; @@ -39,40 +38,17 @@ import static org.junit.Assert.*; public class ChronicleMapTest extends JSR166TestCase { - private static File getPersistenceFile() { - String TMP = System.getProperty("java.io.tmpdir"); - File file = new File(TMP + "/chm-test" + System.nanoTime()); - file.deleteOnExit(); - return file; - } static ChronicleMap newShmIntString(int size) throws IOException { return ChronicleMapBuilder.of(Integer.class, CharSequence.class) .entries(size).create(); } - static ChronicleMap newShmListBoolean(int size) throws IOException { - return ChronicleMapBuilder.of(ArrayList.class, CharSequence.class) - .entries(size).create(); - } - - static ChronicleMap newShmListBoolean() throws IOException { - return ChronicleMapBuilder.of(ArrayList.class, CharSequence.class).create(); - } - static ChronicleMap newStrStrMap(int size) throws IOException { return ChronicleMapBuilder.of(CharSequence.class, CharSequence.class) .entries(size).create(); } - static ChronicleMap newShmIntString() throws IOException { - return ChronicleMapBuilder.of(Integer.class, CharSequence.class).create(); - } - - static ChronicleMap newShmBiBoolean() throws IOException { - return ChronicleMapBuilder.of(BI.class, Boolean.class).create(); - } - /** * Returns a new map from Integers 1-5 to Strings "A"-"E". */