/*
* Copyright 2015 Terracotta, Inc., a Software AG company.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terracotta.offheapstore.storage;
import java.nio.ByteBuffer;
import java.util.concurrent.locks.Lock;
import org.terracotta.offheapstore.paging.OffHeapStorageArea;
import org.terracotta.offheapstore.paging.PageSource;
import org.terracotta.offheapstore.storage.portability.Portability;
import org.terracotta.offheapstore.storage.portability.WriteContext;
import org.terracotta.offheapstore.util.DebuggingUtils;
import org.terracotta.offheapstore.util.Factory;
import static org.terracotta.offheapstore.util.ByteBufferUtils.totalLength;
/**
* A generic ByteBuffer based key/value store.
* <p>
* This storage engine implementation uses {@link Portability} instances to
* convert key/value instances in to ByteBuffers. The content of these
* ByteBuffers are then stored in slices of a single large data area.
*
* @param <K> key type handled by this engine
* @param <V> value type handled by this engine
*
* @author Chris Dennis
*/
public class OffHeapBufferStorageEngine<K, V> extends PortabilityBasedStorageEngine<K, V> implements OffHeapStorageArea.Owner {
private static final int KEY_HASH_OFFSET = 0;
private static final int KEY_LENGTH_OFFSET = 4;
private static final int VALUE_LENGTH_OFFSET = 8;
private static final int DATA_OFFSET = 12;
private static final int HEADER_SIZE = DATA_OFFSET;
/*
* Future design ideas:
*
* We might want to look in to supporting shrinking of the data areas, in case
* our storage demands change (e.g. due to a change in key distribution, or
* change in value types).
*/
public static <K, V> Factory<OffHeapBufferStorageEngine<K, V>> createFactory(final PointerSize width, final PageSource source, final int pageSize, final Portability<? super K> keyPortability, final Portability<? super V> valuePortability, final boolean thief, final boolean victim) {
return new Factory<OffHeapBufferStorageEngine<K, V>>() {
@Override
public OffHeapBufferStorageEngine<K, V> newInstance() {
return new OffHeapBufferStorageEngine<K, V>(width, source, pageSize, keyPortability, valuePortability, thief, victim);
}
};
}
public static <K, V> Factory<OffHeapBufferStorageEngine<K, V>> createFactory(final PointerSize width, final PageSource source, final int pageSize, final Portability<? super K> keyPortability, final Portability<? super V> valuePortability, final boolean thief, final boolean victim, final float compressThreshold) {
return new Factory<OffHeapBufferStorageEngine<K, V>>() {
@Override
public OffHeapBufferStorageEngine<K, V> newInstance() {
return new OffHeapBufferStorageEngine<K, V>(width, source, pageSize, keyPortability, valuePortability, thief, victim, compressThreshold);
}
};
}
public static <K, V> Factory<OffHeapBufferStorageEngine<K, V>> createFactory(final PointerSize width, final PageSource source, final int initialPageSize, final int maximalPageSize, final Portability<? super K> keyPortability, final Portability<? super V> valuePortability, final boolean thief, final boolean victim) {
return new Factory<OffHeapBufferStorageEngine<K, V>>() {
@Override
public OffHeapBufferStorageEngine<K, V> newInstance() {
return new OffHeapBufferStorageEngine<K, V>(width, source, initialPageSize, maximalPageSize, keyPortability, valuePortability, thief, victim);
}
};
}
public static <K, V> Factory<OffHeapBufferStorageEngine<K, V>> createFactory(final PointerSize width, final PageSource source, final int initialPageSize, final int maximalPageSize, final Portability<? super K> keyPortability, final Portability<? super V> valuePortability, final boolean thief, final boolean victim, final float compressThreshold) {
return new Factory<OffHeapBufferStorageEngine<K, V>>() {
@Override
public OffHeapBufferStorageEngine<K, V> newInstance() {
return new OffHeapBufferStorageEngine<K, V>(width, source, initialPageSize, maximalPageSize, keyPortability, valuePortability, thief, victim, compressThreshold);
}
};
}
protected final OffHeapStorageArea storageArea;
protected volatile Owner owner;
/**
* Creates a storage engine using the given page source, and portabilities.
*
* @param width {@code int} or {@code long} based engine
* @param source allocator used for storage allocation
* @param pageSize internal (constant) page size
* @param keyPortability key type portability
* @param valuePortability value type portability
*/
public OffHeapBufferStorageEngine(PointerSize width, PageSource source, int pageSize, Portability<? super K> keyPortability, Portability<? super V> valuePortability) {
this(width, source, pageSize, pageSize, keyPortability, valuePortability);
}
public OffHeapBufferStorageEngine(PointerSize width, PageSource source, int pageSize, Portability<? super K> keyPortability, Portability<? super V> valuePortability, float compressThreshold) {
this(width, source, pageSize, pageSize, keyPortability, valuePortability, compressThreshold);
}
public OffHeapBufferStorageEngine(PointerSize width, PageSource source, int initialPageSize, int maximalPageSize, Portability<? super K> keyPortability, Portability<? super V> valuePortability) {
this(width, source, initialPageSize, maximalPageSize, keyPortability, valuePortability, false, false);
}
public OffHeapBufferStorageEngine(PointerSize width, PageSource source, int initialPageSize, int maximalPageSize, Portability<? super K> keyPortability, Portability<? super V> valuePortability, float compressThreshold) {
this(width, source, initialPageSize, maximalPageSize, keyPortability, valuePortability, false, false, compressThreshold);
}
public OffHeapBufferStorageEngine(PointerSize width, PageSource source, int pageSize, Portability<? super K> keyPortability, Portability<? super V> valuePortability, boolean thief, boolean victim) {
this(width, source, pageSize, pageSize, keyPortability, valuePortability, thief, victim);
}
public OffHeapBufferStorageEngine(PointerSize width, PageSource source, int pageSize, Portability<? super K> keyPortability, Portability<? super V> valuePortability, boolean thief, boolean victim, float compressThreshold) {
this(width, source, pageSize, pageSize, keyPortability, valuePortability, thief, victim, compressThreshold);
}
public OffHeapBufferStorageEngine(PointerSize width, PageSource source, int initialPageSize, int maximalPageSize, Portability<? super K> keyPortability, Portability<? super V> valuePortability, boolean thief, boolean victim) {
super(keyPortability, valuePortability);
this.storageArea = new OffHeapStorageArea(width, this, source, initialPageSize, maximalPageSize, thief, victim);
}
public OffHeapBufferStorageEngine(PointerSize width, PageSource source, int initialPageSize, int maximalPageSize, Portability<? super K> keyPortability, Portability<? super V> valuePortability, boolean thief, boolean victim, float compressThreshold) {
super(keyPortability, valuePortability);
this.storageArea = new OffHeapStorageArea(width, this, source, initialPageSize, maximalPageSize, thief, victim, compressThreshold);
}
@Override
protected void clearInternal() {
storageArea.clear();
}
@Override
protected void free(long address) {
storageArea.free(address);
}
@Override
public ByteBuffer readKeyBuffer(long address) {
int length = storageArea.readInt(address + KEY_LENGTH_OFFSET);
return storageArea.readBuffer(address + DATA_OFFSET, length).asReadOnlyBuffer();
}
@Override
protected WriteContext getKeyWriteContext(long address) {
int keyLength = storageArea.readInt(address + KEY_LENGTH_OFFSET);
return getWriteContext(address + DATA_OFFSET, keyLength);
}
@Override
public ByteBuffer readValueBuffer(long address) {
int keyLength = storageArea.readInt(address + KEY_LENGTH_OFFSET);
int valueLength = storageArea.readInt(address + VALUE_LENGTH_OFFSET);
return storageArea.readBuffer(address + DATA_OFFSET + keyLength, valueLength).asReadOnlyBuffer();
}
@Override
protected WriteContext getValueWriteContext(long address) {
int keyLength = storageArea.readInt(address + KEY_LENGTH_OFFSET);
int valueLength = storageArea.readInt(address + VALUE_LENGTH_OFFSET);
return getWriteContext(address + DATA_OFFSET + keyLength, valueLength);
}
private WriteContext getWriteContext(final long address, final int max) {
return new WriteContext() {
@Override
public void setLong(int offset, long value) {
if (offset < 0 || offset > max) {
throw new IllegalArgumentException();
} else {
storageArea.writeLong(address + offset, value);
}
}
@Override
public void flush() {
//no-op
}
};
}
@Override
protected Long writeMappingBuffers(ByteBuffer keyBuffer, ByteBuffer valueBuffer, int hash) {
int keyLength = keyBuffer.remaining();
int valueLength = valueBuffer.remaining();
long address = storageArea.allocate(keyLength + valueLength + HEADER_SIZE);
if (address >= 0) {
storageArea.writeInt(address + KEY_HASH_OFFSET, hash);
storageArea.writeInt(address + KEY_LENGTH_OFFSET, keyLength);
storageArea.writeInt(address + VALUE_LENGTH_OFFSET, valueLength);
storageArea.writeBuffer(address + DATA_OFFSET, keyBuffer);
storageArea.writeBuffer(address + DATA_OFFSET + keyLength, valueBuffer);
return address;
} else {
return null;
}
}
@Override
protected Long writeMappingBuffersGathering(ByteBuffer[] keyBuffers, ByteBuffer[] valueBuffers, int hash) {
int keyLength = totalLength(keyBuffers);
int valueLength = totalLength(valueBuffers);
long address = storageArea.allocate(keyLength + valueLength + HEADER_SIZE);
if (address >= 0) {
storageArea.writeInt(address + KEY_HASH_OFFSET, hash);
storageArea.writeInt(address + KEY_LENGTH_OFFSET, keyLength);
storageArea.writeInt(address + VALUE_LENGTH_OFFSET, valueLength);
storageArea.writeBuffers(address + DATA_OFFSET, keyBuffers);
storageArea.writeBuffers(address + DATA_OFFSET + keyLength, valueBuffers);
return address;
} else {
return null;
}
}
@Override
public long getAllocatedMemory() {
return storageArea.getAllocatedMemory();
}
@Override
public long getOccupiedMemory() {
return storageArea.getOccupiedMemory();
}
@Override
public long getVitalMemory() {
return getAllocatedMemory();
}
@Override
public long getDataSize() {
//TODO This is an overestimate.
return getOccupiedMemory();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("OffHeapBufferStorageEngine ");
sb.append("allocated=").append(DebuggingUtils.toBase2SuffixedString(getAllocatedMemory())).append("B ");
sb.append("occupied=").append(DebuggingUtils.toBase2SuffixedString(getOccupiedMemory())).append("B\n");
sb.append("Storage Area: ").append(storageArea);
return sb.toString();
}
@Override
public void destroy() {
storageArea.destroy();
}
@Override
public boolean shrink() {
return storageArea.shrink();
}
@Override
public boolean evictAtAddress(long address, boolean shrink) {
int hash = storageArea.readInt(address + KEY_HASH_OFFSET);
int slot = owner.getSlotForHashAndEncoding(hash, address, ~0);
return owner.evict(slot, shrink);
}
@Override
public boolean isThief() {
return owner.isThiefForTableAllocations();
}
@Override
public int readKeyHash(long encoding) {
return storageArea.readInt(encoding + KEY_HASH_OFFSET);
}
@Override
public boolean moved(long from, long to) {
return owner.updateEncoding(readKeyHash(to), from, to, ~0);
}
@Override
public int sizeOf(long address) {
int keyLength = storageArea.readInt(address + KEY_LENGTH_OFFSET);
int valueLength = storageArea.readInt(address + VALUE_LENGTH_OFFSET);
return DATA_OFFSET + keyLength + valueLength;
}
@Override
public void bind(Owner m) {
if (owner != null) {
throw new AssertionError();
}
owner = m;
}
@Override
public Lock writeLock() {
return owner.writeLock();
}
}