Committed by
Gerrit Code Review
Updated ECMap remove call to return the value that was removed
Change-Id: Id7eacc04f4bb9322e4f98da5664c2fa46e0ea6fc
Showing
3 changed files
with
52 additions
and
30 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); |
| 358 | - notifyPeers(new RemoveEntry<>(key, timestamp), | 362 | + if (removedValue == null) { |
| 359 | - peerUpdateFunction.apply(key, null)); | 363 | + return null; |
| 360 | - notifyListeners(new EventuallyConsistentMapEvent<>( | ||
| 361 | - EventuallyConsistentMapEvent.Type.REMOVE, key, null)); | ||
| 362 | } | 364 | } |
| 365 | + notifyPeers(new RemoveEntry<>(key, timestamp), | ||
| 366 | + peerUpdateFunction.apply(key, null)); | ||
| 367 | + notifyListeners(new EventuallyConsistentMapEvent<>( | ||
| 368 | + EventuallyConsistentMapEvent.Type.REMOVE, key, removedValue.orElse(null))); | ||
| 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