net.openhft.chronicle.bytes.internal.SingleMappedFile Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2016-2022 chronicle.software
*
* https://chronicle.software
*
* 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.chronicle.bytes.internal;
import net.openhft.chronicle.bytes.*;
import net.openhft.chronicle.bytes.domestic.ReentrantFileLock;
import net.openhft.chronicle.core.Jvm;
import net.openhft.chronicle.core.OS;
import net.openhft.chronicle.core.annotation.NonNegative;
import net.openhft.chronicle.core.annotation.Positive;
import net.openhft.chronicle.core.io.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.channels.FileLock;
import static net.openhft.chronicle.core.io.Closeable.closeQuietly;
/**
* A memory mapped files which can be randomly accessed in a single chunk. It has no overlapping region to
* avoid wasting bytes at the end of file.
*/
@SuppressWarnings({"rawtypes", "restriction"})
public class SingleMappedFile extends MappedFile {
/**
* The RandomAccessFile for this mapped file
*/
@NotNull
private final RandomAccessFile raf;
/**
* The FileChannel for this mapped file
*/
private final FileChannel fileChannel;
/**
* The MappedBytesStore for this mapped file
*/
private final MappedBytesStore store;
/**
* The capacity of this mapped file
*/
private final long capacity;
/**
* Constructs a new SingleMappedFile with specified parameters.
*
* @param file the file to be mapped.
* @param raf the RandomAccessFile associated with the file.
* @param capacity the capacity of the mapped file.
* @param readOnly if the file is read-only.
* @throws IORuntimeException if any I/O error occurs.
*/
@SuppressWarnings("this-escape")
public SingleMappedFile(@NotNull final File file,
@NotNull final RandomAccessFile raf,
@NonNegative final long capacity,
@Positive final int pageSize,
final boolean readOnly)
throws IORuntimeException {
super(file, readOnly);
this.raf = raf;
this.fileChannel = raf.getChannel();
this.capacity = OS.mapAlign(capacity, PageUtil.getPageSize(file.getAbsolutePath()));
final MapMode mode = readOnly() ? MapMode.READ_ONLY : MapMode.READ_WRITE;
final long beginNs = System.nanoTime();
boolean ok = false;
try {
Jvm.doNotCloseOnInterrupt(getClass(), this.fileChannel);
resizeRafIfTooSmall(this.capacity);
final long address = OS.map(fileChannel, mode, 0, this.capacity, pageSize);
final MappedBytesStore mbs2 = MappedBytesStore.create(this, this, 0, address, this.capacity, this.capacity, pageSize);
mbs2.syncMode(DEFAULT_SYNC_MODE);
final long elapsedNs = System.nanoTime() - beginNs;
if (newChunkListener != null)
newChunkListener.onNewChunk(file().getPath(), 0, elapsedNs / 1000);
if (elapsedNs >= 2_000_000L)
Jvm.perf().on(getClass(), "Took " + elapsedNs / 1_000_000L + " ms to add mapping for " + file());
store = mbs2;
ok = true;
} catch (IOException ioe) {
throw new IORuntimeException(ioe);
} finally {
if (!ok)
close();
}
}
public SingleMappedFile(@NotNull final File file,
@NotNull final RandomAccessFile raf,
@NonNegative final long capacity,
final boolean readOnly)
throws IORuntimeException {
this(file, raf, capacity, PageUtil.getPageSize(file.getAbsolutePath()),readOnly);
}
/**
* Sets the synchronization mode for the underlying MappedBytesStore
*
* @param syncMode The synchronization mode to set
*/
@Override
public void syncMode(SyncMode syncMode) {
store.syncMode(syncMode);
}
/**
* Acquires the MappedBytesStore at the specified position
*
* @param owner The owner of the MappedBytesStore
* @param position The position to acquire
* @param oldByteStore The old byte store
* @param mappedBytesStoreFactory The factory to use when creating new MappedBytesStore
* @return The MappedBytesStore at the specified position
* @throws IllegalArgumentException If position is not zero
* @throws ClosedIllegalStateException If the resource has been released or closed.
* @throws ThreadingIllegalStateException If this resource was accessed by multiple threads in an unsafe way
*/
@NotNull
public MappedBytesStore acquireByteStore(
ReferenceOwner owner,
@NonNegative final long position,
BytesStore,?> oldByteStore,
@NotNull final MappedBytesStoreFactory mappedBytesStoreFactory)
throws IllegalArgumentException, ClosedIllegalStateException, ThreadingIllegalStateException {
if (position != 0)
throw new IllegalArgumentException();
store.reserve(owner);
return store;
}
@SuppressWarnings("try")
private void resizeRafIfTooSmall(@NonNegative final long minSize)
throws IOException {
Jvm.safepoint();
long size = fileChannel.size();
Jvm.safepoint();
if (size >= minSize || readOnly())
return;
// handle a possible race condition between processes.
try {
// A single JVM cannot lock a distinct canonical file more than once.
// We might have several MappedFile objects that maps to
// the same underlying file (possibly via hard or soft links)
// so we use the canonical path as a lock key
// Ensure exclusivity for any and all MappedFile objects handling
// the same canonical file.
synchronized (internalizedToken()) {
size = fileChannel.size();
if (size < minSize) {
final long beginNs = System.nanoTime();
try (FileLock ignore = ReentrantFileLock.lock(file(), fileChannel)) {
size = fileChannel.size();
if (size < minSize) {
Jvm.safepoint();
raf.setLength(minSize);
Jvm.safepoint();
}
}
final long elapsedNs = System.nanoTime() - beginNs;
if (elapsedNs >= 1_000_000L) {
Jvm.perf().on(getClass(), "Took " + elapsedNs / 1000L + " us to grow file " + file());
}
}
}
} catch (IOException ioe) {
throw new IOException("Failed to resize to " + minSize, ioe);
}
}
/**
* Releases resources held by this mapped file
*/
@Override
protected void performRelease() {
try {
final MappedBytesStore mbs = store;
if (mbs != null && RETAIN) {
// this MappedFile is the only referrer to the MappedBytesStore at this point,
// so ensure that it is released
try {
mbs.release(this);
} catch (ClosedIllegalStateException e) {
Jvm.debug().on(getClass(), e);
}
}
} finally {
closeQuietly(raf);
setClosed();
}
}
/**
* Returns a string representing the reference counts of this mapped file and its store
*
* @return A string representing the reference counts
*/
@NotNull
public String referenceCounts() {
@NotNull final StringBuilder sb = new StringBuilder();
sb.append("refCount: ").append(refCount());
@Nullable final MappedBytesStore mbs = store;
long count = 0;
if (mbs != null)
count = mbs.refCount();
sb.append(", ").append(count);
return sb.toString();
}
/**
* Returns the capacity of this mapped file
*
* @return The capacity of this mapped file
*/
public long capacity() {
return capacity;
}
/**
* Returns the size of chunks in this mapped file
*
* @return The size of chunks in this mapped file
*/
public long chunkSize() {
return capacity;
}
/**
* Returns the size of overlaps in this mapped file
*
* @return The size of overlaps in this mapped file
*/
public long overlapSize() {
return 0;
}
@Override
public NewChunkListener getNewChunkListener() {
return newChunkListener;
}
@Override
public void setNewChunkListener(final NewChunkListener listener) {
this.newChunkListener = listener;
}
/**
* Returns the actual size of this mapped file
*
* @return The actual size of this mapped file
* @throws IORuntimeException If an I/O error occurs
* @throws ClosedIllegalStateException If the resource has been released or closed.
* @throws ThreadingIllegalStateException If this resource was accessed by multiple threads in an unsafe way
*/
public long actualSize()
throws IORuntimeException, IllegalStateException {
boolean interrupted = Thread.interrupted();
try {
return fileChannelSize();
// this was seen once deep in the JVM.
} catch (ArrayIndexOutOfBoundsException aiooe) {
// try again.
return actualSize();
} catch (ClosedByInterruptException cbie) {
close();
interrupted = true;
throw new ClosedIllegalStateException("FileChannel closed", cbie);
} catch (IOException e) {
final boolean open = fileChannel.isOpen();
if (open) {
throw new IORuntimeException(e);
} else {
close();
throw new IllegalStateException(e);
}
} finally {
if (interrupted)
Thread.currentThread().interrupt();
}
}
private long fileChannelSize()
throws IOException, ArrayIndexOutOfBoundsException {
return fileChannel.size();
}
/**
* Returns the RandomAccessFile of this mapped file
*
* @return The RandomAccessFile of this mapped file
*/
@NotNull
public RandomAccessFile raf() {
return raf;
}
/**
* This finalize() is used to detect when a component is not released deterministically. It is not required to be run, but provides a warning
*/
@Override
protected void finalize()
throws Throwable {
warnAndReleaseIfNotReleased();
super.finalize();
}
@Override
protected boolean threadSafetyCheck(boolean isUsed) {
// component is thread safe
return true;
}
/**
* Locks a region of this mapped file
*
* @param position The position at which to start the locked region
* @param size The size of the locked region
* @param shared Whether the lock is shared
* @return A lock object representing the locked region
* @throws IOException If an I/O error occurs
*/
public FileLock lock(@NonNegative long position, @NonNegative long size, boolean shared) throws IOException {
return fileChannel.lock(position, size, shared);
}
/**
* Attempts to lock a region of this mapped file
*
* @param position The position at which to start the locked region
* @param size The size of the locked region
* @param shared Whether the lock is shared
* @return A lock object representing the locked region, or null if the region cannot be locked
* @throws IOException If an I/O error occurs
*/
public FileLock tryLock(@NonNegative long position, @NonNegative long size, boolean shared) throws IOException {
return fileChannel.tryLock(position, size, shared);
}
/**
* Returns the number of chunks in this mapped file
*
* @return The number of chunks in this mapped file
*/
public long chunkCount() {
return 1;
}
/**
* Fills the provided array with the number of chunks in this mapped file
*
* @param chunkCount The array to fill
*/
public void chunkCount(long[] chunkCount) {
chunkCount[0] = 1;
}
/**
* Creates a new SingleMappedBytes for this mapped file
*
* @return A new SingleMappedBytes
*/
@Override
public MappedBytes createBytesFor() {
return new SingleMappedBytes(this);
}
@Override
public void unmonitor() {
super.unmonitor();
Monitorable.unmonitor(store);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy