diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMap.java b/src/main/java/net/openhft/chronicle/map/ChronicleMap.java index 616b53ad5..0b80b6b9b 100755 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMap.java @@ -33,19 +33,19 @@ import java.util.concurrent.Future; /** * Extension of {@link ConcurrentMap} interface, stores the data off-heap. * - *

For information on

see {@link AbstractChronicleMapBuilder} - * documentation. + *

For information on

see {@link + * AbstractChronicleMapBuilder} documentation. * - *

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

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

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

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

Note that {@code ChronicleMap} extends {@link Closeable}, don't forget to {@linkplain #close() close} - * map when it is no longer needed. + *

Note that {@code ChronicleMap} extends {@link Closeable}, don't forget to {@linkplain #close() + * close} map when it is no longer needed. * * @param the map key type * @param the map value type @@ -60,38 +60,41 @@ public interface ChronicleMap extends ConcurrentMap, ChronicleHash { long longSize(); /** - * Returns the value to which the specified key is mapped, or {@code null} if this map contains no mapping - * for the key. + * Returns the value to which the specified key is mapped, or {@code null} if this map contains + * no mapping for the key. * - *

If the value class allows reusing, particularly if it is a {@link Byteable} subclass, consider - * {@link #getUsing(Object, Object)} method instead of this to reduce garbage creation. + *

If the value class allows reusing, particularly if it is a {@link Byteable} subclass, + * consider {@link #getUsing(Object, Object)} method instead of this to reduce garbage + * creation. * * @param key the key whose associated value is to be returned - * @return the value to which the specified key is mapped after this method call, or {@code null} if no - * value is mapped + * @return the value to which the specified key is mapped after this method call, or {@code + * null} if no value is mapped * @see #getUsing(Object, Object) */ @Override V get(Object key); /** - * Returns the value to which the specified key is mapped, read to the provided {@code value} object, if - * possible, or returns {@code null}, if this map contains no mapping for the key. + * Returns the value to which the specified key is mapped, read to the provided {@code value} + * object, if possible, or returns {@code null}, if this map contains no mapping for the key. * - *

If the specified key is present in the map, the value data is read to the provided {@code value} - * object via value marshaller's {@link BytesMarshaller#read(Bytes, Object) read(Bytes, value)} or value - * reader's {@link BytesReader#read(Bytes, long, Object) read(Bytes, size, value)} method, depending on - * what deserialization strategy is configured on the builder, using which this map was constructed. If - * the value deserializer is able to reuse the given {@code value} object, calling this method instead of - * {@link #get(Object)} could help to reduce garbage creation. + *

If the specified key is present in the map, the value data is read to the provided {@code + * value} object via value marshaller's {@link BytesMarshaller#read(Bytes, Object) read(Bytes, + * value)} or value reader's {@link BytesReader#read(Bytes, long, Object) read(Bytes, size, + * value)} method, depending on what deserialization strategy is configured on the builder, + * using which this map was constructed. If the value deserializer is able to reuse the given + * {@code value} object, calling this method instead of {@link #get(Object)} could help to + * reduce garbage creation. * *

The provided {@code value} object is allowed to be {@code null}, in this case {@code - * map.getUsing(key, null)} call is semantically equivalent to simple {@code map.get(key)} call. + * map.getUsing(key, null)} call is semantically equivalent to simple {@code map.get(key)} + * call. * * @param key the key whose associated value is to be returned * @param usingValue the object to read value data in, if possible - * @return the value to which the specified key is mapped, or {@code null} if this map contains no mapping - * for the key + * @return the value to which the specified key is mapped, or {@code null} if this map contains + * no mapping for the key * @see #get(Object) * @see #acquireUsing(Object, Object) * @see OnHeapUpdatableChronicleMapBuilder#valueMarshaller(BytesMarshaller) @@ -99,31 +102,33 @@ public interface ChronicleMap extends ConcurrentMap, ChronicleHash { V getUsing(K key, V usingValue); /** - * The method is similar {@link V getUsing(K key, V usingValue);} but in addition read locks the map - * segment to operations to be performed atomically. ( see the example below ) + * The method is similar {@link V getUsing(K key, V usingValue);} but in addition read locks + * the map segment to operations to be performed atomically. ( see the example below ) * - * Returns the ReadContext which holds a map segment lock and provides method to get the value ( to which - * the specified key is mapped) atomically ( see example below for an explanation ), read to the provided - * {@code value} object, if possible, or returns {@code null}, if this map contains no mapping for the - * key. + * Returns the ReadContext which holds a map segment lock and provides method to get the value + * ( to which the specified key is mapped) atomically ( see example below for an explanation ), + * read to the provided {@code value} object, if possible, or returns {@code null}, if this map + * contains no mapping for the key. * - *

If the specified key is present in the map, the readContext.value() uses the provided {@code - * usingValue} object via value marshaller's {@link BytesMarshaller#read(Bytes, Object) read(Bytes, - * value)} or value reader's {@link BytesReader#read(Bytes, long, Object) read(Bytes, size, value)} - * method, depending on what deserialization strategy is configured on the builder, using which this map - * was constructed. If the value deserializer is able to reuse the given {@code usingValue} object, - * calling this method instead of {@link #get(Object)} could help to reduce garbage creation. + *

If the specified key is present in the map, the readContext.value() uses the provided + * {@code usingValue} object via value marshaller's {@link BytesMarshaller#read(Bytes, Object) + * read(Bytes, value)} or value reader's {@link BytesReader#read(Bytes, long, Object) + * read(Bytes, size, value)} method, depending on what deserialization strategy is configured on + * the builder, using which this map was constructed. If the value deserializer is able to reuse + * the given {@code usingValue} object, calling this method instead of {@link #get(Object)} + * could help to reduce garbage creation. *

{@code
      * try (ReadContext rc = map.getUsingLocked(key, bond)) {
      *    if (rc.present ()) { // check whether the key was present
      *    long issueDate = bond.getIssueDate();
      *    String symbol = bond.getSymbol();
-     * // add your logic here ( the lock will ensure this bond can not be changed by another thread )
+     * // add your logic here ( the lock will ensure this bond can not be changed by another thread
+     * )
      * }
      * } // the read lock is released here
      * }
- * To ensure that you can read the 'issueDate' and 'symbol' can be read atomically, these values must be - * read while the segment lock is in place. + * To ensure that you can read the 'issueDate' and 'symbol' can be read atomically, these values + * must be read while the segment lock is in place. * * *

The provided {@code value} object is allowed to be {@code null}, in this case @@ -145,13 +150,14 @@ public interface ChronicleMap extends ConcurrentMap, ChronicleHash { * *

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

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

{@code
@@ -166,11 +172,11 @@ public interface ChronicleMap extends ConcurrentMap, ChronicleHash {
      * default value} or {@link AbstractChronicleMapBuilder#defaultValueProvider(DefaultValueProvider)
      * defaultValueProvider.}
      *
-     * 

If the {@code ChronicleMap} is off-heap updatable, i. e. created via {@link ChronicleMapBuilder} - * builder (values are {@link Byteable}), there is one more option of what to do if the key is absent in - * the map, see {@link ChronicleMapBuilder#prepareValueBytesOnAcquire(PrepareValueBytes)}. By default, - * value bytes are just zeroed out, no default value, either provided for key or constant, is put for the - * absent key. + *

If the {@code ChronicleMap} is off-heap updatable, i. e. created via {@link + * ChronicleMapBuilder} builder (values are {@link Byteable}), there is one more option of what + * to do if the key is absent in the map, see {@link ChronicleMapBuilder#prepareValueBytesOnAcquire(PrepareValueBytes)}. + * By default, value bytes are just zeroed out, no default value, either provided for key or + * constant, is put for the absent key. * * @param key the key whose associated value is to be returned * @param usingValue the object to read value data in, if present. Can not be null @@ -194,8 +200,8 @@ public interface ChronicleMap extends ConcurrentMap, ChronicleHash { R mapForKey(K key, @NotNull Function function); /** - * Apply a mutator to the value for a key and return a result. A write lock is assumed.

If there is no - * entry for this key it will be created and the value empty. + * Apply a mutator to the value for a key and return a result. A write lock is assumed.

If + * there is no entry for this key it will be created and the value empty. * * @param key to apply the mapping to * @param mutator to alter the value and calculate a result @@ -205,13 +211,16 @@ public interface ChronicleMap extends ConcurrentMap, ChronicleHash { R updateForKey(K key, @NotNull Mutator mutator); /** - * Returns a {@link java.util.concurrent.Future} containing the value to which the specified key is - * mapped, or {@code null} if this map contains no mapping for the key. This method behaves the same as - * {@link java.util.Map#get(java.lang.Object)} yet wraps result in a future a {@link + * Returns a {@link java.util.concurrent.Future} containing the value to which the specified key + * is mapped, or {@code null} if this map contains no mapping for the key. This method behaves + * the same as {@link java.util.Map#get(java.lang.Object)} yet wraps result in a future a {@link * java.util.concurrent.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 + * Calling this method may or may not block until the operating is complete depending on the + * implementation. + * + * @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) * @see java.util.concurrent.Future */ @@ -219,18 +228,46 @@ public interface ChronicleMap extends ConcurrentMap, ChronicleHash { /** - * Associates the specified value with the specified key in this map (optional operation). If the map - * previously contained a mapping for the key, the old value is replaced by the specified value. (A map - * m is said to contain a mapping for a key k if and only if {@link #containsKey(Object) - * m.containsKey(k)} would return true.) + * Associates the specified value with the specified key in this map (optional operation). If + * the map previously contained a mapping for the key, the old value is replaced by the + * specified value. (A map m is said to contain a mapping for a key k if and + * only if {@link #containsKey(Object) m.containsKey(k)} would return true.) * - * @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.) - * @see java.util.Map#get(java.lang.Object) + * 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.) + * @see java.util.Map#put(java.lang.Object, java.lang.Object) * @see java.util.concurrent.Future */ - Future putLater(@NotNull K key,@NotNull V value); + Future putLater(@NotNull K key, @NotNull V value); + + + /** + * Removes the mapping for a key from this map if it is present (optional operation). More + * formally, if this map contains a mapping from key k to value v such that + * (key==null ? k==null : key.equals(k)), that mapping is removed. (The map can + * contain at most one such mapping.) + * + *

Returns a future containing, either the value to which this map previously associated the + * key, or null if the map contained no mapping for the key. + * + * This method behaves the same as {@link java.util.Map#remove(java.lang.Object)} yet wraps + * result in a future a {@link java.util.concurrent.Future}. + * + * Calling this method may or may not block until the operating is complete depending on the + * implementation. + * + * @param key key whose mapping is to be removed from the map + * @return a future containing either, the previous value associated with key, or + * null if there was no mapping for key. + * @see java.util.Map#remove(java.lang.Object) + * @see java.util.concurrent.Future + */ + Future removeLater(@NotNull K key); } diff --git a/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java b/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java index 757c43f39..759359e61 100644 --- a/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java @@ -155,6 +155,18 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon }); } + @Override + public Future removeLater(@NotNull final K key) { + return lazyExecutorService().submit(new Callable() { + @Override + public V call() throws Exception { + + V oldValue = StatelessChronicleMap.this.remove(key); + return oldValue; + } + }); + } + private ExecutorService lazyExecutorService() { @@ -485,7 +497,7 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon if (key == null) throw new NullPointerException("key can not be null"); - final long sizeLocation = (removeReturnsNull) ? writeEvent(REMOVE_WITHOUT_ACC) : writeEventAnSkip(REMOVE); + final long sizeLocation = removeReturnsNull ? writeEvent(REMOVE_WITHOUT_ACC) : writeEventAnSkip(REMOVE); writeKey((K) key); diff --git a/src/main/java/net/openhft/chronicle/map/TcpReplicator.java b/src/main/java/net/openhft/chronicle/map/TcpReplicator.java index bef98ca3e..135972782 100644 --- a/src/main/java/net/openhft/chronicle/map/TcpReplicator.java +++ b/src/main/java/net/openhft/chronicle/map/TcpReplicator.java @@ -1641,6 +1641,7 @@ class StatelessServerConnector { final K key = readKey(reader, local); try { + LOG.info("removing entry key="+key); writeValue(map.remove(key), writer, local); } catch (Throwable e) { return sendException(writer, sizeLocation, e); diff --git a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java index aff8d7053..50a71642b 100755 --- a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java @@ -58,7 +58,8 @@ class VanillaChronicleMap, implements ChronicleMap, Serializable { /** - * Because DirectBitSet implementations couldn't find more than 64 continuous clear or set bits. + * Because DirectBitSet implementations couldn't find more than 64 continuous clear or set + * bits. */ static final int MAX_ENTRY_OVERSIZE_FACTOR = 64; static final ThreadLocal tmpBytes = new ThreadLocal<>(); @@ -448,6 +449,38 @@ class VanillaChronicleMap, }; } + @Override + public Future removeLater(@NotNull K key) { + final V v = remove(key); + return new Future() { + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return false; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return true; + } + + @Override + public V get() throws InterruptedException, ExecutionException { + return v; + } + + @Override + public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return v; + } + }; + } + @NotNull @Override public WriteContext acquireUsingLocked(@NotNull K key, @NotNull V usingValue) { @@ -569,9 +602,9 @@ class VanillaChronicleMap, /** - * removes ( if there exists ) an entry from the map, if the {@param key} and {@param expectedValue} match - * that of a maps.entry. If the {@param expectedValue} equals null then ( if there exists ) an entry whose - * key equals {@param key} this is removed. + * removes ( if there exists ) an entry from the map, if the {@param key} and {@param + * expectedValue} match that of a maps.entry. If the {@param expectedValue} equals null then ( + * if there exists ) an entry whose key equals {@param key} this is removed. * * @param k the key of the entry to remove * @param expectedValue null if not required @@ -631,8 +664,8 @@ class VanillaChronicleMap, * replace the value in a map, only if the existing entry equals {@param existingValue} * * @param key the key into the map - * @param existingValue the expected existing value in the map ( could be null when we don't wish to do - * this check ) + * @param existingValue the expected existing value in the map ( could be null when we don't + * wish to do this check ) * @param newValue the new value you wish to store in the map * @return the value that was replaced */ @@ -1174,13 +1207,13 @@ class VanillaChronicleMap, } /** - * Removes a key (or key-value pair) from the Segment.

The entry will only be removed if - * {@code expectedValue} equals to {@code null} or the value previously corresponding to the specified - * key. + * Removes a key (or key-value pair) from the Segment.

The entry will only be + * removed if {@code expectedValue} equals to {@code null} or the value previously + * corresponding to the specified key. * * @param hash2 a hash code related to the {@code keyBytes} - * @return the value of the entry that was removed if the entry corresponding to the {@code keyBytes} - * exists and {@link #removeReturnsNull} is {@code false}, {@code null} otherwise + * @return the value of the entry that was removed if the entry corresponding to the {@code + * keyBytes} exists and {@link #removeReturnsNull} is {@code false}, {@code null} otherwise */ V remove(ThreadLocalCopies copies, MKI metaKeyInterop, KI keyInterop, K key, V expectedValue, long hash2) { @@ -1240,9 +1273,9 @@ class VanillaChronicleMap, } /** - * Replaces the specified value for the key with the given value. {@code newValue} is set only if the - * existing value corresponding to the specified key is equal to {@code expectedValue} or {@code - * expectedValue == null}. + * Replaces the specified value for the key with the given value. {@code newValue} is set + * only if the existing value corresponding to the specified key is equal to {@code + * expectedValue} or {@code expectedValue == null}. * * @param hash2 a hash code related to the {@code keyBytes} * @return the replaced value or {@code null} if the value was not replaced @@ -1319,17 +1352,17 @@ class VanillaChronicleMap, } /** - * Replaces value in existing entry. May cause entry relocation, because there may be not enough space - * for new value in location already allocated for this entry. + * Replaces value in existing entry. May cause entry relocation, because there may be not + * enough space for new value in location already allocated for this entry. * * @param pos index of the first block occupied by the entry - * @param offset relative offset of the entry in Segment bytes (before, i. e. including - * metaData) + * @param offset relative offset of the entry in Segment bytes (before, i. e. + * including metaData) * @param entry relative pointer in Segment bytes * @param valueSizePos relative position of value size in entry * @param entryEndAddr absolute address of the entry end - * @return relative offset of the entry in Segment bytes after putting value (that may cause entry - * relocation) + * @return relative offset of the entry in Segment bytes after putting value (that may cause + * entry relocation) */ long putValue(long pos, long offset, NativeBytes entry, long valueSizePos, long entryEndAddr, ThreadLocalCopies copies, V value, Bytes valueBytes, diff --git a/src/test/java/net/openhft/chronicle/map/ReplicationCheckingMap.java b/src/test/java/net/openhft/chronicle/map/ReplicationCheckingMap.java index 3e267c142..d4176cb45 100644 --- a/src/test/java/net/openhft/chronicle/map/ReplicationCheckingMap.java +++ b/src/test/java/net/openhft/chronicle/map/ReplicationCheckingMap.java @@ -265,6 +265,11 @@ public class ReplicationCheckingMap implements ChronicleMap { throw new UnsupportedOperationException(); } + @Override + public Future removeLater(@NotNull K key) { + throw new UnsupportedOperationException(); + } + @Override public V acquireUsing(@NotNull K key, V usingValue) { return map1.acquireUsing(key, usingValue); diff --git a/src/test/java/net/openhft/chronicle/map/StatelessClientTest.java b/src/test/java/net/openhft/chronicle/map/StatelessClientTest.java index 249fb3c3b..08928a07d 100644 --- a/src/test/java/net/openhft/chronicle/map/StatelessClientTest.java +++ b/src/test/java/net/openhft/chronicle/map/StatelessClientTest.java @@ -448,6 +448,27 @@ public class StatelessClientTest { } + @Test + public void testRemoveLater() throws IOException, + InterruptedException, ExecutionException { + + try (ChronicleMap serverMap = ChronicleMapBuilder.of(Integer.class, CharSequence.class) + .replication((byte) 2, TcpTransportAndNetworkConfig.of(8056)).create()) { + + try (ChronicleMap statelessMap = ChronicleMapBuilder.of(Integer + .class, CharSequence.class) + .statelessClient(new InetSocketAddress("localhost", 8056)).create()) { + statelessMap.put(1, "some value"); + statelessMap.removeLater(1).get(); + Assert.assertEquals(null, statelessMap.get(1)); + Assert.assertEquals(0, statelessMap.size()); + + } + } + + } + + @Test public void testEquals() throws IOException, InterruptedException { diff --git a/src/test/java/net/openhft/chronicle/map/jrs166/map/StatelessChronicleMapTest.java b/src/test/java/net/openhft/chronicle/map/jrs166/map/StatelessChronicleMapTest.java index 5c181ec4b..c67cf2d06 100755 --- a/src/test/java/net/openhft/chronicle/map/jrs166/map/StatelessChronicleMapTest.java +++ b/src/test/java/net/openhft/chronicle/map/jrs166/map/StatelessChronicleMapTest.java @@ -187,6 +187,11 @@ public class StatelessChronicleMapTest extends JSR166TestCase { return d.putLater(key, value); } + @Override + public Future removeLater(@NotNull K key) { + return d.removeLater(key); + } + @Override public V putIfAbsent(K key, V value) { return d.putIfAbsent(key, value);