/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.activemq.artemis.core.io.mapped;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.ref.WeakReference;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.ArrayList;
import io.netty.util.internal.PlatformDependent;
final class MappedByteBufferCache implements AutoCloseable {
public static final int PAGE_SIZE = Integer.parseInt(System.getProperty("os_page_size", "4096"));
private static final Object FILE_LOCK = new Object();
private final RandomAccessFile raf;
private final FileChannel fileChannel;
private final long chunkBytes;
private final long overlapBytes;
private final ArrayList<WeakReference<MappedByteBuffer>> byteBuffers;
private final File file;
private final long mappedSize;
private boolean closed;
private MappedByteBufferCache(File file, RandomAccessFile raf, long chunkBytes, long overlapBytes, long alignment) {
this.byteBuffers = new ArrayList<>();
this.file = file;
this.raf = raf;
this.fileChannel = raf.getChannel();
this.chunkBytes = BytesUtils.align(chunkBytes, alignment);
this.overlapBytes = BytesUtils.align(overlapBytes, alignment);
this.closed = false;
this.mappedSize = this.chunkBytes + this.overlapBytes;
}
public static MappedByteBufferCache of(File file, long chunkSize, long overlapSize) throws FileNotFoundException {
final RandomAccessFile raf = new RandomAccessFile(file, "rw");
return new MappedByteBufferCache(file, raf, chunkSize, overlapSize, PAGE_SIZE);
}
public static boolean inside(long position, long mappedPosition, long mappedLimit) {
return mappedPosition <= position && position < mappedLimit;
}
public File file() {
return file;
}
public long chunkBytes() {
return chunkBytes;
}
public long overlapBytes() {
return overlapBytes;
}
public int indexFor(long position) {
final int chunk = (int) (position / chunkBytes);
return chunk;
}
public long mappedPositionFor(int index) {
return index * chunkBytes;
}
public long mappedLimitFor(long mappedPosition) {
return mappedPosition + chunkBytes;
}
public MappedByteBuffer acquireMappedByteBuffer(final int index) throws IOException, IllegalArgumentException, IllegalStateException {
if (closed)
throw new IOException("Closed");
if (index < 0)
throw new IOException("Attempt to access a negative index: " + index);
while (byteBuffers.size() <= index) {
byteBuffers.add(null);
}
final WeakReference<MappedByteBuffer> mbbRef = byteBuffers.get(index);
if (mbbRef != null) {
final MappedByteBuffer mbb = mbbRef.get();
if (mbb != null) {
return mbb;
}
}
return mapAndAcquire(index);
}
//METHOD BUILT TO SEPARATE THE SLOW PATH TO ENSURE INLINING OF THE MOST OCCURRING CASE
private MappedByteBuffer mapAndAcquire(final int index) throws IOException {
final long chunkStartPosition = mappedPositionFor(index);
final long minSize = chunkStartPosition + mappedSize;
if (fileChannel.size() < minSize) {
try {
synchronized (FILE_LOCK) {
try (FileLock lock = fileChannel.lock()) {
final long size = fileChannel.size();
if (size < minSize) {
raf.setLength(minSize);
}
}
}
} catch (IOException ioe) {
throw new IOException("Failed to resize to " + minSize, ioe);
}
}
final MappedByteBuffer mbb = fileChannel.map(FileChannel.MapMode.READ_WRITE, chunkStartPosition, mappedSize);
mbb.order(ByteOrder.nativeOrder());
byteBuffers.set(index, new WeakReference<>(mbb));
return mbb;
}
public long fileSize() throws IOException {
if (closed)
throw new IllegalStateException("Closed");
return fileChannel.size();
}
public void closeAndResize(long length) {
if (!closed) {
//TO_FIX: unmap in this way is not portable BUT required on Windows that can't resize a memmory mapped file!
for (final WeakReference<MappedByteBuffer> mbbRef : this.byteBuffers) {
if (mbbRef != null) {
final MappedByteBuffer mbb = mbbRef.get();
if (mbb != null) {
try {
PlatformDependent.freeDirectBuffer(mbb);
} catch (Throwable t) {
//TO_FIX: force releasing of the other buffers
}
}
}
}
this.byteBuffers.clear();
try {
if (fileChannel.size() != length) {
try {
synchronized (FILE_LOCK) {
try (FileLock lock = fileChannel.lock()) {
final long size = fileChannel.size();
if (size != length) {
raf.setLength(length);
}
}
}
} catch (IOException ioe) {
throw new IllegalStateException("Failed to resize to " + length, ioe);
}
}
} catch (IOException ex) {
throw new IllegalStateException("Failed to get size", ex);
} finally {
try {
fileChannel.close();
} catch (IOException e) {
throw new IllegalStateException("Failed to close channel", e);
} finally {
try {
raf.close();
} catch (IOException e) {
throw new IllegalStateException("Failed to close RandomAccessFile", e);
}
}
closed = true;
}
}
}
public boolean isClosed() {
return closed;
}
@Override
public void close() {
if (!closed) {
//TO_FIX: unmap in this way is not portable BUT required on Windows that can't resize a memory mapped file!
for (final WeakReference<MappedByteBuffer> mbbRef : this.byteBuffers) {
if (mbbRef != null) {
final MappedByteBuffer mbb = mbbRef.get();
if (mbb != null) {
try {
PlatformDependent.freeDirectBuffer(mbb);
} catch (Throwable t) {
//TO_FIX: force releasing of the other buffers
}
}
}
}
this.byteBuffers.clear();
try {
fileChannel.close();
} catch (IOException e) {
throw new IllegalStateException("Failed to close channel", e);
} finally {
try {
raf.close();
} catch (IOException e) {
throw new IllegalStateException("Failed to close RandomAccessFile", e);
}
}
closed = true;
}
}
}