Committed by
Gerrit Code Review
Updated ECMap remove call to return the value that was removed
Change-Id: Id7eacc04f4bb9322e4f98da5664c2fa46e0ea6fc
Showing
3 changed files
with
50 additions
and
28 deletions
... | @@ -99,16 +99,14 @@ public interface EventuallyConsistentMap<K, V> { | ... | @@ -99,16 +99,14 @@ public interface EventuallyConsistentMap<K, V> { |
99 | /** | 99 | /** |
100 | * Removes the mapping associated with the specified key from the map. | 100 | * Removes the mapping associated with the specified key from the map. |
101 | * <p> | 101 | * <p> |
102 | - * Note: this differs from the specification of {@link java.util.Map} | 102 | + * Clients are expected to register an {@link EventuallyConsistentMapListener} if |
103 | - * because it does not return the previous value associated with the key. | ||
104 | - * Clients are expected to register an | ||
105 | - * {@link EventuallyConsistentMapListener} if | ||
106 | * they are interested in receiving notification of updates to the map. | 103 | * they are interested in receiving notification of updates to the map. |
107 | * </p> | 104 | * </p> |
108 | * | 105 | * |
109 | * @param key the key to remove the mapping for | 106 | * @param key the key to remove the mapping for |
107 | + * @return previous value associated with key, or null if there was no mapping for key. | ||
110 | */ | 108 | */ |
111 | - void remove(K key); | 109 | + V remove(K key); |
112 | 110 | ||
113 | /** | 111 | /** |
114 | * Removes the given key-value mapping from the map, if it exists. | 112 | * Removes the given key-value mapping from the map, if it exists. | ... | ... |
... | @@ -49,6 +49,7 @@ import java.util.HashMap; | ... | @@ -49,6 +49,7 @@ import java.util.HashMap; |
49 | import java.util.LinkedList; | 49 | import java.util.LinkedList; |
50 | import java.util.List; | 50 | import java.util.List; |
51 | import java.util.Map; | 51 | import java.util.Map; |
52 | +import java.util.Optional; | ||
52 | import java.util.Set; | 53 | import java.util.Set; |
53 | import java.util.Timer; | 54 | import java.util.Timer; |
54 | import java.util.concurrent.ConcurrentHashMap; | 55 | import java.util.concurrent.ConcurrentHashMap; |
... | @@ -58,11 +59,14 @@ import java.util.concurrent.ExecutorService; | ... | @@ -58,11 +59,14 @@ import java.util.concurrent.ExecutorService; |
58 | import java.util.concurrent.Executors; | 59 | import java.util.concurrent.Executors; |
59 | import java.util.concurrent.ScheduledExecutorService; | 60 | import java.util.concurrent.ScheduledExecutorService; |
60 | import java.util.concurrent.TimeUnit; | 61 | import java.util.concurrent.TimeUnit; |
62 | +import java.util.concurrent.atomic.AtomicReference; | ||
61 | import java.util.function.BiFunction; | 63 | import java.util.function.BiFunction; |
62 | import java.util.stream.Collectors; | 64 | import java.util.stream.Collectors; |
63 | 65 | ||
64 | import static com.google.common.base.Preconditions.checkNotNull; | 66 | import static com.google.common.base.Preconditions.checkNotNull; |
65 | import static com.google.common.base.Preconditions.checkState; | 67 | import static com.google.common.base.Preconditions.checkState; |
68 | +import static java.util.Objects.isNull; | ||
69 | +import static java.util.Objects.nonNull; | ||
66 | import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; | 70 | import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; |
67 | import static org.onlab.util.BoundedThreadPool.newFixedThreadPool; | 71 | import static org.onlab.util.BoundedThreadPool.newFixedThreadPool; |
68 | import static org.onlab.util.Tools.groupedThreads; | 72 | import static org.onlab.util.Tools.groupedThreads; |
... | @@ -347,42 +351,54 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -347,42 +351,54 @@ public class EventuallyConsistentMapImpl<K, V> |
347 | } | 351 | } |
348 | 352 | ||
349 | @Override | 353 | @Override |
350 | - public void remove(K key) { | 354 | + public V remove(K key) { |
351 | checkState(!destroyed, destroyedMessage); | 355 | checkState(!destroyed, destroyedMessage); |
352 | checkNotNull(key, ERROR_NULL_KEY); | 356 | checkNotNull(key, ERROR_NULL_KEY); |
353 | 357 | ||
354 | // TODO prevent calls here if value is important for timestamp | 358 | // TODO prevent calls here if value is important for timestamp |
355 | Timestamp timestamp = timestampProvider.apply(key, null); | 359 | Timestamp timestamp = timestampProvider.apply(key, null); |
356 | 360 | ||
357 | - if (removeInternal(key, timestamp)) { | 361 | + Optional<V> removedValue = removeInternal(key, timestamp); |
362 | + if (removedValue == null) { | ||
363 | + return null; | ||
364 | + } | ||
358 | notifyPeers(new RemoveEntry<>(key, timestamp), | 365 | notifyPeers(new RemoveEntry<>(key, timestamp), |
359 | peerUpdateFunction.apply(key, null)); | 366 | peerUpdateFunction.apply(key, null)); |
360 | notifyListeners(new EventuallyConsistentMapEvent<>( | 367 | notifyListeners(new EventuallyConsistentMapEvent<>( |
361 | - EventuallyConsistentMapEvent.Type.REMOVE, key, null)); | 368 | + EventuallyConsistentMapEvent.Type.REMOVE, key, removedValue.orElse(null))); |
362 | - } | 369 | + |
370 | + return removedValue.orElse(null); | ||
363 | } | 371 | } |
364 | 372 | ||
365 | - private boolean removeInternal(K key, Timestamp timestamp) { | 373 | + /** |
374 | + * Returns null if the timestamp is for a outdated request i.e. | ||
375 | + * the value is the map is more recent or a tombstone exists with a | ||
376 | + * more recent timestamp. | ||
377 | + * Returns non-empty optional if a value was indeed removed from the map. | ||
378 | + * Returns empty optional if map did not contain a value for the key but the existing | ||
379 | + * tombstone is older than this timestamp. | ||
380 | + * @param key key | ||
381 | + * @param timestamp timestamp for remove request | ||
382 | + * @return Optional value. | ||
383 | + */ | ||
384 | + private Optional<V> removeInternal(K key, Timestamp timestamp) { | ||
366 | if (timestamp == null) { | 385 | if (timestamp == null) { |
367 | - return false; | 386 | + return null; |
368 | } | 387 | } |
369 | 388 | ||
370 | counter.incrementCount(); | 389 | counter.incrementCount(); |
371 | - final MutableBoolean updated = new MutableBoolean(false); | 390 | + final AtomicReference<Optional<V>> removedValue = new AtomicReference<>(null); |
372 | - | ||
373 | items.compute(key, (k, existing) -> { | 391 | items.compute(key, (k, existing) -> { |
374 | if (existing != null && existing.isNewerThan(timestamp)) { | 392 | if (existing != null && existing.isNewerThan(timestamp)) { |
375 | - updated.setFalse(); | ||
376 | return existing; | 393 | return existing; |
377 | } else { | 394 | } else { |
378 | - updated.setTrue(); | 395 | + removedValue.set(existing == null ? Optional.empty() : Optional.of(existing.value())); |
379 | - // remove from items map | ||
380 | return null; | 396 | return null; |
381 | } | 397 | } |
382 | }); | 398 | }); |
383 | 399 | ||
384 | - if (updated.isFalse()) { | 400 | + if (isNull(removedValue.get())) { |
385 | - return false; | 401 | + return null; |
386 | } | 402 | } |
387 | 403 | ||
388 | boolean updatedTombstone = false; | 404 | boolean updatedTombstone = false; |
... | @@ -397,11 +413,14 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -397,11 +413,14 @@ public class EventuallyConsistentMapImpl<K, V> |
397 | } | 413 | } |
398 | } | 414 | } |
399 | 415 | ||
400 | - if (updated.booleanValue() && persistent) { | 416 | + if (persistent) { |
401 | persistentStore.remove(key, timestamp); | 417 | persistentStore.remove(key, timestamp); |
402 | } | 418 | } |
403 | 419 | ||
404 | - return (!tombstonesDisabled && updatedTombstone) || updated.booleanValue(); | 420 | + if (tombstonesDisabled || updatedTombstone) { |
421 | + return removedValue.get(); | ||
422 | + } | ||
423 | + return null; | ||
405 | } | 424 | } |
406 | 425 | ||
407 | @Override | 426 | @Override |
... | @@ -412,7 +431,7 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -412,7 +431,7 @@ public class EventuallyConsistentMapImpl<K, V> |
412 | 431 | ||
413 | Timestamp timestamp = timestampProvider.apply(key, value); | 432 | Timestamp timestamp = timestampProvider.apply(key, value); |
414 | 433 | ||
415 | - if (removeInternal(key, timestamp)) { | 434 | + if (nonNull(removeInternal(key, timestamp))) { |
416 | notifyPeers(new RemoveEntry<>(key, timestamp), | 435 | notifyPeers(new RemoveEntry<>(key, timestamp), |
417 | peerUpdateFunction.apply(key, value)); | 436 | peerUpdateFunction.apply(key, value)); |
418 | notifyListeners(new EventuallyConsistentMapEvent<>( | 437 | notifyListeners(new EventuallyConsistentMapEvent<>( |
... | @@ -641,7 +660,7 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -641,7 +660,7 @@ public class EventuallyConsistentMapImpl<K, V> |
641 | if (remoteDeadTimestamp != null && | 660 | if (remoteDeadTimestamp != null && |
642 | remoteDeadTimestamp.isNewerThan(localValue.timestamp())) { | 661 | remoteDeadTimestamp.isNewerThan(localValue.timestamp())) { |
643 | // sender has a more recent remove | 662 | // sender has a more recent remove |
644 | - if (removeInternal(key, remoteDeadTimestamp)) { | 663 | + if (nonNull(removeInternal(key, remoteDeadTimestamp))) { |
645 | externalEvents.add(new EventuallyConsistentMapEvent<>( | 664 | externalEvents.add(new EventuallyConsistentMapEvent<>( |
646 | EventuallyConsistentMapEvent.Type.REMOVE, key, null)); | 665 | EventuallyConsistentMapEvent.Type.REMOVE, key, null)); |
647 | } | 666 | } |
... | @@ -697,7 +716,7 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -697,7 +716,7 @@ public class EventuallyConsistentMapImpl<K, V> |
697 | local.timestamp())) { | 716 | local.timestamp())) { |
698 | // If the remote has a more recent tombstone than either our local | 717 | // If the remote has a more recent tombstone than either our local |
699 | // value, then do a remove with their timestamp | 718 | // value, then do a remove with their timestamp |
700 | - if (removeInternal(key, remoteDeadTimestamp)) { | 719 | + if (nonNull(removeInternal(key, remoteDeadTimestamp))) { |
701 | externalEvents.add(new EventuallyConsistentMapEvent<>( | 720 | externalEvents.add(new EventuallyConsistentMapEvent<>( |
702 | EventuallyConsistentMapEvent.Type.REMOVE, key, null)); | 721 | EventuallyConsistentMapEvent.Type.REMOVE, key, null)); |
703 | } | 722 | } |
... | @@ -744,7 +763,7 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -744,7 +763,7 @@ public class EventuallyConsistentMapImpl<K, V> |
744 | // TODO clean this for loop up | 763 | // TODO clean this for loop up |
745 | for (AbstractEntry<K, V> entry : events) { | 764 | for (AbstractEntry<K, V> entry : events) { |
746 | final K key = entry.key(); | 765 | final K key = entry.key(); |
747 | - final V value; | 766 | + V value; |
748 | final Timestamp timestamp = entry.timestamp(); | 767 | final Timestamp timestamp = entry.timestamp(); |
749 | final EventuallyConsistentMapEvent.Type type; | 768 | final EventuallyConsistentMapEvent.Type type; |
750 | if (entry instanceof PutEntry) { | 769 | if (entry instanceof PutEntry) { |
... | @@ -764,7 +783,11 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -764,7 +783,11 @@ public class EventuallyConsistentMapImpl<K, V> |
764 | success = putInternal(key, value, timestamp); | 783 | success = putInternal(key, value, timestamp); |
765 | break; | 784 | break; |
766 | case REMOVE: | 785 | case REMOVE: |
767 | - success = removeInternal(key, timestamp); | 786 | + Optional<V> removedValue = removeInternal(key, timestamp); |
787 | + success = removedValue != null; | ||
788 | + if (success) { | ||
789 | + value = removedValue.orElse(null); | ||
790 | + } | ||
768 | break; | 791 | break; |
769 | default: | 792 | default: |
770 | success = false; | 793 | success = false; | ... | ... |
... | @@ -326,8 +326,9 @@ public class EventuallyConsistentMapImplTest { | ... | @@ -326,8 +326,9 @@ public class EventuallyConsistentMapImplTest { |
326 | EventuallyConsistentMapListener<String, String> listener | 326 | EventuallyConsistentMapListener<String, String> listener |
327 | = getListener(); | 327 | = getListener(); |
328 | listener.event(new EventuallyConsistentMapEvent<>( | 328 | listener.event(new EventuallyConsistentMapEvent<>( |
329 | + EventuallyConsistentMapEvent.Type.REMOVE, KEY1, VALUE1)); | ||
330 | + listener.event(new EventuallyConsistentMapEvent<>( | ||
329 | EventuallyConsistentMapEvent.Type.REMOVE, KEY1, null)); | 331 | EventuallyConsistentMapEvent.Type.REMOVE, KEY1, null)); |
330 | - expectLastCall().times(2); | ||
331 | listener.event(new EventuallyConsistentMapEvent<>( | 332 | listener.event(new EventuallyConsistentMapEvent<>( |
332 | EventuallyConsistentMapEvent.Type.PUT, KEY1, VALUE1)); | 333 | EventuallyConsistentMapEvent.Type.PUT, KEY1, VALUE1)); |
333 | listener.event(new EventuallyConsistentMapEvent<>( | 334 | listener.event(new EventuallyConsistentMapEvent<>( |
... | @@ -425,9 +426,9 @@ public class EventuallyConsistentMapImplTest { | ... | @@ -425,9 +426,9 @@ public class EventuallyConsistentMapImplTest { |
425 | EventuallyConsistentMapListener<String, String> listener | 426 | EventuallyConsistentMapListener<String, String> listener |
426 | = getListener(); | 427 | = getListener(); |
427 | listener.event(new EventuallyConsistentMapEvent<>( | 428 | listener.event(new EventuallyConsistentMapEvent<>( |
428 | - EventuallyConsistentMapEvent.Type.REMOVE, KEY1, null)); | 429 | + EventuallyConsistentMapEvent.Type.REMOVE, KEY1, VALUE1)); |
429 | listener.event(new EventuallyConsistentMapEvent<>( | 430 | listener.event(new EventuallyConsistentMapEvent<>( |
430 | - EventuallyConsistentMapEvent.Type.REMOVE, KEY2, null)); | 431 | + EventuallyConsistentMapEvent.Type.REMOVE, KEY2, VALUE2)); |
431 | replay(listener); | 432 | replay(listener); |
432 | 433 | ||
433 | // clear() on an empty map is a no-op - no messages will be sent | 434 | // clear() on an empty map is a no-op - no messages will be sent | ... | ... |
-
Please register or login to post a comment