package org.cache2k.benchmark.impl2015;
/*
* #%L
* Benchmarks: implementation variants
* %%
* Copyright (C) 2013 - 2017 headissue GmbH, Munich
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.cache2k.CacheEntry;
/**
* The cache entry. This is a combined hashtable entry with hashCode and
* and collision list (other field) and it contains a double linked list
* (next and previous) for the eviction algorithm.
*
* @author Jens Wilke
*/
@SuppressWarnings("unchecked")
public class Entry<E extends Entry, K, T>
implements CacheEntry<K,T> {
static final int FETCHED_STATE = 16;
static final int REFRESH_STATE = FETCHED_STATE + 1;
static final int REPUT_STATE = FETCHED_STATE + 3;
static final int FETCH_IN_PROGRESS_VALID = FETCHED_STATE + 4;
/**
* Entry was created for locking purposes of an atomic operation.
*/
static final int ATOMIC_OP_NON_VALID = 10;
static final int LOADED_NON_VALID_AND_PUT = 9;
static final int FETCH_ABORT = 8;
static final int FETCH_IN_PROGRESS_NON_VALID = 7;
/** Storage was checked, no data available */
static final int LOADED_NON_VALID_AND_FETCH = 6;
/** Storage was checked, no data available */
static final int LOADED_NON_VALID = 5;
static final int EXPIRED_STATE = 4;
/** Logically the same as immediately expired */
static final int FETCH_NEXT_TIME_STATE = 3;
static private final int REMOVED_STATE = 2;
static private final int FETCH_IN_PROGRESS_VIRGIN = 1;
static final int VIRGIN_STATE = 0;
static final int EXPIRY_TIME_MIN = 32;
static private final StaleMarker STALE_MARKER_KEY = new StaleMarker();
final static InitialValueInEntryNeverReturned INITIAL_VALUE = new InitialValueInEntryNeverReturned();
public BaseCache.MyTimerTask task;
/**
* Hit counter for clock pro. Not used by every eviction algorithm.
*/
long hitCnt;
/**
* Time the entry was last updated by put or by fetching it from the cache source.
* The time is the time in millis times 2. A set bit 1 means the entry is fetched from
* the storage and not modified since then.
*/
public long fetchedTime;
/**
* Contains the next time a refresh has to occur, or if no background refresh is configured, when the entry
* is expired. Low values have a special meaning, see defined constants.
* Negative values means that we need to check against the wall clock.
*
* Whenever processing is done on the entry, e.g. a refresh or update, the field is used to reflect
* the processing state. This means that, during processing the expiry time is lost. This has no negative
* impact on the visibility of the entry. For example if the entry is refreshed, it is expired, but since
* background refresh is enabled, the expired entry is still returned by the cache.
*/
public volatile long nextRefreshTime;
public K key;
public volatile T value = (T) INITIAL_VALUE;
/**
* Hash implementation: the calculated, modified hash code, retrieved from the key when the entry is
* inserted in the cache
*
* @see BaseCache#modifiedHash(int)
*/
public int hashCode;
/**
* Hash implementation: Link to another entry in the same hash table slot when the hash code collides.
*/
public Entry<E, K, T> another;
/** Lru list: pointer to next element or list head */
public E next;
/** Lru list: pointer to previous element or list head */
public E prev;
public void setLastModification(long t) {
fetchedTime = t << 1;
}
/**
* Memory entry needs to be send to the storage.
*/
public boolean isDirty() {
return (fetchedTime & 1) == 0;
}
public void setLastModificationFromStorage(long t) {
fetchedTime = t << 1 | 1;
}
public void resetDirty() {
fetchedTime = fetchedTime | 1;
}
/** Reset next as a marker for {@link #isRemovedFromReplacementList()} */
public final void removedFromList() {
next = null;
}
/** Check that this entry is removed from the list, may be used in assertions. */
public boolean isRemovedFromReplacementList() {
return isStale () || next == null;
}
public E shortCircuit() {
return next = prev = (E) this;
}
public final boolean isVirgin() {
return
nextRefreshTime == VIRGIN_STATE ||
nextRefreshTime == FETCH_IN_PROGRESS_VIRGIN;
}
public final boolean isFetchNextTimeState() {
return nextRefreshTime == FETCH_NEXT_TIME_STATE;
}
/**
* The entry value was fetched and is valid, which means it can be
* returned by the cache. If a valid entry gets removed from the
* cache the data is still valid. This is because a concurrent get needs to
* return the data. There is also the chance that an entry is removed by eviction,
* or is never inserted to the cache, before the get returns it.
*
* <p/>Even if this is true, the data may be expired. Use hasFreshData() to
* make sure to get not expired data.
*/
public final boolean isDataValidState() {
return isDataValidState(nextRefreshTime);
}
public static boolean isDataValidState(long _nextRefreshTime) {
return _nextRefreshTime >= FETCHED_STATE || _nextRefreshTime < 0;
}
/**
* Starts long operation on entry. Pins the entry in the cache.
*/
public long startFetch() {
long tmp = nextRefreshTime;
if (isVirgin()) {
nextRefreshTime = FETCH_IN_PROGRESS_VIRGIN;
} else {
nextRefreshTime = FETCH_IN_PROGRESS_NON_VALID;
}
return tmp;
}
/**
* If fetch is not stopped, abort and make entry invalid.
* This is a safety measure, since during entry processing an
* exceptions may happen. This can happen regularly e.g. if storage
* is set to read only and a cache put is made.
*/
public void ensureFetchAbort(boolean _finished) {
if (_finished) {
return;
}
if (isFetchInProgress()) {
synchronized (Entry.this) {
if (isFetchInProgress()) {
nextRefreshTime = FETCH_ABORT;
notifyAll();
}
}
}
}
public void ensureFetchAbort(boolean _finished, long _previousNextRefreshTime) {
if (_finished) {
return;
}
if (isFetchInProgress()) {
synchronized (Entry.this) {
if (isFetchInProgress()) {
if (_previousNextRefreshTime == VIRGIN_STATE) {
nextRefreshTime = LOADED_NON_VALID;
} else {
nextRefreshTime = _previousNextRefreshTime;
}
notifyAll();
}
}
}
}
/**
* Entry is not allowed to be evicted
*/
public boolean isPinned() {
return isFetchInProgress();
}
public boolean isFetchInProgress() {
return
nextRefreshTime == REFRESH_STATE ||
nextRefreshTime == LOADED_NON_VALID_AND_FETCH ||
nextRefreshTime == FETCH_IN_PROGRESS_VIRGIN ||
nextRefreshTime == LOADED_NON_VALID_AND_PUT ||
nextRefreshTime == FETCH_IN_PROGRESS_NON_VALID ||
nextRefreshTime == FETCH_IN_PROGRESS_VALID;
}
public void waitForFetch() {
if (!isFetchInProgress()) {
return;
}
do {
try {
wait();
} catch (InterruptedException ignore) {
}
} while (isFetchInProgress());
}
/**
* Returns true if the entry has a valid value and is fresh / not expired.
*/
public final boolean hasFreshData() {
if (nextRefreshTime >= FETCHED_STATE) {
return true;
}
if (needsTimeCheck()) {
long now = System.currentTimeMillis();
return now < -nextRefreshTime;
}
return false;
}
/**
* Same as {@link #hasFreshData}, optimization if current time is known.
*/
public final boolean hasFreshData(long now) {
if (nextRefreshTime >= FETCHED_STATE) {
return true;
}
if (needsTimeCheck()) {
return now < -nextRefreshTime;
}
return false;
}
public final boolean hasFreshData(long now, long _nextRefreshTime) {
if (_nextRefreshTime >= FETCHED_STATE) {
return true;
}
if (_nextRefreshTime < 0) {
return now < -_nextRefreshTime;
}
return false;
}
public boolean isLoadedNonValid() {
return nextRefreshTime == LOADED_NON_VALID;
}
public void setLoadedNonValidAndFetch() {
nextRefreshTime = LOADED_NON_VALID_AND_FETCH;
}
public boolean isLoadedNonValidAndFetch() {
return nextRefreshTime == LOADED_NON_VALID_AND_FETCH;
}
/** Entry is kept in the cache but has expired */
public void setExpiredState() {
nextRefreshTime = EXPIRED_STATE;
}
/**
* The entry expired, but still in the cache. This may happen if
* {@link BaseCache#hasKeepAfterExpired()} is true.
*/
public boolean isExpiredState() {
return isExpiredState(nextRefreshTime);
}
public static boolean isExpiredState(long _nextRefreshTime) {
return _nextRefreshTime == EXPIRED_STATE;
}
public void setRemovedState() {
nextRefreshTime = REMOVED_STATE;
}
public boolean isRemovedState() {
return nextRefreshTime == REMOVED_STATE;
}
public void setGettingRefresh() {
nextRefreshTime = REFRESH_STATE;
}
public boolean isGettingRefresh() {
return nextRefreshTime == REFRESH_STATE;
}
public boolean isBeeingReput() {
return nextRefreshTime == REPUT_STATE;
}
public boolean needsTimeCheck() {
return nextRefreshTime < 0;
}
public boolean isStale() {
return STALE_MARKER_KEY == key;
}
public void setStale() {
key = (K) STALE_MARKER_KEY;
}
public boolean hasException() {
return value instanceof ExceptionWrapper;
}
public Throwable getException() {
if (value instanceof ExceptionWrapper) {
return ((ExceptionWrapper) value).getException();
}
return null;
}
public void setException(Throwable exception) {
value = (T) new ExceptionWrapper(exception);
}
public boolean equalsValue(T v) {
if (value == null) {
return v == value;
}
return value.equals(v);
}
public T getValue() {
if (value instanceof ExceptionWrapper) { return null; }
return value;
}
@Override
public K getKey() {
return key;
}
@Override
public long getLastModification() {
return fetchedTime >> 1;
}
/**
* Expiry time or 0.
*/
public long getValueExpiryTime() {
if (nextRefreshTime < 0) {
return -nextRefreshTime;
} else if (nextRefreshTime > EXPIRY_TIME_MIN) {
return nextRefreshTime;
}
return 0;
}
@Override
public String toString() {
return "Entry{" +
"nextRefreshTime=" + nextRefreshTime +
", valueExpiryTime=" + getValueExpiryTime() +
", key=" + key +
", mHC=" + hashCode +
", value=" + value +
", dirty=" + isDirty() +
'}';
}
/**
* Cache entries always have the object identity as equals method.
*/
@Override
public final boolean equals(Object obj) {
return this == obj;
}
/* check entry states */
static {
Entry e = new Entry();
e.nextRefreshTime = FETCHED_STATE;
e.setGettingRefresh();
e = new Entry();
e.setLoadedNonValidAndFetch();
e.setExpiredState();
}
static class InitialValueInEntryNeverReturned extends Object { }
static class StaleMarker {
@Override
public boolean equals(Object o) { return false; }
}
}