/*
* Copyright 2014 Higher Frequency Trading
*
* http://www.higherfrequencytrading.com
*
* 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 net.openhft.lang.arena;
import net.openhft.lang.io.BytesStore;
import net.openhft.lang.io.DirectBytes;
import net.openhft.lang.io.serialization.ObjectSerializer;
import net.openhft.lang.model.constraints.NotNull;
import sun.misc.Cleaner;
import sun.nio.ch.FileChannelImpl;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class MappedArenaStores implements Closeable {
private static final int MAP_RO = 0;
private static final int MAP_RW = 1;
private static final int MAP_PV = 2;
// retain to prevent GC.
private final File file;
private final RandomAccessFile randomAccessFile;
private final FileChannel fileChannel;
private final FileChannel.MapMode mode;
private final List<MappedArenaStore> storeList = new ArrayList<MappedArenaStore>();
private final ObjectSerializer objectSerializer;
public MappedArenaStores(File file, FileChannel.MapMode mode, long minSize, ObjectSerializer objectSerializer) throws IOException {
if (minSize < 0 || minSize > 128L << 40) {
throw new IllegalArgumentException("invalid minSize: " + minSize);
}
this.file = file;
this.objectSerializer = objectSerializer;
try {
randomAccessFile = new RandomAccessFile(file, accessModeFor(mode));
this.mode = mode;
this.fileChannel = randomAccessFile.getChannel();
storeList.add(new MappedArenaStore(0, minSize));
} catch (Exception e) {
throw wrap(e);
}
}
public DirectBytes acquire(long offset, long size) throws IOException {
MappedArenaStore mas = acquireMAS(offset, size);
return mas.bytes(offset - mas.offset, size);
}
private MappedArenaStore acquireMAS(long offset, long size) throws IOException {
long end = offset + size;
for (MappedArenaStore store : storeList) {
if (store.offset >= offset && store.end <= end)
return store;
}
try {
MappedArenaStore store = new MappedArenaStore(offset, size);
storeList.add(store);
return store;
} catch (Exception e) {
throw wrap(e);
}
}
@Override
public void close() throws IOException {
fileChannel.close();
for (MappedArenaStore store : storeList) {
store.free();
}
}
class MappedArenaStore implements BytesStore {
private final long address;
private final Cleaner cleaner;
private final AtomicInteger refCount = new AtomicInteger(1);
final long offset, size, end;
MappedArenaStore(long offset, long size) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, IOException {
this.offset = offset;
this.size = size;
this.end = offset + size;
if (randomAccessFile.length() < end) {
if (mode != FileChannel.MapMode.READ_WRITE) {
throw new IOException("Cannot resize file to " + end + " as mode is not READ_WRITE");
}
randomAccessFile.setLength(end);
}
this.address = map0(fileChannel, imodeFor(mode), offset, size);
this.cleaner = Cleaner.create(this, new Unmapper(address, size, fileChannel));
}
@Override
public ObjectSerializer objectSerializer() {
return objectSerializer;
}
@Override
public long address() {
return address;
}
@Override
public long size() {
return size;
}
@Override
public void free() {
cleaner.clean();
}
@NotNull
public DirectBytes bytes() {
return new DirectBytes(this, refCount);
}
@NotNull
public DirectBytes bytes(long offset, long length) {
return new DirectBytes(this, refCount, offset, length);
}
@Override
public File file() {
return file;
}
}
private static long map0(FileChannel fileChannel, int imode, long start, long size) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Method map0 = fileChannel.getClass().getDeclaredMethod("map0", int.class, long.class, long.class);
map0.setAccessible(true);
return (Long) map0.invoke(fileChannel, imode, start, size);
}
private static void unmap0(long address, long size) throws IOException {
try {
Method unmap0 = FileChannelImpl.class.getDeclaredMethod("unmap0", long.class, long.class);
unmap0.setAccessible(true);
unmap0.invoke(null, address, size);
} catch (Exception e) {
throw wrap(e);
}
}
private static IOException wrap(Throwable e) {
if (e instanceof InvocationTargetException)
e = e.getCause();
if (e instanceof IOException)
return (IOException) e;
return new IOException(e);
}
private static String accessModeFor(FileChannel.MapMode mode) {
return mode == FileChannel.MapMode.READ_WRITE ? "rw" : "r";
}
private static int imodeFor(FileChannel.MapMode mode) {
int imode = -1;
if (mode == FileChannel.MapMode.READ_ONLY)
imode = MAP_RO;
else if (mode == FileChannel.MapMode.READ_WRITE)
imode = MAP_RW;
else if (mode == FileChannel.MapMode.PRIVATE)
imode = MAP_PV;
assert (imode >= 0);
return imode;
}
static class Unmapper implements Runnable {
private final long size;
private final FileChannel channel;
private volatile long address;
Unmapper(long address, long size, FileChannel channel) {
assert (address != 0);
this.address = address;
this.size = size;
this.channel = channel;
}
public void run() {
if (address == 0)
return;
try {
unmap0(address, size);
address = 0;
if (channel.isOpen()) {
channel.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}