blob: d939635cc276353a241352ec8e3907c4cd602a34 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.ignite.internal.processors.cache;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;
import javax.cache.Cache;
import javax.cache.expiry.ExpiryPolicy;
import javax.cache.processor.EntryProcessor;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteException;
import org.apache.ignite.IgniteLogger;
import org.apache.ignite.SystemProperty;
import org.apache.ignite.cache.CacheInterceptor;
import org.apache.ignite.cache.eviction.EvictableEntry;
import org.apache.ignite.internal.IgniteInternalFuture;
import org.apache.ignite.internal.NodeStoppingException;
import org.apache.ignite.internal.UnregisteredBinaryTypeException;
import org.apache.ignite.internal.UnregisteredClassException;
import org.apache.ignite.internal.pagemem.wal.record.DataEntry;
import org.apache.ignite.internal.pagemem.wal.record.DataRecord;
import org.apache.ignite.internal.pagemem.wal.record.MvccDataEntry;
import org.apache.ignite.internal.pagemem.wal.record.MvccDataRecord;
import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
import org.apache.ignite.internal.processors.cache.GridCacheUpdateAtomicResult.UpdateOutcome;
import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtCacheEntry;
import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxLocalAdapter;
import org.apache.ignite.internal.processors.cache.distributed.dht.atomic.GridDhtAtomicAbstractUpdateFuture;
import org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtLocalPartition;
import org.apache.ignite.internal.processors.cache.distributed.near.GridNearCacheEntry;
import org.apache.ignite.internal.processors.cache.extras.GridCacheEntryExtras;
import org.apache.ignite.internal.processors.cache.extras.GridCacheMvccEntryExtras;
import org.apache.ignite.internal.processors.cache.extras.GridCacheObsoleteEntryExtras;
import org.apache.ignite.internal.processors.cache.extras.GridCacheTtlEntryExtras;
import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
import org.apache.ignite.internal.processors.cache.mvcc.MvccUtils;
import org.apache.ignite.internal.processors.cache.mvcc.MvccVersion;
import org.apache.ignite.internal.processors.cache.mvcc.txlog.TxState;
import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
import org.apache.ignite.internal.processors.cache.persistence.CacheDataRowAdapter;
import org.apache.ignite.internal.processors.cache.persistence.DataRegion;
import org.apache.ignite.internal.processors.cache.persistence.StorageException;
import org.apache.ignite.internal.processors.cache.persistence.wal.WALPointer;
import org.apache.ignite.internal.processors.cache.query.continuous.CacheContinuousQueryListener;
import org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx;
import org.apache.ignite.internal.processors.cache.transactions.IgniteTxEntry;
import org.apache.ignite.internal.processors.cache.transactions.IgniteTxKey;
import org.apache.ignite.internal.processors.cache.transactions.IgniteTxLocalAdapter;
import org.apache.ignite.internal.processors.cache.transactions.TxCounters;
import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccUpdateResult;
import org.apache.ignite.internal.processors.cache.tree.mvcc.data.ResultType;
import org.apache.ignite.internal.processors.cache.version.GridCacheLazyPlainVersionedEntry;
import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
import org.apache.ignite.internal.processors.cache.version.GridCacheVersionConflictContext;
import org.apache.ignite.internal.processors.cache.version.GridCacheVersionedEntryEx;
import org.apache.ignite.internal.processors.dr.GridDrType;
import org.apache.ignite.internal.processors.platform.PlatformProcessor;
import org.apache.ignite.internal.processors.query.schema.SchemaIndexCacheVisitorClosure;
import org.apache.ignite.internal.processors.security.SecurityUtils;
import org.apache.ignite.internal.transactions.IgniteTxDuplicateKeyCheckedException;
import org.apache.ignite.internal.transactions.IgniteTxSerializationCheckedException;
import org.apache.ignite.internal.util.IgniteTree;
import org.apache.ignite.internal.util.future.GridFutureAdapter;
import org.apache.ignite.internal.util.lang.GridClosureException;
import org.apache.ignite.internal.util.lang.GridCursor;
import org.apache.ignite.internal.util.lang.GridMetadataAwareAdapter;
import org.apache.ignite.internal.util.lang.GridTuple;
import org.apache.ignite.internal.util.lang.GridTuple3;
import org.apache.ignite.internal.util.tostring.GridToStringBuilder;
import org.apache.ignite.internal.util.tostring.GridToStringExclude;
import org.apache.ignite.internal.util.tostring.GridToStringInclude;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.T2;
import org.apache.ignite.internal.util.typedef.internal.CU;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgniteBiTuple;
import org.apache.ignite.lang.IgniteInClosure;
import org.apache.ignite.lang.IgnitePredicate;
import org.apache.ignite.lang.IgniteUuid;
import org.apache.ignite.thread.IgniteThread;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static org.apache.ignite.IgniteSystemProperties.getLong;
import static org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_EXPIRED;
import static org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_LOCKED;
import static org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_PUT;
import static org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_READ;
import static org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_REMOVED;
import static org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_UNLOCKED;
import static org.apache.ignite.internal.processors.cache.GridCacheOperation.CREATE;
import static org.apache.ignite.internal.processors.cache.GridCacheOperation.DELETE;
import static org.apache.ignite.internal.processors.cache.GridCacheOperation.READ;
import static org.apache.ignite.internal.processors.cache.GridCacheOperation.TRANSFORM;
import static org.apache.ignite.internal.processors.cache.GridCacheOperation.UPDATE;
import static org.apache.ignite.internal.processors.cache.GridCacheUpdateAtomicResult.UpdateOutcome.INVOKE_NO_OP;
import static org.apache.ignite.internal.processors.cache.GridCacheUpdateAtomicResult.UpdateOutcome.REMOVE_NO_VAL;
import static org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionState.RENTING;
import static org.apache.ignite.internal.processors.cache.mvcc.MvccUtils.MVCC_MAX_SNAPSHOT;
import static org.apache.ignite.internal.processors.cache.mvcc.MvccUtils.compareIgnoreOpCounter;
import static org.apache.ignite.internal.processors.cache.persistence.CacheDataRowAdapter.RowData.NO_KEY;
import static org.apache.ignite.internal.processors.cache.version.GridCacheVersionEx.addConflictVersion;
import static org.apache.ignite.internal.processors.dr.GridDrType.DR_NONE;
/**
* Adapter for cache entry.
*/
@SuppressWarnings({"TooBroadScope"})
public abstract class GridCacheMapEntry extends GridMetadataAwareAdapter implements GridCacheEntryEx {
/** */
public static final GridCacheAtomicVersionComparator ATOMIC_VER_COMPARATOR = new GridCacheAtomicVersionComparator();
/** @see GridCacheMapEntry#ENTRY_LOCK_TIMEOUT */
public static final int DFLT_LOCK_TIMEOUT_ENV = 1000;
/** Property name for entry lock timeout in milliseconds. Default is 1000. */
@SystemProperty(value = "Sets the entry's lock timeout in milliseconds",
type = Long.class, defaults = "" + DFLT_LOCK_TIMEOUT_ENV)
/** */
public static final String ENTRY_LOCK_TIMEOUT_ENV = "ENTRY_LOCK_TIMEOUT";
/** Entry lock time awaiting. */
private static final long ENTRY_LOCK_TIMEOUT = getLong(ENTRY_LOCK_TIMEOUT_ENV, DFLT_LOCK_TIMEOUT_ENV);
/** */
private static final byte IS_DELETED_MASK = 0x01;
/** */
private static final byte IS_UNSWAPPED_MASK = 0x02;
/** */
private static final byte IS_EVICT_DISABLED = 0x04;
/**
* NOTE
* <br/>
* ====
* <br/>
* Make sure to recalculate this value any time when adding or removing fields from entry.
* The size should be count as follows:
* <ul>
* <li>Primitives: byte/boolean = 1, short = 2, int/float = 4, long/double = 8</li>
* <li>References: 8 each</li>
* <li>Each nested object should be analyzed in the same way as above.</li>
* </ul>
* ====
* <br/>
* <ul>
* <li>Reference fields:<ul>
* <li>8 : {@link #cctx}</li>
* <li>8 : {@link #key}</li>
* <li>8 : {@link #val}</li>
* <li>8 : {@link #ver}</li>
* <li>8 : {@link #extras}</li>
* <li>8 : {@link #lock}</li>
* <li>8 : {@link #listenerLock}</li>
* <li>8 : {@link GridMetadataAwareAdapter#data}</li>
* </ul></li>
* <li>Primitive fields:<ul>
* <li>4 : {@link #hash}</li>
* <li>1 : {@link #flags}</li>
* </ul></li>
* <li>Extras:<ul>
* <li>8 : {@link GridCacheEntryExtras#ttl()}</li>
* <li>8 : {@link GridCacheEntryExtras#expireTime()}</li>
* </ul></li>
* <li>Version:<ul>
* <li>4 : {@link GridCacheVersion#topVer}</li>
* <li>4 : {@link GridCacheVersion#nodeOrderDrId}</li>
* <li>8 : {@link GridCacheVersion#order}</li>
* </ul></li>
* <li>Key:<ul>
* <li>8 : {@link CacheObjectAdapter#val}</li>
* <li>8 : {@link CacheObjectAdapter#valBytes}</li>
* <li>4 : {@link KeyCacheObjectImpl#part}</li>
* </ul></li>
* <li>Value:<ul>
* <li>8 : {@link CacheObjectAdapter#val}</li>
* <li>8 : {@link CacheObjectAdapter#valBytes}</li>
* </ul></li>
* </ul>
*/
private static final int SIZE_OVERHEAD = 8 * 8 /* references */ + 5 /* primitives */ + 16 /* extras */
+ 16 /* version */ + 20 /* key */ + 16 /* value */;
/** Static logger to avoid re-creation. Made static for test purpose. */
protected static final AtomicReference<IgniteLogger> logRef = new AtomicReference<>();
/** Logger. */
protected static volatile IgniteLogger log;
/** Cache registry. */
@GridToStringExclude
protected final GridCacheContext<?, ?> cctx;
/** Key. */
@GridToStringInclude(sensitive = true)
protected final KeyCacheObject key;
/** Value. */
@GridToStringInclude(sensitive = true)
protected CacheObject val;
/** Version. */
@GridToStringInclude
protected GridCacheVersion ver;
/** Key hash code. */
@GridToStringInclude
private final int hash;
/** Extras */
@GridToStringInclude
private GridCacheEntryExtras extras;
/** */
@GridToStringExclude
private final ReentrantLock lock = new ReentrantLock();
/** Read Lock for continuous query listener */
@GridToStringExclude
private final ReadWriteLock listenerLock;
/**
* Flags:
* <ul>
* <li>Deleted flag - mask {@link #IS_DELETED_MASK}</li>
* <li>Unswapped flag - mask {@link #IS_UNSWAPPED_MASK}</li>
* </ul>
*/
@GridToStringInclude
protected byte flags;
/**
* @param cctx Cache context.
* @param key Cache key.
*/
protected GridCacheMapEntry(
GridCacheContext<?, ?> cctx,
KeyCacheObject key
) {
if (log == null)
log = U.logger(cctx.kernalContext(), logRef, GridCacheMapEntry.class);
key = (KeyCacheObject)cctx.kernalContext().cacheObjects().prepareForCache(key, cctx);
assert key != null;
this.key = key;
this.hash = key.hashCode();
this.cctx = cctx;
this.listenerLock = cctx.group().listenerLock();
ver = cctx.shared().versions().startVersion();
}
/**
* Sets entry value. If off-heap value storage is enabled, will serialize value to off-heap.
*
* @param val Value to store.
*/
protected void value(@Nullable CacheObject val) {
assert lock.isHeldByCurrentThread();
this.val = val;
}
/** {@inheritDoc} */
@Override public int memorySize() throws IgniteCheckedException {
byte[] kb;
byte[] vb = null;
int extrasSize;
lockEntry();
try {
key.prepareMarshal(cctx.cacheObjectContext());
kb = key.valueBytes(cctx.cacheObjectContext());
if (val != null) {
val.prepareMarshal(cctx.cacheObjectContext());
vb = val.valueBytes(cctx.cacheObjectContext());
}
extrasSize = extrasSize();
}
finally {
unlockEntry();
}
return SIZE_OVERHEAD + extrasSize + kb.length + (vb == null ? 1 : vb.length);
}
/** {@inheritDoc} */
@Override public boolean isInternal() {
return key.internal();
}
/** {@inheritDoc} */
@Override public boolean isDht() {
return false;
}
/** {@inheritDoc} */
@Override public boolean isLocal() {
return false;
}
/** {@inheritDoc} */
@Override public boolean isMvcc() {
return cctx.mvccEnabled();
}
/** {@inheritDoc} */
@Override public boolean isNear() {
return false;
}
/** {@inheritDoc} */
@Override public boolean isReplicated() {
return false;
}
/** {@inheritDoc} */
@Override public boolean detached() {
return false;
}
/** {@inheritDoc} */
@Override public <K, V> GridCacheContext<K, V> context() {
return (GridCacheContext<K, V>)cctx;
}
/** {@inheritDoc} */
@Override public boolean isNew() throws GridCacheEntryRemovedException {
assert lock.isHeldByCurrentThread();
checkObsolete();
return isStartVersion();
}
/** {@inheritDoc} */
@Override public boolean isNewLocked() throws GridCacheEntryRemovedException {
lockEntry();
try {
checkObsolete();
return isStartVersion();
}
finally {
unlockEntry();
}
}
/**
* @return {@code True} if start version.
*/
public boolean isStartVersion() {
return cctx.shared().versions().isStartVersion(ver);
}
/** {@inheritDoc} */
@Override public boolean valid(AffinityTopologyVersion topVer) {
return true;
}
/** {@inheritDoc} */
@Override public int partition() {
return 0;
}
/**
* @return Local partition that owns this entry.
*/
protected GridDhtLocalPartition localPartition() {
return null;
}
/** {@inheritDoc} */
@Override public boolean partitionValid() {
return true;
}
/** {@inheritDoc} */
@Nullable @Override public GridCacheEntryInfo info() {
GridCacheEntryInfo info = null;
lockEntry();
try {
if (!obsolete()) {
info = new GridCacheEntryInfo();
info.key(key);
info.cacheId(cctx.cacheId());
long expireTime = expireTimeExtras();
boolean expired = expireTime != 0 && expireTime <= U.currentTimeMillis();
info.ttl(ttlExtras());
info.expireTime(expireTime);
info.version(ver);
info.setNew(isStartVersion());
info.setDeleted(deletedUnlocked());
if (!expired)
info.value(val);
}
}
finally {
unlockEntry();
}
return info;
}
/** {@inheritDoc} */
@Nullable @Override public List<GridCacheEntryInfo> allVersionsInfo() throws IgniteCheckedException {
assert cctx.mvccEnabled();
lockEntry();
try {
if (obsolete())
return Collections.emptyList();
GridCursor<? extends CacheDataRow> cur =
cctx.offheap().dataStore(localPartition()).mvccAllVersionsCursor(cctx, key, NO_KEY);
List<GridCacheEntryInfo> res = new ArrayList<>();
while (cur.next()) {
CacheDataRow row = cur.get();
GridCacheMvccEntryInfo info = new GridCacheMvccEntryInfo();
info.key(key);
info.value(row.value());
info.cacheId(cctx.cacheId());
info.version(row.version());
info.setNew(false);
info.setDeleted(false);
byte txState = row.mvccTxState() != TxState.NA ? row.mvccTxState() :
MvccUtils.state(cctx, row.mvccCoordinatorVersion(), row.mvccCounter(),
row.mvccOperationCounter());
if (txState == TxState.ABORTED)
continue;
info.mvccVersion(row.mvccCoordinatorVersion(), row.mvccCounter(), row.mvccOperationCounter());
info.mvccTxState(txState);
byte newTxState = row.newMvccTxState() != TxState.NA ? row.newMvccTxState() :
MvccUtils.state(cctx, row.newMvccCoordinatorVersion(), row.newMvccCounter(),
row.newMvccOperationCounter());
if (newTxState != TxState.ABORTED) {
info.newMvccVersion(row.newMvccCoordinatorVersion(),
row.newMvccCounter(),
row.newMvccOperationCounter());
info.newMvccTxState(newTxState);
}
long expireTime = row.expireTime();
long ttl;
ttl = expireTime == CU.EXPIRE_TIME_ETERNAL ? CU.TTL_ETERNAL : expireTime - U.currentTimeMillis();
if (ttl < 0)
ttl = CU.TTL_MINIMUM;
info.ttl(ttl);
info.expireTime(expireTime);
res.add(info);
}
return res;
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public final CacheObject unswap() throws IgniteCheckedException, GridCacheEntryRemovedException {
return unswap(true);
}
/** {@inheritDoc} */
@Override public final CacheObject unswap(CacheDataRow row) throws IgniteCheckedException, GridCacheEntryRemovedException {
row = unswap(row, true);
return row != null ? row.value() : null;
}
/** {@inheritDoc} */
@Nullable @Override public final CacheObject unswap(boolean needVal)
throws IgniteCheckedException, GridCacheEntryRemovedException {
CacheDataRow row = unswap(null, true);
return row != null ? row.value() : null;
}
/**
* Unswaps an entry.
*
* @param row Already extracted cache data.
* @param checkExpire If {@code true} checks for expiration, as result entry can be obsoleted or marked deleted.
* @return Value.
* @throws IgniteCheckedException If failed.
* @throws GridCacheEntryRemovedException If entry was removed.
*/
@Nullable protected CacheDataRow unswap(@Nullable CacheDataRow row, boolean checkExpire)
throws IgniteCheckedException, GridCacheEntryRemovedException {
boolean obsolete = false;
boolean deferred = false;
GridCacheVersion ver0 = null;
cctx.shared().database().checkpointReadLock();
lockEntry();
try {
checkObsolete();
if (isStartVersion() && ((flags & IS_UNSWAPPED_MASK) == 0)) {
assert row == null || Objects.equals(row.key(), key) :
"Unexpected row key [row.key=" + row.key() + ", cacheEntry.key=" + key + "]";
CacheDataRow read = row == null ? cctx.offheap().read(this) : row;
flags |= IS_UNSWAPPED_MASK;
if (read != null) {
CacheObject val = read.value();
update(val, read.expireTime(), 0, read.version(), false);
if (!(checkExpire && read.expireTime() > 0) || (read.expireTime() > U.currentTimeMillis()))
return read;
else {
if (onExpired(this.val, null)) {
if (cctx.deferredDelete()) {
deferred = true;
ver0 = ver;
}
else
obsolete = true;
}
}
}
}
}
finally {
unlockEntry();
cctx.shared().database().checkpointReadUnlock();
}
if (obsolete) {
onMarkedObsolete();
cctx.cache().removeEntry(this);
}
if (deferred) {
assert ver0 != null;
cctx.onDeferredDelete(this, ver0);
}
return null;
}
/**
* @return Value bytes and flag indicating whether value is byte array.
*/
protected IgniteBiTuple<byte[], Byte> valueBytes0() {
assert lock.isHeldByCurrentThread();
assert val != null;
try {
byte[] bytes = val.valueBytes(cctx.cacheObjectContext());
return new IgniteBiTuple<>(bytes, val.cacheObjectType());
}
catch (IgniteCheckedException e) {
throw new IgniteException(e);
}
}
/**
* @param tx Transaction.
* @param key Key.
* @param reload flag.
* @param taskName Task name.
* @return Read value.
* @throws IgniteCheckedException If failed.
*/
@Nullable protected Object readThrough(@Nullable IgniteInternalTx tx, KeyCacheObject key, boolean reload,
String taskName) throws IgniteCheckedException {
return cctx.store().load(tx, key);
}
/** {@inheritDoc} */
@Override public final CacheObject innerGet(
@Nullable GridCacheVersion ver,
@Nullable IgniteInternalTx tx,
boolean readThrough,
boolean updateMetrics,
boolean evt,
Object transformClo,
String taskName,
@Nullable IgniteCacheExpiryPolicy expirePlc,
boolean keepBinary)
throws IgniteCheckedException, GridCacheEntryRemovedException {
return (CacheObject)innerGet0(
ver,
tx,
readThrough,
evt,
updateMetrics,
transformClo,
taskName,
expirePlc,
false,
keepBinary,
false,
null);
}
/** {@inheritDoc} */
@Override public EntryGetResult innerGetAndReserveForLoad(boolean updateMetrics,
boolean evt,
String taskName,
@Nullable IgniteCacheExpiryPolicy expiryPlc,
boolean keepBinary,
@Nullable ReaderArguments readerArgs) throws IgniteCheckedException, GridCacheEntryRemovedException {
return (EntryGetResult)innerGet0(
/*ver*/null,
/*tx*/null,
/*readThrough*/false,
evt,
updateMetrics,
/*transformClo*/null,
taskName,
expiryPlc,
true,
keepBinary,
/*reserve*/true,
readerArgs);
}
/** {@inheritDoc} */
@Override public EntryGetResult innerGetVersioned(
@Nullable GridCacheVersion ver,
IgniteInternalTx tx,
boolean updateMetrics,
boolean evt,
Object transformClo,
String taskName,
@Nullable IgniteCacheExpiryPolicy expiryPlc,
boolean keepBinary,
@Nullable ReaderArguments readerArgs)
throws IgniteCheckedException, GridCacheEntryRemovedException {
return (EntryGetResult)innerGet0(
ver,
tx,
false,
evt,
updateMetrics,
transformClo,
taskName,
expiryPlc,
true,
keepBinary,
false,
readerArgs);
}
/** */
@SuppressWarnings({"TooBroadScope"})
private Object innerGet0(
GridCacheVersion nextVer,
IgniteInternalTx tx,
boolean readThrough,
boolean evt,
boolean updateMetrics,
Object transformClo,
String taskName,
@Nullable IgniteCacheExpiryPolicy expiryPlc,
boolean retVer,
boolean keepBinary,
boolean reserveForLoad,
@Nullable ReaderArguments readerArgs
) throws IgniteCheckedException, GridCacheEntryRemovedException {
assert !(retVer && readThrough);
assert !(reserveForLoad && readThrough);
// Disable read-through if there is no store.
if (readThrough && !cctx.readThrough())
readThrough = false;
GridCacheVersion startVer;
GridCacheVersion resVer = null;
boolean obsolete = false;
boolean deferred = false;
GridCacheVersion ver0 = null;
Object res = null;
lockEntry();
try {
checkObsolete();
CacheObject val;
AffinityTopologyVersion topVer = tx != null ? tx.topologyVersion() : cctx.affinity().affinityTopologyVersion();
boolean valid = valid(topVer);
if (valid) {
val = this.val;
if (val == null) {
if (isStartVersion()) {
unswap(null, false);
val = this.val;
if (val != null && tx == null)
updatePlatformCache(val, topVer);
}
}
if (val != null) {
long expireTime = expireTimeExtras();
if (expireTime > 0 && (expireTime < U.currentTimeMillis())) {
if (onExpired((CacheObject)cctx.unwrapTemporary(val), null)) {
val = null;
evt = false;
if (cctx.deferredDelete()) {
deferred = true;
ver0 = ver;
}
else
obsolete = true;
}
}
}
}
else
val = null;
CacheObject ret = val;
if (ret == null) {
if (updateMetrics && cctx.statisticsEnabled())
cctx.cache().metrics0().onRead(false);
}
else {
if (updateMetrics && cctx.statisticsEnabled())
cctx.cache().metrics0().onRead(true);
}
if (evt && cctx.events().isRecordable(EVT_CACHE_OBJECT_READ)) {
transformClo = EntryProcessorResourceInjectorProxy.unwrap(transformClo);
GridCacheMvcc mvcc = mvccExtras();
cctx.events().addEvent(
partition(),
key,
tx,
mvcc != null ? mvcc.anyOwner() : null,
EVT_CACHE_OBJECT_READ,
ret,
ret != null,
ret,
ret != null,
transformClo != null ? transformClo.getClass().getName() : null,
taskName,
keepBinary);
// No more notifications.
evt = false;
}
if (ret != null && expiryPlc != null)
updateTtlUnlocked(version(), expiryPlc);
if (retVer && resVer == null) {
resVer = (isNear() && cctx.transactional()) ? ((GridNearCacheEntry)this).dhtVersion() : this.ver;
if (resVer == null)
ret = null;
}
// Cache version for optimistic check.
startVer = ver;
addReaderIfNeed(readerArgs);
if (ret != null) {
assert !obsolete;
assert !deferred;
// If return value is consistent, then done.
res = retVer ? entryGetResult(ret, resVer, false) : ret;
}
else if (reserveForLoad && !obsolete) {
assert !readThrough;
assert retVer;
boolean reserve = !evictionDisabled();
if (reserve)
flags |= IS_EVICT_DISABLED;
res = entryGetResult(null, resVer, reserve);
}
}
finally {
unlockEntry();
}
if (obsolete) {
onMarkedObsolete();
throw new GridCacheEntryRemovedException();
}
if (deferred)
cctx.onDeferredDelete(this, ver0);
if (res != null)
return res;
CacheObject ret = null;
if (readThrough) {
IgniteInternalTx tx0 = null;
if (tx != null && tx.local()) {
if (cctx.isReplicated() || cctx.isColocated() || tx.near())
tx0 = tx;
else if (tx.dht()) {
GridCacheVersion ver = tx.nearXidVersion();
tx0 = cctx.dht().near().context().tm().tx(ver);
}
}
Object storeVal = readThrough(tx0, key, false, taskName);
ret = cctx.toCacheObject(storeVal);
}
if (ret == null && !evt)
return null;
lockEntry();
try {
long ttl = ttlExtras();
// If version matched, set value.
if (startVer.equals(ver)) {
if (ret != null) {
// Detach value before index update.
ret = cctx.kernalContext().cacheObjects().prepareForCache(ret, cctx);
nextVer = nextVer != null ? nextVer : nextVersion();
long expTime = CU.toExpireTime(ttl);
// Update indexes before actual write to entry.
storeValue(ret, expTime, nextVer);
update(ret, expTime, ttl, nextVer, true);
if (cctx.deferredDelete() && deletedUnlocked() && !isInternal() && !detached())
deletedUnlocked(false);
assert readerArgs == null;
}
if (evt && cctx.events().isRecordable(EVT_CACHE_OBJECT_READ)) {
transformClo = EntryProcessorResourceInjectorProxy.unwrap(transformClo);
GridCacheMvcc mvcc = mvccExtras();
cctx.events().addEvent(
partition(),
key,
tx,
mvcc != null ? mvcc.anyOwner() : null,
EVT_CACHE_OBJECT_READ,
ret,
ret != null,
null,
false,
transformClo != null ? transformClo.getClass().getName() : null,
taskName,
keepBinary);
}
}
}
finally {
unlockEntry();
}
assert ret == null || !retVer;
return ret;
}
/**
* Creates EntryGetResult or EntryGetWithTtlResult if expire time information exists.
*
* @param val Value.
* @param ver Version.
* @param reserve Reserve flag.
* @return EntryGetResult.
*/
private EntryGetResult entryGetResult(CacheObject val, GridCacheVersion ver, boolean reserve) {
return extras == null || extras.expireTime() == 0
? new EntryGetResult(val, ver, reserve)
: new EntryGetWithTtlResult(val, ver, reserve, rawExpireTime(), rawTtl());
}
/** {@inheritDoc} */
@SuppressWarnings({"TooBroadScope"})
@Nullable @Override public final CacheObject innerReload()
throws IgniteCheckedException, GridCacheEntryRemovedException {
CU.checkStore(cctx);
GridCacheVersion startVer;
boolean wasNew;
lockEntry();
try {
checkObsolete();
// Cache version for optimistic check.
startVer = ver;
wasNew = isNew();
}
finally {
unlockEntry();
}
String taskName = cctx.kernalContext().job().currentTaskName();
// Check before load.
CacheObject ret = cctx.toCacheObject(readThrough(null, key, true, taskName));
boolean touch = false;
try {
ensureFreeSpace();
lockEntry();
try {
long ttl = ttlExtras();
// Generate new version.
GridCacheVersion nextVer = cctx.versions().nextForLoad(ver);
// If entry was loaded during read step.
if (wasNew && !isNew())
// Map size was updated on entry creation.
return ret;
// If version matched, set value.
if (startVer.equals(ver)) {
long expTime = CU.toExpireTime(ttl);
// Detach value before index update.
ret = cctx.kernalContext().cacheObjects().prepareForCache(ret, cctx);
// Update indexes.
if (ret != null) {
storeValue(ret, expTime, nextVer);
if (cctx.deferredDelete() && !isInternal() && !detached() && deletedUnlocked())
deletedUnlocked(false);
}
else {
if (cctx.mvccEnabled())
cctx.offheap().mvccRemoveAll(this);
else
removeValue();
if (cctx.deferredDelete() && !isInternal() && !detached() && !deletedUnlocked())
deletedUnlocked(true);
}
update(ret, expTime, ttl, nextVer, true);
touch = true;
// If value was set - return, otherwise try again.
return ret;
}
}
finally {
unlockEntry();
}
touch = true;
return ret;
}
finally {
if (touch)
touch();
}
}
/**
* @param nodeId Node ID.
*/
protected void recordNodeId(UUID nodeId, AffinityTopologyVersion topVer) {
// No-op.
}
/** {@inheritDoc} */
@Override public final GridCacheUpdateTxResult mvccSet(
IgniteInternalTx tx,
UUID affNodeId,
CacheObject val,
EntryProcessor entryProc,
Object[] invokeArgs,
long ttl0,
AffinityTopologyVersion topVer,
MvccSnapshot mvccVer,
GridCacheOperation op,
boolean needHistory,
boolean noCreate,
boolean needOldVal,
CacheEntryPredicate filter,
boolean retVal,
boolean keepBinary) throws IgniteCheckedException, GridCacheEntryRemovedException {
assert tx != null;
final boolean valid = valid(tx.topologyVersion());
final boolean invoke = entryProc != null;
final GridCacheVersion newVer;
WALPointer logPtr = null;
ensureFreeSpace();
lockEntry();
MvccUpdateResult res;
try {
checkObsolete();
newVer = tx.writeVersion();
assert newVer != null : "Failed to get write version for tx: " + tx;
// Determine new ttl and expire time.
long expireTime, ttl = ttl0;
if (ttl == -1L) {
ttl = ttlExtras();
expireTime = expireTimeExtras();
}
else
expireTime = CU.toExpireTime(ttl);
assert ttl >= 0 : ttl;
assert expireTime >= 0 : expireTime;
// Detach value before index update.
val = cctx.kernalContext().cacheObjects().prepareForCache(val, cctx);
assert val != null || invoke;
res = cctx.offheap().mvccUpdate(this, val, newVer, expireTime, mvccVer, tx.local(), needHistory,
noCreate, needOldVal, filter, retVal, keepBinary, entryProc, invokeArgs);
assert res != null;
// VERSION_FOUND is possible only on primary node when inserting the same key, or on backup when
// updating the key which just has been rebalanced.
assert res.resultType() != ResultType.VERSION_FOUND || op == CREATE && tx.local() || !tx.local();
// PREV_NOT_NULL on CREATE is possible only on primary.
assert res.resultType() != ResultType.PREV_NOT_NULL || op != CREATE || tx.local();
if (res.resultType() == ResultType.VERSION_MISMATCH)
throw serializationError();
else if (res.resultType() == ResultType.FILTERED) {
GridCacheUpdateTxResult updRes = new GridCacheUpdateTxResult(invoke);
assert !invoke || res.invokeResult() != null;
if (invoke) // No-op invoke happened.
updRes.invokeResult(res.invokeResult());
updRes.filtered(true);
if (retVal)
updRes.prevValue(res.oldValue());
return updRes;
}
else if (noCreate && !invoke && res.resultType() == ResultType.PREV_NULL)
return new GridCacheUpdateTxResult(false);
else if (res.resultType() == ResultType.LOCKED) {
unlockEntry();
MvccVersion lockVer = res.resultVersion();
GridFutureAdapter<GridCacheUpdateTxResult> resFut = new GridFutureAdapter<>();
IgniteInternalFuture<?> lockFut = cctx.kernalContext().coordinators().waitForLock(cctx, mvccVer, lockVer);
lockFut.listen(new MvccUpdateLockListener(tx, this, affNodeId, topVer, val, ttl0, mvccVer,
op, needHistory, noCreate, resFut, needOldVal, filter, retVal, keepBinary, entryProc, invokeArgs));
return new GridCacheUpdateTxResult(false, resFut);
}
else if (op == CREATE && tx.local() && (res.resultType() == ResultType.PREV_NOT_NULL ||
res.resultType() == ResultType.VERSION_FOUND))
throw new IgniteTxDuplicateKeyCheckedException("Duplicate key during INSERT [key=" + key + ']');
if (cctx.deferredDelete() && deletedUnlocked() && !detached())
deletedUnlocked(false);
if (res.resultType() == ResultType.PREV_NULL) {
TxCounters counters = tx.txCounters(true);
if (compareIgnoreOpCounter(res.resultVersion(), mvccVer) == 0) {
if (res.isKeyAbsentBefore())
counters.incrementUpdateCounter(cctx.cacheId(), partition());
}
else
counters.incrementUpdateCounter(cctx.cacheId(), partition());
counters.accumulateSizeDelta(cctx.cacheId(), partition(), 1);
}
else if (res.resultType() == ResultType.PREV_NOT_NULL && compareIgnoreOpCounter(res.resultVersion(), mvccVer) != 0) {
TxCounters counters = tx.txCounters(true);
counters.incrementUpdateCounter(cctx.cacheId(), partition());
}
else if (res.resultType() == ResultType.REMOVED_NOT_NULL) {
TxCounters counters = tx.txCounters(true);
if (compareIgnoreOpCounter(res.resultVersion(), mvccVer) == 0) {
if (res.isKeyAbsentBefore()) // Do not count own update removal.
counters.decrementUpdateCounter(cctx.cacheId(), partition());
}
else
counters.incrementUpdateCounter(cctx.cacheId(), partition());
counters.accumulateSizeDelta(cctx.cacheId(), partition(), -1);
}
if (cctx.group().logDataRecords()) {
logPtr = cctx.group().wal().log(new MvccDataRecord(new MvccDataEntry(
cctx.cacheId(),
key,
val,
res.resultType() == ResultType.PREV_NULL ? CREATE :
(res.resultType() == ResultType.REMOVED_NOT_NULL) ? DELETE : UPDATE,
tx.nearXidVersion(),
newVer,
expireTime,
key.partition(),
0L,
mvccVer)
));
}
update(val, expireTime, ttl, newVer, true);
recordNodeId(affNodeId, topVer);
}
finally {
if (lockedByCurrentThread()) {
unlockEntry();
cctx.evicts().touch(this);
}
}
onUpdateFinished(0L);
GridCacheUpdateTxResult updRes = valid ? new GridCacheUpdateTxResult(true, 0L, logPtr) :
new GridCacheUpdateTxResult(false, logPtr);
if (retVal && (res.resultType() == ResultType.PREV_NOT_NULL || res.resultType() == ResultType.VERSION_FOUND))
updRes.prevValue(res.oldValue());
if (needOldVal && compareIgnoreOpCounter(res.resultVersion(), mvccVer) != 0 && (
res.resultType() == ResultType.PREV_NOT_NULL || res.resultType() == ResultType.REMOVED_NOT_NULL))
updRes.oldValue(res.oldValue());
updRes.newValue(res.newValue());
if (invoke && res.resultType() != ResultType.VERSION_FOUND) {
assert res.invokeResult() != null;
updRes.invokeResult(res.invokeResult());
}
updRes.mvccHistory(res.history());
return updRes;
}
/** {@inheritDoc} */
@Override public final GridCacheUpdateTxResult mvccRemove(
IgniteInternalTx tx,
UUID affNodeId,
AffinityTopologyVersion topVer,
MvccSnapshot mvccVer,
boolean needHistory,
boolean needOldVal,
@Nullable CacheEntryPredicate filter,
boolean retVal) throws IgniteCheckedException, GridCacheEntryRemovedException {
assert tx != null;
assert mvccVer != null;
final boolean valid = valid(tx.topologyVersion());
final GridCacheVersion newVer;
WALPointer logPtr = null;
lockEntry();
MvccUpdateResult res;
try {
checkObsolete();
newVer = tx.writeVersion();
assert newVer != null : "Failed to get write version for tx: " + tx;
res = cctx.offheap().mvccRemove(this, mvccVer, tx.local(), needHistory, needOldVal, filter, retVal);
assert res != null;
if (res.resultType() == ResultType.VERSION_MISMATCH)
throw serializationError();
else if (res.resultType() == ResultType.PREV_NULL)
return new GridCacheUpdateTxResult(false);
else if (res.resultType() == ResultType.FILTERED) {
GridCacheUpdateTxResult updRes = new GridCacheUpdateTxResult(false);
updRes.filtered(true);
return updRes;
}
else if (res.resultType() == ResultType.LOCKED) {
unlockEntry();
MvccVersion lockVer = res.resultVersion();
GridFutureAdapter<GridCacheUpdateTxResult> resFut = new GridFutureAdapter<>();
IgniteInternalFuture<?> lockFut = cctx.kernalContext().coordinators().waitForLock(cctx, mvccVer, lockVer);
lockFut.listen(new MvccRemoveLockListener(tx, this, affNodeId, topVer, mvccVer, needHistory,
resFut, needOldVal, retVal, filter));
return new GridCacheUpdateTxResult(false, resFut);
}
if (cctx.deferredDelete() && deletedUnlocked() && !detached())
deletedUnlocked(false);
if (res.resultType() == ResultType.PREV_NOT_NULL) {
TxCounters counters = tx.txCounters(true);
if (compareIgnoreOpCounter(res.resultVersion(), mvccVer) == 0) {
if (res.isKeyAbsentBefore()) // Do not count own update removal.
counters.decrementUpdateCounter(cctx.cacheId(), partition());
}
else
counters.incrementUpdateCounter(cctx.cacheId(), partition());
counters.accumulateSizeDelta(cctx.cacheId(), partition(), -1);
}
if (cctx.group().logDataRecords())
logPtr = logMvccUpdate(tx, null, 0, 0L, mvccVer);
update(null, 0, 0, newVer, true);
recordNodeId(affNodeId, topVer);
}
finally {
if (lockedByCurrentThread()) {
unlockEntry();
cctx.evicts().touch(this);
}
}
onUpdateFinished(0L);
GridCacheUpdateTxResult updRes = valid ? new GridCacheUpdateTxResult(true, 0L, logPtr) :
new GridCacheUpdateTxResult(false, logPtr);
if (retVal && (res.resultType() == ResultType.PREV_NOT_NULL || res.resultType() == ResultType.VERSION_FOUND))
updRes.prevValue(res.oldValue());
if (needOldVal && compareIgnoreOpCounter(res.resultVersion(), mvccVer) != 0 &&
(res.resultType() == ResultType.PREV_NOT_NULL || res.resultType() == ResultType.REMOVED_NOT_NULL))
updRes.oldValue(res.oldValue());
updRes.mvccHistory(res.history());
return updRes;
}
/** {@inheritDoc} */
@Override public GridCacheUpdateTxResult mvccLock(GridDhtTxLocalAdapter tx, MvccSnapshot mvccVer)
throws GridCacheEntryRemovedException, IgniteCheckedException {
assert tx != null;
assert mvccVer != null;
final boolean valid = valid(tx.topologyVersion());
final GridCacheVersion newVer;
WALPointer logPtr = null;
lockEntry();
try {
checkObsolete();
newVer = tx.writeVersion();
assert newVer != null : "Failed to get write version for tx: " + tx;
assert tx.local();
MvccUpdateResult res = cctx.offheap().mvccLock(this, mvccVer);
assert res != null;
if (res.resultType() == ResultType.VERSION_MISMATCH)
throw serializationError();
else if (res.resultType() == ResultType.LOCKED) {
unlockEntry();
MvccVersion lockVer = res.resultVersion();
GridFutureAdapter<GridCacheUpdateTxResult> resFut = new GridFutureAdapter<>();
IgniteInternalFuture<?> lockFut = cctx.kernalContext().coordinators().waitForLock(cctx, mvccVer, lockVer);
lockFut.listen(new MvccAcquireLockListener(tx, this, mvccVer, resFut));
return new GridCacheUpdateTxResult(false, resFut);
}
}
finally {
if (lockedByCurrentThread()) {
unlockEntry();
cctx.evicts().touch(this);
}
}
onUpdateFinished(0L);
return new GridCacheUpdateTxResult(valid, logPtr);
}
/** {@inheritDoc} */
@Override public final GridCacheUpdateTxResult innerSet(
@Nullable IgniteInternalTx tx,
UUID evtNodeId,
UUID affNodeId,
CacheObject val,
boolean writeThrough,
boolean retval,
long ttl,
boolean evt,
boolean metrics,
boolean keepBinary,
boolean oldValPresent,
@Nullable CacheObject oldVal,
AffinityTopologyVersion topVer,
CacheEntryPredicate[] filter,
GridDrType drType,
long drExpireTime,
@Nullable GridCacheVersion explicitVer,
String taskName,
@Nullable GridCacheVersion dhtVer,
@Nullable Long updateCntr
) throws IgniteCheckedException, GridCacheEntryRemovedException {
CacheObject old;
final boolean valid = valid(tx != null ? tx.topologyVersion() : topVer);
// Lock should be held by now.
if (!cctx.isAll(this, filter))
return new GridCacheUpdateTxResult(false);
final GridCacheVersion newVer;
boolean intercept = cctx.config().getInterceptor() != null;
Object key0 = null;
Object val0 = null;
WALPointer logPtr = null;
long updateCntr0;
ensureFreeSpace();
lockListenerReadLock();
lockEntry();
try {
checkObsolete();
if (isNear()) {
assert dhtVer != null;
// It is possible that 'get' could load more recent value.
if (!((GridNearCacheEntry)this).recordDhtVersion(dhtVer))
return new GridCacheUpdateTxResult(false, logPtr);
}
assert tx == null || (!tx.local() && tx.onePhaseCommit()) || tx.ownsLock(this) :
"Transaction does not own lock for update [entry=" + this + ", tx=" + tx + ']';
// Load and remove from swap if it is new.
boolean startVer = isStartVersion();
boolean internal = isInternal() || !context().userCache();
Map<UUID, CacheContinuousQueryListener> lsnrCol =
notifyContinuousQueries() ?
cctx.continuousQueries().updateListeners(internal, false) : null;
if (startVer && (retval || intercept || lsnrCol != null))
unswap(retval);
newVer = explicitVer != null ? explicitVer : tx == null ?
nextVersion() : tx.writeVersion();
assert newVer != null : "Failed to get write version for tx: " + tx;
old = oldValPresent ? oldVal : this.val;
if (intercept)
intercept = !skipInterceptor(explicitVer);
if (intercept) {
val0 = cctx.unwrapBinaryIfNeeded(val, keepBinary, false, null);
CacheLazyEntry e = new CacheLazyEntry(cctx, key, old, keepBinary);
key0 = e.key();
Object interceptorVal = cctx.config().getInterceptor().onBeforePut(e, val0);
if (interceptorVal == null)
return new GridCacheUpdateTxResult(false, logPtr);
else if (interceptorVal != val0)
val0 = cctx.unwrapTemporary(interceptorVal);
val = cctx.toCacheObject(val0);
}
// Determine new ttl and expire time.
long expireTime;
if (drExpireTime >= 0) {
assert ttl >= 0 : ttl;
expireTime = drExpireTime;
}
else {
if (ttl == -1L) {
ttl = ttlExtras();
expireTime = expireTimeExtras();
}
else
expireTime = CU.toExpireTime(ttl);
}
assert ttl >= 0 : ttl;
assert expireTime >= 0 : expireTime;
// Detach value before index update.
val = cctx.kernalContext().cacheObjects().prepareForCache(val, cctx);
assert val != null;
storeValue(val, expireTime, newVer);
if (cctx.deferredDelete() && deletedUnlocked() && !isInternal() && !detached())
deletedUnlocked(false);
updateCntr0 = nextPartitionCounter(tx, updateCntr);
if (tx != null && cctx.group().logDataRecords())
logPtr = logTxUpdate(tx, val, addConflictVersion(tx.writeVersion(), newVer), expireTime, updateCntr0);
update(val, expireTime, ttl, newVer, true);
drReplicate(drType, val, newVer, topVer);
recordNodeId(affNodeId, topVer);
if (metrics && cctx.statisticsEnabled() && tx != null) {
cctx.cache().metrics0().onWrite();
IgniteTxEntry txEntry = tx.entry(txKey());
if (txEntry != null) {
T2<GridCacheOperation, CacheObject> entryProcRes = txEntry.entryProcessorCalculatedValue();
if (entryProcRes != null && UPDATE.equals(entryProcRes.get1()))
cctx.cache().metrics0().onInvokeUpdate(old != null);
}
}
if (evt && newVer != null && cctx.events().isRecordable(EVT_CACHE_OBJECT_PUT)) {
CacheObject evtOld = cctx.unwrapTemporary(old);
cctx.events().addEvent(partition(),
key,
evtNodeId,
tx,
null,
newVer,
EVT_CACHE_OBJECT_PUT,
val,
val != null,
evtOld,
evtOld != null || hasValueUnlocked(),
null, taskName,
keepBinary);
}
if (lsnrCol != null) {
cctx.continuousQueries().onEntryUpdated(
lsnrCol,
key,
val,
old,
internal,
partition(),
tx.local(),
false,
updateCntr0,
null,
topVer);
}
}
finally {
unlockEntry();
unlockListenerReadLock();
}
onUpdateFinished(updateCntr0);
if (log.isDebugEnabled())
log.debug("Updated cache entry [val=" + val + ", old=" + old + ", entry=" + this + ']');
// Persist outside of synchronization. The correctness of the
// value will be handled by current transaction.
if (writeThrough)
cctx.store().put(tx, key, val, newVer);
if (intercept)
cctx.config().getInterceptor().onAfterPut(new CacheLazyEntry(cctx, key, key0, val, val0, keepBinary, updateCntr0));
updatePlatformCache(val, topVer);
return valid ? new GridCacheUpdateTxResult(true, updateCntr0, logPtr) :
new GridCacheUpdateTxResult(false, logPtr);
}
/**
* @param cpy Copy flag.
* @return Key value.
*/
protected Object keyValue(boolean cpy) {
return key.value(cctx.cacheObjectContext(), cpy);
}
/** {@inheritDoc} */
@Override public final GridCacheUpdateTxResult innerRemove(
@Nullable IgniteInternalTx tx,
UUID evtNodeId,
UUID affNodeId,
boolean retval,
boolean evt,
boolean metrics,
boolean keepBinary,
boolean oldValPresent,
@Nullable CacheObject oldVal,
AffinityTopologyVersion topVer,
CacheEntryPredicate[] filter,
GridDrType drType,
@Nullable GridCacheVersion explicitVer,
String taskName,
@Nullable GridCacheVersion dhtVer,
@Nullable Long updateCntr
) throws IgniteCheckedException, GridCacheEntryRemovedException {
assert cctx.transactional();
CacheObject old;
GridCacheVersion newVer;
final boolean valid = valid(tx != null ? tx.topologyVersion() : topVer);
// Lock should be held by now.
if (!cctx.isAll(this, filter))
return new GridCacheUpdateTxResult(false);
GridCacheVersion obsoleteVer = null;
boolean intercept = cctx.config().getInterceptor() != null;
IgniteBiTuple<Boolean, Object> interceptRes = null;
CacheLazyEntry entry0 = null;
long updateCntr0;
WALPointer logPtr = null;
boolean deferred;
boolean marked = false;
lockListenerReadLock();
lockEntry();
try {
checkObsolete();
if (isNear()) {
assert dhtVer != null;
// It is possible that 'get' could load more recent value.
if (!((GridNearCacheEntry)this).recordDhtVersion(dhtVer))
return new GridCacheUpdateTxResult(false, logPtr);
}
assert tx == null || (!tx.local() && tx.onePhaseCommit()) || tx.ownsLock(this) :
"Transaction does not own lock for remove[entry=" + this + ", tx=" + tx + ']';
boolean startVer = isStartVersion();
newVer = explicitVer != null ? explicitVer : tx == null ? nextVersion() : tx.writeVersion();
boolean internal = isInternal() || !context().userCache();
Map<UUID, CacheContinuousQueryListener> lsnrCol =
notifyContinuousQueries() ?
cctx.continuousQueries().updateListeners(internal, false) : null;
if (startVer && (retval || intercept || lsnrCol != null))
unswap();
old = oldValPresent ? oldVal : val;
if (intercept)
intercept = !skipInterceptor(explicitVer);
if (intercept) {
entry0 = new CacheLazyEntry(cctx, key, old, keepBinary);
interceptRes = cctx.config().getInterceptor().onBeforeRemove(entry0);
if (cctx.cancelRemove(interceptRes)) {
CacheObject ret = cctx.toCacheObject(cctx.unwrapTemporary(interceptRes.get2()));
return new GridCacheUpdateTxResult(false, logPtr);
}
}
removeValue();
update(null, 0, 0, newVer, true);
if (cctx.deferredDelete() && !detached() && !isInternal()) {
if (!deletedUnlocked()) {
deletedUnlocked(true);
if (tx != null) {
GridCacheMvcc mvcc = mvccExtras();
if (mvcc == null || mvcc.isEmpty(tx.xidVersion()))
clearReaders();
else {
// Optimize memory usage - do not allocate additional array.
List<GridCacheMvccCandidate> locs = mvcc.localCandidatesNoCopy(false);
GridCacheVersion txVer = tx.xidVersion();
UUID originatingNodeId = tx.originatingNodeId();
boolean hasOriginatingNodeId = false;
for (GridCacheMvccCandidate c : locs) {
if (c.reentry() || Objects.equals(c.version(), txVer))
continue;
if (Objects.equals(c.otherNodeId(), originatingNodeId)) {
hasOriginatingNodeId = true;
break;
}
}
// Remove reader only if there are no other active transactions from it.
if (!hasOriginatingNodeId)
clearReader(originatingNodeId);
}
}
}
}
updateCntr0 = nextPartitionCounter(tx, updateCntr);
if (tx != null && cctx.group().logDataRecords())
logPtr = logTxUpdate(tx, null, addConflictVersion(tx.writeVersion(), newVer), 0, updateCntr0);
drReplicate(drType, null, newVer, topVer);
if (metrics && cctx.statisticsEnabled()) {
cctx.cache().metrics0().onRemove();
T2<GridCacheOperation, CacheObject> entryProcRes = tx.entry(txKey()).entryProcessorCalculatedValue();
if (entryProcRes != null && DELETE.equals(entryProcRes.get1()))
cctx.cache().metrics0().onInvokeRemove(old != null);
}
if (tx == null)
obsoleteVer = newVer;
else {
// Only delete entry if the lock is not explicit.
if (lockedBy(tx.xidVersion()))
obsoleteVer = tx.xidVersion();
else if (log.isDebugEnabled())
log.debug("Obsolete version was not set because lock was explicit: " + this);
}
if (evt && newVer != null && cctx.events().isRecordable(EVT_CACHE_OBJECT_REMOVED)) {
CacheObject evtOld = cctx.unwrapTemporary(old);
cctx.events().addEvent(partition(),
key,
evtNodeId,
tx,
null,
newVer,
EVT_CACHE_OBJECT_REMOVED,
null,
false,
evtOld,
evtOld != null || hasValueUnlocked(),
null,
taskName,
keepBinary);
}
if (lsnrCol != null) {
cctx.continuousQueries().onEntryUpdated(
lsnrCol,
key,
null,
old,
internal,
partition(),
tx.local(),
false,
updateCntr0,
null,
topVer);
}
deferred = cctx.deferredDelete() && !detached();
if (intercept)
entry0.updateCounter(updateCntr0);
if (!deferred) {
// If entry is still removed.
assert newVer == ver;
if (obsoleteVer == null || !(marked = markObsolete0(obsoleteVer, true, null))) {
if (log.isDebugEnabled())
log.debug("Entry could not be marked obsolete (it is still used): " + this);
}
else {
recordNodeId(affNodeId, topVer);
if (log.isDebugEnabled())
log.debug("Entry was marked obsolete: " + this);
}
}
}
finally {
unlockEntry();
unlockListenerReadLock();
}
if (marked) {
assert !deferred;
onMarkedObsolete();
}
onUpdateFinished(updateCntr0);
if (intercept)
cctx.config().getInterceptor().onAfterRemove(entry0);
if (valid)
return new GridCacheUpdateTxResult(true, updateCntr0, logPtr);
else
return new GridCacheUpdateTxResult(false, logPtr);
}
/** {@inheritDoc} */
@SuppressWarnings("unchecked")
@Override public GridCacheUpdateAtomicResult innerUpdate(
final GridCacheVersion newVer,
final UUID evtNodeId,
final UUID affNodeId,
final GridCacheOperation op,
@Nullable final Object writeObj,
@Nullable final Object[] invokeArgs,
final boolean writeThrough,
final boolean readThrough,
final boolean retval,
final boolean keepBinary,
@Nullable final IgniteCacheExpiryPolicy expiryPlc,
final boolean evt,
final boolean metrics,
final boolean primary,
final boolean verCheck,
final boolean readRepairRecovery,
final AffinityTopologyVersion topVer,
@Nullable final CacheEntryPredicate[] filter,
final GridDrType drType,
final long explicitTtl,
final long explicitExpireTime,
@Nullable final GridCacheVersion conflictVer,
final boolean conflictResolve,
final boolean intercept,
final String taskName,
@Nullable final CacheObject prevVal,
@Nullable final Long updateCntr,
@Nullable final GridDhtAtomicAbstractUpdateFuture fut,
boolean transformOp
) throws IgniteCheckedException, GridCacheEntryRemovedException, GridClosureException {
assert cctx.atomic() && !detached();
AtomicCacheUpdateClosure c;
if (!primary && !isNear())
ensureFreeSpace();
lockListenerReadLock();
lockEntry();
try {
checkObsolete();
boolean internal = isInternal() || !context().userCache();
Map<UUID, CacheContinuousQueryListener> lsnrs = cctx.continuousQueries().updateListeners(internal, false);
boolean needVal = lsnrs != null || intercept || retval || op == GridCacheOperation.TRANSFORM
|| !F.isEmptyOrNulls(filter);
// Possibly read value from store.
boolean readFromStore = readThrough && needVal && (cctx.readThrough() &&
(op == GridCacheOperation.TRANSFORM || cctx.loadPreviousValue()));
c = new AtomicCacheUpdateClosure(this,
topVer,
newVer,
op,
writeObj,
invokeArgs,
readFromStore,
writeThrough,
keepBinary,
expiryPlc,
primary,
verCheck,
readRepairRecovery,
filter,
explicitTtl,
explicitExpireTime,
conflictVer,
conflictResolve,
intercept,
updateCntr,
cctx.disableTriggeringCacheInterceptorOnConflict()
);
key.valueBytes(cctx.cacheObjectContext());
if (isNear()) {
CacheDataRow dataRow = val != null ? new CacheDataRowAdapter(key, val, ver, expireTimeExtras()) : null;
c.call(dataRow);
}
else
cctx.offheap().invoke(cctx, key, localPartition(), c);
GridCacheUpdateAtomicResult updateRes = c.updateRes;
assert updateRes != null : c;
// We should ignore expired old row. Expired oldRow instance is needed for correct row replacement\deletion only.
CacheObject oldVal = c.oldRow != null && !c.oldRowExpiredFlag ? c.oldRow.value() : null;
CacheObject updateVal = null;
GridCacheVersion updateVer = c.newVer;
boolean updateMetrics = metrics && cctx.statisticsEnabled();
// Apply metrics.
if (updateMetrics &&
updateRes.outcome().updateReadMetrics() &&
needVal)
cctx.cache().metrics0().onRead(oldVal != null);
if (updateMetrics && INVOKE_NO_OP.equals(updateRes.outcome()) && (transformOp || updateRes.transformed()))
cctx.cache().metrics0().onReadOnlyInvoke(oldVal != null);
else if (updateMetrics && REMOVE_NO_VAL.equals(updateRes.outcome())
&& (transformOp || updateRes.transformed()))
cctx.cache().metrics0().onInvokeRemove(oldVal != null);
switch (updateRes.outcome()) {
case VERSION_CHECK_FAILED: {
if (!cctx.isNear()) {
CacheObject evtVal;
if (op == GridCacheOperation.TRANSFORM) {
EntryProcessor<Object, Object, ?> entryProcessor =
(EntryProcessor<Object, Object, ?>)writeObj;
CacheInvokeEntry<Object, Object> entry =
new CacheInvokeEntry<>(key, prevVal, version(), keepBinary, this);
IgniteThread.onEntryProcessorEntered(true);
try {
entryProcessor.process(entry, invokeArgs);
evtVal = entry.modified() ?
cctx.toCacheObject(cctx.unwrapTemporary(entry.getValue())) : prevVal;
}
catch (Exception ignore) {
evtVal = prevVal;
}
finally {
IgniteThread.onEntryProcessorLeft();
}
}
else
evtVal = (CacheObject)writeObj;
assert !primary && updateCntr != null;
onUpdateFinished(updateCntr);
cctx.continuousQueries().onEntryUpdated(
key,
evtVal,
prevVal,
isInternal() || !context().userCache(),
partition(),
primary,
false,
updateCntr,
null,
topVer);
}
return updateRes;
}
case CONFLICT_USE_OLD:
case FILTER_FAILED:
case INVOKE_NO_OP:
case INTERCEPTOR_CANCEL:
return updateRes;
}
assert updateRes.outcome() == UpdateOutcome.SUCCESS || updateRes.outcome() == UpdateOutcome.REMOVE_NO_VAL;
CacheObject evtOld = null;
if (evt && op == TRANSFORM && cctx.events().isRecordable(EVT_CACHE_OBJECT_READ)) {
assert writeObj instanceof EntryProcessor : writeObj;
evtOld = cctx.unwrapTemporary(oldVal);
Object transformClo = EntryProcessorResourceInjectorProxy.unwrap(writeObj);
cctx.events().addEvent(partition(),
key,
evtNodeId,
null,
null,
updateVer,
EVT_CACHE_OBJECT_READ,
evtOld, evtOld != null,
evtOld, evtOld != null,
transformClo.getClass().getName(),
taskName,
keepBinary);
}
if (c.op == UPDATE) {
updateVal = val;
assert updateVal != null : c;
drReplicate(drType, updateVal, updateVer, topVer);
recordNodeId(affNodeId, topVer);
if (evt && cctx.events().isRecordable(EVT_CACHE_OBJECT_PUT)) {
if (evtOld == null)
evtOld = cctx.unwrapTemporary(oldVal);
cctx.events().addEvent(partition(),
key,
evtNodeId,
null,
null,
updateVer,
EVT_CACHE_OBJECT_PUT,
updateVal,
true,
evtOld,
evtOld != null,
null,
taskName,
keepBinary);
}
}
else {
assert c.op == DELETE : c.op;
clearReaders();
drReplicate(drType, null, updateVer, topVer);
recordNodeId(affNodeId, topVer);
if (evt && cctx.events().isRecordable(EVT_CACHE_OBJECT_REMOVED)) {
if (evtOld == null)
evtOld = cctx.unwrapTemporary(oldVal);
cctx.events().addEvent(partition(),
key,
evtNodeId,
null,
null,
updateVer,
EVT_CACHE_OBJECT_REMOVED,
null, false,
evtOld, evtOld != null,
null,
taskName,
keepBinary);
}
}
if (updateRes.success())
updateMetrics(c.op, metrics, transformOp || updateRes.transformed(), oldVal != null);
// Continuous query filter should be perform under lock.
if (lsnrs != null) {
CacheObject evtVal = cctx.unwrapTemporary(updateVal);
CacheObject evtOldVal = cctx.unwrapTemporary(oldVal);
cctx.continuousQueries().onEntryUpdated(lsnrs,
key,
evtVal,
evtOldVal,
internal,
partition(),
primary,
false,
c.updateRes.updateCounter(),
fut,
topVer);
}
if (intercept && c.wasIntercepted) {
assert c.op == UPDATE || c.op == DELETE : c.op;
Cache.Entry<?, ?> entry = new CacheLazyEntry<>(
cctx,
key,
null,
c.op == UPDATE ? updateVal : oldVal,
null,
keepBinary,
c.updateRes.updateCounter()
);
if (c.op == UPDATE)
cctx.config().getInterceptor().onAfterPut(entry);
else
cctx.config().getInterceptor().onAfterRemove(entry);
}
updatePlatformCache(c.op == UPDATE ? updateVal : null, topVer);
}
finally {
unlockEntry();
unlockListenerReadLock();
}
onUpdateFinished(c.updateRes.updateCounter());
return c.updateRes;
}
/**
* @param val Value.
* @param cacheObj Cache object.
* @param keepBinary Keep binary flag.
* @param cpy Copy flag.
* @return Cache object value.
*/
@Nullable private Object value(@Nullable Object val, @Nullable CacheObject cacheObj, boolean keepBinary, boolean cpy) {
if (val != null)
return val;
return cctx.unwrapBinaryIfNeeded(cacheObj, keepBinary, cpy, null);
}
/**
* @param expiry Expiration policy.
* @return Tuple holding initial TTL and expire time with the given expiry.
*/
private static IgniteBiTuple<Long, Long> initialTtlAndExpireTime(IgniteCacheExpiryPolicy expiry) {
assert expiry != null;
long initTtl = expiry.forCreate();
long initExpireTime;
if (initTtl == CU.TTL_ZERO) {
initTtl = CU.TTL_MINIMUM;
initExpireTime = CU.expireTimeInPast();
}
else if (initTtl == CU.TTL_NOT_CHANGED) {
initTtl = CU.TTL_ETERNAL;
initExpireTime = CU.EXPIRE_TIME_ETERNAL;
}
else
initExpireTime = CU.toExpireTime(initTtl);
return F.t(initTtl, initExpireTime);
}
/**
* Get TTL, expire time and remove flag for the given entry, expiration policy and explicit TTL and expire time.
*
* @param expiry Expiration policy.
* @param ttl Explicit TTL.
* @param expireTime Explicit expire time.
* @return Result.
*/
private GridTuple3<Long, Long, Boolean> ttlAndExpireTime(IgniteCacheExpiryPolicy expiry, long ttl, long expireTime) {
assert !obsolete();
boolean rmv = false;
// 1. If TTL is not changed, then calculate it based on expiry.
if (ttl == CU.TTL_NOT_CHANGED) {
if (expiry != null)
ttl = hasValueUnlocked() ? expiry.forUpdate() : expiry.forCreate();
}
// 2. If TTL is zero, then set delete marker.
if (ttl == CU.TTL_ZERO) {
rmv = true;
ttl = CU.TTL_ETERNAL;
}
// 3. If TTL is still not changed, then either use old entry TTL or set it to "ETERNAL".
if (ttl == CU.TTL_NOT_CHANGED) {
if (isStartVersion())
ttl = CU.TTL_ETERNAL;
else {
ttl = ttlExtras();
expireTime = expireTimeExtras();
}
}
// 4 If expire time was not set explicitly, then calculate it.
if (expireTime == CU.EXPIRE_TIME_CALCULATE)
expireTime = CU.toExpireTime(ttl);
return F.t(ttl, expireTime, rmv);
}
/**
* Perform DR if needed.
*
* @param drType DR type.
* @param val Value.
* @param ver Version.
* @param topVer Topology version.
* @throws IgniteCheckedException In case of exception.
*/
private void drReplicate(GridDrType drType, @Nullable CacheObject val, GridCacheVersion ver, AffinityTopologyVersion topVer)
throws IgniteCheckedException {
if (cctx.isDrEnabled() && drType != DR_NONE && !isInternal())
cctx.dr().replicate(key, val, rawTtl(), rawExpireTime(), ver.conflictVersion(), drType, topVer);
}
/**
* @return {@code true} if entry has readers. It makes sense only for dht entry.
* @throws GridCacheEntryRemovedException If removed.
*/
protected boolean hasReaders() throws GridCacheEntryRemovedException {
return false;
}
/**
*
*/
protected void clearReaders() {
// No-op.
}
/**
* @param nodeId Node ID to clear.
* @throws GridCacheEntryRemovedException If removed.
*/
protected void clearReader(UUID nodeId) throws GridCacheEntryRemovedException {
// No-op.
}
/** {@inheritDoc} */
@Override public boolean clear(GridCacheVersion ver, boolean readers) throws IgniteCheckedException {
lockEntry();
try {
if (obsolete())
return false;
try {
if ((!hasReaders() || readers)) {
// markObsolete will clear the value.
if (!(markObsolete0(ver, true, null))) {
if (log.isDebugEnabled())
log.debug("Entry could not be marked obsolete (it is still used): " + this);
return false;
}
clearReaders();
}
else {
if (log.isDebugEnabled())
log.debug("Entry could not be marked obsolete (it still has readers): " + this);
return false;
}
}
catch (GridCacheEntryRemovedException ignore) {
assert false;
return false;
}
if (log.isTraceEnabled()) {
log.trace("entry clear [key=" + key +
", entry=" + System.identityHashCode(this) +
", val=" + val + ']');
}
if (cctx.mvccEnabled())
cctx.offheap().mvccRemoveAll(this);
else
removeValue();
}
finally {
unlockEntry();
}
onMarkedObsolete();
cctx.cache().removeEntry(this); // Clear cache.
return true;
}
/** {@inheritDoc} */
@Override public GridCacheVersion obsoleteVersion() {
lockEntry();
try {
return obsoleteVersionExtras();
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public boolean markObsolete(GridCacheVersion ver) {
boolean obsolete;
lockEntry();
try {
obsolete = markObsolete0(ver, true, null);
}
finally {
unlockEntry();
}
if (obsolete)
onMarkedObsolete();
return obsolete;
}
/** {@inheritDoc} */
@Override public boolean markObsoleteIfEmpty(@Nullable GridCacheVersion obsoleteVer) throws IgniteCheckedException {
boolean obsolete = false;
boolean deferred = false;
GridCacheVersion ver0 = null;
lockEntry();
try {
if (obsoleteVersionExtras() != null)
return false;
if (hasValueUnlocked()) {
long expireTime = expireTimeExtras();
if (expireTime > 0 && (expireTime < U.currentTimeMillis())) {
if (obsoleteVer == null)
obsoleteVer = nextVersion();
if (onExpired(this.val, obsoleteVer)) {
if (cctx.deferredDelete()) {
deferred = true;
ver0 = ver;
}
else
obsolete = true;
}
}
}
else {
if (cctx.deferredDelete() && !isStartVersion() && !detached()) {
if (!deletedUnlocked()) {
update(null, 0L, 0L, ver, true);
deletedUnlocked(true);
deferred = true;
ver0 = ver;
}
}
else {
if (obsoleteVer == null)
obsoleteVer = nextVersion();
obsolete = markObsolete0(obsoleteVer, true, null);
}
}
}
finally {
unlockEntry();
if (obsolete)
onMarkedObsolete();
if (deferred)
cctx.onDeferredDelete(this, ver0);
}
return obsolete;
}
/** {@inheritDoc} */
@Override public boolean markObsoleteVersion(GridCacheVersion ver) {
assert cctx.deferredDelete();
boolean marked;
lockEntry();
try {
if (obsoleteVersionExtras() != null)
return true;
if (!this.ver.equals(ver))
return false;
marked = markObsolete0(ver, true, null);
}
finally {
unlockEntry();
}
if (marked)
onMarkedObsolete();
return marked;
}
/**
* @return {@code True} if this entry should not be evicted from cache.
*/
protected boolean evictionDisabled() {
return (flags & IS_EVICT_DISABLED) != 0;
}
/**
* <p>
* Note that {@link #onMarkedObsolete()} should always be called after this method returns {@code true}.
*
* @param ver Version.
* @param clear {@code True} to clear.
* @param extras Predefined extras.
* @return {@code True} if entry is obsolete, {@code false} if entry is still used by other threads or nodes.
*/
protected final boolean markObsolete0(GridCacheVersion ver, boolean clear, GridCacheObsoleteEntryExtras extras) {
assert lock.isHeldByCurrentThread();
if (evictionDisabled()) {
assert !obsolete() : this;
return false;
}
GridCacheVersion obsoleteVer = obsoleteVersionExtras();
if (ver != null) {
// If already obsolete, then do nothing.
if (obsoleteVer != null)
return true;
GridCacheMvcc mvcc = mvccExtras();
if (mvcc == null || mvcc.isEmpty(ver)) {
obsoleteVer = ver;
obsoleteVersionExtras(obsoleteVer, extras);
if (clear)
value(null);
if (log.isTraceEnabled()) {
log.trace("markObsolete0 [key=" + key +
", entry=" + System.identityHashCode(this) +
", clear=" + clear +
']');
}
}
return obsoleteVer != null;
}
else
return obsoleteVer != null;
}
/** {@inheritDoc} */
@Override public void onMarkedObsolete() {
// No-op.
}
/** {@inheritDoc} */
@Override public final boolean obsolete() {
lockEntry();
try {
return obsoleteVersionExtras() != null;
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public final boolean obsolete(GridCacheVersion exclude) {
lockEntry();
try {
GridCacheVersion obsoleteVer = obsoleteVersionExtras();
return obsoleteVer != null && !obsoleteVer.equals(exclude);
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public boolean invalidate(GridCacheVersion newVer)
throws IgniteCheckedException {
lockEntry();
try {
assert newVer != null;
value(null);
ver = newVer;
flags &= ~IS_EVICT_DISABLED;
onInvalidate();
return obsoleteVersionExtras() != null;
}
finally {
unlockEntry();
}
}
/**
* Called when entry invalidated.
*/
protected void onInvalidate() {
// No-op.
}
/**
* @param val New value.
* @param expireTime Expiration time.
* @param ttl Time to live.
* @param ver Update version.
*/
protected final void update(@Nullable CacheObject val, long expireTime, long ttl, GridCacheVersion ver, boolean addTracked) {
assert ver != null;
assert lock.isHeldByCurrentThread();
assert ttl != CU.TTL_ZERO && ttl != CU.TTL_NOT_CHANGED && ttl >= 0 : ttl;
boolean trackNear = addTracked && isNear() && cctx.config().isEagerTtl();
long oldExpireTime = expireTimeExtras();
if (trackNear && oldExpireTime != 0 && (expireTime != oldExpireTime || isStartVersion()))
cctx.ttl().removeTrackedEntry((GridNearCacheEntry)this);
value(val);
ttlAndExpireTimeExtras(ttl, expireTime);
this.ver = ver;
flags &= ~IS_EVICT_DISABLED;
if (trackNear && expireTime != 0 && (expireTime != oldExpireTime || isStartVersion()))
cctx.ttl().addTrackedEntry((GridNearCacheEntry)this);
}
/**
* @return {@code True} if should notify continuous query manager on updates of this entry.
*/
private boolean notifyContinuousQueries() {
return !isNear();
}
/**
* Update TTL if it is changed.
*
* @param expiryPlc Expiry policy.
*/
private void updateTtlUnlocked(ExpiryPolicy expiryPlc) throws IgniteCheckedException {
long ttl = CU.toTtl(expiryPlc.getExpiryForAccess());
if (ttl != CU.TTL_NOT_CHANGED)
updateTtlUnlocked(ttl);
}
/**
* Update TTL is it is changed.
*
* @param ver Version.
* @param expiryPlc Expiry policy.
* @throws GridCacheEntryRemovedException If failed.
*/
private void updateTtlUnlocked(
GridCacheVersion ver,
IgniteCacheExpiryPolicy expiryPlc
) throws GridCacheEntryRemovedException, IgniteCheckedException {
long ttl = expiryPlc.forAccess();
if (ttl != CU.TTL_NOT_CHANGED) {
updateTtlUnlocked(ttl);
expiryPlc.ttlUpdated(key(), ver, hasReaders() ? ((GridDhtCacheEntry)this).readers() : null);
}
}
/**
* @param ttl Time to live.
*/
private void updateTtlUnlocked(long ttl) throws IgniteCheckedException {
assert ttl >= 0 || ttl == CU.TTL_ZERO : ttl;
assert lock.isHeldByCurrentThread();
long expireTime;
if (ttl == CU.TTL_ZERO) {
ttl = CU.TTL_MINIMUM;
expireTime = CU.expireTimeInPast();
}
else
expireTime = CU.toExpireTime(ttl);
ttlAndExpireTimeExtras(ttl, expireTime);
cctx.shared().database().checkpointReadLock();
try {
storeValue(val, expireTime, ver);
}
finally {
cctx.shared().database().checkpointReadUnlock();
}
}
/**
* @throws GridCacheEntryRemovedException If entry is obsolete.
*/
protected void checkObsolete() throws GridCacheEntryRemovedException {
assert lock.isHeldByCurrentThread();
if (obsoleteVersionExtras() != null)
throw new GridCacheEntryRemovedException();
}
/** {@inheritDoc} */
@Override public KeyCacheObject key() {
return key;
}
/** {@inheritDoc} */
@Override public IgniteTxKey txKey() {
return cctx.txKey(key);
}
/** {@inheritDoc} */
@Override public GridCacheVersion version() throws GridCacheEntryRemovedException {
lockEntry();
try {
checkObsolete();
return ver;
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public boolean checkSerializableReadVersion(GridCacheVersion serReadVer)
throws GridCacheEntryRemovedException {
lockEntry();
try {
checkObsolete();
if (!serReadVer.equals(ver)) {
boolean empty = isStartVersion() || deletedUnlocked();
if (serReadVer.equals(IgniteTxEntry.SER_READ_EMPTY_ENTRY_VER))
return empty;
else if (serReadVer.equals(IgniteTxEntry.SER_READ_NOT_EMPTY_VER))
return !empty;
return false;
}
return true;
}
finally {
unlockEntry();
}
}
/**
* Gets hash value for the entry key.
*
* @return Hash value.
*/
int hash() {
return hash;
}
/** {@inheritDoc} */
@Nullable @Override public CacheObject mvccPeek(boolean onheapOnly)
throws GridCacheEntryRemovedException, IgniteCheckedException {
if (onheapOnly)
return null;
lockEntry();
try {
checkObsolete();
CacheDataRow row = cctx.offheap().mvccRead(cctx, key, MVCC_MAX_SNAPSHOT);
return row != null ? row.value() : null;
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Nullable @Override public CacheObject peek(
boolean heap,
boolean offheap,
AffinityTopologyVersion topVer,
@Nullable IgniteCacheExpiryPolicy expiryPlc)
throws GridCacheEntryRemovedException, IgniteCheckedException {
assert heap || offheap;
boolean rmv = false;
try {
boolean deferred;
GridCacheVersion ver0;
lockEntry();
try {
checkObsolete();
if (!valid(topVer))
return null;
if (val == null && offheap)
unswap(null, false);
if (checkExpired()) {
if (cctx.deferredDelete()) {
deferred = true;
ver0 = ver;
}
else {
rmv = markObsolete0(nextVersion(), true, null);
return null;
}
}
else {
CacheObject val = this.val;
if (val != null && expiryPlc != null)
updateTtlUnlocked(version(), expiryPlc);
return val;
}
}
finally {
unlockEntry();
}
if (deferred) {
assert ver0 != null;
cctx.onDeferredDelete(this, ver0);
}
return null;
}
finally {
if (rmv) {
onMarkedObsolete();
cctx.cache().removeEntry(this);
}
}
}
/** {@inheritDoc} */
@Nullable @Override public CacheObject peek()
throws GridCacheEntryRemovedException, IgniteCheckedException {
IgniteInternalTx tx = cctx.tm().localTx();
AffinityTopologyVersion topVer = tx != null ? tx.topologyVersion() : cctx.affinity().affinityTopologyVersion();
return peek(true, false, topVer, null);
}
/**
* TODO: IGNITE-3500: do we need to generate event and invalidate value?
*
* @return {@code true} if expired.
* @throws IgniteCheckedException In case of failure.
*/
private boolean checkExpired() throws IgniteCheckedException {
assert lock.isHeldByCurrentThread();
long expireTime = expireTimeExtras();
if (expireTime > 0) {
long delta = expireTime - U.currentTimeMillis();
if (delta <= 0) {
if (cctx.mvccEnabled())
cctx.offheap().mvccRemoveAll(this);
else
removeValue();
return true;
}
}
return false;
}
/**
* @return Value.
*/
@Override public CacheObject rawGet() {
lockEntry();
try {
return val;
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public final boolean hasValue() {
lockEntry();
try {
return hasValueUnlocked();
}
finally {
unlockEntry();
}
}
/**
* @return {@code True} if this entry has value.
*/
protected final boolean hasValueUnlocked() {
assert lock.isHeldByCurrentThread();
return val != null;
}
/**
* Checks, that changes were got by DR.
*
* @param explicitVer – Explicit version (if any).
* @return {@code true} if changes were got by DR and {@code false} otherwise.
*/
private boolean isRemoteDrUpdate(@Nullable GridCacheVersion explicitVer) {
return explicitVer != null && explicitVer.dataCenterId() != cctx.dr().dataCenterId();
}
/**
* Checks, that cache interceptor should be skipped.
* <p>
* It is expects by default behavior that Interceptor methods ({@link CacheInterceptor#onBeforePut(Cache.Entry,
* Object)}, {@link CacheInterceptor#onAfterPut(Cache.Entry)}, {@link CacheInterceptor#onBeforeRemove(Cache.Entry)}
* and {@link CacheInterceptor#onAfterRemove(Cache.Entry)}) will be called, but {@link
* CacheInterceptor#onGet(Object, Object)}. This can even make DR-update flow broken in case of non-idempotent
* Interceptor and force users to call onGet manually as the only workaround. Also, user may want to skip
* Interceptor to avoid redundant entry transformation for DR updates and exchange with internal data b/w data
* centres which is a normal case.
*
* @param explicitVer - Explicit version (if any).
* @return {@code true} if cache interceptor should be skipped and {@code false} otherwise.
*/
private boolean skipInterceptor(@Nullable GridCacheVersion explicitVer) {
return isRemoteDrUpdate(explicitVer) && cctx.disableTriggeringCacheInterceptorOnConflict();
}
/** {@inheritDoc} */
@Override public CacheObject rawPut(CacheObject val, long ttl) {
lockEntry();
try {
CacheObject old = this.val;
update(val, CU.toExpireTime(ttl), ttl, nextVersion(), true);
return old;
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public boolean initialValue(
CacheObject val,
GridCacheVersion ver,
MvccVersion mvccVer,
MvccVersion newMvccVer,
byte mvccTxState,
byte newMvccTxState,
long ttl,
long expireTime,
boolean preload,
AffinityTopologyVersion topVer,
GridDrType drType,
boolean fromStore,
boolean primary,
CacheDataRow row
) throws IgniteCheckedException, GridCacheEntryRemovedException {
assert !primary || !(preload || fromStore);
ensureFreeSpace();
boolean deferred = false;
boolean obsolete = false;
GridCacheVersion oldVer = null;
lockListenerReadLock();
lockEntry();
try {
checkObsolete();
boolean walEnabled = !cctx.isNear() && cctx.group().logDataRecords();
long expTime = expireTime < 0 ? CU.toExpireTime(ttl) : expireTime;
val = cctx.kernalContext().cacheObjects().prepareForCache(val, cctx);
final boolean unswapped = ((flags & IS_UNSWAPPED_MASK) != 0);
boolean update;
IgnitePredicate<CacheDataRow> p = new IgnitePredicate<CacheDataRow>() {
@Override public boolean apply(@Nullable CacheDataRow row) {
boolean update0;
GridCacheVersion currentVer = row != null ? row.version() : GridCacheMapEntry.this.ver;
boolean isStartVer = cctx.shared().versions().isStartVersion(currentVer);
if (cctx.group().persistenceEnabled()) {
if (!isStartVer) {
if (cctx.atomic())
update0 = ATOMIC_VER_COMPARATOR.compare(currentVer, ver) < 0;
else
update0 = currentVer.compareTo(ver) < 0;
}
else
update0 = true;
}
else
update0 = isStartVer;
update0 |= (!preload && deletedUnlocked());
return update0;
}
};
if (unswapped) {
update = p.apply(null);
if (update) {
// If entry is already unswapped and we are modifying it, we must run deletion callbacks for old value.
long oldExpTime = expireTimeUnlocked();
if (oldExpTime > 0 && oldExpTime < U.currentTimeMillis()) {
if (onExpired(this.val, null)) {
if (cctx.deferredDelete()) {
deferred = true;
oldVer = this.ver;
}
else if (val == null)
obsolete = true;
}
}
if (cctx.mvccEnabled()) {
assert !preload;
cctx.offheap().mvccInitialValue(this, val, ver, expTime, mvccVer, newMvccVer);
}
else
storeValue(val, expTime, ver, null, row);
}
}
else {
if (cctx.mvccEnabled()) {
// cannot identify whether the entry is exist on the fly
unswap(false);
if (update = p.apply(null)) {
// If entry is already unswapped and we are modifying it, we must run deletion callbacks for old value.
long oldExpTime = expireTimeUnlocked();
long delta = (oldExpTime == 0 ? 0 : oldExpTime - U.currentTimeMillis());
if (delta < 0) {
if (onExpired(this.val, null)) {
if (cctx.deferredDelete()) {
deferred = true;
oldVer = this.ver;
}
else if (val == null)
obsolete = true;
}
}
assert !preload;
cctx.offheap().mvccInitialValue(this, val, ver, expTime, mvccVer, newMvccVer);
}
}
else
// Optimization to access storage only once.
update = storeValue(val, expTime, ver, p, row);
}
if (update) {
update(val, expTime, ttl, ver, true);
boolean skipQryNtf = false;
if (val == null) {
skipQryNtf = true;
if (cctx.deferredDelete() && !deletedUnlocked() && !isInternal())
deletedUnlocked(true);
}
else if (deletedUnlocked())
deletedUnlocked(false);
long updateCntr = 0;
if (!preload)
updateCntr = nextPartitionCounter(topVer, true, true, null);
if (walEnabled) {
if (cctx.mvccEnabled()) {
cctx.group().wal().log(new MvccDataRecord(new MvccDataEntry(
cctx.cacheId(),
key,
val,
val == null ? DELETE : GridCacheOperation.CREATE,
null,
ver,
expireTime,
partition(),
updateCntr,
mvccVer == null ? MvccUtils.INITIAL_VERSION : mvccVer
)));
}
else {
cctx.group().wal().log(new DataRecord(new DataEntry(
cctx.cacheId(),
key,
val,
val == null ? DELETE : GridCacheOperation.CREATE,
null,
ver,
expireTime,
partition(),
updateCntr,
DataEntry.flags(primary, preload, fromStore)
)));
}
}
drReplicate(drType, val, ver, topVer);
if (!skipQryNtf) {
cctx.continuousQueries().onEntryUpdated(
key,
val,
null,
this.isInternal() || !this.context().userCache(),
this.partition(),
true,
preload,
updateCntr,
null,
topVer);
}
updatePlatformCache(val, topVer);
onUpdateFinished(updateCntr);
if (!fromStore && cctx.store().isLocal()) {
if (val != null)
cctx.store().put(null, key, val, ver);
}
return true;
}
return false;
}
finally {
unlockEntry();
unlockListenerReadLock();
// It is necessary to execute these callbacks outside of lock to avoid deadlocks.
if (obsolete) {
onMarkedObsolete();
cctx.cache().removeEntry(this);
}
if (deferred) {
assert oldVer != null;
cctx.onDeferredDelete(this, oldVer);
}
}
}
/**
* @param cntr Updated partition counter.
*/
protected void onUpdateFinished(long cntr) {
// No-op.
}
/**
* @param topVer Topology version for current operation.
* @param primary Primary node update flag.
* @param initial {@code True} if initial value.
* @param primaryCntr Counter assigned on primary node.
* @return Update counter.
*/
protected long nextPartitionCounter(AffinityTopologyVersion topVer, boolean primary, boolean initial,
@Nullable Long primaryCntr) {
return 0;
}
/**
* @param tx Tx.
* @param updateCntr Update counter.
*/
protected long nextPartitionCounter(IgniteInternalTx tx, @Nullable Long updateCntr) {
return 0;
}
/** {@inheritDoc} */
@Override public GridCacheVersionedEntryEx versionedEntry(final boolean keepBinary)
throws IgniteCheckedException, GridCacheEntryRemovedException {
lockEntry();
try {
boolean isNew = isStartVersion();
if (isNew)
unswap(null, false);
CacheObject val = this.val;
return new GridCacheLazyPlainVersionedEntry<>(cctx,
key,
val,
ttlExtras(),
expireTimeExtras(),
ver.conflictVersion(),
isNew,
keepBinary);
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public void clearReserveForLoad(GridCacheVersion ver) {
lockEntry();
try {
if (obsoleteVersionExtras() != null)
return;
if (ver.equals(this.ver)) {
assert evictionDisabled() : this;
flags &= ~IS_EVICT_DISABLED;
}
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public EntryGetResult versionedValue(CacheObject val,
GridCacheVersion curVer,
GridCacheVersion newVer,
@Nullable IgniteCacheExpiryPolicy loadExpiryPlc,
@Nullable ReaderArguments readerArgs
) throws IgniteCheckedException, GridCacheEntryRemovedException {
lockEntry();
try {
checkObsolete();
addReaderIfNeed(readerArgs);
if (curVer == null || curVer.equals(ver)) {
if (val != this.val) {
GridCacheMvcc mvcc = mvccExtras();
if (mvcc != null && !mvcc.isEmpty())
return entryGetResult(this.val, ver, false);
if (newVer == null)
newVer = cctx.cache().nextVersion();
long ttl;
long expTime;
if (loadExpiryPlc != null) {
IgniteBiTuple<Long, Long> initTtlAndExpireTime = initialTtlAndExpireTime(loadExpiryPlc);
ttl = initTtlAndExpireTime.get1();
expTime = initTtlAndExpireTime.get2();
}
else {
ttl = ttlExtras();
expTime = expireTimeExtras();
}
// Detach value before index update.
val = cctx.kernalContext().cacheObjects().prepareForCache(val, cctx);
if (val != null) {
storeValue(val, expTime, newVer);
if (deletedUnlocked())
deletedUnlocked(false);
}
// Version does not change for load ops.
update(val, expTime, ttl, newVer, true);
return entryGetResult(val, newVer, false);
}
assert !evictionDisabled() : this;
}
return entryGetResult(this.val, ver, false);
}
finally {
unlockEntry();
}
}
/**
* @param readerArgs Reader arguments
*/
private void addReaderIfNeed(@Nullable ReaderArguments readerArgs) {
if (readerArgs != null) {
assert this instanceof GridDhtCacheEntry : this;
assert lock.isHeldByCurrentThread();
try {
((GridDhtCacheEntry)this).addReader(readerArgs.reader(),
readerArgs.messageId(),
readerArgs.topologyVersion());
}
catch (GridCacheEntryRemovedException e) {
assert false : this;
}
}
}
/**
* Gets next version for this cache entry.
*
* @return Next version.
*/
private GridCacheVersion nextVersion() {
// Do not change topology version when generating next version.
return cctx.versions().next(ver);
}
/** {@inheritDoc} */
@Override public boolean hasLockCandidate(GridCacheVersion ver) throws GridCacheEntryRemovedException {
lockEntry();
try {
checkObsolete();
GridCacheMvcc mvcc = mvccExtras();
return mvcc != null && mvcc.hasCandidate(ver);
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public boolean hasLockCandidate(long threadId) throws GridCacheEntryRemovedException {
lockEntry();
try {
checkObsolete();
GridCacheMvcc mvcc = mvccExtras();
return mvcc != null && mvcc.localCandidate(threadId) != null;
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public boolean lockedByAny(GridCacheVersion... exclude)
throws GridCacheEntryRemovedException {
lockEntry();
try {
checkObsolete();
GridCacheMvcc mvcc = mvccExtras();
return mvcc != null && !mvcc.isEmpty(exclude);
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public boolean lockedByThread() throws GridCacheEntryRemovedException {
return lockedByThread(Thread.currentThread().getId());
}
/** {@inheritDoc} */
@Override public boolean lockedLocally(GridCacheVersion lockVer)
throws GridCacheEntryRemovedException {
lockEntry();
try {
checkObsolete();
GridCacheMvcc mvcc = mvccExtras();
return mvcc != null && mvcc.isLocallyOwned(lockVer);
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public boolean lockedByThread(long threadId, GridCacheVersion exclude)
throws GridCacheEntryRemovedException {
lockEntry();
try {
checkObsolete();
GridCacheMvcc mvcc = mvccExtras();
return mvcc != null && mvcc.isLocallyOwnedByThread(threadId, false, exclude);
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public boolean lockedLocallyByIdOrThread(GridCacheVersion lockVer, long threadId)
throws GridCacheEntryRemovedException {
lockEntry();
try {
GridCacheMvcc mvcc = mvccExtras();
return mvcc != null && mvcc.isLocallyOwnedByIdOrThread(lockVer, threadId);
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public boolean lockedByThread(long threadId) throws GridCacheEntryRemovedException {
lockEntry();
try {
checkObsolete();
GridCacheMvcc mvcc = mvccExtras();
return mvcc != null && mvcc.isLocallyOwnedByThread(threadId, true);
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public boolean lockedBy(GridCacheVersion ver) throws GridCacheEntryRemovedException {
lockEntry();
try {
checkObsolete();
GridCacheMvcc mvcc = mvccExtras();
return mvcc != null && mvcc.isOwnedBy(ver);
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public boolean lockedByThreadUnsafe(long threadId) {
lockEntry();
try {
GridCacheMvcc mvcc = mvccExtras();
return mvcc != null && mvcc.isLocallyOwnedByThread(threadId, true);
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public boolean lockedByUnsafe(GridCacheVersion ver) {
lockEntry();
try {
GridCacheMvcc mvcc = mvccExtras();
return mvcc != null && mvcc.isOwnedBy(ver);
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public boolean lockedLocallyUnsafe(GridCacheVersion lockVer) {
lockEntry();
try {
GridCacheMvcc mvcc = mvccExtras();
return mvcc != null && mvcc.isLocallyOwned(lockVer);
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public boolean hasLockCandidateUnsafe(GridCacheVersion ver) {
lockEntry();
try {
GridCacheMvcc mvcc = mvccExtras();
return mvcc != null && mvcc.hasCandidate(ver);
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public Collection<GridCacheMvccCandidate> localCandidates(GridCacheVersion... exclude)
throws GridCacheEntryRemovedException {
lockEntry();
try {
checkObsolete();
GridCacheMvcc mvcc = mvccExtras();
return mvcc == null ? Collections.<GridCacheMvccCandidate>emptyList() : mvcc.localCandidates(exclude);
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public Collection<GridCacheMvccCandidate> remoteMvccSnapshot(GridCacheVersion... exclude) {
return Collections.emptyList();
}
/** {@inheritDoc} */
@Nullable @Override public GridCacheMvccCandidate candidate(GridCacheVersion ver)
throws GridCacheEntryRemovedException {
lockEntry();
try {
checkObsolete();
GridCacheMvcc mvcc = mvccExtras();
return mvcc == null ? null : mvcc.candidate(ver);
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public GridCacheMvccCandidate localCandidate(long threadId)
throws GridCacheEntryRemovedException {
lockEntry();
try {
checkObsolete();
GridCacheMvcc mvcc = mvccExtras();
return mvcc == null ? null : mvcc.localCandidate(threadId);
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public GridCacheMvccCandidate candidate(UUID nodeId, long threadId)
throws GridCacheEntryRemovedException {
boolean loc = cctx.nodeId().equals(nodeId);
lockEntry();
try {
checkObsolete();
GridCacheMvcc mvcc = mvccExtras();
return mvcc == null ? null : loc ? mvcc.localCandidate(threadId) :
mvcc.remoteCandidate(nodeId, threadId);
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public GridCacheMvccCandidate localOwner() throws GridCacheEntryRemovedException {
lockEntry();
try {
checkObsolete();
GridCacheMvcc mvcc = mvccExtras();
return mvcc == null ? null : mvcc.localOwner();
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public long rawExpireTime() {
lockEntry();
try {
return expireTimeExtras();
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public long expireTimeUnlocked() {
assert lock.isHeldByCurrentThread();
return expireTimeExtras();
}
/** {@inheritDoc} */
@Override public boolean onTtlExpired(GridCacheVersion obsoleteVer) throws GridCacheEntryRemovedException {
assert obsoleteVer != null;
boolean obsolete = false;
boolean deferred = false;
GridCacheVersion ver0 = null;
lockEntry();
try {
checkObsolete();
if (isStartVersion())
unswap(null, false);
long expireTime = expireTimeExtras();
if (!(expireTime > 0 && expireTime <= U.currentTimeMillis()))
return false;
CacheObject expiredVal = this.val;
if (expiredVal == null)
return false;
if (onExpired(expiredVal, obsoleteVer)) {
if (cctx.deferredDelete()) {
deferred = true;
ver0 = ver;
}
else
obsolete = true;
}
}
catch (NodeStoppingException ignore) {
if (log.isDebugEnabled())
log.warning("Node is stopping while removing expired value.", ignore);
}
catch (IgniteCheckedException e) {
U.error(log, "Failed to clean up expired cache entry: " + this, e);
}
finally {
unlockEntry();
if (obsolete) {
onMarkedObsolete();
cctx.cache().removeEntry(this);
}
if (deferred) {
assert ver0 != null;
cctx.onDeferredDelete(this, ver0);
}
if ((obsolete || deferred) && cctx.statisticsEnabled())
cctx.cache().metrics0().onEvict();
}
return true;
}
/**
* @param expiredVal Expired value.
* @param obsoleteVer Version.
* @return {@code True} if entry was marked as removed.
* @throws IgniteCheckedException If failed.
*/
private boolean onExpired(CacheObject expiredVal, GridCacheVersion obsoleteVer) throws IgniteCheckedException {
assert expiredVal != null;
boolean rmvd = false;
if (mvccExtras() != null)
return false;
if (cctx.deferredDelete() && !detached() && !isInternal()) {
if (!deletedUnlocked() && !isStartVersion()) {
update(null, 0L, 0L, ver, true);
deletedUnlocked(true);
rmvd = true;
}
}
else {
if (obsoleteVer == null)
obsoleteVer = nextVersion();
if (markObsolete0(obsoleteVer, true, null))
rmvd = true;
}
if (log.isTraceEnabled())
log.trace("onExpired clear [key=" + key + ", entry=" + System.identityHashCode(this) + ']');
if (cctx.mvccEnabled())
cctx.offheap().mvccRemoveAll(this);
else
removeValue();
if (cctx.events().isRecordable(EVT_CACHE_OBJECT_EXPIRED)) {
cctx.events().addEvent(partition(),
key,
cctx.localNodeId(),
null,
EVT_CACHE_OBJECT_EXPIRED,
null,
false,
expiredVal,
expiredVal != null,
null,
null,
true);
}
cctx.continuousQueries().onEntryExpired(this, key, expiredVal);
updatePlatformCache(null, null);
return rmvd;
}
/** {@inheritDoc} */
@Override public long rawTtl() {
lockEntry();
try {
return ttlExtras();
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public long expireTime() throws GridCacheEntryRemovedException {
IgniteTxLocalAdapter tx = currentTx();
if (tx != null) {
long time = tx.entryExpireTime(txKey());
if (time > 0)
return time;
}
lockEntry();
try {
checkObsolete();
return expireTimeExtras();
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public long ttl() throws GridCacheEntryRemovedException {
IgniteTxLocalAdapter tx = currentTx();
if (tx != null) {
long entryTtl = tx.entryTtl(txKey());
if (entryTtl > 0)
return entryTtl;
}
lockEntry();
try {
checkObsolete();
return ttlExtras();
}
finally {
unlockEntry();
}
}
/**
* @return Current transaction.
*/
private IgniteTxLocalAdapter currentTx() {
return cctx.tm().localTx();
}
/** {@inheritDoc} */
@Override public void updateTtl(
GridCacheVersion ver,
IgniteCacheExpiryPolicy expiryPlc
) throws GridCacheEntryRemovedException {
lockEntry();
try {
checkObsolete();
if (hasValueUnlocked()) {
try {
updateTtlUnlocked(ver, expiryPlc);
}
catch (IgniteCheckedException e) {
U.error(log, "Failed to update TTL: " + e, e);
}
}
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public void updateTtl(@Nullable GridCacheVersion ver, long ttl) throws GridCacheEntryRemovedException {
lockEntry();
try {
checkObsolete();
if (hasValueUnlocked()) {
try {
updateTtlUnlocked(ttl);
}
catch (IgniteCheckedException e) {
U.error(log, "Failed to update TTL: " + e, e);
}
}
/*
TODO IGNITE-305.
try {
if (var == null || ver.equals(version()))
updateTtl(ttl);
}
catch (GridCacheEntryRemovedException ignored) {
// No-op.
}
*/
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public CacheObject valueBytes() throws GridCacheEntryRemovedException {
lockEntry();
try {
checkObsolete();
return this.val;
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Nullable @Override public CacheObject valueBytes(@Nullable GridCacheVersion ver)
throws IgniteCheckedException, GridCacheEntryRemovedException {
CacheObject val = null;
lockEntry();
try {
checkObsolete();
if (ver == null || this.ver.equals(ver))
val = this.val;
}
finally {
unlockEntry();
}
return val;
}
/**
* Stores value in offheap.
*
* @param val Value.
* @param expireTime Expire time.
* @param ver New entry version.
* @throws IgniteCheckedException If update failed.
*/
protected boolean storeValue(@Nullable CacheObject val,
long expireTime,
GridCacheVersion ver) throws IgniteCheckedException {
return storeValue(val, expireTime, ver, null, null);
}
/**
* Stores value in off-heap.
*
* @param val Value.
* @param expireTime Expire time.
* @param ver New entry version.
* @param predicate Optional predicate.
* @param row Pre-created data row, associated with this cache entry.
* @return {@code True} if storage was modified.
* @throws IgniteCheckedException If update failed.
*/
protected boolean storeValue(
@Nullable CacheObject val,
long expireTime,
GridCacheVersion ver,
@Nullable IgnitePredicate<CacheDataRow> predicate,
@Nullable CacheDataRow row
) throws IgniteCheckedException {
assert lock.isHeldByCurrentThread();
assert localPartition() == null || localPartition().state() != RENTING : localPartition();
UpdateClosure closure = new UpdateClosure(this, val, ver, expireTime, predicate, row);
cctx.offheap().invoke(cctx, key, localPartition(), closure);
return closure.treeOp != IgniteTree.OperationType.NOOP;
}
/**
* @param op Update operation.
* @param val Write value.
* @param writeVer Write version.
* @param expireTime Expire time.
* @param updCntr Update counter.
* @param primary {@code True} if node is primary for entry in the moment of logging.
*/
protected void logUpdate(
GridCacheOperation op,
CacheObject val,
GridCacheVersion writeVer,
long expireTime,
long updCntr,
boolean primary
) throws IgniteCheckedException {
// We log individual updates only in ATOMIC cache.
assert cctx.atomic();
try {
if (cctx.group().logDataRecords())
cctx.group().wal().log(new DataRecord(new DataEntry(
cctx.cacheId(),
key,
val,
op,
null,
writeVer,
expireTime,
partition(),
updCntr,
DataEntry.flags(primary))));
}
catch (StorageException e) {
throw new IgniteCheckedException("Failed to log ATOMIC cache update [key=" + key + ", op=" + op +
", val=" + val + ']', e);
}
}
/**
* @param tx Transaction.
* @param val Value.
* @param writeVer New entry version.
* @param expireTime Expire time (or 0 if not applicable).
* @param updCntr Update counter.
* @throws IgniteCheckedException In case of log failure.
*/
protected WALPointer logTxUpdate(
IgniteInternalTx tx,
CacheObject val,
GridCacheVersion writeVer,
long expireTime,
long updCntr
) throws IgniteCheckedException {
assert cctx.transactional() && !cctx.transactionalSnapshot();
if (tx.local()) { // For remote tx we log all updates in batch: GridDistributedTxRemoteAdapter.commitIfLocked()
GridCacheOperation op;
if (val == null)
op = DELETE;
else
op = this.val == null ? GridCacheOperation.CREATE : UPDATE;
return cctx.group().wal().log(new DataRecord(new DataEntry(
cctx.cacheId(),
key,
val,
op,
tx.nearXidVersion(),
writeVer,
expireTime,
key.partition(),
updCntr,
DataEntry.flags(CU.txOnPrimary(tx)))));
}
else
return null;
}
/**
* @param tx Transaction.
* @param val Value.
* @param expireTime Expire time (or 0 if not applicable). *
* @param updCntr Update counter.
* @param mvccVer Mvcc version.
* @throws IgniteCheckedException In case of log failure.
*/
protected WALPointer logMvccUpdate(IgniteInternalTx tx, CacheObject val, long expireTime, long updCntr,
MvccSnapshot mvccVer)
throws IgniteCheckedException {
assert mvccVer != null;
assert cctx.transactionalSnapshot();
if (tx.local()) { // For remote tx we log all updates in batch: GridDistributedTxRemoteAdapter.commitIfLocked()
GridCacheOperation op;
if (val == null)
op = DELETE;
else
op = this.val == null ? GridCacheOperation.CREATE : UPDATE;
return cctx.group().wal().log(new MvccDataRecord(new MvccDataEntry(
cctx.cacheId(),
key,
val,
op,
tx.nearXidVersion(),
tx.writeVersion(),
expireTime,
key.partition(),
updCntr,
mvccVer)));
}
else
return null;
}
/**
* Removes value from offheap.
*
* @throws IgniteCheckedException If failed.
*/
protected void removeValue() throws IgniteCheckedException {
assert lock.isHeldByCurrentThread();
// Removals are possible from RENTING partition on clearing/evicting.
cctx.offheap().remove(cctx, key, partition(), localPartition());
}
/** {@inheritDoc} */
@Override public <K, V> Cache.Entry<K, V> wrap() {
try {
IgniteInternalTx tx = cctx.tm().userTx();
CacheObject val;
if (tx != null) {
GridTuple<CacheObject> peek = tx.peek(cctx, false, key);
val = peek == null ? rawGet() : peek.get();
}
else
val = rawGet();
return new CacheEntryImpl<>(key.<K>value(cctx.cacheObjectContext(), false),
CU.<V>value(val, cctx, false), ver);
}
catch (GridCacheFilterFailedException ignored) {
throw new IgniteException("Should never happen.");
}
}
/** {@inheritDoc} */
@Override public <K, V> Cache.Entry<K, V> wrapLazyValue(boolean keepBinary) {
return new LazyValueEntry<>(key, keepBinary);
}
/** {@inheritDoc} */
@Override @Nullable public CacheObject peekVisibleValue() {
try {
IgniteInternalTx tx = cctx.tm().userTx();
if (tx != null) {
GridTuple<CacheObject> peek = tx.peek(cctx, false, key);
if (peek != null)
return peek.get();
}
if (detached())
return rawGet();
for (; ; ) {
GridCacheEntryEx e = cctx.cache().peekEx(key);
if (e == null)
return null;
try {
return e.peek();
}
catch (GridCacheEntryRemovedException ignored) {
// No-op.
}
catch (IgniteCheckedException ex) {
throw new IgniteException(ex);
}
}
}
catch (GridCacheFilterFailedException ignored) {
throw new IgniteException("Should never happen.");
}
}
/** {@inheritDoc} */
@Override public void updateIndex(
SchemaIndexCacheVisitorClosure clo
) throws IgniteCheckedException, GridCacheEntryRemovedException {
lockEntry();
try {
if (isInternal())
return;
checkObsolete();
CacheDataRow row = cctx.offheap().read(this);
if (row != null)
clo.apply(row);
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public <K, V> EvictableEntry<K, V> wrapEviction() {
return new CacheEvictableEntryImpl<>(this);
}
/** {@inheritDoc} */
@Override public <K, V> CacheEntryImplEx<K, V> wrapVersioned() {
lockEntry();
try {
return new CacheEntryImplEx<>(key.<K>value(cctx.cacheObjectContext(), false), null, ver);
}
finally {
unlockEntry();
}
}
/**
* Evicts necessary number of data pages if per-page eviction is configured in current {@link DataRegion}.
*/
private void ensureFreeSpace() throws IgniteCheckedException {
// Deadlock alert: evicting data page causes removing (and locking) all entries on the page one by one.
assert !lock.isHeldByCurrentThread();
cctx.shared().database().ensureFreeSpace(cctx.dataRegion());
}
/**
* @return Entry which holds key, value and version.
*/
private <K, V> CacheEntryImplEx<K, V> wrapVersionedWithValue() {
lockEntry();
try {
V val = this.val == null ? null : this.val.<V>value(cctx.cacheObjectContext(), false);
return new CacheEntryImplEx<>(key.<K>value(cctx.cacheObjectContext(), false), val, ver);
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public boolean evictInternal(
GridCacheVersion obsoleteVer,
@Nullable CacheEntryPredicate[] filter,
boolean evictOffheap)
throws IgniteCheckedException {
boolean marked = false;
try {
if (F.isEmptyOrNulls(filter)) {
lockEntry();
try {
if (evictionDisabled()) {
assert !obsolete();
return false;
}
if (obsoleteVersionExtras() != null)
return true;
// TODO IGNITE-5286: need keep removed entries in heap map, otherwise removes can be lost.
if (cctx.deferredDelete() && deletedUnlocked())
return false;
if (!hasReaders() && markObsolete0(obsoleteVer, false, null)) {
// Nullify value after swap.
value(null);
if (evictOffheap)
removeValue();
marked = true;
return true;
}
}
finally {
unlockEntry();
}
}
else {
// For optimistic check.
while (true) {
GridCacheVersion v;
lockEntry();
try {
v = ver;
}
finally {
unlockEntry();
}
if (!cctx.isAll(/*version needed for sync evicts*/this, filter))
return false;
lockEntry();
try {
if (evictionDisabled()) {
assert !obsolete();
return false;
}
if (obsoleteVersionExtras() != null)
return true;
if (!v.equals(ver))
// Version has changed since entry passed the filter. Do it again.
continue;
// TODO IGNITE-5286: need keep removed entries in heap map, otherwise removes can be lost.
if (cctx.deferredDelete() && deletedUnlocked())
return false;
if (!hasReaders() && markObsolete0(obsoleteVer, false, null)) {
// Nullify value after swap.
value(null);
if (evictOffheap)
removeValue();
marked = true;
return true;
}
else
return false;
}
finally {
unlockEntry();
}
}
}
}
catch (GridCacheEntryRemovedException ignore) {
if (log.isDebugEnabled())
log.debug("Got removed entry when evicting (will simply return): " + this);
return true;
}
finally {
if (marked)
onMarkedObsolete();
}
return false;
}
/**
* @param filter Entry filter.
* @return {@code True} if entry is visitable.
*/
public final boolean visitable(CacheEntryPredicate[] filter) {
boolean rmv = false;
try {
lockEntry();
try {
if (obsoleteOrDeleted())
return false;
if (checkExpired()) {
rmv = markObsolete0(nextVersion(), true, null);
return false;
}
}
finally {
unlockEntry();
}
if (filter != CU.empty0() && !cctx.isAll(this, filter))
return false;
}
catch (IgniteCheckedException e) {
U.error(log, "An exception was thrown while filter checking.", e);
RuntimeException ex = e.getCause(RuntimeException.class);
if (ex != null)
throw ex;
Error err = e.getCause(Error.class);
if (err != null)
throw err;
return false;
}
finally {
if (rmv) {
onMarkedObsolete();
cctx.cache().removeEntry(this);
}
}
IgniteInternalTx tx = cctx.tm().localTx();
if (tx != null) {
IgniteTxEntry e = tx.entry(txKey());
boolean rmvd = e != null && e.op() == DELETE;
return !rmvd;
}
return true;
}
/** {@inheritDoc} */
@Override public final boolean deleted() {
if (!cctx.deferredDelete())
return false;
lockEntry();
try {
return deletedUnlocked();
}
finally {
unlockEntry();
}
}
/** {@inheritDoc} */
@Override public final boolean obsoleteOrDeleted() {
lockEntry();
try {
return obsoleteVersionExtras() != null ||
(cctx.deferredDelete() && (deletedUnlocked() || !hasValueUnlocked()));
}
finally {
unlockEntry();
}
}
/**
* @return {@code True} if deleted.
*/
@SuppressWarnings("SimplifiableIfStatement")
protected final boolean deletedUnlocked() {
assert lock.isHeldByCurrentThread();
if (!cctx.deferredDelete())
return false;
return (flags & IS_DELETED_MASK) != 0;
}
/**
* @param deleted {@code True} if deleted.
*/
protected final void deletedUnlocked(boolean deleted) {
assert lock.isHeldByCurrentThread();
assert cctx.deferredDelete();
if (deleted) {
assert !deletedUnlocked() : this;
flags |= IS_DELETED_MASK;
decrementMapPublicSize();
}
else {
assert deletedUnlocked() : this;
flags &= ~IS_DELETED_MASK;
incrementMapPublicSize();
}
}
/**
* Increments public size of map.
*/
protected void incrementMapPublicSize() {
GridDhtLocalPartition locPart = localPartition();
if (locPart != null)
locPart.incrementPublicSize(null, this);
else
cctx.incrementPublicSize(this);
}
/**
* Decrements public size of map.
*/
protected void decrementMapPublicSize() {
GridDhtLocalPartition locPart = localPartition();
if (locPart != null)
locPart.decrementPublicSize(null, this);
else
cctx.decrementPublicSize(this);
}
/**
* @return MVCC.
*/
@Nullable protected final GridCacheMvcc mvccExtras() {
return extras != null ? extras.mvcc() : null;
}
/**
* @return All MVCC local and non near candidates.
*/
@SuppressWarnings("ForLoopReplaceableByForEach")
@Nullable public final List<GridCacheMvccCandidate> mvccAllLocal() {
lockEntry();
try {
GridCacheMvcc mvcc = extras != null ? extras.mvcc() : null;
if (mvcc == null)
return null;
List<GridCacheMvccCandidate> allLocs = mvcc.allLocal();
if (allLocs == null || allLocs.isEmpty())
return null;
List<GridCacheMvccCandidate> locs = new ArrayList<>(allLocs.size());
for (int i = 0; i < allLocs.size(); i++) {
GridCacheMvccCandidate loc = allLocs.get(i);
if (!loc.nearLocal())
locs.add(loc);
}
return locs.isEmpty() ? null : locs;
}
finally {
unlockEntry();
}
}
/**
* @param mvcc MVCC.
*/
protected final void mvccExtras(@Nullable GridCacheMvcc mvcc) {
extras = (extras != null) ? extras.mvcc(mvcc) : mvcc != null ? new GridCacheMvccEntryExtras(mvcc) : null;
}
/**
* @return Obsolete version.
*/
@Nullable protected final GridCacheVersion obsoleteVersionExtras() {
return extras != null ? extras.obsoleteVersion() : null;
}
/**
* @param obsoleteVer Obsolete version.
* @param ext Extras.
*/
private void obsoleteVersionExtras(@Nullable GridCacheVersion obsoleteVer, GridCacheObsoleteEntryExtras ext) {
extras = (extras != null) ?
extras.obsoleteVersion(obsoleteVer) :
obsoleteVer != null ?
(ext != null) ? ext : new GridCacheObsoleteEntryExtras(obsoleteVer) :
null;
}
/**
* @param prevOwners Previous owners.
* @param owners Current owners.
* @param val Entry value.
*/
protected final void checkOwnerChanged(
@Nullable CacheLockCandidates prevOwners,
@Nullable CacheLockCandidates owners,
CacheObject val
) {
checkOwnerChanged(prevOwners, owners, val, false);
}
/**
* @param prevOwners Previous owners.
* @param owners Current owners.
* @param val Entry value.
* @param inThreadChain {@code True} if called during thread chain checking.
*/
protected final void checkOwnerChanged(
@Nullable CacheLockCandidates prevOwners,
@Nullable CacheLockCandidates owners,
CacheObject val,
boolean inThreadChain
) {
assert !lock.isHeldByCurrentThread();
if (prevOwners != null && owners == null) {
cctx.mvcc().callback().onOwnerChanged(this, null);
if (cctx.events().isRecordable(EVT_CACHE_OBJECT_UNLOCKED)) {
boolean hasVal = hasValue();
GridCacheMvccCandidate cand = prevOwners.candidate(0);
cctx.events().addEvent(partition(),
key,
cand.nodeId(),
cand,
EVT_CACHE_OBJECT_UNLOCKED,
val,
hasVal,
val,
hasVal,
null,
null,
true);
}
}
if (owners != null) {
for (int i = 0; i < owners.size(); i++) {
GridCacheMvccCandidate owner = owners.candidate(i);
boolean locked = prevOwners == null || !prevOwners.hasCandidate(owner.version());
if (locked) {
cctx.mvcc().callback().onOwnerChanged(this, owner);
if (owner.local() && !inThreadChain)
checkThreadChain(owner);
if (cctx.events().isRecordable(EVT_CACHE_OBJECT_LOCKED)) {
boolean hasVal = hasValue();
// Event notification.
cctx.events().addEvent(partition(),
key,
owner.nodeId(),
owner,
EVT_CACHE_OBJECT_LOCKED,
val,
hasVal,
val,
hasVal,
null,
null,
true);
}
}
}
}
}
/**
* @param owner Starting candidate in the chain.
*/
protected abstract void checkThreadChain(GridCacheMvccCandidate owner);
/**
* Updates metrics.
*
* @param op Operation.
* @param metrics Update merics flag.
* @param transformed {@code True} if transform operation caused update.
* @param hasOldVal {@code True} if entry has old value.
*/
private void updateMetrics(GridCacheOperation op, boolean metrics, boolean transformed, boolean hasOldVal) {
if (metrics && cctx.statisticsEnabled()) {
if (op == DELETE) {
cctx.cache().metrics0().onRemove();
if (transformed)
cctx.cache().metrics0().onInvokeRemove(hasOldVal);
}
else if (op == READ && transformed)
cctx.cache().metrics0().onReadOnlyInvoke(hasOldVal);
else {
cctx.cache().metrics0().onWrite();
if (transformed)
cctx.cache().metrics0().onInvokeUpdate(hasOldVal);
}
}
}
/**
* @return TTL.
*/
public long ttlExtras() {
return extras != null ? extras.ttl() : 0;
}
/**
* @return Expire time.
*/
public long expireTimeExtras() {
return extras != null ? extras.expireTime() : 0L;
}
/**
* @param ttl TTL.
* @param expireTime Expire time.
*/
protected void ttlAndExpireTimeExtras(long ttl, long expireTime) {
assert ttl != CU.TTL_NOT_CHANGED && ttl != CU.TTL_ZERO;
extras = (extras != null) ? extras.ttlAndExpireTime(ttl, expireTime) : expireTime != CU.EXPIRE_TIME_ETERNAL ?
new GridCacheTtlEntryExtras(ttl, expireTime) : null;
}
/**
* @return Size of extras object.
*/
private int extrasSize() {
return extras != null ? extras.size() : 0;
}
/** {@inheritDoc} */
@Override public void txUnlock(IgniteInternalTx tx) throws GridCacheEntryRemovedException {
removeLock(tx.xidVersion());
}
/** {@inheritDoc} */
@Override public void onUnlock() {
// No-op.
}
/** {@inheritDoc} */
@Override public void lockEntry() {
lock.lock();
}
/** {@inheritDoc} */
@Override public boolean tryLockEntry(long timeout) {
try {
return lock.tryLock(timeout, TimeUnit.MILLISECONDS);
}
catch (InterruptedException ignite) {
Thread.currentThread().interrupt();
return false;
}
}
/** {@inheritDoc} */
@Override public void unlockEntry() {
lock.unlock();
}
/**
* This method would obtain read lock for continuous query listener setup. This
* is to prevent race condition between entry update and continuous query setup.
* You should make sure you obtain this read lock first before locking the entry
* in order to ensure that the entry update is completed and existing continuous
* query notified before the next cache listener update
*/
private void lockListenerReadLock() {
listenerLock.readLock().lock();
}
/**
* unlock the listener read lock
*
* @see #lockListenerReadLock()
*/
private void unlockListenerReadLock() {
listenerLock.readLock().unlock();
}
/** {@inheritDoc} */
@Override public boolean lockedByCurrentThread() {
return lock.isHeldByCurrentThread();
}
/** {@inheritDoc} */
@Override public void touch() {
context().evicts().touch(this);
}
/** {@inheritDoc} */
@Override public boolean equals(Object o) {
// Identity comparison left on purpose.
return o == this;
}
/** {@inheritDoc} */
@Override public int hashCode() {
return hash;
}
/** {@inheritDoc} */
@Override public String toString() {
return toStringWithTryLock(() -> S.toString(GridCacheMapEntry.class, this));
}
/**
* Does thread safe {@link #toString} for {@link GridCacheMapEntry} classes.
*
* @param dfltToStr {@link #toString()} supplier.
* @return Result of dfltToStr call If lock acquired or a short representation of {@link GridCacheMapEntry}.
*/
protected String toStringWithTryLock(Supplier<String> dfltToStr) {
if (tryLockEntry(ENTRY_LOCK_TIMEOUT)) {
try {
return dfltToStr.get();
}
finally {
unlockEntry();
}
}
else {
String keySens = GridToStringBuilder.includeSensitive() ? ", key=" + key : "";
return "GridCacheMapEntry [err='Partial result represented because entry lock wasn't acquired."
+ " Waiting time elapsed.'"
+ keySens
+ ", hash=" + hash
+ "]";
}
}
/** */
private static class MvccRemoveLockListener implements IgniteInClosure<IgniteInternalFuture> {
/** */
private static final long serialVersionUID = -1578749008606139541L;
/** */
private final IgniteInternalTx tx;
/** */
private final AffinityTopologyVersion topVer;
/** */
private final UUID affNodeId;
/** */
private final MvccSnapshot mvccVer;
/** */
private final boolean needHistory;
/** */
private final GridFutureAdapter<GridCacheUpdateTxResult> resFut;
/** Need previous value flag. */
private final boolean needVal;
/** Filter. */
private final CacheEntryPredicate filter;
/** */
private GridCacheMapEntry entry;
/** */
private IgniteUuid futId;
/** */
private int batchNum;
/** Flag if it is need to return the old value (value before current tx has been started). */
private final boolean needOldVal;
/**
*
* @param tx Transaction.
* @param entry Entry.
* @param affNodeId Node id.
* @param topVer Topology version.
* @param mvccVer Mvcc version.
* @param needHistory Need history flag.
* @param resFut Result future.
* @param needOldVal Flag if it is need to return the old value (value before current tx has been started).
*/
MvccRemoveLockListener(IgniteInternalTx tx,
GridCacheMapEntry entry,
UUID affNodeId,
AffinityTopologyVersion topVer,
MvccSnapshot mvccVer,
boolean needHistory,
GridFutureAdapter<GridCacheUpdateTxResult> resFut,
boolean needOldVal,
boolean retVal,
@Nullable CacheEntryPredicate filter) {
this.tx = tx;
this.entry = entry;
this.topVer = topVer;
this.affNodeId = affNodeId;
this.mvccVer = mvccVer;
this.needHistory = needHistory;
this.resFut = resFut;
this.needOldVal = needOldVal;
this.needVal = retVal;
this.filter = filter;
}
/** {@inheritDoc} */
@Override public void apply(IgniteInternalFuture lockFut) {
WALPointer logPtr = null;
boolean valid;
GridCacheContext cctx = entry.context();
GridCacheVersion newVer = tx.writeVersion();
MvccUpdateResult res;
try {
lockFut.get();
while (true) {
entry.lockEntry();
if (entry.obsoleteVersionExtras() == null)
break;
entry.unlockEntry();
entry = (GridCacheMapEntry)cctx.cache().entryEx(entry.key());
}
valid = entry.valid(tx.topologyVersion());
boolean needOldVal = tx.txState().useMvccCaching(cctx.cacheId());
cctx.shared().database().checkpointReadLock();
try {
res = cctx.offheap().mvccRemove(entry, mvccVer, tx.local(), needHistory, needOldVal, filter, needVal);
}
finally {
cctx.shared().database().checkpointReadUnlock();
}
assert res != null;
if (res.resultType() == ResultType.VERSION_MISMATCH) {
resFut.onDone(serializationError());
return;
}
else if (res.resultType() == ResultType.PREV_NULL) {
resFut.onDone(new GridCacheUpdateTxResult(false));
return;
}
else if (res.resultType() == ResultType.FILTERED) {
GridCacheUpdateTxResult updRes = new GridCacheUpdateTxResult(false);
updRes.filtered(true);
resFut.onDone(updRes);
return;
}
else if (res.resultType() == ResultType.LOCKED) {
entry.unlockEntry();
IgniteInternalFuture<?> lockFuture =
cctx.kernalContext().coordinators().waitForLock(cctx, mvccVer, res.resultVersion());
lockFuture.listen(this);
return;
}
if (cctx.deferredDelete() && entry.deletedUnlocked() && !entry.detached())
entry.deletedUnlocked(false);
if (res.resultType() == ResultType.PREV_NOT_NULL) {
TxCounters counters = tx.txCounters(true);
if (compareIgnoreOpCounter(res.resultVersion(), mvccVer) == 0) {
if (res.isKeyAbsentBefore()) // Do not count own update removal.
counters.decrementUpdateCounter(cctx.cacheId(), entry.partition());
}
else
counters.incrementUpdateCounter(cctx.cacheId(), entry.partition());
counters.accumulateSizeDelta(cctx.cacheId(), entry.partition(), -1);
}
if (cctx.group().logDataRecords())
entry.logMvccUpdate(tx, null, 0, 0, mvccVer);
entry.update(null, 0, 0, newVer, true);
entry.recordNodeId(affNodeId, topVer);
}
catch (IgniteCheckedException e) {
resFut.onDone(e);
return;
}
finally {
if (entry.lockedByCurrentThread()) {
entry.unlockEntry();
cctx.evicts().touch(entry);
}
}
entry.onUpdateFinished(0L);
GridCacheUpdateTxResult updRes = valid ? new GridCacheUpdateTxResult(true, 0L, logPtr)
: new GridCacheUpdateTxResult(false, logPtr);
updRes.mvccHistory(res.history());
if (needOldVal && compareIgnoreOpCounter(res.resultVersion(), mvccVer) != 0 &&
(res.resultType() == ResultType.PREV_NOT_NULL || res.resultType() == ResultType.REMOVED_NOT_NULL))
updRes.oldValue(res.oldValue());
resFut.onDone(updRes);
}
}
/** */
private static class MvccAcquireLockListener implements IgniteInClosure<IgniteInternalFuture> {
/** */
private static final long serialVersionUID = -1578749008606139541L;
/** */
private final IgniteInternalTx tx;
/** */
private final MvccSnapshot mvccVer;
/** */
private final GridFutureAdapter<GridCacheUpdateTxResult> resFut;
/** */
private GridCacheMapEntry entry;
/** */
MvccAcquireLockListener(IgniteInternalTx tx,
GridCacheMapEntry entry,
MvccSnapshot mvccVer,
GridFutureAdapter<GridCacheUpdateTxResult> resFut) {
this.tx = tx;
this.entry = entry;
this.mvccVer = mvccVer;
this.resFut = resFut;
}
/** {@inheritDoc} */
@Override public void apply(IgniteInternalFuture lockFut) {
WALPointer logPtr = null;
boolean valid;
GridCacheContext cctx = entry.context();
try {
lockFut.get();
while (true) {
entry.lockEntry();
if (entry.obsoleteVersionExtras() == null)
break;
entry.unlockEntry();
entry = (GridCacheMapEntry)cctx.cache().entryEx(entry.key());
}
valid = entry.valid(tx.topologyVersion());
cctx.shared().database().checkpointReadLock();
MvccUpdateResult res;
try {
res = cctx.offheap().mvccLock(entry, mvccVer);
}
finally {
cctx.shared().database().checkpointReadUnlock();
}
assert res != null;
if (res.resultType() == ResultType.VERSION_MISMATCH) {
resFut.onDone(serializationError());
return;
}
else if (res.resultType() == ResultType.LOCKED) {
entry.unlockEntry();
cctx.kernalContext().coordinators().waitForLock(cctx, mvccVer, res.resultVersion()).listen(this);
return;
}
}
catch (IgniteCheckedException e) {
resFut.onDone(e);
return;
}
finally {
if (entry.lockedByCurrentThread()) {
entry.unlockEntry();
cctx.evicts().touch(entry);
}
}
entry.onUpdateFinished(0L);
resFut.onDone(new GridCacheUpdateTxResult(valid, logPtr));
}
}
/** */
private static class MvccUpdateLockListener implements IgniteInClosure<IgniteInternalFuture> {
/** */
private static final long serialVersionUID = 8452738214760268397L;
/** */
private final IgniteInternalTx tx;
/** */
private final UUID affNodeId;
/** */
private final AffinityTopologyVersion topVer;
/** */
private final CacheObject val;
/** */
private final long ttl;
/** */
private final MvccSnapshot mvccVer;
/** */
private final GridFutureAdapter<GridCacheUpdateTxResult> resFut;
/** */
private GridCacheMapEntry entry;
/** */
private GridCacheOperation op;
/** */
private final boolean keepBinary;
/** Entry processor. */
private final EntryProcessor entryProc;
/** Invoke arguments. */
private final Object[] invokeArgs;
/** Filter. */
private final CacheEntryPredicate filter;
/** */
private final boolean needHistory;
/** */
private final boolean noCreate;
/** Need previous value flag.*/
private final boolean needVal;
/** Flag if it is need to return the old value (value before current tx has been started). */
private boolean needOldVal;
/** */
MvccUpdateLockListener(IgniteInternalTx tx,
GridCacheMapEntry entry,
UUID affNodeId,
AffinityTopologyVersion topVer,
CacheObject val,
long ttl,
MvccSnapshot mvccVer,
GridCacheOperation op,
boolean needHistory,
boolean noCreate,
GridFutureAdapter<GridCacheUpdateTxResult> resFut,
boolean needOldVal,
CacheEntryPredicate filter,
boolean needVal,
boolean keepBinary,
EntryProcessor entryProc,
Object[] invokeArgs) {
this.tx = tx;
this.entry = entry;
this.affNodeId = affNodeId;
this.topVer = topVer;
this.val = val;
this.ttl = ttl;
this.mvccVer = mvccVer;
this.op = op;
this.needHistory = needHistory;
this.noCreate = noCreate;
this.filter = filter;
this.needVal = needVal;
this.resFut = resFut;
this.needOldVal = needOldVal;
this.keepBinary = keepBinary;
this.entryProc = entryProc;
this.invokeArgs = invokeArgs;
}
/** {@inheritDoc} */
@Override public void apply(IgniteInternalFuture lockFut) {
WALPointer logPtr = null;
boolean valid;
GridCacheContext cctx = entry.context();
GridCacheVersion newVer = tx.writeVersion();
final boolean invoke = entryProc != null;
MvccUpdateResult res;
try {
lockFut.get();
entry.ensureFreeSpace();
while (true) {
entry.lockEntry();
if (entry.obsoleteVersionExtras() == null)
break;
entry.unlockEntry();
entry = (GridCacheMapEntry)cctx.cache().entryEx(entry.key());
}
valid = entry.valid(tx.topologyVersion());
// Determine new ttl and expire time.
long expireTime, ttl = this.ttl;
if (ttl == -1L) {
ttl = entry.ttlExtras();
expireTime = entry.expireTimeExtras();
}
else
expireTime = CU.toExpireTime(ttl);
assert ttl >= 0 : ttl;
assert expireTime >= 0 : expireTime;
cctx.shared().database().checkpointReadLock();
try {
res = cctx.offheap().mvccUpdate(entry, val, newVer, expireTime, mvccVer, tx.local(), needHistory,
noCreate, needOldVal, filter, needVal, keepBinary, entryProc, invokeArgs);
}
finally {
cctx.shared().database().checkpointReadUnlock();
}
assert res != null;
if (res.resultType() == ResultType.VERSION_MISMATCH) {
resFut.onDone(serializationError());
return;
}
else if (res.resultType() == ResultType.LOCKED) {
entry.unlockEntry();
cctx.kernalContext().coordinators().waitForLock(cctx, mvccVer, res.resultVersion()).listen(this);
return;
}
else if (res.resultType() == ResultType.FILTERED) {
GridCacheUpdateTxResult updRes = new GridCacheUpdateTxResult(invoke);
if (invoke) { // No-op invoke happened.
assert res.invokeResult() != null;
updRes.invokeResult(res.invokeResult());
}
updRes.filtered(true);
if (needVal)
updRes.prevValue(res.oldValue());
resFut.onDone(updRes);
return;
}
else if (op == CREATE && tx.local() && (res.resultType() == ResultType.PREV_NOT_NULL ||
res.resultType() == ResultType.VERSION_FOUND)) {
resFut.onDone(new IgniteTxDuplicateKeyCheckedException("Duplicate key during INSERT [key=" + entry.key() + ']'));
return;
}
if (cctx.deferredDelete() && entry.deletedUnlocked() && !entry.detached())
entry.deletedUnlocked(false);
if (res.resultType() == ResultType.PREV_NULL) {
TxCounters counters = tx.txCounters(true);
if (compareIgnoreOpCounter(res.resultVersion(), mvccVer) == 0) {
if (res.isKeyAbsentBefore())
counters.incrementUpdateCounter(cctx.cacheId(), entry.partition());
}
else
counters.incrementUpdateCounter(cctx.cacheId(), entry.partition());
counters.accumulateSizeDelta(cctx.cacheId(), entry.partition(), 1);
}
else if (res.resultType() == ResultType.PREV_NOT_NULL && compareIgnoreOpCounter(res.resultVersion(), mvccVer) != 0) {
TxCounters counters = tx.txCounters(true);
counters.incrementUpdateCounter(cctx.cacheId(), entry.partition());
}
else if (res.resultType() == ResultType.REMOVED_NOT_NULL) {
TxCounters counters = tx.txCounters(true);
if (compareIgnoreOpCounter(res.resultVersion(), mvccVer) == 0) {
if (res.isKeyAbsentBefore()) // Do not count own update removal.
counters.decrementUpdateCounter(cctx.cacheId(), entry.partition());
}
else
counters.incrementUpdateCounter(cctx.cacheId(), entry.partition());
counters.accumulateSizeDelta(cctx.cacheId(), entry.partition(), -1);
}
if (cctx.group().logDataRecords())
logPtr = cctx.group().wal().log(new MvccDataRecord(new MvccDataEntry(
cctx.cacheId(),
entry.key(),
val,
res.resultType() == ResultType.PREV_NULL ? CREATE :
(res.resultType() == ResultType.REMOVED_NOT_NULL) ? DELETE : UPDATE,
tx.nearXidVersion(),
newVer,
expireTime,
entry.key().partition(),
0L,
mvccVer)));
entry.update(val, expireTime, ttl, newVer, true);
entry.recordNodeId(affNodeId, topVer);
}
catch (IgniteCheckedException e) {
resFut.onDone(e);
return;
}
finally {
if (entry.lockedByCurrentThread()) {
entry.unlockEntry();
cctx.evicts().touch(entry);
}
}
entry.onUpdateFinished(0L);
GridCacheUpdateTxResult updRes = valid ? new GridCacheUpdateTxResult(true, 0L, logPtr)
: new GridCacheUpdateTxResult(false, logPtr);
if (invoke) {
assert res.invokeResult() != null;
updRes.invokeResult(res.invokeResult());
}
updRes.newValue(res.newValue());
if (needOldVal && compareIgnoreOpCounter(res.resultVersion(), mvccVer) != 0 &&
(res.resultType() == ResultType.PREV_NOT_NULL || res.resultType() == ResultType.REMOVED_NOT_NULL))
updRes.oldValue(res.oldValue());
updRes.mvccHistory(res.history());
resFut.onDone(updRes);
}
}
/**
*
*/
private class LazyValueEntry<K, V> implements Cache.Entry<K, V> {
/** */
private final KeyCacheObject key;
/** */
private boolean keepBinary;
/**
* @param key Key.
* @param keepBinary Keep binary flag.
*/
private LazyValueEntry(KeyCacheObject key, boolean keepBinary) {
this.key = key;
this.keepBinary = keepBinary;
}
/** {@inheritDoc} */
@Override public K getKey() {
return (K)cctx.cacheObjectContext().unwrapBinaryIfNeeded(key, keepBinary, true, null);
}
/** {@inheritDoc} */
@Override public V getValue() {
return (V)cctx.cacheObjectContext().unwrapBinaryIfNeeded(peekVisibleValue(), keepBinary, true, null);
}
/** {@inheritDoc} */
@Override public <T> T unwrap(Class<T> cls) {
if (cls.isAssignableFrom(IgniteCache.class))
return (T)cctx.grid().cache(cctx.name());
if (cls.isAssignableFrom(getClass()))
return (T)this;
if (cls.isAssignableFrom(EvictableEntry.class))
return (T)wrapEviction();
if (cls.isAssignableFrom(CacheEntryImplEx.class))
return cls == CacheEntryImplEx.class ? (T)wrapVersioned() : (T)wrapVersionedWithValue();
if (cls.isAssignableFrom(GridCacheVersion.class))
return (T)ver;
if (cls.isAssignableFrom(GridCacheMapEntry.this.getClass()))
return (T)GridCacheMapEntry.this;
throw new IllegalArgumentException("Unwrapping to class is not supported: " + cls);
}
/** {@inheritDoc} */
@Override public String toString() {
return "IteratorEntry [key=" + key + ']';
}
}
/**
*
*/
private static class UpdateClosure implements IgniteCacheOffheapManager.OffheapInvokeClosure {
/** */
private final GridCacheMapEntry entry;
/** */
@Nullable private final CacheObject val;
/** */
private final GridCacheVersion ver;
/** */
private final long expireTime;
/** */
@Nullable private final IgnitePredicate<CacheDataRow> predicate;
/** */
private CacheDataRow newRow;
/** */
private CacheDataRow oldRow;
/** */
private boolean oldRowExpiredFlag;
/** */
private IgniteTree.OperationType treeOp = IgniteTree.OperationType.PUT;
/**
* @param entry Entry.
* @param val New value.
* @param ver New version.
* @param expireTime New expire time.
* @param predicate Optional predicate.
*/
UpdateClosure(GridCacheMapEntry entry, @Nullable CacheObject val, GridCacheVersion ver, long expireTime,
@Nullable IgnitePredicate<CacheDataRow> predicate, @Nullable CacheDataRow newRow) {
this.entry = entry;
this.val = val;
this.ver = ver;
this.expireTime = expireTime;
this.predicate = predicate;
this.newRow = newRow;
}
/** {@inheritDoc} */
@Override public void call(@Nullable CacheDataRow oldRow) throws IgniteCheckedException {
if (oldRow != null) {
oldRow.key(entry.key);
oldRow = checkRowExpired(oldRow);
}
this.oldRow = oldRow;
if (predicate != null && !predicate.apply(oldRow)) {
treeOp = IgniteTree.OperationType.NOOP;
return;
}
if (val != null) {
if (newRow == null) {
newRow = entry.cctx.offheap().dataStore(entry.localPartition()).createRow(
entry.cctx,
entry.key,
val,
ver,
expireTime,
oldRow);
}
treeOp = oldRow != null && oldRow.link() == newRow.link() ?
IgniteTree.OperationType.IN_PLACE : IgniteTree.OperationType.PUT;
}
else
treeOp = oldRow != null ? IgniteTree.OperationType.REMOVE : IgniteTree.OperationType.NOOP;
}
/** {@inheritDoc} */
@Override public CacheDataRow newRow() {
return newRow;
}
/** {@inheritDoc} */
@Override public IgniteTree.OperationType operationType() {
return treeOp;
}
/** {@inheritDoc} */
@Nullable @Override public CacheDataRow oldRow() {
return oldRow;
}
/** {@inheritDoc} */
@Override public boolean oldRowExpiredFlag() {
return oldRowExpiredFlag;
}
/**
* Checks row for expiration and fire expire events if needed.
*
* @param row old row.
* @return {@code Null} if row was expired, row itself otherwise.
* @throws IgniteCheckedException
*/
private CacheDataRow checkRowExpired(CacheDataRow row) throws IgniteCheckedException {
assert row != null;
if (!(row.expireTime() > 0 && row.expireTime() <= U.currentTimeMillis()))
return row;
GridCacheContext cctx = entry.context();
CacheObject expiredVal = row.value();
if (cctx.deferredDelete() && !entry.detached() && !entry.isInternal()) {
entry.update(null, CU.TTL_ETERNAL, CU.EXPIRE_TIME_ETERNAL, entry.ver, true);
if (!entry.deletedUnlocked() && !entry.isStartVersion())
entry.deletedUnlocked(true);
}
else
entry.markObsolete0(cctx.cache().nextVersion(), true, null);
if (cctx.events().isRecordable(EVT_CACHE_OBJECT_EXPIRED)) {
cctx.events().addEvent(entry.partition(),
entry.key(),
cctx.localNodeId(),
null,
EVT_CACHE_OBJECT_EXPIRED,
null,
false,
expiredVal,
expiredVal != null,
null,
null,
true);
}
cctx.continuousQueries().onEntryExpired(entry, entry.key(), expiredVal);
entry.updatePlatformCache(null, null);
oldRowExpiredFlag = true;
return null;
}
}
/**
*
*/
private static class AtomicCacheUpdateClosure implements IgniteCacheOffheapManager.OffheapInvokeClosure {
/** */
private final GridCacheMapEntry entry;
/** */
private final AffinityTopologyVersion topVer;
/** */
private GridCacheVersion newVer;
/** */
private GridCacheOperation op;
/** */
private Object writeObj;
/** */
private Object[] invokeArgs;
/** */
private final boolean readThrough;
/** */
private final boolean writeThrough;
/** */
private final boolean keepBinary;
/** */
private final IgniteCacheExpiryPolicy expiryPlc;
/** */
private final boolean primary;
/** */
private final boolean verCheck;
/** */
private final boolean readRepairRecovery;
/** */
private final CacheEntryPredicate[] filter;
/** */
private final long explicitTtl;
/** */
private final long explicitExpireTime;
/** */
private GridCacheVersion conflictVer;
/** */
private final boolean conflictResolve;
/** */
private final boolean intercept;
/** */
private final Long updateCntr;
/** */
private final boolean skipInterceptorOnConflict;
/** */
private GridCacheUpdateAtomicResult updateRes;
/** */
private IgniteTree.OperationType treeOp;
/** */
private CacheDataRow newRow;
/** */
private CacheDataRow oldRow;
/** OldRow expiration flag. */
private boolean oldRowExpiredFlag;
/** Disable interceptor invocation onAfter* methods flag. */
private boolean wasIntercepted;
/** */
AtomicCacheUpdateClosure(
GridCacheMapEntry entry,
AffinityTopologyVersion topVer,
GridCacheVersion newVer,
GridCacheOperation op,
Object writeObj,
Object[] invokeArgs,
boolean readThrough,
boolean writeThrough,
boolean keepBinary,
@Nullable IgniteCacheExpiryPolicy expiryPlc,
boolean primary,
boolean verCheck,
boolean readRepairRecovery,
@Nullable CacheEntryPredicate[] filter,
long explicitTtl,
long explicitExpireTime,
@Nullable GridCacheVersion conflictVer,
boolean conflictResolve,
boolean intercept,
@Nullable Long updateCntr,
boolean skipInterceptorOnConflict) {
assert op == UPDATE || op == DELETE || op == TRANSFORM : op;
this.entry = entry;
this.topVer = topVer;
this.newVer = newVer;
this.op = op;
this.writeObj = writeObj;
this.invokeArgs = invokeArgs;
this.readThrough = readThrough;
this.writeThrough = writeThrough;
this.keepBinary = keepBinary;
this.expiryPlc = expiryPlc;
this.primary = primary;
this.verCheck = verCheck;
this.readRepairRecovery = readRepairRecovery;
this.filter = filter;
this.explicitTtl = explicitTtl;
this.explicitExpireTime = explicitExpireTime;
this.conflictVer = conflictVer;
this.conflictResolve = conflictResolve;
this.intercept = intercept;
this.updateCntr = updateCntr;
this.skipInterceptorOnConflict = skipInterceptorOnConflict;
switch (op) {
case UPDATE:
treeOp = IgniteTree.OperationType.PUT;
break;
case DELETE:
treeOp = IgniteTree.OperationType.REMOVE;
break;
}
}
/** {@inheritDoc} */
@Nullable @Override public CacheDataRow oldRow() {
return oldRow;
}
/** {@inheritDoc} */
@Override public boolean oldRowExpiredFlag() {
return oldRowExpiredFlag;
}
/** {@inheritDoc} */
@Override public CacheDataRow newRow() {
return newRow;
}
/** {@inheritDoc} */
@Override public IgniteTree.OperationType operationType() {
return treeOp;
}
/** {@inheritDoc} */
@Override public void call(@Nullable CacheDataRow oldRow) throws IgniteCheckedException {
assert entry.isNear() || oldRow == null || oldRow.link() != 0 : oldRow;
GridCacheContext cctx = entry.context();
CacheObject oldVal;
CacheObject storeLoadedVal = null;
this.oldRow = oldRow;
if (oldRow != null) {
oldRow.key(entry.key());
// unswap
entry.update(oldRow.value(), oldRow.expireTime(), 0, oldRow.version(), false);
if (checkRowExpired(oldRow)) {
oldRowExpiredFlag = true;
oldRow = null;
}
}
oldVal = (oldRow != null) ? oldRow.value() : null;
if (oldVal == null && readThrough) {
storeLoadedVal = cctx.toCacheObject(cctx.store().load(null, entry.key));
if (storeLoadedVal != null) {
oldVal = cctx.kernalContext().cacheObjects().prepareForCache(storeLoadedVal, cctx);
entry.val = oldVal;
if (entry.deletedUnlocked())
entry.deletedUnlocked(false);
}
}
else if (oldVal != null && entry.deletedUnlocked())
entry.deletedUnlocked(false);
CacheInvokeEntry<Object, Object> invokeEntry = null;
IgniteBiTuple<Object, Exception> invokeRes = null;
boolean invoke = op == TRANSFORM;
boolean transformed = false;
if (invoke) {
invokeEntry = new CacheInvokeEntry<>(entry.key, oldVal, entry.ver, keepBinary, entry);
invokeRes = runEntryProcessor(invokeEntry);
op = writeObj == null ? DELETE : UPDATE;
transformed = true;
}
CacheObject newVal = (CacheObject)writeObj;
GridCacheVersionConflictContext<?, ?> conflictCtx = null;
if (conflictResolve) {
conflictCtx = resolveConflict(newVal, invokeRes);
if (updateRes != null) {
assert conflictCtx != null && conflictCtx.isUseOld() : conflictCtx;
assert treeOp == IgniteTree.OperationType.NOOP : treeOp;
return;
}
}
if (conflictCtx == null && !readRepairRecovery) {
// Perform version check only in case there was no explicit conflict resolution.
versionCheck(invokeRes);
if (updateRes != null) {
assert treeOp == IgniteTree.OperationType.NOOP : treeOp;
return;
}
}
if (!F.isEmptyOrNulls(filter)) {
boolean pass = cctx.isAllLocked(entry, filter);
if (!pass) {
initResultOnCancelUpdate(storeLoadedVal, !cctx.putIfAbsentFilter(filter));
updateRes = new GridCacheUpdateAtomicResult(UpdateOutcome.FILTER_FAILED,
oldVal,
null,
invokeRes,
CU.TTL_ETERNAL,
CU.EXPIRE_TIME_ETERNAL,
null,
null,
0,
false);
return;
}
}
if (invoke) {
if (!invokeEntry.modified()) {
initResultOnCancelUpdate(storeLoadedVal, true);
updateRes = new GridCacheUpdateAtomicResult(UpdateOutcome.INVOKE_NO_OP,
oldVal,
null,
invokeRes,
CU.TTL_ETERNAL,
CU.EXPIRE_TIME_ETERNAL,
null,
null,
0,
true);
return;
}
else if ((invokeRes == null || invokeRes.getValue() == null) && writeObj != null) {
try {
cctx.validateKeyAndValue(entry.key, (CacheObject)writeObj);
}
catch (Exception e) {
initResultOnCancelUpdate(null, true);
updateRes = new GridCacheUpdateAtomicResult(UpdateOutcome.INVOKE_NO_OP,
oldVal,
null,
new IgniteBiTuple<>(null, e),
CU.TTL_ETERNAL,
CU.EXPIRE_TIME_ETERNAL,
null,
null,
0,
false);
return;
}
}
op = writeObj == null ? DELETE : UPDATE;
}
// Incorporate conflict version into new version if needed.
newVer = addConflictVersion(newVer, conflictVer);
if (op == UPDATE) {
assert writeObj != null;
update(conflictCtx, invokeRes, storeLoadedVal != null, transformed);
}
else {
assert op == DELETE && writeObj == null : op;
remove(conflictCtx, invokeRes, storeLoadedVal != null, transformed);
}
assert updateRes != null && treeOp != null;
}
/**
* Check row expiration and fire expire events if needed.
*
* @param row Old row.
* @return {@code True} if row was expired, {@code False} otherwise.
* @throws IgniteCheckedException if failed.
*/
private boolean checkRowExpired(CacheDataRow row) throws IgniteCheckedException {
assert row != null;
if (!(row.expireTime() > 0 && row.expireTime() <= U.currentTimeMillis()))
return false;
GridCacheContext cctx = entry.context();
CacheObject expiredVal = row.value();
if (cctx.deferredDelete() && !entry.detached() && !entry.isInternal()) {
entry.update(null, CU.TTL_ETERNAL, CU.EXPIRE_TIME_ETERNAL, entry.ver, true);
if (!entry.deletedUnlocked())
entry.deletedUnlocked(true);
}
else
entry.markObsolete0(cctx.cache().nextVersion(), true, null);
if (cctx.events().isRecordable(EVT_CACHE_OBJECT_EXPIRED)) {
cctx.events().addEvent(entry.partition(),
entry.key(),
cctx.localNodeId(),
null,
EVT_CACHE_OBJECT_EXPIRED,
null,
false,
expiredVal,
expiredVal != null,
null,
null,
true);
}
cctx.continuousQueries().onEntryExpired(entry, entry.key(), expiredVal);
entry.updatePlatformCache(null, null);
return true;
}
/**
* @param storeLoadedVal Value loaded from store.
* @param updateExpireTime {@code True} if need update expire time.
* @throws IgniteCheckedException If failed.
*/
private void initResultOnCancelUpdate(@Nullable CacheObject storeLoadedVal, boolean updateExpireTime)
throws IgniteCheckedException {
boolean needUpdate = false;
if (storeLoadedVal != null) {
long initTtl;
long initExpireTime;
if (expiryPlc != null) {
IgniteBiTuple<Long, Long> initTtlAndExpireTime = initialTtlAndExpireTime(expiryPlc);
initTtl = initTtlAndExpireTime.get1();
initExpireTime = initTtlAndExpireTime.get2();
}
else {
initTtl = CU.TTL_ETERNAL;
initExpireTime = CU.EXPIRE_TIME_ETERNAL;
}
entry.update(storeLoadedVal, initExpireTime, initTtl, entry.ver, true);
needUpdate = true;
}
else if (updateExpireTime && expiryPlc != null && entry.val != null) {
long ttl = expiryPlc.forAccess();
if (ttl != CU.TTL_NOT_CHANGED) {
long expireTime;
if (ttl == CU.TTL_ZERO) {
ttl = CU.TTL_MINIMUM;
expireTime = CU.expireTimeInPast();
}
else
expireTime = CU.toExpireTime(ttl);
if (entry.expireTimeExtras() != expireTime) {
entry.update(entry.val, expireTime, ttl, entry.ver, true);
expiryPlc.ttlUpdated(entry.key, entry.ver, null);
needUpdate = true;
storeLoadedVal = entry.val;
}
}
}
if (needUpdate) {
newRow = entry.localPartition().dataStore().createRow(
entry.cctx,
entry.key,
storeLoadedVal,
newVer,
entry.expireTimeExtras(),
oldRow);
treeOp = IgniteTree.OperationType.PUT;
}
else
treeOp = IgniteTree.OperationType.NOOP;
}
/**
* @param conflictCtx Conflict context.
* @param invokeRes Entry processor result (for invoke operation).
* @param readFromStore {@code True} if initial entry value was {@code null} and it was read from store.
* @param transformed {@code True} if update caused by transformation operation.
* @throws IgniteCheckedException If failed.
*/
private void update(@Nullable GridCacheVersionConflictContext<?, ?> conflictCtx,
@Nullable IgniteBiTuple<Object, Exception> invokeRes,
boolean readFromStore,
boolean transformed)
throws IgniteCheckedException {
GridCacheContext cctx = entry.context();
final CacheObject oldVal = entry.val;
CacheObject updated = (CacheObject)writeObj;
long newSysTtl;
long newSysExpireTime;
long newTtl;
long newExpireTime;
// Conflict context is null if there were no explicit conflict resolution.
if (conflictCtx == null) {
// Calculate TTL and expire time for local update.
if (explicitTtl != CU.TTL_NOT_CHANGED) {
// If conflict existed, expire time must be explicit.
assert conflictVer == null || explicitExpireTime != CU.EXPIRE_TIME_CALCULATE;
newSysTtl = newTtl = explicitTtl;
newSysExpireTime = explicitExpireTime;
newExpireTime = explicitExpireTime != CU.EXPIRE_TIME_CALCULATE ?
explicitExpireTime : CU.toExpireTime(explicitTtl);
}
else {
newSysTtl = expiryPlc == null ? CU.TTL_NOT_CHANGED :
entry.val != null ? expiryPlc.forUpdate() : expiryPlc.forCreate();
if (newSysTtl == CU.TTL_NOT_CHANGED) {
newSysExpireTime = CU.EXPIRE_TIME_CALCULATE;
newTtl = entry.ttlExtras();
newExpireTime = entry.expireTimeExtras();
}
else if (newSysTtl == CU.TTL_ZERO) {
op = DELETE;
writeObj = null;
remove(conflictCtx, invokeRes, readFromStore, false);
return;
}
else {
newSysExpireTime = CU.EXPIRE_TIME_CALCULATE;
newTtl = newSysTtl;
newExpireTime = CU.toExpireTime(newTtl);
}
}
}
else {
newSysTtl = newTtl = conflictCtx.ttl();
newSysExpireTime = newExpireTime = conflictCtx.expireTime();
}
if (intercept && (conflictVer == null || !skipInterceptorOnConflict)) {
Object updated0 = cctx.unwrapBinaryIfNeeded(updated, keepBinary, false, null);
CacheLazyEntry<Object, Object> interceptEntry =
new CacheLazyEntry<>(cctx, entry.key, null, oldVal, null, keepBinary);
Object interceptorVal = null;
try {
interceptorVal = cctx.config().getInterceptor().onBeforePut(interceptEntry, updated0);
}
catch (Throwable e) {
throw new IgniteCheckedException(e);
}
wasIntercepted = true;
if (interceptorVal == null) {
treeOp = IgniteTree.OperationType.NOOP;
updateRes = new GridCacheUpdateAtomicResult(UpdateOutcome.INTERCEPTOR_CANCEL,
oldVal,
null,
invokeRes,
CU.TTL_ETERNAL,
CU.EXPIRE_TIME_ETERNAL,
null,
null,
0,
false);
return;
}
else if (interceptorVal != updated0) {
updated0 = cctx.unwrapTemporary(interceptorVal);
updated = cctx.toCacheObject(updated0);
}
}
updated = cctx.kernalContext().cacheObjects().prepareForCache(updated, cctx);
if (writeThrough)
// Must persist inside synchronization in non-tx mode.
cctx.store().put(null, entry.key, updated, newVer);
if (entry.val == null) {
boolean new0 = entry.isStartVersion();
assert entry.deletedUnlocked() || new0 || entry.isInternal() : "Invalid entry [entry=" + entry +
", locNodeId=" + cctx.localNodeId() + ']';
if (!new0 && !entry.isInternal())
entry.deletedUnlocked(false);
}
else {
assert !entry.deletedUnlocked() : "Invalid entry [entry=" + this +
", locNodeId=" + cctx.localNodeId() + ']';
}
long updateCntr0 = entry.nextPartitionCounter(topVer, primary, false, updateCntr);
entry.logUpdate(op, updated, newVer, newExpireTime, updateCntr0, primary);
if (!entry.isNear()) {
newRow = entry.localPartition().dataStore().createRow(
entry.cctx,
entry.key,
updated,
newVer,
newExpireTime,
oldRow);
treeOp = oldRow != null && oldRow.link() == newRow.link() ?
IgniteTree.OperationType.NOOP : IgniteTree.OperationType.PUT;
}
else
treeOp = IgniteTree.OperationType.PUT;
entry.update(updated, newExpireTime, newTtl, newVer, true);
if (entry.isNear()) {
boolean updatedDht = ((GridNearCacheEntry)entry).recordDhtVersion(newVer);
assert updatedDht : this;
}
updateRes = new GridCacheUpdateAtomicResult(UpdateOutcome.SUCCESS,
oldVal,
updated,
invokeRes,
newSysTtl,
newSysExpireTime,
null,
conflictCtx,
updateCntr0,
transformed);
}
/**
* @param conflictCtx Conflict context.
* @param invokeRes Entry processor result (for invoke operation).
* @param readFromStore {@code True} if initial entry value was {@code null} and it was read from store.
* @param transformed {@code True} if remove caused by tranformation operation.
* @throws IgniteCheckedException If failed.
*/
@SuppressWarnings("unchecked")
private void remove(@Nullable GridCacheVersionConflictContext<?, ?> conflictCtx,
@Nullable IgniteBiTuple<Object, Exception> invokeRes,
boolean readFromStore,
boolean transformed)
throws IgniteCheckedException {
GridCacheContext cctx = entry.context();
CacheObject oldVal = entry.val;
IgniteBiTuple<Boolean, Object> interceptRes = null;
if (intercept && (conflictVer == null || !skipInterceptorOnConflict)) {
CacheLazyEntry<Object, Object> intercepEntry =
new CacheLazyEntry<>(cctx, entry.key, null, oldVal, null, keepBinary);
interceptRes = cctx.config().getInterceptor().onBeforeRemove(intercepEntry);
wasIntercepted = true;
if (cctx.cancelRemove(interceptRes)) {
treeOp = IgniteTree.OperationType.NOOP;
updateRes = new GridCacheUpdateAtomicResult(UpdateOutcome.INTERCEPTOR_CANCEL,
cctx.toCacheObject(cctx.unwrapTemporary(interceptRes.get2())),
null,
invokeRes,
CU.TTL_ETERNAL,
CU.EXPIRE_TIME_ETERNAL,
null,
null,
0,
false);
return;
}
}
if (writeThrough)
// Must persist inside synchronization in non-tx mode.
cctx.store().remove(null, entry.key);
long updateCntr0 = entry.nextPartitionCounter(topVer, primary, false, updateCntr);
entry.logUpdate(op, null, newVer, 0, updateCntr0, primary);
if (oldVal != null) {
assert !entry.deletedUnlocked();
if (!entry.isInternal())
entry.deletedUnlocked(true);
}
else {
boolean new0 = entry.isStartVersion();
assert entry.deletedUnlocked() || new0 || entry.isInternal() : "Invalid entry [entry=" + this +
", locNodeId=" + cctx.localNodeId() + ']';
if (new0) {
if (!entry.isInternal())
entry.deletedUnlocked(true);
}
}
GridCacheVersion enqueueVer = newVer;
entry.update(null, CU.TTL_ETERNAL, CU.EXPIRE_TIME_ETERNAL, newVer, true);
treeOp = (oldRow == null || readFromStore) ? IgniteTree.OperationType.NOOP :
IgniteTree.OperationType.REMOVE;
UpdateOutcome outcome = oldVal != null ? UpdateOutcome.SUCCESS : UpdateOutcome.REMOVE_NO_VAL;
if (interceptRes != null)
oldVal = cctx.toCacheObject(cctx.unwrapTemporary(interceptRes.get2()));
updateRes = new GridCacheUpdateAtomicResult(outcome,
oldVal,
null,
invokeRes,
CU.TTL_NOT_CHANGED,
CU.EXPIRE_TIME_CALCULATE,
enqueueVer,
conflictCtx,
updateCntr0,
transformed);
}
/**
* @param newVal New entry value.
* @param invokeRes Entry processor result (for invoke operation).
* @return Conflict context.
* @throws IgniteCheckedException If failed.
*/
private GridCacheVersionConflictContext<?, ?> resolveConflict(
CacheObject newVal,
@Nullable IgniteBiTuple<Object, Exception> invokeRes)
throws IgniteCheckedException {
GridCacheContext cctx = entry.context();
// Cache is conflict-enabled.
if (cctx.conflictNeedResolve()) {
GridCacheVersion oldConflictVer = entry.ver.conflictVersion();
// Prepare old and new entries for conflict resolution.
GridCacheVersionedEntryEx oldEntry = new GridCacheLazyPlainVersionedEntry<>(cctx,
entry.key,
entry.val,
entry.ttlExtras(),
entry.expireTimeExtras(),
entry.ver.conflictVersion(),
entry.isStartVersion(),
keepBinary);
GridTuple3<Long, Long, Boolean> expiration = entry.ttlAndExpireTime(expiryPlc,
explicitTtl,
explicitExpireTime);
GridCacheVersionedEntryEx newEntry = new GridCacheLazyPlainVersionedEntry<>(
cctx,
entry.key,
newVal,
expiration.get1(),
expiration.get2(),
conflictVer != null ? conflictVer : newVer,
keepBinary);
// Resolve conflict.
GridCacheVersionConflictContext<?, ?> conflictCtx = cctx.conflictResolve(oldEntry, newEntry, verCheck);
assert conflictCtx != null;
// Use old value?
if (conflictCtx.isUseOld()) {
GridCacheVersion newConflictVer = conflictVer != null ? conflictVer : newVer;
// Handle special case with atomic comparator.
if (!entry.isStartVersion() && // Not initial value,
verCheck && // and atomic version check,
oldConflictVer.dataCenterId() == newConflictVer.dataCenterId() && // and data centers are equal,
ATOMIC_VER_COMPARATOR.compare(oldConflictVer, newConflictVer) == 0 && // and both versions are equal,
cctx.writeThrough() && // and store is enabled,
primary) { // and we are primary.
CacheObject val = entry.val;
if (val == null) {
assert entry.deletedUnlocked();
cctx.store().remove(null, entry.key);
}
else
cctx.store().put(null, entry.key, val, entry.ver);
}
treeOp = IgniteTree.OperationType.NOOP;
updateRes = new GridCacheUpdateAtomicResult(UpdateOutcome.CONFLICT_USE_OLD,
entry.val,
null,
invokeRes,
CU.TTL_ETERNAL,
CU.EXPIRE_TIME_ETERNAL,
null,
null,
0,
false);
}
// Will update something.
else {
// Merge is a local update which override passed value bytes.
if (conflictCtx.isMerge()) {
writeObj = cctx.toCacheObject(conflictCtx.mergeValue());
conflictVer = null;
}
else
assert conflictCtx.isUseNew();
// Update value is known at this point, so update operation type.
op = writeObj != null ? UPDATE : DELETE;
}
return conflictCtx;
}
else
// Nullify conflict version on this update, so that we will use regular version during next updates.
conflictVer = null;
return null;
}
/**
* @param invokeRes Entry processor result (for invoke operation).
* @throws IgniteCheckedException If failed.
*/
private void versionCheck(@Nullable IgniteBiTuple<Object, Exception> invokeRes) throws IgniteCheckedException {
GridCacheContext cctx = entry.context();
if (verCheck) {
if (!entry.isStartVersion() && ATOMIC_VER_COMPARATOR.compare(entry.ver, newVer) >= 0) {
if (ATOMIC_VER_COMPARATOR.compare(entry.ver, newVer) == 0 && cctx.writeThrough() && primary) {
if (log.isDebugEnabled())
log.debug("Received entry update with same version as current (will update store) " +
"[entry=" + this + ", newVer=" + newVer + ']');
CacheObject val = entry.val;
if (val == null) {
assert entry.deletedUnlocked();
cctx.store().remove(null, entry.key);
}
else
cctx.store().put(null, entry.key, val, entry.ver);
}
else {
if (log.isDebugEnabled())
log.debug("Received entry update with smaller version than current (will ignore) " +
"[entry=" + this + ", newVer=" + newVer + ']');
}
treeOp = IgniteTree.OperationType.NOOP;
updateRes = new GridCacheUpdateAtomicResult(UpdateOutcome.VERSION_CHECK_FAILED,
entry.val,
null,
invokeRes,
CU.TTL_ETERNAL,
CU.EXPIRE_TIME_ETERNAL,
null,
null,
0,
false);
}
}
else {
assert entry.isStartVersion() || ATOMIC_VER_COMPARATOR.compare(entry.ver, newVer) <= 0 :
"Invalid version for inner update [name=" + cctx.group().cacheOrGroupName() +
", topVer=" + cctx.group().topology().readyTopologyVersion() +
", isNew=" + entry.isStartVersion() + ", entry=" + entry + ", newVer=" + newVer + ']';
}
}
/**
* @param invokeEntry Entry for {@link EntryProcessor}.
* @return Entry processor return value.
*/
private IgniteBiTuple<Object, Exception> runEntryProcessor(CacheInvokeEntry<Object, Object> invokeEntry) {
EntryProcessor<Object, Object, ?> entryProcessor = SecurityUtils.sandboxedProxy(
entry.context().kernalContext(), EntryProcessor.class, (EntryProcessor<Object, Object, ?>)writeObj);
IgniteThread.onEntryProcessorEntered(true);
if (invokeEntry.cctx.kernalContext().deploy().enabled() &&
invokeEntry.cctx.kernalContext().deploy().isGlobalLoader(entryProcessor.getClass().getClassLoader())) {
U.restoreDeploymentContext(invokeEntry.cctx.kernalContext(), invokeEntry.cctx.kernalContext()
.deploy().getClassLoaderId(entryProcessor.getClass().getClassLoader()));
}
try {
Object computed = entryProcessor.process(invokeEntry, invokeArgs);
if (invokeEntry.modified()) {
GridCacheContext cctx = entry.context();
writeObj = cctx.toCacheObject(cctx.unwrapTemporary(invokeEntry.getValue()));
}
else
writeObj = invokeEntry.valObj;
if (computed != null)
return new IgniteBiTuple<>(entry.cctx.unwrapTemporary(computed), null);
return null;
}
catch (UnregisteredClassException | UnregisteredBinaryTypeException e) {
throw e;
}
catch (Exception e) {
writeObj = invokeEntry.valObj;
return new IgniteBiTuple<>(null, e);
}
finally {
IgniteThread.onEntryProcessorLeft();
}
}
/** {@inheritDoc} */
@Override public String toString() {
return S.toString(AtomicCacheUpdateClosure.class, this);
}
}
/** {@inheritDoc} */
@Override public GridCacheUpdateTxResult mvccUpdateRowsWithPreloadInfo(
IgniteInternalTx tx,
UUID affNodeId,
AffinityTopologyVersion topVer,
List<GridCacheEntryInfo> entries,
GridCacheOperation op,
MvccSnapshot mvccVer,
IgniteUuid futId,
int batchNum)
throws IgniteCheckedException, GridCacheEntryRemovedException {
assert mvccVer != null &&
MvccUtils.mvccVersionIsValid(mvccVer.coordinatorVersion(), mvccVer.counter(), mvccVer.operationCounter());
assert !F.isEmpty(entries);
WALPointer logPtr = null;
ensureFreeSpace();
CacheObject val = null;
CacheObject oldVal = null;
lockEntry();
try {
checkObsolete();
boolean logDataRecs = cctx.group().logDataRecords();
List<DataEntry> walEntries = logDataRecs ? new ArrayList<>(entries.size() + 1) : Collections.EMPTY_LIST;
// Apply updates in reverse order (oldest is last) until any of previous versions found.
// If we found prev version then it means history has been actualized either with previous update
// or via rebalance.
for (int i = 0; i < entries.size(); i++) {
GridCacheMvccEntryInfo info = (GridCacheMvccEntryInfo)entries.get(i);
assert info.mvccTxState() == TxState.COMMITTED ||
MvccUtils.compare(info, mvccVer.coordinatorVersion(), mvccVer.counter()) == 0;
assert info.newMvccTxState() == TxState.COMMITTED ||
MvccUtils.compareNewVersion(info, mvccVer.coordinatorVersion(), mvccVer.counter()) == 0 ||
info.newMvccCoordinatorVersion() == MvccUtils.MVCC_CRD_COUNTER_NA;
boolean added = cctx.offheap().mvccUpdateRowWithPreloadInfo(this,
info.value(),
info.version(),
info.expireTime(),
info.mvccVersion(),
info.newMvccVersion(),
info.mvccTxState(),
info.newMvccTxState());
if (logDataRecs)
walEntries.add(toMvccDataEntry(info, tx));
if (oldVal == null
&& MvccUtils.compare(info.mvccVersion(),
mvccVer.coordinatorVersion(),
mvccVer.counter()) != 0
&& MvccUtils.compareNewVersion(info,
mvccVer.coordinatorVersion(),
mvccVer.counter()) == 0)
oldVal = info.value(); // Old means a value before current transaction.
if (!added)
break;
}
GridCacheMvccEntryInfo last = (GridCacheMvccEntryInfo)entries.get(0);
if (logDataRecs)
Collections.reverse(walEntries);
if (op == DELETE) {
assert MvccUtils.compareNewVersion(last, mvccVer) == 0;
if (logDataRecs)
walEntries.add(new MvccDataEntry(
cctx.cacheId(),
key,
null,
DELETE,
tx.nearXidVersion(),
last.version(),
last.expireTime(),
key.partition(),
0,
last.mvccVersion()));
}
else {
assert last.newMvccCoordinatorVersion() == MvccUtils.MVCC_CRD_COUNTER_NA;
assert MvccUtils.compare(last, mvccVer) == 0;
val = last.value();
}
if (logDataRecs)
logPtr = cctx.group().wal().log(new MvccDataRecord(walEntries));
}
finally {
if (lockedByCurrentThread()) {
unlockEntry();
cctx.evicts().touch(this);
}
}
GridCacheUpdateTxResult res = new GridCacheUpdateTxResult(true, logPtr);
res.newValue(val);
res.oldValue(oldVal);
return res;
}
/** {@inheritDoc} */
@Override public boolean mvccPreloadEntry(List<GridCacheMvccEntryInfo> entryHist)
throws IgniteCheckedException, GridCacheEntryRemovedException {
assert !entryHist.isEmpty();
WALPointer logPtr = null;
ensureFreeSpace();
boolean updated = false;
lockEntry();
try {
checkObsolete();
key.valueBytes(cctx.cacheObjectContext());
if (cctx.offheap().mvccApplyHistoryIfAbsent(this, entryHist)) {
updated = true;
if (!cctx.isNear() && cctx.group().logDataRecords()) {
MvccDataRecord rec;
if (entryHist.size() == 1) {
GridCacheMvccEntryInfo info = entryHist.get(0);
rec = new MvccDataRecord(toMvccDataEntry(info, null));
}
else {
// Batched WAL update.
List<DataEntry> dataEntries = new ArrayList<>(entryHist.size());
for (GridCacheMvccEntryInfo info : entryHist)
dataEntries.add(toMvccDataEntry(info, null));
rec = new MvccDataRecord(dataEntries);
}
logPtr = cctx.group().wal().log(rec);
}
}
}
finally {
if (lockedByCurrentThread()) {
unlockEntry();
cctx.evicts().touch(this);
}
}
if (logPtr != null)
cctx.shared().wal().flush(logPtr, false);
return updated;
}
/**
* Converts mvcc entry info to WAL record entry.
* @param info Mvcc entry info.
* @return Mvcc data entry.
*/
private @NotNull MvccDataEntry toMvccDataEntry(@NotNull GridCacheMvccEntryInfo info, @Nullable IgniteInternalTx tx) {
return new MvccDataEntry(
cctx.cacheId(),
key,
info.value(),
CREATE,
tx == null ? null : tx.nearXidVersion(),
info.version(),
info.expireTime(),
key.partition(),
0,
info.mvccVersion()
);
}
/**
* @return Common serialization error.
*/
private static IgniteTxSerializationCheckedException serializationError() {
return new IgniteTxSerializationCheckedException(
"Cannot serialize transaction due to write conflict (transaction is marked for rollback)"
);
}
/**
* Invokes platform cache update callback, if applicable.
*
* @param val Updated value, null on remove.
* @param ver Topology version, null on remove.
*/
protected void updatePlatformCache(@Nullable CacheObject val, @Nullable AffinityTopologyVersion ver) {
if (!hasPlatformCache())
return;
PlatformProcessor proc = this.cctx.kernalContext().platform();
if (!proc.hasContext() || !proc.context().isPlatformCacheSupported())
return;
try {
CacheObjectContext ctx = this.cctx.cacheObjectContext();
// val is null when entry is removed.
byte[] keyBytes = this.key.valueBytes(ctx);
byte[] valBytes = val == null ? null : val.valueBytes(ctx);
proc.context().updatePlatformCache(this.cctx.cacheId(), keyBytes, valBytes, partition(), ver);
}
catch (Throwable e) {
U.error(log, "Failed to update Platform Cache: " + e);
}
}
/**
* Gets a value indicating whether platform cache exists for current cache.
*
* @return True when platform cache exists for this cache; false otherwise.
*/
@SuppressWarnings("rawtypes")
private boolean hasPlatformCache() {
GridCacheAdapter cache = cctx.cache();
return cache != null && cache.cacheCfg.getPlatformCacheConfiguration() != null;
}
}