blob: 7fabfe0d4406b89ea64c99091e61764f91ad8227 [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.persistence.pagemem;
import java.util.function.BiConsumer;
import org.apache.ignite.internal.mem.IgniteOutOfMemoryException;
import org.apache.ignite.internal.pagemem.FullPageId;
import org.apache.ignite.internal.pagemem.PageIdUtils;
import org.apache.ignite.internal.util.GridLongList;
import org.apache.ignite.internal.util.GridUnsafe;
import org.apache.ignite.internal.util.typedef.internal.A;
import org.apache.ignite.internal.util.typedef.internal.U;
import static org.apache.ignite.IgniteSystemProperties.IGNITE_LONG_LONG_HASH_MAP_LOAD_FACTOR;
import static org.apache.ignite.IgniteSystemProperties.getFloat;
/**
*
*/
public class FullPageIdTable implements LoadedPagesMap {
/** Load factor. */
private static final float LOAD_FACTOR = getFloat(IGNITE_LONG_LONG_HASH_MAP_LOAD_FACTOR, 2.5f);
/** */
private static final int BYTES_PER_ENTRY = /*cache ID*/4 + /*partition tag*/4 + /*page ID*/8 + /*pointer*/8;
/** */
private static final FullPageId EMPTY_FULL_PAGE_ID = new FullPageId(0, 0);
/** */
private static final long EMPTY_PAGE_ID = EMPTY_FULL_PAGE_ID.pageId();
/** */
private static final int EMPTY_CACHE_GRP_ID = EMPTY_FULL_PAGE_ID.groupId();
/** */
private static final long REMOVED_PAGE_ID = 0x8000000000000000L;
/** */
private static final int REMOVED_CACHE_GRP_ID = 0;
/** */
private static final int EQUAL = 0;
/** */
private static final int EMPTY = 1;
/** */
private static final int REMOVED = -1;
/** */
private static final int NOT_EQUAL = 2;
/** */
private static final int OUTDATED = -3;
/** Tag offset from entry base. */
private static final int TAG_OFFSET = 4;
/** Page id offset from entry base. */
private static final int PAGE_ID_OFFSET = 8;
/** Value offset from entry base. */
private static final int VALUE_OFFSET = 16;
/** Max size, in elements. */
protected int capacity;
/** Maximum number of steps to try before failing. */
protected int maxSteps;
/** Pointer to the values array. */
protected long valPtr;
/**
* @return Estimated memory size required for this map to store the given number of elements.
*/
public static long requiredMemory(long elementCnt) {
assert LOAD_FACTOR != 0;
return ((long)(elementCnt * LOAD_FACTOR)) * BYTES_PER_ENTRY + 8;
}
/**
* @param addr Base address.
* @param len Allocated memory length.
* @param clear If {@code true}, then memory is considered dirty and will be cleared. Otherwise,
* map will assume that the given memory region is in valid state.
*/
public FullPageIdTable(long addr, long len, boolean clear) {
valPtr = addr;
capacity = (int)((len - 8) / BYTES_PER_ENTRY);
maxSteps = capacity;
if (clear)
clear();
}
/** {@inheritDoc} */
@Override public final int size() {
return GridUnsafe.getInt(valPtr);
}
/** {@inheritDoc} */
@Override public final int capacity() {
return capacity;
}
/** {@inheritDoc} */
@Override public long get(int grpId, long pageId, int reqVer, long absent, long outdated) {
assert assertKey(grpId, pageId);
int idx = getKey(grpId, pageId, reqVer, false);
if (idx == -1)
return absent;
if (idx == -2)
return outdated;
return valueAt(idx);
}
/** {@inheritDoc} */
@Override public long refresh(int grpId, long pageId, int ver) {
assert assertKey(grpId, pageId);
int idx = getKey(grpId, pageId, ver, true);
if (!(idx >= 0) || !(tagAt(idx) < ver)) {
A.ensure(idx >= 0, "[idx=" + idx + ", tag=" + ver + ", cacheId=" + grpId +
", pageId=" + U.hexLong(pageId) + ']');
A.ensure(tagAt(idx) < ver, "[idx=" + idx + ", tag=" + ver + ", cacheId=" + grpId +
", pageId=" + U.hexLong(pageId) + ", tagAtIdx=" + tagAt(idx) + ']');
}
setTagAt(idx, ver);
return valueAt(idx);
}
/** {@inheritDoc} */
@Override public void put(int grpId, long pageId, long val, int ver) {
assert assertKey(grpId, pageId);
int idx = putKey(grpId, pageId, ver);
setValueAt(idx, val);
}
/** {@inheritDoc} */
@Override public boolean remove(int grpId, long pageId) {
assert assertKey(grpId, pageId);
int idx = removeKey(grpId, pageId);
boolean valRmv = idx >= 0;
if (valRmv)
setValueAt(idx, 0);
return valRmv;
}
/** {@inheritDoc} */
@Override public ReplaceCandidate getNearestAt(final int idxStart) {
for (int i = idxStart; i < capacity + idxStart; i++) {
final int idx2 = normalizeIndex(i);
if (isValuePresentAt(idx2)) {
long base = entryBase(idx2);
int grpId = GridUnsafe.getInt(base);
int tag = GridUnsafe.getInt(base + TAG_OFFSET);
long pageId = GridUnsafe.getLong(base + PAGE_ID_OFFSET);
long val = GridUnsafe.getLong(base + VALUE_OFFSET);
return new ReplaceCandidate(tag, val, new FullPageId(pageId, grpId));
}
}
return null;
}
/** {@inheritDoc} */
@Override public GridLongList removeIf(int startIdxToClear, int endIdxToClear, KeyPredicate keyPred) {
assert endIdxToClear > startIdxToClear
: "Start and end indexes are not consistent: {" + startIdxToClear + ", " + endIdxToClear + "}";
int sz = endIdxToClear - startIdxToClear;
GridLongList list = new GridLongList(sz);
for (int idx = startIdxToClear; idx < endIdxToClear; idx++) {
long base = entryBase(idx);
int grpId = GridUnsafe.getInt(base);
long pageId = GridUnsafe.getLong(base + PAGE_ID_OFFSET);
if (isRemoved(grpId, pageId) || isEmpty(grpId, pageId))
continue;
if (!keyPred.test(grpId, pageId))
continue;
long res = valueAt(idx);
setRemoved(idx);
list.add(res);
}
return list;
}
/**
* @param cacheId Cache ID.
* @param pageId Page ID.
* @return Key index.
*/
private int putKey(int cacheId, long pageId, int tag) {
int step = 1;
int idx = U.safeAbs(FullPageId.hashCode(cacheId, pageId)) % capacity;
int foundIdx = -1;
int res;
do {
res = testKeyAt(idx, cacheId, pageId, tag);
if (res == OUTDATED) {
foundIdx = idx;
break;
}
else if (res == EMPTY) {
if (foundIdx == -1)
foundIdx = idx;
break;
}
else if (res == REMOVED) {
// Must continue search to the first empty slot to ensure there are no duplicate mappings.
if (foundIdx == -1)
foundIdx = idx;
}
else if (res == EQUAL)
return idx;
else
assert res == NOT_EQUAL;
idx++;
if (idx >= capacity)
idx -= capacity;
}
while (++step <= maxSteps);
if (foundIdx != -1) {
setKeyAt(foundIdx, cacheId, pageId);
setTagAt(foundIdx, tag);
if (res != OUTDATED)
incrementSize();
return foundIdx;
}
throw new IgniteOutOfMemoryException("No room for a new key");
}
/**
* @param cacheId Cache ID.
* @param pageId Page ID.
* @param refresh Refresh.
* @return Key index.
*/
private int getKey(int cacheId, long pageId, int tag, boolean refresh) {
int step = 1;
int index = U.safeAbs(FullPageId.hashCode(cacheId, pageId)) % capacity;
do {
long res = testKeyAt(index, cacheId, pageId, tag);
if (res == EQUAL)
return index;
else if (res == EMPTY)
return -1;
else if (res == OUTDATED)
return !refresh ? -2 : index;
else
assert res == REMOVED || res == NOT_EQUAL;
index++;
if (index >= capacity)
index -= capacity;
}
while (++step <= maxSteps);
return -1;
}
/**
* @param grpId Cache group ID.
* @param pageId Page ID.
* @return {@code true} if group & page id indicates cell has state 'Removed'.
*/
private boolean isRemoved(int grpId, long pageId) {
return pageId == REMOVED_PAGE_ID && grpId == REMOVED_CACHE_GRP_ID;
}
/**
* @param idx cell index, normalized.
* @return {@code true} if cell with index idx has state 'Empty'.
*/
private boolean isRemoved(int idx) {
long base = entryBase(idx);
int grpId = GridUnsafe.getInt(base);
long pageId = GridUnsafe.getLong(base + PAGE_ID_OFFSET);
return isRemoved(grpId, pageId);
}
/**
* Sets cell state to 'Removed' or to 'Empty' if next cell is already 'Empty'.
* @param idx cell index, normalized.
*/
private void setRemoved(int idx) {
setKeyAt(idx, REMOVED_CACHE_GRP_ID, REMOVED_PAGE_ID);
setValueAt(idx, 0);
}
/**
* @param grpId Cache group ID.
* @param pageId Page ID.
* @return {@code true} if group & page id indicates cell has state 'Empty'.
*/
private boolean isEmpty(int grpId, long pageId) {
return pageId == EMPTY_PAGE_ID && grpId == EMPTY_CACHE_GRP_ID;
}
/**
* @param idx cell index, normalized.
* @return {@code true} if cell with index idx has state 'Empty'.
*/
private boolean isEmpty(int idx) {
long base = entryBase(idx);
int grpId = GridUnsafe.getInt(base);
long pageId = GridUnsafe.getLong(base + PAGE_ID_OFFSET);
return isEmpty(grpId, pageId);
}
/**
* Sets cell state to 'Empty'.
*
* @param idx cell index, normalized.
*/
private void setEmpty(int idx) {
setKeyAt(idx, EMPTY_CACHE_GRP_ID, EMPTY_PAGE_ID);
setValueAt(idx, 0);
}
/**
* @param i index probably outsize internal array of cells.
* @return corresponding index inside cells array.
*/
private int normalizeIndex(int i) {
assert i < 2 * capacity;
return i < capacity ? i : i - capacity;
}
/**
* @param cacheId Cache ID.
* @param pageId Page ID.
* @return Key index.
*/
private int removeKey(int cacheId, long pageId) {
int step = 1;
int idx = U.safeAbs(FullPageId.hashCode(cacheId, pageId)) % capacity;
int foundIdx = -1;
do {
long res = testKeyAt(idx, cacheId, pageId, -1);
if (res == EQUAL || res == OUTDATED) {
foundIdx = idx;
break;
}
else if (res == EMPTY)
return -1;
else
assert res == REMOVED || res == NOT_EQUAL;
idx++;
if (idx >= capacity)
idx -= capacity;
}
while (++step <= maxSteps);
if (foundIdx != -1) {
setRemoved(foundIdx);
decrementSize();
}
return foundIdx;
}
/**
* @param index Entry index.
* @return Key value.
*/
private int testKeyAt(int index, int testCacheId, long testPageId, int testTag) {
long base = entryBase(index);
int grpId = GridUnsafe.getInt(base);
int tag = GridUnsafe.getInt(base + TAG_OFFSET);
long pageId = GridUnsafe.getLong(base + PAGE_ID_OFFSET);
if (isRemoved(grpId, pageId))
return REMOVED;
else if (pageId == testPageId && grpId == testCacheId && tag >= testTag)
return EQUAL;
else if (pageId == testPageId && grpId == testCacheId && tag < testTag)
return OUTDATED;
else if (isEmpty(grpId, pageId))
return EMPTY;
else
return NOT_EQUAL;
}
/**
* @param idx Index to test.
* @return {@code True} if value set for index.
*/
private boolean isValuePresentAt(final int idx) {
long base = entryBase(idx);
int grpId = GridUnsafe.getInt(base);
long pageId = GridUnsafe.getLong(base + PAGE_ID_OFFSET);
return !isRemoved(grpId, pageId) && !isEmpty(grpId, pageId);
}
/**
* @param grpId Cache group ID.
* @param pageId Page ID.
* @return {@code True} if checks succeeded.
*/
private boolean assertKey(int grpId, long pageId) {
assert grpId != EMPTY_CACHE_GRP_ID && PageIdUtils.isEffectivePageId(pageId):
"grpId=" + grpId + ", pageId=" + U.hexLong(pageId);
return true;
}
/**
* @param idx Entry index.
* @param grpId Cache group ID to write.
* @param pageId Page ID to write.
*/
private void setKeyAt(int idx, int grpId, long pageId) {
long base = entryBase(idx);
GridUnsafe.putInt(base, grpId);
GridUnsafe.putLong(base + PAGE_ID_OFFSET, pageId);
}
/**
* Gets distance from the ideal key location to the actual location if this entry is present in the table,
* or returns negative distance that needed to be scanned to determine the absence of the mapping.
*
* @param cacheId Cache ID.
* @param pageId Page ID.
* @param tag Tag.
* @return Distance scanned if the entry is found or negative distance scanned, if entry was not found.
*/
public int distanceFromIdeal(int cacheId, long pageId, int tag) {
int step = 1;
int index = U.safeAbs(FullPageId.hashCode(cacheId, pageId)) % capacity;
int scans = 0;
boolean found = false;
do {
scans++;
long res = testKeyAt(index, cacheId, pageId, tag);
if (res == EQUAL || res == OUTDATED) {
found = true;
break;
}
else if (res == EMPTY)
break;
else
assert res == REMOVED || res == NOT_EQUAL;
index++;
if (index >= capacity)
index -= capacity;
}
while (++step <= maxSteps);
return found ? scans : -scans;
}
/** {@inheritDoc} */
@Override public void forEach(BiConsumer<FullPageId, Long> act) {
for (int i = 0; i < capacity; i++) {
if (isValuePresentAt(i)) {
long base = entryBase(i);
int cacheId = GridUnsafe.getInt(base);
long pageId = GridUnsafe.getLong(base + PAGE_ID_OFFSET);
long val = GridUnsafe.getLong(base + VALUE_OFFSET);
act.accept(new FullPageId(pageId, cacheId), val);
}
}
}
/**
* @param index Entry index.
* @return Value.
*/
private long valueAt(int index) {
return GridUnsafe.getLong(entryBase(index) + VALUE_OFFSET);
}
/**
* @param index Entry index.
* @param value Value.
*/
private void setValueAt(int index, long value) {
GridUnsafe.putLong(entryBase(index) + VALUE_OFFSET, value);
}
/**
* @param idx Entry index.
* @return address of entry.
*/
private long entryBase(int idx) {
return valPtr + 8 + (long)idx * BYTES_PER_ENTRY;
}
/**
* @param idx Index to get tag for.
* @return Tag at the given index.
*/
private int tagAt(int idx) {
return GridUnsafe.getInt(entryBase(idx) + TAG_OFFSET);
}
/**
* @param idx Index to set tag for.
* @param tag Tag to set at the given index.
*/
private void setTagAt(int idx, int tag) {
GridUnsafe.putInt(entryBase(idx) + TAG_OFFSET, tag);
}
/**
*
*/
public void clear() {
GridUnsafe.setMemory(valPtr, (long)capacity * BYTES_PER_ENTRY + 8, (byte)0);
}
/**
*
*/
private void incrementSize() {
GridUnsafe.putInt(valPtr, GridUnsafe.getInt(valPtr) + 1);
}
/**
*
*/
private void decrementSize() {
GridUnsafe.putInt(valPtr, GridUnsafe.getInt(valPtr) - 1);
}
}