/* * Copyright 2016 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.io; 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.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; abstract class AbstractMappedStore implements BytesStore, Closeable { private static final int MAP_RO = 0; private static final int MAP_RW = 1; private static final int MAP_PV = 2; protected final MmapInfoHolder mmapInfoHolder; // retain to prevent GC. private final File file; private final RandomAccessFile raf; private final Cleaner cleaner; private final AtomicInteger refCount = new AtomicInteger(1); private final FileChannel.MapMode mode; private ObjectSerializer objectSerializer; AbstractMappedStore(MmapInfoHolder mmapInfoHolder, File file, FileChannel.MapMode mode, long startInFile, long size, ObjectSerializer objectSerializer) throws IOException { validateSize(size); this.file = file; this.mmapInfoHolder = mmapInfoHolder; this.mmapInfoHolder.setSize(size); this.objectSerializer = objectSerializer; this.mode = mode; try { this.raf = new RandomAccessFile(file, accesModeFor(mode)); resizeIfNeeded(startInFile, size); map(startInFile); this.cleaner = Cleaner.create(this, new Unmapper(mmapInfoHolder, raf)); } catch (Exception e) { throw wrap(e); } } protected static void validateSize(long size) { if (size <= 0 || size > 128L << 40) { throw new IllegalArgumentException("invalid size: " + size); } } 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 accesModeFor(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; } protected final void resizeIfNeeded(long startInFile, long newSize) throws IOException { if (file.getAbsolutePath().startsWith("/dev/")) { return; } if (startInFile > 0) { if (raf.length() >= startInFile + newSize) { return; } } else if (startInFile == 0) { if (raf.length() == newSize) { return; } } else { throw new IllegalArgumentException( "Start offset in file needs to be positive: " + startInFile); } if (mode != FileChannel.MapMode.READ_WRITE) { throw new IOException( "Cannot resize file to " + newSize + " as mode is not READ_WRITE"); } raf.setLength(startInFile + newSize); } protected final void map(long startInFile) throws IOException { try { mmapInfoHolder.setAddress( map0(raf.getChannel(), imodeFor(mode), startInFile, mmapInfoHolder.getSize())); } catch (Exception e) { throw wrap(e); } } protected final void unmapAndSyncToDisk() throws IOException { unmap0(mmapInfoHolder.getAddress(), mmapInfoHolder.getSize()); syncToDisk(); } public final void syncToDisk() throws IOException { raf.getChannel().force(true); } @Override public final ObjectSerializer objectSerializer() { return objectSerializer; } @Override public final long address() { return mmapInfoHolder.getAddress(); } @Override public final long size() { return mmapInfoHolder.getSize(); } @Override public final void free() { cleaner.clean(); } @Override public final void close() { free(); } @NotNull public final DirectBytes bytes() { return new DirectBytes(this, refCount); } @NotNull public final DirectBytes bytes(long offset, long length) { return new DirectBytes(this, refCount, offset, length); } public final File file() { return file; } static final class MmapInfoHolder { private long address, size; private volatile boolean locked; private void checkLock() { if (locked) { throw new IllegalStateException(); } } void lock() { this.locked = true; } long getAddress() { return address; } void setAddress(long address) { checkLock(); this.address = address; } long getSize() { return size; } void setSize(long size) { checkLock(); this.size = size; } } private static final class Unmapper implements Runnable { private final MmapInfoHolder mmapInfoHolder; private final RandomAccessFile raf; /* * This is not for synchronization (since calling this from multiple * threads through .free / .close is an user error!) but rather to make * sure that if an explicit cleanup was performed, the cleaner does not * retry cleaning up the resources. */ private volatile boolean cleanedUp; Unmapper(MmapInfoHolder mmapInfo, RandomAccessFile raf) { this.mmapInfoHolder = mmapInfo; this.raf = raf; } public void run() { if (cleanedUp) { return; } cleanedUp = true; try { unmap0(mmapInfoHolder.getAddress(), mmapInfoHolder.getSize()); raf.getChannel().force(true); // this also closes the underlying channel as per the documentation raf.close(); } catch (IOException e) { UnmapperLoggerHolder.LOGGER.log(Level.SEVERE, "An exception has occurred while cleaning up a MappedStore instance: " + e.getMessage(), e); } } } private static final class UnmapperLoggerHolder { private static final Logger LOGGER = Logger.getLogger(Unmapper.class.getName()); } }