mirror of
https://github.com/OpenHFT/Chronicle-Map.git
synced 2021-09-13 01:26:12 +00:00
SPI implementation
This commit is contained in:
@@ -16,12 +16,11 @@
|
||||
|
||||
package net.openhft.chronicle.map;
|
||||
|
||||
import net.openhft.chronicle.hash.KeyContext;
|
||||
import net.openhft.chronicle.hash.function.SerializableFunction;
|
||||
import net.openhft.chronicle.hash.impl.hashlookup.HashLookupIteration;
|
||||
import net.openhft.chronicle.hash.impl.util.CharSequences;
|
||||
import net.openhft.lang.io.Bytes;
|
||||
import net.openhft.lang.io.MultiStoreBytes;
|
||||
import net.openhft.chronicle.map.impl.CompiledMapIterationContext;
|
||||
import net.openhft.chronicle.map.impl.IterationContextInterface;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
@@ -33,117 +32,10 @@ import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static net.openhft.chronicle.hash.impl.HashContext.SearchState.DELETED;
|
||||
import static net.openhft.chronicle.hash.impl.util.Objects.requireNonNull;
|
||||
|
||||
interface AbstractChronicleMap<K, V> extends ChronicleMap<K, V>, Serializable {
|
||||
|
||||
@Override
|
||||
VanillaContext<K, ?, ?, V, ?, ?> context(K key);
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
default boolean containsKey(Object key) {
|
||||
try (MapKeyContext<K, V> c = context((K) key)) {
|
||||
return c.containsKey();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
default V put(K key, V value) {
|
||||
try (MapKeyContext<K, V> c = context(key)) {
|
||||
// We cannot read the previous value under read lock, because then we will need
|
||||
// to release the read lock -> acquire write lock, the value might be updated in
|
||||
// between, that will break ConcurrentMap.put() atomicity guarantee. So, we acquire
|
||||
// update lock from the start:
|
||||
c.updateLock().lock();
|
||||
V prevValue = prevValueOnPut(c);
|
||||
c.put(value);
|
||||
return prevValue;
|
||||
}
|
||||
}
|
||||
|
||||
default V prevValueOnPut(MapKeyContext<K, V> context) {
|
||||
return context.getUsing(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
default V putIfAbsent(K key, V value) {
|
||||
checkValue(value);
|
||||
try (MapKeyContext<K, V> c = context(key)) {
|
||||
// putIfAbsent() shouldn't actually put most of the time,
|
||||
// so check if the key is present under read lock first:
|
||||
if (c.readLock().tryLock()) {
|
||||
// c.get() returns cached value, that might be unexpected by user,
|
||||
// so use getUsing(null) which surely creates a new value instance:
|
||||
V currentValue = c.getUsing(null);
|
||||
if (currentValue != null)
|
||||
return currentValue;
|
||||
// Key is absent
|
||||
upgradeReadToUpdateLockWithUnlockingIfNeeded(c);
|
||||
}
|
||||
// Entry with this key might be put into the map before we acquired
|
||||
// update lock (exclusive) at any time, so even if we successfully upgraded
|
||||
// to update lock, we should check if the value is still absent again
|
||||
V currentValue = c.getUsing(null);
|
||||
if (currentValue != null)
|
||||
return currentValue;
|
||||
// Key is absent
|
||||
c.put(value);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void checkValue(V value);
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
default V get(Object key) {
|
||||
return getUsing((K) key, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
default V getUsing(K key, V usingValue) {
|
||||
try (MapKeyContext<K, V> c = context(key)) {
|
||||
return c.getUsing(usingValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
default V acquireUsing(K key, V usingValue) {
|
||||
try (VanillaContext<K, ?, ?, V, ?, ?> c = context(key)) {
|
||||
// acquireUsing() should just read an existing value most of the time,
|
||||
// so try to check if the key is already present under read lock first:
|
||||
if (c.readLock().tryLock()) {
|
||||
V value = c.getUsing(usingValue);
|
||||
if (value != null)
|
||||
return value;
|
||||
// Key is absent
|
||||
upgradeReadToUpdateLockWithUnlockingIfNeeded(c);
|
||||
} else {
|
||||
c.updateLock().lock();
|
||||
}
|
||||
// Entry with this key might be put into the map before we acquired
|
||||
// update lock (exclusive) at any time, so even if we successfully upgraded
|
||||
// to update lock, we should check if the value is still absent again
|
||||
V value = c.getUsing(usingValue);
|
||||
if (value != null)
|
||||
return value;
|
||||
// Key is absent
|
||||
putDefaultValue(c);
|
||||
return c.getUsing(usingValue);
|
||||
}
|
||||
}
|
||||
|
||||
default void upgradeReadToUpdateLockWithUnlockingIfNeeded(KeyContext c) {
|
||||
if (!c.updateLock().tryLock()) {
|
||||
c.readLock().unlock();
|
||||
c.updateLock().lock();
|
||||
}
|
||||
}
|
||||
|
||||
void putDefaultValue(VanillaContext context);
|
||||
|
||||
@Override
|
||||
default <R> R getMapped(K key, @NotNull SerializableFunction<? super V, R> function) {
|
||||
requireNonNull(function);
|
||||
@@ -184,76 +76,8 @@ interface AbstractChronicleMap<K, V> extends ChronicleMap<K, V>, Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
default V remove(Object key) {
|
||||
try (MapKeyContext<K, V> c = context((K) key)) {
|
||||
// We cannot read the previous value under read lock, because then we will need
|
||||
// to release the read lock -> acquire write lock, the value might be updated in
|
||||
// between, that will break ConcurrentMap.remove() atomicity guarantee. So, we acquire
|
||||
// update lock from the start:
|
||||
c.updateLock().lock();
|
||||
V prevValue = prevValueOnRemove(c);
|
||||
c.remove();
|
||||
return prevValue;
|
||||
}
|
||||
}
|
||||
|
||||
default V prevValueOnRemove(MapKeyContext<K, V> context) {
|
||||
return context.getUsing(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
default boolean remove(Object key, Object value) {
|
||||
if (value == null)
|
||||
return false; // CHM compatibility; General ChronicleMap policy is to throw NPE
|
||||
checkValue((V) value);
|
||||
try (MapKeyContext<K, V> c = context((K) key)) {
|
||||
// remove(key, value) should find the entry & remove most of the time,
|
||||
// so don't try to check key presence and value equivalence under read lock first,
|
||||
// as in putIfAbsent()/acquireUsing(), start with update lock:
|
||||
c.updateLock().lock();
|
||||
return c.containsKey() && c.valueEqualTo((V) value) && c.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
default V replace(K key, V value) {
|
||||
checkValue(value);
|
||||
try (MapKeyContext<K, V> c = context(key)) {
|
||||
// replace(key, value) should find the key & put the value most of the time,
|
||||
// so don't try to check key presence under read lock first,
|
||||
// as in putIfAbsent()/acquireUsing(), start with update lock:
|
||||
c.updateLock().lock();
|
||||
// 1. c.get() returns cached value, that might be unexpected by user,
|
||||
// so use getUsing(null) which surely creates a new value instance:
|
||||
// 2. Always "try to read" the previous value (not via if (c.containsKey()) {...
|
||||
// because BytesChronicleMap relies on it to write null to the output stream
|
||||
V prevValue = c.getUsing(null);
|
||||
if (prevValue != null)
|
||||
c.put(value);
|
||||
return prevValue;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean replace(K key, V oldValue, V newValue) {
|
||||
checkValue(oldValue);
|
||||
checkValue(newValue);
|
||||
try (MapKeyContext<K, V> c = context(key)) {
|
||||
// replace(key, old, new) should find the entry & put new value most of the time,
|
||||
// so don't try to check key presence and value equivalence under read lock first,
|
||||
// as in putIfAbsent()/acquireUsing(), start with update lock:
|
||||
c.updateLock().lock();
|
||||
return c.containsKey() && c.valueEqualTo(oldValue) && c.put(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
int actualSegments();
|
||||
|
||||
VanillaContext<K, ?, ?, V, ?, ?> mapContext();
|
||||
|
||||
@Override
|
||||
default boolean containsValue(Object value) {
|
||||
return !forEachEntryWhile(c -> !c.valueEqualTo((V) value));
|
||||
@@ -429,84 +253,22 @@ interface AbstractChronicleMap<K, V> extends ChronicleMap<K, V>, Serializable {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
IterationContextInterface<K, V> iterationContext();
|
||||
|
||||
@Override
|
||||
default boolean forEachEntryWhile(final Predicate<? super MapKeyContext<K, V>> predicate) {
|
||||
default boolean forEachEntryWhile(final Predicate<? super MapKeyContext<K, V>> action) {
|
||||
boolean interrupt = false;
|
||||
iteration:
|
||||
try (VanillaContext<K, ?, ?, V, ?, ?> c = mapContext()) {
|
||||
ForEachWhilePredicate<K, V> hashLookupPredicate =
|
||||
new ForEachWhilePredicate<>(c, predicate);
|
||||
try (IterationContextInterface<K, V> c = iterationContext()) {
|
||||
for (int segmentIndex = actualSegments() - 1; segmentIndex >= 0; segmentIndex--) {
|
||||
c.segmentIndex = segmentIndex;
|
||||
c.forEachEntry = true;
|
||||
try {
|
||||
c.updateLock().lock();
|
||||
if (c.size() == 0)
|
||||
continue;
|
||||
c.initSegment();
|
||||
c.hashLookup.forEachRemoving(hashLookupPredicate);
|
||||
if (hashLookupPredicate.shouldBreak) {
|
||||
interrupt = true;
|
||||
break iteration;
|
||||
}
|
||||
} finally {
|
||||
c.forEachEntry = false;
|
||||
c.closeSegmentIndex();
|
||||
c.initTheSegmentIndex(segmentIndex);
|
||||
if (!c.forEachRemoving(e -> action.test(c.deprecatedMapKeyContextOnIteration()))) {
|
||||
interrupt = true;
|
||||
break iteration;
|
||||
}
|
||||
}
|
||||
}
|
||||
return !interrupt;
|
||||
}
|
||||
|
||||
|
||||
static class ForEachWhilePredicate<K, V> implements HashLookupIteration {
|
||||
private final VanillaContext<K, ?, ?, V, ?, ?> c;
|
||||
private final Predicate<? super MapKeyContext<K, V>> predicate;
|
||||
boolean shouldRemove = false;
|
||||
boolean shouldBreak = false;
|
||||
|
||||
public ForEachWhilePredicate(VanillaContext<K, ?, ?, V, ?, ?> c,
|
||||
Predicate<? super MapKeyContext<K, V>> predicate) {
|
||||
this.c = c;
|
||||
this.predicate = predicate;
|
||||
shouldBreak = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(long hash, long pos) {
|
||||
c.pos = pos;
|
||||
c.initKeyFromPos();
|
||||
try {
|
||||
if (!c.containsKey()) { // for replicated map
|
||||
shouldRemove = false;
|
||||
return;
|
||||
}
|
||||
shouldBreak = !predicate.test(c);
|
||||
c.closeKey0();
|
||||
// release all exclusive locks: possibly if context.remove() is performed
|
||||
// in the callback, or acquired manually
|
||||
while (c.writeLockCount > 0) {
|
||||
c.writeLock().unlock();
|
||||
}
|
||||
if (!c.isUpdateLocked()) {
|
||||
throw new IllegalStateException("Shouldn't release update lock " +
|
||||
"inside forEachEntry() callback");
|
||||
}
|
||||
shouldRemove = c.searchState0() == DELETED;
|
||||
} finally {
|
||||
c.closeKeySearch();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove() {
|
||||
return shouldRemove;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean continueIteration() {
|
||||
return !shouldBreak;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user