/* * 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.model.constraints.NotNull; import org.slf4j.LoggerFactory; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteOrder; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.List; /** * This manages the full life cycle of a file and its mappings. */ public class MappedFile { private final String filename; private final long blockSize; private final long overlapSize; private final FileChannel fileChannel; private final List<MappedMemory> maps = new ArrayList<MappedMemory>(); // short list of the last two mappings. private volatile MappedMemory map0, map1; public MappedFile(String filename, long blockSize) throws FileNotFoundException { this(filename, blockSize, 0L); } public MappedFile(String filename, long blockSize, long overlapSize) throws FileNotFoundException { this.filename = filename; this.blockSize = blockSize; this.overlapSize = overlapSize; fileChannel = new RandomAccessFile(filename, "rw").getChannel(); } public static MappedByteBuffer getMap(@NotNull FileChannel fileChannel, long start, int size) throws IOException { for (int i = 1; ; i++) { try { // long startTime = System.nanoTime(); @SuppressWarnings("UnnecessaryLocalVariable") MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, start, size); map.order(ByteOrder.nativeOrder()); // long time = System.nanoTime() - startTime; // System.out.printf("Took %,d us to map %,d MB%n", time / 1000, size / 1024 / 1024); // System.out.println("Map size: "+size); return map; } catch (IOException e) { if (e.getMessage() == null || !e.getMessage().endsWith("user-mapped section open")) { throw e; } try { //noinspection BusyWait Thread.sleep(1); } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); throw e; } } } } public String name() { return filename; } public MappedMemory acquire(long index) throws IOException { MappedMemory map0 = this.map0, map1 = this.map1; if (map0 != null && map0.index() == index) { map0.reserve(); return map0; } if (map1 != null && map1.index() == index) { map1.reserve(); return map1; } return acquire0(index); } /** * gets the refCount a given index, or returns 0, if the there is no mapping for this index * * @param index * @return the mapping at this {@code index} * @throws IndexOutOfBoundsException if the index is out of range */ public int getRefCount(long index) { try { for (MappedMemory m : maps) { if (m.index() == index) return m.refCount(); } } catch (Exception e) { return 0; } return 0; } private synchronized MappedMemory acquire0(long index) throws IOException { if (map1 != null) map1.release(); map1 = map0; map0 = new MappedMemory(fileChannel.map(FileChannel.MapMode.READ_WRITE, index * blockSize, blockSize + overlapSize), index); map0.reserve(); maps.add(map0); // clean up duds. for (int i = maps.size() - 1; i >= 0; i--) { if (maps.get(i).refCount() <= 0) maps.remove(i); } return map0; } public synchronized void close() throws IOException { if (map1 != null) { map1.release(); map1 = null; } if (map0 != null) { map0.release(); map0 = null; } // clean up errant maps. int count = 0; for (int i = maps.size() - 1; i >= 0; i--) { if (maps.get(i).refCount() <= 0) { maps.get(i).close(); count++; } } if (count > 1) { LoggerFactory.getLogger(MappedFile.class).info("{} memory mappings left unreleased, num= {}", filename, count); } maps.clear(); fileChannel.close(); } public long size() { try { return fileChannel.size(); } catch (IOException e) { return 0; } } public long blockSize() { return blockSize; } public void release(MappedMemory mappedMemory) { if (mappedMemory.release()) { if (map0 == mappedMemory) map0 = null; if (map1 == mappedMemory) map1 = null; } } }