/*
* Copyright Terracotta, Inc.
*
* 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.ehcache.clustered.server.offheap;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.ehcache.clustered.common.internal.store.Chain;
import org.ehcache.clustered.common.internal.store.ServerStore;
import org.ehcache.clustered.server.KeySegmentMapper;
import org.ehcache.clustered.server.ServerStoreEvictionListener;
import org.ehcache.clustered.server.state.ResourcePageSource;
import org.terracotta.offheapstore.MapInternals;
import org.terracotta.offheapstore.exceptions.OversizeMappingException;
import org.terracotta.offheapstore.paging.PageSource;
import static org.terracotta.offheapstore.util.MemoryUnit.BYTES;
import static org.terracotta.offheapstore.util.MemoryUnit.KILOBYTES;
import static org.terracotta.offheapstore.util.MemoryUnit.MEGABYTES;
public class OffHeapServerStore implements ServerStore, MapInternals {
private static final long MAX_PAGE_SIZE_IN_KB = KILOBYTES.convert(8, MEGABYTES);
private final List<OffHeapChainMap<Long>> segments;
private final KeySegmentMapper mapper;
OffHeapServerStore(PageSource source, KeySegmentMapper mapper) {
this.mapper = mapper;
segments = new ArrayList<OffHeapChainMap<Long>>(mapper.getSegments());
for (int i = 0; i < mapper.getSegments(); i++) {
segments.add(new OffHeapChainMap<Long>(source, LongPortability.INSTANCE, KILOBYTES.toBytes(4), MEGABYTES.toBytes(8), false));
}
}
public OffHeapServerStore(ResourcePageSource source, KeySegmentMapper mapper) {
this.mapper = mapper;
segments = new ArrayList<OffHeapChainMap<Long>>(mapper.getSegments());
long maxSize = getMaxSize(source.getPool().getSize());
for (int i = 0; i < mapper.getSegments(); i++) {
segments.add(new OffHeapChainMap<Long>(source, LongPortability.INSTANCE, KILOBYTES.toBytes(4), (int) KILOBYTES.toBytes(maxSize), false));
}
}
public List<OffHeapChainMap<Long>> getSegments() {
return segments;
}
static long getMaxSize(long poolSize) {
long l = Long.highestOneBit(poolSize);
long sizeInKb = KILOBYTES.convert(l, BYTES);
long maxSize = sizeInKb >> 5;
if (maxSize >= MAX_PAGE_SIZE_IN_KB) {
maxSize = MAX_PAGE_SIZE_IN_KB;
}
return maxSize;
}
public void setEvictionListener(final ServerStoreEvictionListener listener) {
OffHeapChainMap.ChainMapEvictionListener<Long> chainMapEvictionListener = new OffHeapChainMap.ChainMapEvictionListener<Long>() {
@Override
public void onEviction(Long key) {
listener.onEviction(key);
}
};
for (OffHeapChainMap<Long> segment : segments) {
segment.setEvictionListener(chainMapEvictionListener);
}
}
@Override
public Chain get(long key) {
return segmentFor(key).get(key);
}
@Override
public void append(long key, ByteBuffer payLoad) {
try {
segmentFor(key).append(key, payLoad);
} catch (OversizeMappingException e) {
if (handleOversizeMappingException(key)) {
try {
segmentFor(key).append(key, payLoad);
return;
} catch (OversizeMappingException ex) {
//ignore
}
}
writeLockAll();
try {
do {
try {
segmentFor(key).append(key, payLoad);
return;
} catch (OversizeMappingException ex) {
e = ex;
}
} while (handleOversizeMappingException(key));
throw e;
} finally {
writeUnlockAll();
}
}
}
@Override
public Chain getAndAppend(long key, ByteBuffer payLoad) {
try {
return segmentFor(key).getAndAppend(key, payLoad);
} catch (OversizeMappingException e) {
if (handleOversizeMappingException(key)) {
try {
return segmentFor(key).getAndAppend(key, payLoad);
} catch (OversizeMappingException ex) {
//ignore
}
}
writeLockAll();
try {
do {
try {
return segmentFor(key).getAndAppend(key, payLoad);
} catch (OversizeMappingException ex) {
e = ex;
}
} while (handleOversizeMappingException(key));
throw e;
} finally {
writeUnlockAll();
}
}
}
@Override
public void replaceAtHead(long key, Chain expect, Chain update) {
try {
segmentFor(key).replaceAtHead(key, expect, update);
} catch (OversizeMappingException e) {
if (handleOversizeMappingException(key)) {
try {
segmentFor(key).replaceAtHead(key, expect, update);
return;
} catch (OversizeMappingException ex) {
//ignore
}
}
writeLockAll();
try {
do {
try {
segmentFor(key).replaceAtHead(key, expect, update);
return;
} catch (OversizeMappingException ex) {
e = ex;
}
} while (handleOversizeMappingException(key));
throw e;
} finally {
writeUnlockAll();
}
}
}
public void put(long key, Chain chain) {
try {
segmentFor(key).put(key, chain);
} catch (OversizeMappingException e) {
if (handleOversizeMappingException(key)) {
try {
segmentFor(key).put(key, chain);
} catch (OversizeMappingException ex) {
//ignore
}
}
writeLockAll();
try {
do {
try {
segmentFor(key).put(key, chain);
} catch (OversizeMappingException ex) {
e = ex;
}
} while (handleOversizeMappingException(key));
throw e;
} finally {
writeUnlockAll();
}
}
}
@Override
public void clear() {
for (OffHeapChainMap<Long> segment : segments) {
segment.clear();
}
}
OffHeapChainMap<Long> segmentFor(long key) {
return segments.get(mapper.getSegmentForKey(key));
}
private void writeLockAll() {
for (OffHeapChainMap<Long> s : segments) {
s.writeLock().lock();
}
}
private void writeUnlockAll() {
for (OffHeapChainMap<Long> s : segments) {
s.writeLock().unlock();
}
}
boolean handleOversizeMappingException(long hash) {
boolean evicted = false;
OffHeapChainMap<Long> target = segmentFor(hash);
for (OffHeapChainMap<Long> s : segments) {
if (s != target) {
evicted |= s.shrink();
}
}
return evicted;
}
public void close() {
writeLockAll();
try {
clear();
} finally {
writeUnlockAll();
}
segments.clear();
}
// stats
@Override
public long getAllocatedMemory() {
long total = 0L;
for (MapInternals segment : segments) {
total += segment.getAllocatedMemory();
}
return total;
}
@Override
public long getOccupiedMemory() {
long total = 0L;
for (MapInternals segment : segments) {
total += segment.getOccupiedMemory();
}
return total;
}
@Override
public long getDataAllocatedMemory() {
long total = 0L;
for (MapInternals segment : segments) {
total += segment.getDataAllocatedMemory();
}
return total;
}
@Override
public long getDataOccupiedMemory() {
long total = 0L;
for (MapInternals segment : segments) {
total += segment.getDataOccupiedMemory();
}
return total;
}
@Override
public long getDataSize() {
long total = 0L;
for (MapInternals segment : segments) {
total += segment.getDataSize();
}
return total;
}
@Override
public long getSize() {
long total = 0L;
for (MapInternals segment : segments) {
total += segment.getSize();
}
return total;
}
@Override
public long getTableCapacity() {
long total = 0L;
for (MapInternals segment : segments) {
total += segment.getTableCapacity();
}
return total;
}
@Override
public long getUsedSlotCount() {
long total = 0L;
for (MapInternals segment : segments) {
total += segment.getUsedSlotCount();
}
return total;
}
@Override
public long getRemovedSlotCount() {
long total = 0L;
for (MapInternals segment : segments) {
total += segment.getRemovedSlotCount();
}
return total;
}
@Override
public int getReprobeLength() {
int total = 0;
for (MapInternals segment : segments) {
total += segment.getReprobeLength();
}
return total;
}
@Override
public long getVitalMemory() {
long total = 0L;
for (MapInternals segment : segments) {
total += segment.getVitalMemory();
}
return total;
}
@Override
public long getDataVitalMemory() {
long total = 0L;
for (MapInternals segment : segments) {
total += segment.getDataVitalMemory();
}
return total;
}
}