/*
* 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.distributed.dht;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteException;
import org.apache.ignite.IgniteLogger;
import org.apache.ignite.internal.IgniteInternalFuture;
import org.apache.ignite.internal.NodeStoppingException;
import org.apache.ignite.internal.pagemem.wal.record.delta.PartitionMetaStateRecord;
import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
import org.apache.ignite.internal.processors.cache.GridCacheAdapter;
import org.apache.ignite.internal.processors.cache.GridCacheConcurrentMapImpl;
import org.apache.ignite.internal.processors.cache.GridCacheContext;
import org.apache.ignite.internal.processors.cache.GridCacheEntryEx;
import org.apache.ignite.internal.processors.cache.GridCacheMapEntry;
import org.apache.ignite.internal.processors.cache.GridCacheMapEntryFactory;
import org.apache.ignite.internal.processors.cache.KeyCacheObject;
import org.apache.ignite.internal.processors.cache.database.CacheDataRow;
import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPreloader;
import org.apache.ignite.internal.processors.cache.extras.GridCacheObsoleteEntryExtras;
import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
import org.apache.ignite.internal.processors.query.QueryUtils;
import org.apache.ignite.internal.util.future.GridFutureAdapter;
import org.apache.ignite.internal.util.lang.GridIterator;
import org.apache.ignite.internal.util.tostring.GridToStringExclude;
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.IgniteUuid;
import org.jetbrains.annotations.NotNull;
import org.jsr166.ConcurrentLinkedDeque8;
import static org.apache.ignite.IgniteSystemProperties.IGNITE_ATOMIC_CACHE_DELETE_HISTORY_SIZE;
import static org.apache.ignite.IgniteSystemProperties.IGNITE_CACHE_REMOVED_ENTRIES_TTL;
import static org.apache.ignite.events.EventType.EVT_CACHE_REBALANCE_OBJECT_UNLOADED;
import static org.apache.ignite.internal.processors.cache.IgniteCacheOffheapManager.CacheDataStore;
import static org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtPartitionState.EVICTED;
import static org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtPartitionState.LOST;
import static org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtPartitionState.MOVING;
import static org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtPartitionState.OWNING;
import static org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtPartitionState.RENTING;
/**
* Key partition.
*/
public class GridDhtLocalPartition extends GridCacheConcurrentMapImpl implements Comparable<GridDhtLocalPartition>, GridReservable {
/** Maximum size for delete queue. */
public static final int MAX_DELETE_QUEUE_SIZE = Integer.getInteger(IGNITE_ATOMIC_CACHE_DELETE_HISTORY_SIZE, 200_000);
/** Maximum size for {@link #rmvQueue}. */
private final int rmvQueueMaxSize;
/** Removed items TTL. */
private final long rmvdEntryTtl;
/** Static logger to avoid re-creation. */
private static final AtomicReference<IgniteLogger> logRef = new AtomicReference<>();
/** Logger. */
private static volatile IgniteLogger log;
/** Partition ID. */
private final int id;
/** State. 32 bits - size, 16 bits - reservations, 13 bits - reserved, 3 bits - GridDhtPartitionState. */
@GridToStringExclude
private final AtomicLong state = new AtomicLong((long)MOVING.ordinal() << 32);
/** Evict guard. Must be CASed to -1 only when partition state is EVICTED. */
@GridToStringExclude
private final AtomicInteger evictGuard = new AtomicInteger();
/** Rent future. */
@GridToStringExclude
private final GridFutureAdapter<?> rent;
/** Context. */
private final GridCacheContext cctx;
/** Create time. */
@GridToStringExclude
private final long createTime = U.currentTimeMillis();
/** Eviction history. */
private volatile Map<KeyCacheObject, GridCacheVersion> evictHist = new HashMap<>();
/** Lock. */
private final ReentrantLock lock = new ReentrantLock();
/** Remove queue. */
private final ConcurrentLinkedDeque8<RemovedEntryHolder> rmvQueue = new ConcurrentLinkedDeque8<>();
/** Group reservations. */
private final CopyOnWriteArrayList<GridDhtPartitionsReservation> reservations = new CopyOnWriteArrayList<>();
/** */
private final CacheDataStore store;
/** Partition updates. */
private final ConcurrentNavigableMap<Long, Boolean> updates = new ConcurrentSkipListMap<>();
/** Last applied update. */
private final AtomicLong lastApplied = new AtomicLong(0);
/** Set if failed to move partition to RENTING state due to reservations, to be checked when
* reservation is released. */
private volatile boolean shouldBeRenting;
/**
* @param cctx Context.
* @param id Partition ID.
* @param entryFactory Entry factory.
*/
@SuppressWarnings("ExternalizableWithoutPublicNoArgConstructor")
GridDhtLocalPartition(GridCacheContext cctx, int id, GridCacheMapEntryFactory entryFactory) {
super(cctx, entryFactory, Math.max(10, GridCacheAdapter.DFLT_START_CACHE_SIZE / cctx.affinity().partitions()));
this.id = id;
this.cctx = cctx;
log = U.logger(cctx.kernalContext(), logRef, this);
rent = new GridFutureAdapter<Object>() {
@Override public String toString() {
return "PartitionRentFuture [part=" + GridDhtLocalPartition.this + ']';
}
};
int delQueueSize = CU.isSystemCache(cctx.name()) ? 100 :
Math.max(MAX_DELETE_QUEUE_SIZE / cctx.affinity().partitions(), 20);
rmvQueueMaxSize = U.ceilPow2(delQueueSize);
rmvdEntryTtl = Long.getLong(IGNITE_CACHE_REMOVED_ENTRIES_TTL, 10_000);
try {
store = cctx.offheap().createCacheDataStore(id);
}
catch (IgniteCheckedException e) {
// TODO ignite-db
throw new IgniteException(e);
}
}
/**
* @return Data store.
*/
public CacheDataStore dataStore() {
return store;
}
/**
* Adds group reservation to this partition.
*
* @param r Reservation.
* @return {@code false} If such reservation already added.
*/
public boolean addReservation(GridDhtPartitionsReservation r) {
assert (getPartState(state.get())) != EVICTED : "we can reserve only active partitions";
assert (getReservations(state.get())) != 0 : "partition must be already reserved before adding group reservation";
return reservations.addIfAbsent(r);
}
/**
* @param r Reservation.
*/
public void removeReservation(GridDhtPartitionsReservation r) {
if (!reservations.remove(r))
throw new IllegalStateException("Reservation was already removed.");
}
/**
* @return Partition ID.
*/
public int id() {
return id;
}
/**
* @return Create time.
*/
long createTime() {
return createTime;
}
/**
* @return Partition state.
*/
public GridDhtPartitionState state() {
return getPartState(state.get());
}
/**
* @return {@code True} if partition is marked for transfer to renting state.
*/
public boolean shouldBeRenting() {
return shouldBeRenting;
}
/**
* @return Reservations.
*/
public int reservations() {
return getReservations(state.get());
}
/**
* @return {@code True} if partition is empty.
*/
public boolean isEmpty() {
if (cctx.allowFastEviction())
return size() == 0;
return store.size() == 0 && size() == 0;
}
/**
* @return Last applied update.
*/
public long lastAppliedUpdate() {
return lastApplied.get();
}
/**
* @param cntr Received counter.
*/
public void onUpdateReceived(long cntr) {
boolean changed = updates.putIfAbsent(cntr, true) == null;
if (!changed)
return;
while (true) {
Map.Entry<Long, Boolean> entry = updates.firstEntry();
if (entry == null)
return;
long first = entry.getKey();
long cntr0 = lastApplied.get();
if (first <= cntr0)
updates.remove(first);
else if (first == cntr0 + 1)
if (lastApplied.compareAndSet(cntr0, first))
updates.remove(first);
else
break;
else
break;
}
}
/**
* @return If partition is moving or owning or renting.
*/
public boolean valid() {
GridDhtPartitionState state = state();
return state == MOVING || state == OWNING || state == RENTING;
}
/**
* @param entry Entry to remove.
*/
void onRemoved(GridDhtCacheEntry entry) {
assert entry.obsolete() : entry;
// Make sure to remove exactly this entry.
removeEntry(entry);
// Attempt to evict.
try {
tryEvict();
}
catch (NodeStoppingException ignore) {
// No-op.
}
}
/**
*
*/
public void cleanupRemoveQueue() {
while (rmvQueue.sizex() >= rmvQueueMaxSize) {
RemovedEntryHolder item = rmvQueue.pollFirst();
if (item != null)
cctx.dht().removeVersionedEntry(item.key(), item.version());
}
if (!cctx.isDrEnabled()) {
RemovedEntryHolder item = rmvQueue.peekFirst();
while (item != null && item.expireTime() < U.currentTimeMillis()) {
item = rmvQueue.pollFirst();
if (item == null)
break;
cctx.dht().removeVersionedEntry(item.key(), item.version());
item = rmvQueue.peekFirst();
}
}
}
/**
* @param key Removed key.
* @param ver Removed version.
*/
public void onDeferredDelete(KeyCacheObject key, GridCacheVersion ver) {
cleanupRemoveQueue();
rmvQueue.add(new RemovedEntryHolder(key, ver, rmvdEntryTtl));
}
/**
* Locks partition.
*/
@SuppressWarnings({"LockAcquiredButNotSafelyReleased"})
public void lock() {
lock.lock();
}
/**
* Unlocks partition.
*/
public void unlock() {
lock.unlock();
}
/**
* @param key Key.
* @param ver Version.
*/
public void onEntryEvicted(KeyCacheObject key, GridCacheVersion ver) {
assert key != null;
assert ver != null;
assert lock.isHeldByCurrentThread(); // Only one thread can enter this method at a time.
if (state() != MOVING)
return;
Map<KeyCacheObject, GridCacheVersion> evictHist0 = evictHist;
if (evictHist0 != null) {
GridCacheVersion ver0 = evictHist0.get(key);
if (ver0 == null || ver0.isLess(ver)) {
GridCacheVersion ver1 = evictHist0.put(key, ver);
assert ver1 == ver0;
}
}
}
/**
* Cache preloader should call this method within partition lock.
*
* @param key Key.
* @param ver Version.
* @return {@code True} if preloading is permitted.
*/
public boolean preloadingPermitted(KeyCacheObject key, GridCacheVersion ver) {
assert key != null;
assert ver != null;
assert lock.isHeldByCurrentThread(); // Only one thread can enter this method at a time.
if (state() != MOVING)
return false;
Map<KeyCacheObject, GridCacheVersion> evictHist0 = evictHist;
if (evictHist0 != null) {
GridCacheVersion ver0 = evictHist0.get(key);
// Permit preloading if version in history
// is missing or less than passed in.
return ver0 == null || ver0.isLess(ver);
}
return false;
}
/**
* Reserves a partition so it won't be cleared.
*
* @return {@code True} if reserved.
*/
@Override public boolean reserve() {
while (true) {
long state = this.state.get();
if (getPartState(state) == EVICTED)
return false;
long newState = setReservations(state, getReservations(state) + 1);
if (this.state.compareAndSet(state, newState))
return true;
}
}
/**
* Releases previously reserved partition.
*/
@Override public void release() {
release0(0);
}
/** {@inheritDoc} */
@Override protected void release(int sizeChange, GridCacheEntryEx e) {
release0(sizeChange);
}
/**
* @param sizeChange Size change delta.
*/
private void release0(int sizeChange) {
while (true) {
long state = this.state.get();
int reservations = getReservations(state);
if (reservations == 0)
return;
assert getPartState(state) != EVICTED;
long newState = setReservations(state, --reservations);
newState = setSize(newState, getSize(newState) + sizeChange);
assert getSize(newState) == getSize(state) + sizeChange;
// Decrement reservations.
if (this.state.compareAndSet(state, newState)) {
if (reservations == 0 && shouldBeRenting)
rent(true);
tryEvictAsync(false);
break;
}
}
}
/**
* @param stateToRestore State to restore.
*/
public void restoreState(GridDhtPartitionState stateToRestore) {
state.set(setPartState(state.get(), stateToRestore));
}
/**
* @param state Current aggregated value.
* @param toState State to switch to.
* @return {@code true} if cas succeeds.
*/
private boolean casState(long state, GridDhtPartitionState toState) {
if (cctx.shared().database().persistenceEnabled()) {
synchronized (this) {
boolean update = this.state.compareAndSet(state, setPartState(state, toState));
if (update)
try {
cctx.shared().wal().log(new PartitionMetaStateRecord(cctx.cacheId(), id, toState, updateCounter()));
}
catch (IgniteCheckedException e) {
log.error("Error while writing to log", e);
}
return update;
}
}
else
return this.state.compareAndSet(state, setPartState(state, toState));
}
/**
* @return {@code True} if transitioned to OWNING state.
*/
boolean own() {
while (true) {
long state = this.state.get();
GridDhtPartitionState partState = getPartState(state);
if (partState == RENTING || partState == EVICTED)
return false;
if (partState == OWNING)
return true;
assert partState == MOVING || partState == LOST;
if (casState(state, OWNING)) {
if (log.isDebugEnabled())
log.debug("Owned partition: " + this);
// No need to keep history any more.
evictHist = null;
return true;
}
}
}
/**
* Forcibly moves partition to a MOVING state.
*/
void moving() {
while (true) {
long state = this.state.get();
GridDhtPartitionState partState = getPartState(state);
assert partState == OWNING : "Only OWNed partitions should be moved to MOVING state";
if (casState(state, MOVING)) {
if (log.isDebugEnabled())
log.debug("Forcibly moved partition to a MOVING state: " + this);
break;
}
}
}
/**
* @return {@code True} if partition state changed.
*/
boolean markLost() {
while (true) {
long state = this.state.get();
GridDhtPartitionState partState = getPartState(state);
if (partState == LOST)
return false;
if (casState(state, LOST)) {
if (log.isDebugEnabled())
log.debug("Marked partition as LOST: " + this);
// No need to keep history any more.
evictHist = null;
return true;
}
}
}
/**
* @param updateSeq Update sequence.
* @return Future to signal that this node is no longer an owner or backup.
*/
IgniteInternalFuture<?> rent(boolean updateSeq) {
long state = this.state.get();
GridDhtPartitionState partState = getPartState(state);
if (partState == RENTING || partState == EVICTED)
return rent;
shouldBeRenting = true;
if (getReservations(state) == 0 && casState(state, RENTING)) {
shouldBeRenting = false;
if (log.isDebugEnabled())
log.debug("Moved partition to RENTING state: " + this);
// Evict asynchronously, as the 'rent' method may be called
// from within write locks on local partition.
tryEvictAsync(updateSeq);
}
return rent;
}
/**
* @param updateSeq Update sequence.
*/
void tryEvictAsync(boolean updateSeq) {
assert cctx.kernalContext().state().active();
long state = this.state.get();
GridDhtPartitionState partState = getPartState(state);
if (isEmpty() && !QueryUtils.isEnabled(cctx.config()) && getSize(state) == 0 &&
partState == RENTING && getReservations(state) == 0 && !groupReserved() &&
casState(state, EVICTED)) {
if (log.isDebugEnabled())
log.debug("Evicted partition: " + this);
if (markForDestroy())
finishDestroy(updateSeq);
}
else if (partState == RENTING || shouldBeRenting())
cctx.preloader().evictPartitionAsync(this);
}
/**
* @return {@code true} If there is a group reservation.
*/
boolean groupReserved() {
for (GridDhtPartitionsReservation reservation : reservations) {
if (!reservation.invalidate())
return true; // Failed to invalidate reservation -> we are reserved.
}
return false;
}
/**
* @return {@code True} if evicting thread was added.
*/
private boolean addEvicting() {
while (true) {
int cnt = evictGuard.get();
if (cnt != 0)
return false;
if (evictGuard.compareAndSet(cnt, cnt + 1))
return true;
}
}
/**
*
*/
private void clearEvicting() {
boolean free;
while (true) {
int cnt = evictGuard.get();
assert cnt > 0;
if (evictGuard.compareAndSet(cnt, cnt - 1)) {
free = cnt == 1;
break;
}
}
if (free && state() == EVICTED) {
if (markForDestroy())
finishDestroy(true);
}
}
/**
* @return {@code True} if partition is safe to destroy
*/
private boolean markForDestroy() {
while (true) {
int cnt = evictGuard.get();
if (cnt != 0)
return false;
if (evictGuard.compareAndSet(0, -1))
return true;
}
}
/**
* @param updateSeq Update sequence request.
*/
private void finishDestroy(boolean updateSeq) {
assert state() == EVICTED : this;
assert evictGuard.get() == -1;
if (cctx.isDrEnabled())
cctx.dr().partitionEvicted(id);
cctx.continuousQueries().onPartitionEvicted(id);
cctx.dataStructures().onPartitionEvicted(id);
destroyCacheDataStore();
rent.onDone();
((GridDhtPreloader)cctx.preloader()).onPartitionEvicted(this, updateSeq);
clearDeferredDeletes();
}
/**
* @throws NodeStoppingException If node is stopping.
*/
public void tryEvict() throws NodeStoppingException {
long state = this.state.get();
GridDhtPartitionState partState = getPartState(state);
if (partState != RENTING || getReservations(state) != 0 || groupReserved())
return;
if (addEvicting()) {
try {
// Attempt to evict partition entries from cache.
clearAll();
if (isEmpty() && getSize(state) == 0 && casState(state, EVICTED)) {
if (log.isDebugEnabled())
log.debug("Evicted partition: " + this);
// finishDestroy() will be initiated by clearEvicting().
}
}
finally {
clearEvicting();
}
}
}
/**
* Release created data store for this partition.
*/
private void destroyCacheDataStore() {
try {
CacheDataStore store = dataStore();
cctx.offheap().destroyCacheDataStore(id, store);
}
catch (IgniteCheckedException e) {
log.error("Unable to destroy cache data store on partition eviction [id=" + id + "]", e);
}
}
/**
*
*/
void onUnlock() {
try {
tryEvict();
}
catch (NodeStoppingException ignore) {
// No-op.
}
}
/**
* @param topVer Topology version.
* @return {@code True} if local node is primary for this partition.
*/
public boolean primary(AffinityTopologyVersion topVer) {
return cctx.affinity().primaryByPartition(cctx.localNode(), id, topVer);
}
/**
* @param topVer Topology version.
* @return {@code True} if local node is backup for this partition.
*/
public boolean backup(AffinityTopologyVersion topVer) {
return cctx.affinity().backupByPartition(cctx.localNode(), id, topVer);
}
/**
* @return Next update index.
*/
public long nextUpdateCounter() {
return store.nextUpdateCounter();
}
/**
* @return Current update index.
*/
public long updateCounter() {
return store.updateCounter();
}
/**
* @return Initial update counter.
*/
public Long initialUpdateCounter() {
return store.initialUpdateCounter();
}
/**
* @param val Update index value.
*/
public void updateCounter(long val) {
store.updateCounter(val);
}
/**
* @param val Initial update index value.
*/
public void initialUpdateCounter(long val) {
store.updateInitialCounter(val);
}
/**
* Clears values for this partition.
*
* @throws NodeStoppingException If node stopping.
*/
public void clearAll() throws NodeStoppingException {
GridCacheVersion clearVer = cctx.versions().next();
boolean rec = cctx.events().isRecordable(EVT_CACHE_REBALANCE_OBJECT_UNLOADED);
Iterator<GridCacheMapEntry> it = allEntries().iterator();
GridCacheObsoleteEntryExtras extras = new GridCacheObsoleteEntryExtras(clearVer);
while (it.hasNext()) {
GridCacheMapEntry cached = null;
cctx.shared().database().checkpointReadLock();
try {
cached = it.next();
if (cached instanceof GridDhtCacheEntry && ((GridDhtCacheEntry)cached).clearInternal(clearVer, extras)) {
removeEntry(cached);
if (!cached.isInternal()) {
if (rec) {
cctx.events().addEvent(cached.partition(),
cached.key(),
cctx.localNodeId(),
(IgniteUuid)null,
null,
EVT_CACHE_REBALANCE_OBJECT_UNLOADED,
null,
false,
cached.rawGet(),
cached.hasValue(),
null,
null,
null,
false);
}
}
}
}
catch (GridDhtInvalidPartitionException e) {
assert isEmpty() && state() == EVICTED : "Invalid error [e=" + e + ", part=" + this + ']';
break; // Partition is already concurrently cleared and evicted.
}
catch (NodeStoppingException e) {
if (log.isDebugEnabled())
log.debug("Failed to clear cache entry for evicted partition: " + cached.partition());
rent.onDone(e);
throw e;
}
catch (IgniteCheckedException e) {
U.error(log, "Failed to clear cache entry for evicted partition: " + cached, e);
}
finally {
cctx.shared().database().checkpointReadUnlock();
}
}
if (!cctx.allowFastEviction()) {
try {
GridIterator<CacheDataRow> it0 = cctx.offheap().iterator(id);
while (it0.hasNext()) {
cctx.shared().database().checkpointReadLock();
try {
CacheDataRow row = it0.next();
GridCacheMapEntry cached = putEntryIfObsoleteOrAbsent(cctx.affinity().affinityTopologyVersion(),
row.key(),
true,
false);
if (cached instanceof GridDhtCacheEntry && ((GridDhtCacheEntry)cached).clearInternal(clearVer, extras)) {
if (rec) {
cctx.events().addEvent(cached.partition(),
cached.key(),
cctx.localNodeId(),
(IgniteUuid)null,
null,
EVT_CACHE_REBALANCE_OBJECT_UNLOADED,
null,
false,
cached.rawGet(),
cached.hasValue(),
null,
null,
null,
false);
}
}
}
catch (GridDhtInvalidPartitionException e) {
assert isEmpty() && state() == EVICTED : "Invalid error [e=" + e + ", part=" + this + ']';
break; // Partition is already concurrently cleared and evicted.
}
finally {
cctx.shared().database().checkpointReadUnlock();
}
}
}
catch (NodeStoppingException e) {
if (log.isDebugEnabled())
log.debug("Failed to get iterator for evicted partition: " + id);
rent.onDone(e);
throw e;
}
catch (IgniteCheckedException e) {
U.error(log, "Failed to get iterator for evicted partition: " + id, e);
}
}
}
/**
*
*/
private void clearDeferredDeletes() {
for (RemovedEntryHolder e : rmvQueue)
cctx.dht().removeVersionedEntry(e.key(), e.version());
}
/** {@inheritDoc} */
@Override public int hashCode() {
return id;
}
/** {@inheritDoc} */
@SuppressWarnings({"OverlyStrongTypeCast"})
@Override public boolean equals(Object obj) {
return obj instanceof GridDhtLocalPartition && (obj == this || ((GridDhtLocalPartition)obj).id() == id);
}
/** {@inheritDoc} */
@Override public int compareTo(@NotNull GridDhtLocalPartition part) {
if (part == null)
return 1;
return Integer.compare(id, part.id());
}
/** {@inheritDoc} */
@Override public String toString() {
return S.toString(GridDhtLocalPartition.class, this,
"state", state(),
"reservations", reservations(),
"empty", isEmpty(),
"createTime", U.format(createTime));
}
/** {@inheritDoc} */
@Override public int publicSize() {
return getSize(state.get());
}
/** {@inheritDoc} */
@Override public void incrementPublicSize(GridCacheEntryEx e) {
while (true) {
long state = this.state.get();
if (this.state.compareAndSet(state, setSize(state, getSize(state) + 1)))
return;
}
}
/** {@inheritDoc} */
@Override public void decrementPublicSize(GridCacheEntryEx e) {
while (true) {
long state = this.state.get();
assert getPartState(state) != EVICTED;
if (this.state.compareAndSet(state, setSize(state, getSize(state) - 1)))
return;
}
}
/**
* @param state Composite state.
* @return Partition state.
*/
private static GridDhtPartitionState getPartState(long state) {
return GridDhtPartitionState.fromOrdinal((int)(state & (0x0000000000000007L)));
}
/**
* @param state Composite state to update.
* @param partState Partition state.
* @return Updated composite state.
*/
private static long setPartState(long state, GridDhtPartitionState partState) {
return (state & (~0x0000000000000007L)) | partState.ordinal();
}
/**
* @param state Composite state.
* @return Reservations.
*/
private static int getReservations(long state) {
return (int)((state & 0x00000000FFFF0000L) >> 16);
}
/**
* @param state Composite state to update.
* @param reservations Reservations to set.
* @return Updated composite state.
*/
private static long setReservations(long state, int reservations) {
return (state & (~0x00000000FFFF0000L)) | (reservations << 16);
}
/**
* @param state Composite state.
* @return Size.
*/
private static int getSize(long state) {
return (int)((state & 0xFFFFFFFF00000000L) >> 32);
}
/**
* @param state Composite state to update.
* @param size Size to set.
* @return Updated composite state.
*/
private static long setSize(long state, int size) {
return (state & (~0xFFFFFFFF00000000L)) | ((long)size << 32);
}
/**
* Removed entry holder.
*/
private static class RemovedEntryHolder {
/** Cache key */
private final KeyCacheObject key;
/** Entry version */
private final GridCacheVersion ver;
/** Entry expire time. */
private final long expireTime;
/**
* @param key Key.
* @param ver Entry version.
* @param ttl TTL.
*/
private RemovedEntryHolder(KeyCacheObject key, GridCacheVersion ver, long ttl) {
this.key = key;
this.ver = ver;
expireTime = U.currentTimeMillis() + ttl;
}
/**
* @return Key.
*/
KeyCacheObject key() {
return key;
}
/**
* @return Version.
*/
GridCacheVersion version() {
return ver;
}
/**
* @return item expired time
*/
long expireTime() {
return expireTime;
}
/** {@inheritDoc} */
@Override public String toString() {
return S.toString(RemovedEntryHolder.class, this);
}
}
}