
com.hazelcast.cache.impl.AbstractCacheRecordStore Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2008-2024, Hazelcast, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hazelcast.cache.impl;
import com.hazelcast.cache.CacheEventType;
import com.hazelcast.cache.CacheNotExistsException;
import com.hazelcast.cache.impl.maxsize.impl.EntryCountCacheEvictionChecker;
import com.hazelcast.cache.impl.record.CacheRecord;
import com.hazelcast.cache.impl.record.CacheRecordFactory;
import com.hazelcast.cache.impl.record.SampleableCacheRecordMap;
import com.hazelcast.cluster.Address;
import com.hazelcast.config.CacheConfig;
import com.hazelcast.config.Config;
import com.hazelcast.config.EventJournalConfig;
import com.hazelcast.config.EvictionConfig;
import com.hazelcast.config.InMemoryFormat;
import com.hazelcast.config.MaxSizePolicy;
import com.hazelcast.config.WanConsumerConfig;
import com.hazelcast.config.WanReplicationConfig;
import com.hazelcast.config.WanReplicationRef;
import com.hazelcast.core.ManagedContext;
import com.hazelcast.internal.diagnostics.StoreLatencyPlugin;
import com.hazelcast.internal.eviction.ClearExpiredRecordsTask;
import com.hazelcast.internal.eviction.EvictionCandidate;
import com.hazelcast.internal.eviction.EvictionChecker;
import com.hazelcast.internal.eviction.EvictionListener;
import com.hazelcast.internal.eviction.EvictionPolicyEvaluatorProvider;
import com.hazelcast.internal.eviction.ExpiredKey;
import com.hazelcast.internal.eviction.impl.evaluator.EvictionPolicyEvaluator;
import com.hazelcast.internal.eviction.impl.strategy.sampling.SamplingEvictionStrategy;
import com.hazelcast.internal.iteration.IterationPointer;
import com.hazelcast.internal.nearcache.impl.invalidation.InvalidationQueue;
import com.hazelcast.internal.serialization.Data;
import com.hazelcast.internal.serialization.SerializationService;
import com.hazelcast.internal.services.ObjectNamespace;
import com.hazelcast.internal.util.Clock;
import com.hazelcast.internal.util.ExceptionUtil;
import com.hazelcast.internal.util.Timer;
import com.hazelcast.internal.util.UuidUtil;
import com.hazelcast.internal.util.comparators.ValueComparator;
import com.hazelcast.internal.util.comparators.ValueComparatorUtil;
import com.hazelcast.map.impl.MapEntries;
import com.hazelcast.spi.eviction.EvictionPolicyComparator;
import com.hazelcast.spi.impl.NodeEngine;
import com.hazelcast.spi.impl.NodeEngineImpl;
import com.hazelcast.spi.impl.eventservice.EventRegistration;
import com.hazelcast.spi.impl.eventservice.EventService;
import com.hazelcast.spi.impl.tenantcontrol.TenantContextual;
import com.hazelcast.spi.merge.SplitBrainMergePolicy;
import com.hazelcast.spi.merge.SplitBrainMergeTypes.CacheMergeTypes;
import com.hazelcast.spi.properties.ClusterProperty;
import com.hazelcast.spi.tenantcontrol.TenantControl;
import com.hazelcast.wan.impl.CallerProvenance;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import javax.annotation.Nonnull;
import javax.cache.configuration.Factory;
import javax.cache.expiry.Duration;
import javax.cache.expiry.ExpiryPolicy;
import javax.cache.integration.CacheLoader;
import javax.cache.integration.CacheLoaderException;
import javax.cache.integration.CacheWriter;
import javax.cache.integration.CacheWriterException;
import javax.cache.processor.EntryProcessor;
import java.io.Closeable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.UUID;
import static com.hazelcast.cache.impl.CacheEventContextUtil.createBaseEventContext;
import static com.hazelcast.cache.impl.CacheEventContextUtil.createCacheCompleteEvent;
import static com.hazelcast.cache.impl.CacheEventContextUtil.createCacheCreatedEvent;
import static com.hazelcast.cache.impl.CacheEventContextUtil.createCacheExpiredEvent;
import static com.hazelcast.cache.impl.CacheEventContextUtil.createCacheRemovedEvent;
import static com.hazelcast.cache.impl.CacheEventContextUtil.createCacheUpdatedEvent;
import static com.hazelcast.cache.impl.operation.MutableOperation.IGNORE_COMPLETION;
import static com.hazelcast.cache.impl.record.CacheRecord.TIME_NOT_AVAILABLE;
import static com.hazelcast.cache.impl.record.CacheRecordFactory.isExpiredAt;
import static com.hazelcast.internal.config.ConfigValidator.checkCacheEvictionConfig;
import static com.hazelcast.internal.util.EmptyStatement.ignore;
import static com.hazelcast.internal.util.MapUtil.createHashMap;
import static com.hazelcast.internal.util.SetUtil.createHashSet;
import static com.hazelcast.internal.util.ThreadUtil.assertRunningOnPartitionThread;
import static com.hazelcast.spi.impl.merge.MergingValueFactory.createMergingEntry;
import static java.util.Collections.emptySet;
@SuppressWarnings({"checkstyle:methodcount", "checkstyle:classfanoutcomplexity",
"checkstyle:classdataabstractioncoupling"})
public abstract class AbstractCacheRecordStore>
implements ICacheRecordStore, EvictionListener {
public static final UUID SOURCE_NOT_AVAILABLE = UuidUtil.NIL_UUID;
protected static final int DEFAULT_INITIAL_CAPACITY = 256;
protected final int partitionId;
protected final int partitionCount;
protected final boolean persistWanReplicatedData;
protected final boolean disablePerEntryInvalidationEvents;
/**
* the full name of the cache, including the manager scope prefix
*/
protected final String name;
protected final NodeEngine nodeEngine;
protected final CacheConfig cacheConfig;
protected final SerializationService ss;
protected final EvictionConfig evictionConfig;
protected final ValueComparator valueComparator;
protected final EvictionChecker evictionChecker;
protected final ObjectNamespace objectNamespace;
protected final AbstractCacheService cacheService;
protected final CacheRecordFactory cacheRecordFactory;
protected final EventJournalConfig eventJournalConfig;
protected final ClearExpiredRecordsTask clearExpiredRecordsTask;
protected final SamplingEvictionStrategy evictionStrategy;
protected final EvictionPolicyEvaluator evictionPolicyEvaluator;
protected final Map> batchEvent = new HashMap<>();
protected final CompositeCacheRSMutationObserver compositeCacheRSMutationObserver;
protected boolean primary;
protected boolean eventsEnabled = true;
protected boolean eventsBatchingEnabled;
protected CRM records;
protected TenantContextual cacheLoader;
protected TenantContextual cacheWriter;
protected CacheContext cacheContext;
protected CacheStatisticsImpl statistics;
protected TenantContextual defaultExpiryPolicy;
protected Iterator> expirationIterator;
protected InvalidationQueue expiredKeys = new InvalidationQueue<>();
protected boolean hasEntryWithExpiration;
protected boolean wanReplicateEvictions;
@SuppressWarnings({"checkstyle:npathcomplexity", "checkstyle:executablestatementcount", "checkstyle:methodlength"})
public AbstractCacheRecordStore(String cacheNameWithPrefix, int partitionId, NodeEngine nodeEngine,
AbstractCacheService cacheService) {
this.name = cacheNameWithPrefix;
this.partitionId = partitionId;
this.nodeEngine = nodeEngine;
this.ss = nodeEngine.getSerializationService();
this.partitionCount = nodeEngine.getPartitionService().getPartitionCount();
this.cacheService = cacheService;
this.cacheConfig = cacheService.getCacheConfig(cacheNameWithPrefix);
if (cacheConfig == null) {
throw new CacheNotExistsException("Cache " + cacheNameWithPrefix + " is already destroyed or not created yet, on "
+ nodeEngine.getLocalMember());
}
this.eventJournalConfig = cacheConfig.getEventJournalConfig();
this.evictionConfig = cacheConfig.getEvictionConfig();
if (evictionConfig == null) {
throw new IllegalStateException("Eviction config cannot be null!");
}
this.disablePerEntryInvalidationEvents = cacheConfig.isDisablePerEntryInvalidationEvents();
EvictionPolicyComparator evictionPolicyComparator = createEvictionPolicyComparator(evictionConfig);
evictionPolicyComparator = injectDependencies(evictionPolicyComparator);
this.evictionPolicyEvaluator = new EvictionPolicyEvaluator<>(evictionPolicyComparator);
this.cacheContext = cacheService.getOrCreateCacheContext(cacheNameWithPrefix);
this.records = createRecordCacheMap();
this.evictionChecker = createCacheEvictionChecker(evictionConfig.getSize(), evictionConfig.getMaxSizePolicy());
this.evictionStrategy = createEvictionStrategy(evictionConfig);
this.objectNamespace = CacheService.getObjectNamespace(cacheNameWithPrefix);
this.persistWanReplicatedData = canPersistWanReplicatedData(cacheConfig, nodeEngine);
this.cacheRecordFactory = new CacheRecordFactory(cacheConfig.getInMemoryFormat(), ss);
this.valueComparator = getValueComparatorOf(cacheConfig.getInMemoryFormat());
this.clearExpiredRecordsTask = cacheService.getExpirationManager().getTask();
this.compositeCacheRSMutationObserver = new CompositeCacheRSMutationObserver();
if (cacheConfig.isStatisticsEnabled()) {
statistics = cacheService.createCacheStatIfAbsent(cacheNameWithPrefix);
}
this.wanReplicateEvictions = isWanReplicationEnabled()
&& cacheService.getNodeEngine().getProperties().getBoolean(ClusterProperty.WAN_REPLICATE_ICACHE_EVICTIONS);
TenantControl tenantControl = nodeEngine
.getTenantControlService()
.getTenantControl(ICacheService.SERVICE_NAME, cacheNameWithPrefix);
cacheLoader = TenantContextual.create(this::initCacheLoader,
() -> cacheConfig.getCacheLoaderFactory() != null, tenantControl);
cacheWriter = TenantContextual.create(this::initCacheWriter,
() -> cacheConfig.getCacheWriterFactory() != null, tenantControl);
defaultExpiryPolicy = TenantContextual.create(this::initDefaultExpiryPolicy,
this::defaultExpiryPolicyExists, tenantControl);
init();
}
public SerializationService getSerializationService() {
return ss;
}
private CacheLoader initCacheLoader() {
Factory cacheLoaderFactory = cacheConfig.getCacheLoaderFactory();
cacheLoaderFactory = injectDependencies(cacheLoaderFactory);
CacheLoader cacheLoader = cacheLoaderFactory.create();
cacheLoader = injectDependencies(cacheLoader);
registerResourceIfItIsClosable(cacheLoader);
return cacheLoader;
}
private CacheWriter initCacheWriter() {
Factory cacheWriterFactory = cacheConfig.getCacheWriterFactory();
cacheWriterFactory = injectDependencies(cacheWriterFactory);
CacheWriter cacheWriter = cacheWriterFactory.create();
cacheWriter = injectDependencies(cacheWriter);
registerResourceIfItIsClosable(cacheWriter);
return cacheWriter;
}
private ExpiryPolicy initDefaultExpiryPolicy() {
Factory expiryPolicyFactory = cacheConfig.getExpiryPolicyFactory();
expiryPolicyFactory = injectDependencies(expiryPolicyFactory);
ExpiryPolicy defaultExpiryPolicy = expiryPolicyFactory.create();
defaultExpiryPolicy = injectDependencies(defaultExpiryPolicy);
registerResourceIfItIsClosable(defaultExpiryPolicy);
return defaultExpiryPolicy;
}
private Boolean defaultExpiryPolicyExists() {
if (cacheConfig.getExpiryPolicyFactory() != null) {
return true;
} else {
throw new IllegalStateException("Expiry policy factory cannot be null!");
}
}
// Overridden in EE
protected ValueComparator getValueComparatorOf(InMemoryFormat inMemoryFormat) {
return ValueComparatorUtil.getValueComparatorOf(inMemoryFormat);
}
private boolean canPersistWanReplicatedData(CacheConfig cacheConfig, NodeEngine nodeEngine) {
boolean persistWanReplicatedData = false;
WanReplicationRef wanReplicationRef = cacheConfig.getWanReplicationRef();
if (wanReplicationRef != null) {
String wanReplicationRefName = wanReplicationRef.getName();
Config config = nodeEngine.getConfig();
WanReplicationConfig wanReplicationConfig = config.getWanReplicationConfig(wanReplicationRefName);
if (wanReplicationConfig != null) {
WanConsumerConfig wanConsumerConfig = wanReplicationConfig.getConsumerConfig();
if (wanConsumerConfig != null) {
persistWanReplicatedData = wanConsumerConfig.isPersistWanReplicatedData();
}
}
}
return persistWanReplicatedData;
}
private boolean persistenceEnabledFor(@Nonnull CallerProvenance provenance) {
switch (provenance) {
case WAN:
return persistWanReplicatedData;
case NOT_WAN:
return true;
default:
throw new IllegalArgumentException("Unexpected provenance: `" + provenance + "`");
}
}
public void instrument(NodeEngine nodeEngine) {
StoreLatencyPlugin plugin = ((NodeEngineImpl) nodeEngine).getDiagnostics().getPlugin(StoreLatencyPlugin.class);
if (plugin == null) {
return;
}
cacheLoader = cacheLoader.delegate(new LatencyTrackingCacheLoader(cacheLoader, plugin, cacheConfig.getName()));
cacheWriter = cacheWriter.delegate(new LatencyTrackingCacheWriter(cacheWriter, plugin, cacheConfig.getName()));
}
private boolean isPrimary() {
final Address owner = nodeEngine.getPartitionService().getPartition(partitionId, false).getOwnerOrNull();
final Address thisAddress = nodeEngine.getThisAddress();
return owner != null && owner.equals(thisAddress);
}
@SuppressWarnings("unchecked")
private T injectDependencies(T obj) {
ManagedContext managedContext = ss.getManagedContext();
return (T) managedContext.initialize(obj);
}
private void registerResourceIfItIsClosable(Object resource) {
if (resource instanceof Closeable closeable) {
cacheService.addCacheResource(name, closeable);
}
}
@Override
public void init() {
primary = isPrimary();
records.setEntryCounting(primary);
markExpirable(TIME_NOT_AVAILABLE);
addMutationObservers();
}
// Overridden in EE.
protected void addMutationObservers() {
if (eventJournalConfig != null && eventJournalConfig.isEnabled()) {
compositeCacheRSMutationObserver.add(
new EventJournalRSMutationObserver(cacheService, eventJournalConfig, objectNamespace, partitionId));
}
}
protected boolean isReadThrough() {
return cacheConfig.isReadThrough();
}
protected boolean isWriteThrough() {
return cacheConfig.isWriteThrough();
}
protected boolean isStatisticsEnabled() {
return statistics != null;
}
protected abstract CRM createRecordCacheMap();
protected abstract CacheEntryProcessorEntry createCacheEntryProcessorEntry(Data key, R record,
long now, int completionId);
protected abstract R createRecord(Object value, long creationTime, long expiryTime);
protected abstract Data valueToData(Object value);
protected abstract Object dataToValue(Data data);
protected abstract Object recordToValue(R record);
protected abstract Data recordToData(R record);
protected abstract Data toHeapData(Object obj);
/**
* Creates an instance for checking if the maximum cache size has been reached. Supports only the
* {@link MaxSizePolicy#ENTRY_COUNT} policy. Returns null if other {@code maxSizePolicy} is used.
*
* @param size the maximum number of entries
* @param maxSizePolicy the way in which the size is interpreted, only the {@link MaxSizePolicy#ENTRY_COUNT} policy is
* supported.
* @return the instance which will check if the maximum number of entries has been reached or null if the
* {@code maxSizePolicy} is not {@link MaxSizePolicy#ENTRY_COUNT}
* @throws IllegalArgumentException if the {@code maxSizePolicy} is null
*/
protected EvictionChecker createCacheEvictionChecker(int size, MaxSizePolicy maxSizePolicy) {
if (maxSizePolicy == null) {
throw new IllegalArgumentException("Max-Size policy cannot be null");
}
if (maxSizePolicy == MaxSizePolicy.ENTRY_COUNT) {
return new EntryCountCacheEvictionChecker(size, records, partitionCount);
}
return null;
}
protected EvictionPolicyComparator createEvictionPolicyComparator(EvictionConfig evictionConfig) {
checkCacheEvictionConfig(evictionConfig);
return EvictionPolicyEvaluatorProvider.getEvictionPolicyComparator(evictionConfig, nodeEngine.getConfigClassLoader());
}
protected SamplingEvictionStrategy createEvictionStrategy(EvictionConfig cacheEvictionConfig) {
return SamplingEvictionStrategy.INSTANCE;
}
protected boolean isEvictionEnabled() {
return evictionStrategy != null && evictionPolicyEvaluator != null;
}
protected boolean isEventsEnabled() {
return eventsEnabled && (cacheContext.getCacheEntryListenerCount() > 0 || isWanReplicationEnabled());
}
protected boolean isInvalidationEnabled() {
return primary && cacheContext.getInvalidationListenerCount() > 0;
}
@Override
public boolean evictIfRequired() {
if (!isEvictionEnabled()) {
return false;
}
boolean evicted = evictionStrategy.evict(records, evictionPolicyEvaluator, evictionChecker, this);
if (isStatisticsEnabled() && evicted && primary) {
statistics.increaseCacheEvictions(1);
}
return evicted;
}
@Override
public void sampleAndForceRemoveEntries(int entryCountToRemove) {
assertRunningOnPartitionThread();
Queue keysToRemove = new LinkedList<>();
Iterable> entries = records.sample(entryCountToRemove);
for (EvictionCandidate entry : entries) {
keysToRemove.add(entry.getAccessor());
}
Data dataKey;
while ((dataKey = keysToRemove.poll()) != null) {
forceRemoveRecord(dataKey);
}
}
protected void forceRemoveRecord(Data key) {
// overridden in other context
removeRecord(key);
}
protected Data toData(Object obj) {
if (obj instanceof Data data) {
return data;
} else if (obj instanceof CacheRecord) {
return recordToData((R) obj);
} else {
return valueToData(obj);
}
}
protected Object toValue(Object obj) {
if (obj instanceof Data data) {
return dataToValue(data);
} else if (obj instanceof CacheRecord) {
return recordToValue((R) obj);
} else {
return obj;
}
}
protected Object toStorageValue(Object obj) {
if (obj instanceof Data data) {
if (cacheConfig.getInMemoryFormat() == InMemoryFormat.OBJECT) {
return dataToValue(data);
} else {
return obj;
}
} else if (obj instanceof CacheRecord) {
return recordToValue((R) obj);
} else {
return obj;
}
}
public Data toEventData(Object obj) {
return isEventsEnabled() ? toHeapData(obj) : null;
}
private long getAdjustedExpireTime(Duration duration, long now) {
return duration.getAdjustedTime(now);
}
protected ExpiryPolicy getExpiryPolicy(CacheRecord record, ExpiryPolicy expiryPolicy) {
if (expiryPolicy != null) {
return expiryPolicy;
}
if (record != null && record.getExpiryPolicy() != null) {
return (ExpiryPolicy) toValue(record.getExpiryPolicy());
}
return defaultExpiryPolicy.get();
}
protected boolean evictIfExpired(Data key, R record, long now) {
return processExpiredEntry(key, record, now);
}
protected boolean processExpiredEntry(Data key, R record, long now) {
return processExpiredEntry(key, record, now, SOURCE_NOT_AVAILABLE);
}
protected boolean processExpiredEntry(Data key, R record, long now, UUID source) {
return processExpiredEntry(key, record, now, source, null);
}
protected boolean processExpiredEntry(Data key, R record, long now, UUID source, UUID origin) {
// The event journal will get REMOVED instead of EXPIRED
boolean isExpired = record != null && record.isExpiredAt(now);
if (!isExpired) {
return false;
}
if (isStatisticsEnabled()) {
statistics.increaseCacheExpiries(1);
}
R removedRecord = doRemoveRecord(key, source);
Data keyEventData = toEventData(key);
Data recordEventData = toEventData(removedRecord);
if (removedRecord != null) {
onProcessExpiredEntry(key, removedRecord, removedRecord.getExpirationTime(), now, source, origin);
if (isEventsEnabled()) {
publishEvent(createCacheExpiredEvent(keyEventData, recordEventData,
TIME_NOT_AVAILABLE, origin, IGNORE_COMPLETION));
}
}
return true;
}
protected R processExpiredEntry(Data key, R record, long expiryTime, long now, UUID source) {
return processExpiredEntry(key, record, expiryTime, now, source, null);
}
protected R processExpiredEntry(Data key, R record, long expiryTime, long now, UUID source, UUID origin) {
// The event journal will get REMOVED instead of EXPIRED
if (!isExpiredAt(expiryTime, now)) {
return record;
}
if (isStatisticsEnabled()) {
statistics.increaseCacheExpiries(1);
}
R removedRecord = doRemoveRecord(key, source);
Data keyEventData = toEventData(key);
Data recordEventData = toEventData(removedRecord);
onProcessExpiredEntry(key, removedRecord, expiryTime, now, source, origin);
if (isEventsEnabled()) {
publishEvent(createCacheExpiredEvent(keyEventData, recordEventData, TIME_NOT_AVAILABLE,
origin, IGNORE_COMPLETION));
}
return null;
}
protected void onProcessExpiredEntry(Data key, R record, long expiryTime, long now, UUID source, UUID origin) {
accumulateOrSendExpiredKeysToBackup(key, record);
}
protected void accumulateOrSendExpiredKeysToBackup(Data key, R record) {
if (cacheConfig.getTotalBackupCount() == 0) {
return;
}
if (key != null && record != null) {
this.expiredKeys.offer(new ExpiredKey(toHeapData(key), record.getCreationTime()));
}
clearExpiredRecordsTask.tryToSendBackupExpiryOp(this, true);
}
@Override
public boolean isExpirable() {
return hasEntryWithExpiration || getConfig().getExpiryPolicyFactory() != null;
}
public R accessRecord(Data key, R record, ExpiryPolicy expiryPolicy, long now) {
onRecordAccess(key, record, getExpiryPolicy(record, expiryPolicy), now);
return record;
}
@Override
public void onEvict(Data key, R record, boolean wasExpired) {
if (wasExpired) {
compositeCacheRSMutationObserver.onExpire(key, record.getValue());
} else {
compositeCacheRSMutationObserver.onEvict(key, record.getValue());
}
invalidateEntry(key);
if (wanReplicateEvictions) {
cacheService.getCacheWanEventPublisher().publishWanRemove(name, toHeapData(key));
}
}
protected void invalidateEntry(Data key, UUID source) {
if (isInvalidationEnabled()) {
if (key == null) {
cacheService.sendInvalidationEvent(name, null, source);
} else if (!disablePerEntryInvalidationEvents) {
cacheService.sendInvalidationEvent(name, toHeapData(key), source);
}
}
}
protected void invalidateEntry(Data key) {
invalidateEntry(key, SOURCE_NOT_AVAILABLE);
}
protected void updateGetAndPutStat(boolean isPutSucceed, boolean getValue, boolean oldValueNull, long startNanos) {
if (isStatisticsEnabled()) {
if (isPutSucceed) {
statistics.increaseCachePuts(1);
statistics.addPutTimeNanos(Timer.nanosElapsed(startNanos));
}
if (getValue) {
if (oldValueNull) {
statistics.increaseCacheMisses(1);
} else {
statistics.increaseCacheHits(1);
}
statistics.addGetTimeNanos(Timer.nanosElapsed(startNanos));
}
}
}
protected long updateAccessDuration(Data key, R record, ExpiryPolicy expiryPolicy, long now) {
long expiryTime = TIME_NOT_AVAILABLE;
try {
Duration expiryDuration = expiryPolicy.getExpiryForAccess();
if (expiryDuration != null) {
expiryTime = getAdjustedExpireTime(expiryDuration, now);
record.setExpirationTime(expiryTime);
if (isEventsEnabled()) {
CacheEventContext cacheEventContext =
createBaseEventContext(CacheEventType.EXPIRATION_TIME_UPDATED, toEventData(key),
toEventData(record.getValue()), expiryTime, null, IGNORE_COMPLETION);
cacheEventContext.setAccessHit(record.getHits());
publishEvent(cacheEventContext);
}
}
} catch (Exception e) {
ignore(e);
}
return expiryTime;
}
protected long onRecordAccess(Data key, R record, ExpiryPolicy expiryPolicy, long now) {
record.setLastAccessTime(now);
record.incrementHits();
return updateAccessDuration(key, record, expiryPolicy, now);
}
protected void updateReplaceStat(boolean result, boolean isHit, long startNanos) {
if (isStatisticsEnabled()) {
if (result) {
statistics.increaseCachePuts(1);
statistics.addPutTimeNanos(Timer.nanosElapsed(startNanos));
}
if (isHit) {
statistics.increaseCacheHits(1);
} else {
statistics.increaseCacheMisses(1);
}
}
}
protected void publishEvent(CacheEventContext cacheEventContext) {
if (isEventsEnabled()) {
cacheEventContext.setCacheName(name);
if (eventsBatchingEnabled) {
CacheEventDataImpl cacheEventData =
new CacheEventDataImpl(name, cacheEventContext.getEventType(), cacheEventContext.getDataKey(),
cacheEventContext.getDataValue(), cacheEventContext.getDataOldValue(),
cacheEventContext.isOldValueAvailable());
Set cacheEventDataSet = batchEvent.remove(cacheEventContext.getEventType());
if (cacheEventDataSet == null) {
cacheEventDataSet = new HashSet<>();
batchEvent.put(cacheEventContext.getEventType(), cacheEventDataSet);
}
cacheEventDataSet.add(cacheEventData);
} else {
cacheService.publishEvent(cacheEventContext);
}
}
}
protected void publishBatchedEvents(String cacheName, CacheEventType cacheEventType, int orderKey) {
if (isEventsEnabled()) {
Set cacheEventDatas = batchEvent.remove(cacheEventType);
if (cacheEventDatas != null) {
cacheService.publishEvent(cacheName, new CacheEventSet(cacheEventType, cacheEventDatas), orderKey);
}
}
}
protected boolean compare(Object v1, Object v2) {
if (v1 == null && v2 == null) {
return true;
}
if (v1 == null || v2 == null) {
return false;
}
return v1.equals(v2);
}
protected R createRecord(long expiryTime) {
return createRecord(null, Clock.currentTimeMillis(), expiryTime);
}
protected R createRecord(Object value, long expiryTime) {
return createRecord(value, Clock.currentTimeMillis(), expiryTime);
}
protected R createRecord(Data keyData, Object value, long expirationTime, int completionId) {
R record = createRecord(value, expirationTime);
if (isEventsEnabled()) {
publishEvent(createCacheCreatedEvent(toEventData(keyData), toEventData(value),
expirationTime, null, completionId));
}
return record;
}
@SuppressWarnings("checkstyle:parameternumber")
protected void onCreateRecordError(Data key, Object value, long expiryTime, long now, boolean disableWriteThrough,
int completionId, UUID origin, R record, Throwable error) {
}
protected R createRecord(Data key, Object value, long expiryTime, long now,
boolean disableWriteThrough, int completionId, UUID origin) {
R record = createRecord(value, now, expiryTime);
try {
doPutRecord(key, record, origin, true);
} catch (Throwable error) {
onCreateRecordError(key, value, expiryTime, now, disableWriteThrough,
completionId, origin, record, error);
throw ExceptionUtil.rethrow(error);
}
try {
if (!disableWriteThrough) {
writeThroughCache(key, value);
}
} catch (Throwable error) {
// Writing to `CacheWriter` failed, so we should revert entry (remove added record).
final R removed = records.remove(key);
if (removed != null) {
compositeCacheRSMutationObserver.onRemove(
key, removed.getValue());
}
// Disposing key/value/record should be handled inside `onCreateRecordWithExpiryError`.
onCreateRecordError(key, value, expiryTime, now, disableWriteThrough,
completionId, origin, record, error);
throw ExceptionUtil.rethrow(error);
}
if (isEventsEnabled()) {
publishEvent(createCacheCreatedEvent(toEventData(key), toEventData(value),
expiryTime, origin, completionId));
}
return record;
}
protected R createRecordWithExpiry(Data key, Object value, long expiryTime,
long now, boolean disableWriteThrough, int completionId, UUID origin) {
if (!isExpiredAt(expiryTime, now)) {
return createRecord(key, value, expiryTime, now, disableWriteThrough, completionId, origin);
}
if (isEventsEnabled()) {
publishEvent(createCacheCompleteEvent(toEventData(key), TIME_NOT_AVAILABLE,
origin, completionId));
}
return null;
}
protected R createRecordWithExpiry(Data key, Object value, long expiryTime,
long now, boolean disableWriteThrough, int completionId) {
return createRecordWithExpiry(key, value, expiryTime, now, disableWriteThrough, completionId, SOURCE_NOT_AVAILABLE);
}
protected R createRecordWithExpiry(Data key, Object value, ExpiryPolicy expiryPolicy,
long now, boolean disableWriteThrough, int completionId) {
return createRecordWithExpiry(key, value, expiryPolicy, now, disableWriteThrough, completionId, SOURCE_NOT_AVAILABLE);
}
protected R createRecordWithExpiry(Data key, Object value, ExpiryPolicy expiryPolicy,
long now, boolean disableWriteThrough, int completionId, UUID origin) {
expiryPolicy = getExpiryPolicy(null, expiryPolicy);
Duration expiryDuration;
try {
expiryDuration = expiryPolicy.getExpiryForCreation();
} catch (Exception e) {
expiryDuration = Duration.ETERNAL;
}
long expiryTime = getAdjustedExpireTime(expiryDuration, now);
return createRecordWithExpiry(key, value, expiryTime, now, disableWriteThrough, completionId, origin);
}
protected void onUpdateRecord(Data key, R record, Object value, Data oldDataValue) {
compositeCacheRSMutationObserver.onUpdate(key, oldDataValue, value);
}
protected void onUpdateRecordError(Data key, R record, Object value, Data newDataValue,
Data oldDataValue, Throwable error) {
}
protected void onUpdateExpiryPolicy(Data key, R record, Data oldDataExpiryPolicy) {
}
protected void onUpdateExpiryPolicyError(Data key, R record, Data oldDataExpiryPolicy) {
}
protected void updateRecord(Data key, CacheRecord record, long expiryTime, long now, UUID origin) {
record.setExpirationTime(expiryTime);
invalidateEntry(key, origin);
}
@SuppressWarnings("checkstyle:parameternumber")
protected void updateRecord(Data key, R record, Object value, long expiryTime, long now,
boolean disableWriteThrough, int completionId, UUID source, UUID origin) {
Data dataOldValue = null;
Data dataValue = null;
Object recordValue = value;
try {
updateExpiryTime(record, expiryTime);
if (isExpiredAt(expiryTime, now)) {
// No need to update record value if it is expired
if (!disableWriteThrough) {
writeThroughCache(key, value);
}
} else {
switch (cacheConfig.getInMemoryFormat()) {
case BINARY:
recordValue = toData(value);
dataValue = (Data) recordValue;
dataOldValue = toData(record);
break;
case OBJECT:
if (value instanceof Data data) {
recordValue = dataToValue(data);
dataValue = data;
} else {
dataValue = valueToData(value);
}
dataOldValue = toData(record);
break;
case NATIVE:
recordValue = toData(value);
dataValue = (Data) recordValue;
dataOldValue = toData(record);
break;
default:
throw new IllegalArgumentException("Invalid storage format: " + cacheConfig.getInMemoryFormat());
}
if (!disableWriteThrough) {
writeThroughCache(key, value);
// If writing to `CacheWriter` fails no need to revert. Because we have not update record value yet
// with its new value but just converted new value to required storage type.
}
Data eventDataKey = toEventData(key);
Data eventDataValue = toEventData(dataValue);
Data eventDataOldValue = toEventData(dataOldValue);
Data eventDataExpiryPolicy = toEventData(record.getExpiryPolicy());
updateRecordValue(record, recordValue);
onUpdateRecord(key, record, value, dataOldValue);
invalidateEntry(key, source);
if (isEventsEnabled()) {
publishEvent(createCacheUpdatedEvent(eventDataKey, eventDataValue, eventDataOldValue,
record.getCreationTime(), record.getExpirationTime(),
record.getLastAccessTime(), record.getHits(),
origin, completionId, eventDataExpiryPolicy));
}
}
} catch (Throwable error) {
onUpdateRecordError(key, record, value, dataValue, dataOldValue, error);
throw ExceptionUtil.rethrow(error);
}
}
private boolean updateExpiryTime(R record, long expiryTime) {
if (expiryTime == TIME_NOT_AVAILABLE) {
return false;
}
boolean expiryTimeChanged = record.getExpirationTime() != expiryTime;
markExpirable(expiryTime);
record.setExpirationTime(expiryTime);
return expiryTimeChanged;
}
protected void updateExpiryPolicyOfRecord(Data key, R record, Object expiryPolicy) {
Object inMemoryExpiryPolicy;
Data dataOldExpiryPolicy = null;
switch (cacheConfig.getInMemoryFormat()) {
case OBJECT:
inMemoryExpiryPolicy = toValue(expiryPolicy);
dataOldExpiryPolicy = toData(getExpiryPolicyOrNull(record));
break;
case BINARY:
case NATIVE:
inMemoryExpiryPolicy = toData(expiryPolicy);
dataOldExpiryPolicy = toData(getExpiryPolicyOrNull(record));
break;
default:
throw new IllegalArgumentException("Invalid storage format: " + cacheConfig.getInMemoryFormat());
}
try {
onUpdateExpiryPolicy(key, record, dataOldExpiryPolicy);
record.setExpiryPolicy(inMemoryExpiryPolicy);
} catch (Throwable error) {
onUpdateExpiryPolicyError(key, record, dataOldExpiryPolicy);
throw ExceptionUtil.rethrow(error);
}
}
protected Object extractExpiryPolicyOfRecord(CacheRecord record) {
Object policyData = record.getExpiryPolicy();
if (policyData == null) {
return null;
}
switch (cacheConfig.getInMemoryFormat()) {
case NATIVE:
case BINARY:
return policyData;
case OBJECT:
return toValue(policyData);
default:
throw new IllegalArgumentException("Invalid storage format: " + cacheConfig.getInMemoryFormat());
}
}
protected void updateRecordValue(R record, Object recordValue) {
record.setValue(recordValue);
}
@SuppressWarnings("checkstyle:parameternumber")
protected boolean updateRecordWithExpiry(Data key, Object value, R record, long expiryTime, long now,
boolean disableWriteThrough, int completionId,
UUID source, UUID origin) {
updateRecord(key, record, value, expiryTime, now, disableWriteThrough, completionId, source, origin);
return processExpiredEntry(key, record, expiryTime, now, source) != null;
}
protected boolean updateRecordWithExpiry(Data key, Object value, R record, long expiryTime,
long now, boolean disableWriteThrough, int completionId) {
return updateRecordWithExpiry(key, value, record, expiryTime, now, disableWriteThrough,
completionId, SOURCE_NOT_AVAILABLE);
}
protected boolean updateRecordWithExpiry(Data key, Object value, R record, long expiryTime,
long now, boolean disableWriteThrough, int completionId, UUID source) {
return updateRecordWithExpiry(key, value, record, expiryTime, now,
disableWriteThrough, completionId, source, null);
}
protected boolean updateRecordWithExpiry(Data key, Object value, R record, ExpiryPolicy expiryPolicy,
long now, boolean disableWriteThrough, int completionId) {
return updateRecordWithExpiry(key, value, record, expiryPolicy, now,
disableWriteThrough, completionId, SOURCE_NOT_AVAILABLE);
}
protected boolean updateRecordWithExpiry(Data key, Object value, R record, ExpiryPolicy expiryPolicy,
long now, boolean disableWriteThrough, int completionId, UUID source) {
return updateRecordWithExpiry(key, value, record, expiryPolicy, now,
disableWriteThrough, completionId, source, null);
}
@SuppressWarnings("checkstyle:parameternumber")
protected boolean updateRecordWithExpiry(Data key, Object value, R record, ExpiryPolicy expiryPolicy, long now,
boolean disableWriteThrough, int completionId, UUID source, UUID origin) {
expiryPolicy = getExpiryPolicy(record, expiryPolicy);
long expiryTime = TIME_NOT_AVAILABLE;
try {
Duration expiryDuration = expiryPolicy.getExpiryForUpdate();
if (expiryDuration != null) {
expiryTime = getAdjustedExpireTime(expiryDuration, now);
}
} catch (Exception e) {
ignore(e);
}
return updateRecordWithExpiry(key, value, record, expiryTime, now,
disableWriteThrough, completionId, source, origin);
}
protected void updateRecordWithExpiry(Data key, CacheRecord record, ExpiryPolicy expiryPolicy, long now, UUID source) {
expiryPolicy = getExpiryPolicy(record, expiryPolicy);
long expiryTime = TIME_NOT_AVAILABLE;
try {
Duration expiryDuration = expiryPolicy.getExpiryForUpdate();
if (expiryDuration != null) {
expiryTime = getAdjustedExpireTime(expiryDuration, now);
}
} catch (Exception e) {
ignore(e);
}
updateRecord(key, record, expiryTime, now, source);
}
protected void onDeleteRecord(Data key, R record, boolean deleted) {
}
protected boolean deleteRecord(Data key, int completionId) {
return deleteRecord(key, completionId, SOURCE_NOT_AVAILABLE);
}
protected boolean deleteRecord(Data key, int completionId, UUID source) {
return deleteRecord(key, completionId, source, null);
}
protected boolean deleteRecord(Data key, int completionId, UUID source, UUID origin) {
R removedRecord = null;
try {
removedRecord = doRemoveRecord(key, source);
if (isEventsEnabled()) {
Data eventDataKey = toEventData(key);
Data eventDataValue = toEventData(removedRecord);
publishEvent(createCacheRemovedEvent(eventDataKey, eventDataValue,
TIME_NOT_AVAILABLE, origin, completionId));
}
} finally {
onDeleteRecord(key, removedRecord, removedRecord != null);
}
return removedRecord != null;
}
public R readThroughRecord(Data key, long now) {
Object value = readThroughCache(key);
if (value == null) {
return null;
}
Duration expiryDuration;
try {
expiryDuration = defaultExpiryPolicy.get().getExpiryForCreation();
} catch (Exception e) {
expiryDuration = Duration.ETERNAL;
}
long expiryTime = getAdjustedExpireTime(expiryDuration, now);
if (isExpiredAt(expiryTime, now)) {
return null;
}
return createRecord(key, value, expiryTime, IGNORE_COMPLETION);
}
public Object readThroughCache(Data key) throws CacheLoaderException {
if (isReadThrough()) {
try (TenantControl.Closeable ctx = cacheLoader.getTenantControl().setTenant()) {
if (cacheLoader.exists()) {
Object o = dataToValue(key);
return cacheLoader.get().load(o);
}
} catch (Exception e) {
if (!(e instanceof CacheLoaderException)) {
throw new CacheLoaderException("Exception in CacheLoader during load", e);
} else {
throw (CacheLoaderException) e;
}
}
}
return null;
}
public void writeThroughCache(Data key, Object value) throws CacheWriterException {
if (isWriteThrough()) {
try (TenantControl.Closeable ctx = cacheWriter.getTenantControl().setTenant()) {
if (cacheWriter.exists()) {
Object objKey = dataToValue(key);
Object objValue = toValue(value);
cacheWriter.get().write(new CacheEntry
© 2015 - 2025 Weber Informatics LLC | Privacy Policy