com.kolibrifx.plovercrest.server.internal.Window Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of plovercrest-server Show documentation
Show all versions of plovercrest-server Show documentation
Plovercrest server library.
The newest version!
/*
* Copyright (c) 2010-2017, KolibriFX AS. Licensed under the Apache License, version 2.0.
*/
package com.kolibrifx.plovercrest.server.internal;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.util.ArrayList;
import com.kolibrifx.plovercrest.client.PlovercrestException;
import com.kolibrifx.plovercrest.client.TableClosedException;
public final class Window {
private static class LocalBuffer {
ByteBuffer buf;
LocalBuffer(final MappedByteBuffer src) {
buf = src.duplicate();
}
void close() {
buf = null;
}
}
private final String fileName;
private final FileChannel channel;
private final long startOffset;
private final long windowSize;
private boolean closed;
private MappedByteBuffer mappedFile;
private ThreadLocal threadLocalBuffer;
private final Object mappedFileLock = new Object();
private final ArrayList allLocalBuffers;
Window(final String fileName, final FileChannel channel, final long offset, final long windowSize) {
if (windowSize < 8) {
throw new IllegalArgumentException("Window size must be at least 8 bytes, was " + windowSize);
}
if (windowSize > Integer.MAX_VALUE) {
throw new IllegalArgumentException("Window size cannot be larger than Integer.MAX_VALUE, was "
+ windowSize);
}
this.fileName = fileName;
this.channel = channel;
this.startOffset = offset;
this.windowSize = windowSize;
this.allLocalBuffers = new ArrayList();
remap();
}
public void remap() {
synchronized (mappedFileLock) {
if (closed) {
return;
}
try {
mappedFile = channel.map(MapMode.READ_WRITE, startOffset,
Math.min(channel.size() - startOffset, windowSize));
allLocalBuffers.clear();
threadLocalBuffer = new ThreadLocal() {
@Override
protected LocalBuffer initialValue() {
synchronized (mappedFileLock) {
if (closed) {
throw new TableClosedException(fileName);
}
final LocalBuffer localBuf = new LocalBuffer(mappedFile);
allLocalBuffers.add(localBuf);
return localBuf;
}
}
};
} catch (final IOException e) {
throw new PlovercrestException("Failed to remap file", e);
}
}
}
public long writeBuffer(final long offset, final ByteBuffer bytes) {
final ByteBuffer threadBuf = threadLocalBuffer.get().buf;
final long length = bytes.remaining();
try {
threadBuf.position((int) (offset - startOffset));
threadBuf.put(bytes);
return length - bytes.remaining();
} catch (final NullPointerException e) {
throw new TableClosedException(fileName);
}
}
public long writeLong(final long offset, final long value) {
try {
mappedFile.putLong((int) (offset - startOffset), value);
return 8;
} catch (final NullPointerException e) {
throw new TableClosedException(fileName);
}
}
public long writeInt(final long offset, final int value) {
try {
mappedFile.putInt((int) (offset - startOffset), value);
return 4;
} catch (final NullPointerException e) {
throw new TableClosedException(fileName);
}
}
public long startOffset() {
return startOffset;
}
public long endOffset() {
return startOffset + windowSize;
}
public long readBuffer(final long offset, final ByteBuffer buffer) {
final ByteBuffer threadBuf = threadLocalBuffer.get().buf;
if (threadBuf == null) {
throw new TableClosedException(fileName);
}
threadBuf.position((int) (offset - startOffset));
final int pos = buffer.position();
try {
final int limit = threadBuf.limit();
threadBuf.limit(threadBuf.position() + buffer.remaining());
buffer.put(threadBuf);
threadBuf.limit(limit);
return buffer.position() - pos;
} catch (final NullPointerException e) {
throw new TableClosedException(fileName);
}
}
public long readLong(final long offset) {
try {
return mappedFile.getLong((int) (offset - startOffset));
} catch (final NullPointerException e) {
throw new TableClosedException(fileName);
}
}
public int readInt(final long offset) {
try {
return mappedFile.getInt((int) (offset - startOffset));
} catch (final NullPointerException e) {
throw new TableClosedException(fileName);
}
}
public int remaining(final long offset) {
try {
return mappedFile.limit() - (int) (offset - startOffset);
} catch (final NullPointerException e) {
throw new TableClosedException(fileName);
}
}
/**
* Is the given offset within this window's range?
*
* Note: does NOT check the file size, only the window size.
*/
public boolean isWithinRange(final long offset) {
return (startOffset <= offset) && (offset < startOffset + windowSize);
}
/**
* Forces any changes made to be written to disk.
*/
public void force() {
mappedFile.force();
}
private void unmap(final boolean flush) {
// Note: mappedFileLock should be held here
// This is part of a workaround for the lack of a MappedByteBuffer.unmap() function.
// We make sure all references to mappedFile, and its thread-local duplicate() results,
// are set to null, so it can be garbage collected as soon as possible.
// System.gc() is not called here, since that could become a performance issue.
// http://bugs.sun.com/view_bug.do?bug_id=4724038
// http://bugs.sun.com/view_bug.do?bug_id=4715154
if (mappedFile != null) {
if (flush) {
mappedFile.force();
}
mappedFile = null;
}
for (final LocalBuffer localBuf : allLocalBuffers) {
localBuf.close();
}
allLocalBuffers.clear();
}
/**
* The closest we get to unmap(): set the underlying MappedByteBuffer to null.
*/
public void close(final boolean flush) {
synchronized (mappedFileLock) {
closed = true;
unmap(flush);
}
}
}