org.infinispan.hotrod.impl.cache.InvalidatedNearRemoteCache Maven / Gradle / Ivy
The newest version!
package org.infinispan.hotrod.impl.cache;
import static org.infinispan.hotrod.impl.Util.await;
import static org.infinispan.hotrod.impl.logging.Log.HOTROD;
import java.lang.invoke.MethodHandles;
import java.net.SocketAddress;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.atomic.AtomicInteger;
import org.infinispan.api.common.CacheEntry;
import org.infinispan.api.common.CacheEntryVersion;
import org.infinispan.api.common.CacheOptions;
import org.infinispan.api.common.CacheWriteOptions;
import org.infinispan.hotrod.impl.logging.Log;
import org.infinispan.hotrod.impl.logging.LogFactory;
import org.infinispan.hotrod.impl.operations.CacheOperationsFactory;
import org.infinispan.hotrod.impl.operations.ClientListenerOperation;
import org.infinispan.hotrod.impl.operations.RetryAwareCompletionStage;
import org.infinispan.hotrod.impl.operations.UpdateBloomFilterOperation;
import org.infinispan.hotrod.near.NearCacheService;
/**
* Near cache implementation enabling
*
* @param
* @param
*/
public class InvalidatedNearRemoteCache extends DelegatingRemoteCache {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private static final boolean trace = log.isTraceEnabled();
private final NearCacheService nearcache;
private final ClientStatistics clientStatistics;
// This field is used to control updating the near cache with updating the remote server's bloom filter
// representation. This value will be non null when bloom filter is enabled, otherwise null. When this
// value is even it means there is no bloom filter update being sent and the near cache can be updated
// locally from reads. When a bloom filter is being replicated though the value will be odd and the
// near cache cannot be updated.
private final AtomicInteger bloomFilterUpdateVersion;
private volatile SocketAddress listenerAddress;
InvalidatedNearRemoteCache(RemoteCache remoteCache, ClientStatistics clientStatistics,
NearCacheService nearcache) {
super(remoteCache);
this.clientStatistics = clientStatistics;
this.nearcache = nearcache;
this.bloomFilterUpdateVersion = nearcache.getBloomFilterBits() > 0 ? new AtomicInteger() : null;
}
@Override
RemoteCache newDelegatingCache(RemoteCache innerCache) {
return new InvalidatedNearRemoteCache<>(innerCache, clientStatistics, (NearCacheService) nearcache);
}
public static InvalidatedNearRemoteCache delegatingNearCache(RemoteCacheImpl remoteCache,
NearCacheService nearCacheService) {
return new InvalidatedNearRemoteCache<>(remoteCache, remoteCache.getClientStatistics(), nearCacheService);
}
@Override
public CompletionStage get(K key, CacheOptions options) {
return getEntry(key, options).thenApply(v -> v != null ? v.value() : null);
}
private int getCurrentVersion() {
if (bloomFilterUpdateVersion != null) {
return bloomFilterUpdateVersion.get();
}
return 0;
}
@Override
public CompletionStage> getEntry(K key, CacheOptions options) {
CacheEntry nearValue = nearcache.get(key);
// We rely upon the fact that a CacheEntry will never be null from a remote lookup but our placeholder will be null
if (nearValue == null || nearValue.value() == null) {
clientStatistics.incrementNearCacheMisses();
// Note we are using a fake Object to ensure equals of CacheEntryImpl isn't true for the same key
// We cannot cache this value as we could have 2 concurrent gets and an update and we could cache a previous one
CacheEntryImpl calculatingPlaceholder = new CacheEntryImpl<>((K) new Object(), null, null);
boolean cache = nearcache.putIfAbsent(key, calculatingPlaceholder);
int prevVersion = getCurrentVersion();
RetryAwareCompletionStage> remoteValue = super.getEntry(key, options, listenerAddress);
if (!cache) {
return remoteValue.toCompletableFuture();
}
return remoteValue.thenApply(e -> {
boolean shouldRemove = true;
// We cannot cache the value if a retry was required - which means we did not talk to the listener node
if (e != null) {
// If previous version is odd we can't cache as that means it was started during
// a bloom filter update. We also can't cache if the new version doesn't match the prior
// as it overlapped a bloom update.
if ((prevVersion & 1) == 1 || prevVersion != getCurrentVersion()) {
if (trace) {
log.tracef("Unable to cache returned value for key %s as operation was performed during a" +
" bloom filter update");
}
}
// Having a listener address means it has a bloom filter. When we have a bloom filter we cannot
// cache values upon a retry as we can't guarantee the bloom filter is updated on the server properly
else if (listenerAddress != null && remoteValue.wasRetried()) {
if (trace) {
log.tracef("Unable to cache returned value for key %s as operation was retried", key);
}
} else {
nearcache.replace(key, calculatingPlaceholder, e);
e.metadata().expiration().maxIdle().ifPresent(m -> HOTROD.nearCacheMaxIdleUnsupported());
shouldRemove = false;
}
}
if (shouldRemove) {
nearcache.remove(key, calculatingPlaceholder);
}
return e;
});
} else {
clientStatistics.incrementNearCacheHits();
return CompletableFuture.completedFuture(nearValue);
}
}
@Override
public CompletionStage> put(K key, V value, CacheWriteOptions options) {
options.expiration().maxIdle().ifPresent(m -> HOTROD.nearCacheMaxIdleUnsupported());
return super.put(key, value, options).thenApply(v -> {
nearcache.remove(key);
return v;
});
}
@Override
public CompletionStage> putIfAbsent(K key, V value, CacheWriteOptions options) {
options.expiration().maxIdle().ifPresent(m -> HOTROD.nearCacheMaxIdleUnsupported());
return super.putIfAbsent(key, value, options).thenApply(v -> {
nearcache.remove(key);
return v;
});
}
@Override
public CompletionStage setIfAbsent(K key, V value, CacheWriteOptions options) {
options.expiration().maxIdle().ifPresent(m -> HOTROD.nearCacheMaxIdleUnsupported());
return super.setIfAbsent(key, value, options).thenApply(v -> {
nearcache.remove(key);
return v;
});
}
@Override
public CompletionStage set(K key, V value, CacheWriteOptions options) {
options.expiration().maxIdle().ifPresent(m -> HOTROD.nearCacheMaxIdleUnsupported());
return super.set(key, value, options).thenApply(v -> {
nearcache.remove(key);
return v;
});
}
@Override
public CompletionStage replace(K key, V value, CacheEntryVersion version, CacheWriteOptions options) {
options.expiration().maxIdle().ifPresent(m -> HOTROD.nearCacheMaxIdleUnsupported());
return super.replace(key, value, version, options).thenApply(v -> {
if (v) {
nearcache.remove(key);
}
return v;
});
}
@Override
public CompletionStage> getOrReplaceEntry(K key, V value, CacheEntryVersion version, CacheWriteOptions options) {
options.expiration().maxIdle().ifPresent(m -> HOTROD.nearCacheMaxIdleUnsupported());
return super.getOrReplaceEntry(key, value, version, options).thenApply(v -> {
nearcache.remove(key);
return v;
});
}
@Override
public CompletionStage remove(K key, CacheOptions options) {
return super.remove(key, options).thenApply(v -> {
if (v) {
nearcache.remove(key);
}
return v;
});
}
@Override
public CompletionStage remove(K key, CacheEntryVersion version, CacheOptions options) {
return super.remove(key, version, options).thenApply(v -> {
if (v) {
nearcache.remove(key);
}
return v;
});
}
@Override
public CompletionStage> getAndRemove(K key, CacheOptions options) {
return super.getAndRemove(key, options).thenApply(v -> {
nearcache.remove(key);
return v;
});
}
@Override
public CompletionStage clear(CacheOptions options) {
return super.clear(options).thenRun(nearcache::clear);
}
@Override
public CompletionStage putAll(Map entries, CacheWriteOptions options) {
options.expiration().maxIdle().ifPresent(m -> HOTROD.nearCacheMaxIdleUnsupported());
return super.putAll(entries, options).thenRun(() -> entries.keySet().forEach(nearcache::remove));
}
public void start() {
listenerAddress = nearcache.start(this);
}
public void stop() {
nearcache.stop(this);
}
public void clearNearCache() {
nearcache.clear();
}
// Increments the bloom filter version if it is even and returns whether it was incremented
private boolean incrementBloomVersionIfEven() {
if (bloomFilterUpdateVersion != null) {
int prev;
do {
prev = bloomFilterUpdateVersion.get();
// Odd number means we are already sending bloom filter
if ((prev & 1) == 1) {
return false;
}
} while (!bloomFilterUpdateVersion.compareAndSet(prev, prev + 1));
}
return true;
}
CompletionStage incrementBloomVersionUponCompletion(CompletionStage stage) {
if (bloomFilterUpdateVersion != null) {
return stage
.whenComplete((ignore, t) -> bloomFilterUpdateVersion.incrementAndGet());
}
return stage;
}
@Override
public CompletionStage updateBloomFilter() {
// Not being able to increment the even version means we have a concurrent update - so skip
if (!incrementBloomVersionIfEven()) {
if (trace) {
log.tracef("Already have a concurrent bloom filter update for listenerId(%s) - skipping",
org.infinispan.commons.util.Util.printArray(nearcache.getListenerId()));
}
return CompletableFuture.completedFuture(null);
}
byte[] bloomFilterBits = nearcache.calculateBloomBits();
if (trace) {
log.tracef("Sending bloom filter bits(%s) update to %s for listenerId(%s)",
org.infinispan.commons.util.Util.printArray(bloomFilterBits), listenerAddress,
org.infinispan.commons.util.Util.printArray(nearcache.getListenerId()));
}
CacheOperationsFactory cacheOperationsFactory = getOperationsFactory();
UpdateBloomFilterOperation bloopOp = cacheOperationsFactory.newUpdateBloomFilterOperation(CacheOptions.DEFAULT, listenerAddress, bloomFilterBits);
return incrementBloomVersionUponCompletion(bloopOp.execute());
}
public SocketAddress getBloomListenerAddress() {
return listenerAddress;
}
public void setBloomListenerAddress(SocketAddress socketAddress) {
this.listenerAddress = socketAddress;
}
@Override
public SocketAddress addNearCacheListener(Object listener, int bloomBits) {
ClientListenerOperation op = getOperationsFactory().newAddNearCacheListenerOperation(listener, CacheOptions.DEFAULT, getDataFormat(),
bloomBits, this);
// no timeout, see below
return await(op.execute());
}
}