Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve exception handling for readLongs/readInts/readFloats in ByteBufferIndexInput #12944

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -164,24 +164,24 @@ public void readLongs(long[] dst, int offset, int length) throws IOException {
// is no ByteBuffer#getLongs to read multiple longs at once. So we use the
// below trick in order to be able to leverage LongBuffer#get(long[]) to
// read multiple longs at once with as little overhead as possible.
if (curLongBufferViews == null) {
// readLELongs is only used for postings today, so we compute the long
// views lazily so that other data-structures don't have to pay for the
// associated initialization/memory overhead.
curLongBufferViews = new LongBuffer[Long.BYTES];
for (int i = 0; i < Long.BYTES; ++i) {
// Compute a view for each possible alignment. We cache these views
// because #asLongBuffer() has some cost that we don't want to pay on
// each invocation of #readLELongs.
if (i < curBuf.limit()) {
curLongBufferViews[i] =
curBuf.duplicate().position(i).order(ByteOrder.LITTLE_ENDIAN).asLongBuffer();
} else {
curLongBufferViews[i] = EMPTY_LONGBUFFER;
try {
if (curLongBufferViews == null) {
// readLELongs is only used for postings today, so we compute the long
// views lazily so that other data-structures don't have to pay for the
// associated initialization/memory overhead.
curLongBufferViews = new LongBuffer[Long.BYTES];
for (int i = 0; i < Long.BYTES; ++i) {
// Compute a view for each possible alignment. We cache these views
// because #asLongBuffer() has some cost that we don't want to pay on
// each invocation of #readLELongs.
if (i < curBuf.limit()) {
curLongBufferViews[i] =
curBuf.duplicate().position(i).order(ByteOrder.LITTLE_ENDIAN).asLongBuffer();
} else {
curLongBufferViews[i] = EMPTY_LONGBUFFER;
}
}
}
}
try {
final int position = curBuf.position();
guard.getLongs(
curLongBufferViews[position & 0x07].position(position >>> 3), dst, offset, length);
Expand All @@ -199,18 +199,18 @@ public void readLongs(long[] dst, int offset, int length) throws IOException {
@Override
public void readInts(int[] dst, int offset, int length) throws IOException {
// See notes about readLongs above
if (curIntBufferViews == null) {
curIntBufferViews = new IntBuffer[Integer.BYTES];
for (int i = 0; i < Integer.BYTES; ++i) {
if (i < curBuf.limit()) {
curIntBufferViews[i] =
curBuf.duplicate().position(i).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer();
} else {
curIntBufferViews[i] = EMPTY_INTBUFFER;
try {
if (curIntBufferViews == null) {
curIntBufferViews = new IntBuffer[Integer.BYTES];
for (int i = 0; i < Integer.BYTES; ++i) {
if (i < curBuf.limit()) {
curIntBufferViews[i] =
curBuf.duplicate().position(i).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer();
} else {
curIntBufferViews[i] = EMPTY_INTBUFFER;
}
}
}
}
try {
final int position = curBuf.position();
guard.getInts(
curIntBufferViews[position & 0x03].position(position >>> 2), dst, offset, length);
Expand All @@ -228,20 +228,20 @@ public void readInts(int[] dst, int offset, int length) throws IOException {
@Override
public final void readFloats(float[] floats, int offset, int len) throws IOException {
// See notes about readLongs above
if (curFloatBufferViews == null) {
curFloatBufferViews = new FloatBuffer[Float.BYTES];
for (int i = 0; i < Float.BYTES; ++i) {
// Compute a view for each possible alignment.
if (i < curBuf.limit()) {
ByteBuffer dup = curBuf.duplicate().order(ByteOrder.LITTLE_ENDIAN);
dup.position(i);
curFloatBufferViews[i] = dup.asFloatBuffer();
} else {
curFloatBufferViews[i] = EMPTY_FLOATBUFFER;
try {
if (curFloatBufferViews == null) {
curFloatBufferViews = new FloatBuffer[Float.BYTES];
for (int i = 0; i < Float.BYTES; ++i) {
// Compute a view for each possible alignment.
if (i < curBuf.limit()) {
ByteBuffer dup = curBuf.duplicate().order(ByteOrder.LITTLE_ENDIAN);
dup.position(i);
curFloatBufferViews[i] = dup.asFloatBuffer();
} else {
curFloatBufferViews[i] = EMPTY_FLOATBUFFER;
}
}
}
}
try {
final int position = curBuf.position();
FloatBuffer floatBuffer = curFloatBufferViews[position & 0x03];
floatBuffer.position(position >>> 2);
Expand Down Expand Up @@ -548,6 +548,7 @@ private void unsetBuffers() {
curBufIndex = 0;
curLongBufferViews = null;
curIntBufferViews = null;
curFloatBufferViews = null;
}

/** Optimization of ByteBufferIndexInput for when there is only one buffer */
Expand Down
37 changes: 25 additions & 12 deletions lucene/core/src/test/org/apache/lucene/store/TestMultiMMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Locale;
import org.apache.lucene.tests.store.BaseChunkedDirectoryTestCase;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.IOConsumer;
import org.junit.BeforeClass;

/**
Expand Down Expand Up @@ -83,31 +84,43 @@ public void testSeekingExceptions() throws IOException {
// TODO: can we improve ByteBuffersDirectory (without overhead) and move these clone safety tests
// to the base test case?

public void testCloneSafety() throws Exception {
Directory mmapDir = getDirectory(createTempDir("testCloneSafety"));
IndexOutput io = mmapDir.createOutput("bytes", newIOContext(random()));
io.writeVInt(5);
io.close();
IndexInput one = mmapDir.openInput("bytes", IOContext.DEFAULT);
IndexInput two = one.clone();
IndexInput three = two.clone(); // clone of clone
one.close();
private void assertAlreadyClosed(
IndexInput one, IndexInput two, IndexInput three, IOConsumer<DataInput> consumer) {
expectThrows(
AlreadyClosedException.class,
() -> {
one.readVInt();
consumer.accept(one);
});
expectThrows(
AlreadyClosedException.class,
() -> {
two.readVInt();
consumer.accept(two);
});
expectThrows(
AlreadyClosedException.class,
() -> {
three.readVInt();
consumer.accept(three);
});
}

public void testCloneSafety() throws Exception {
Directory mmapDir = getDirectory(createTempDir("testCloneSafety"));
IndexOutput io = mmapDir.createOutput("bytes", newIOContext(random()));
long[] longs = new long[2];
int[] ints = new int[2];
float[] floats = new float[2];
io.writeVInt(5);
io.writeLong(1);
io.writeLong(2);
io.close();
IndexInput one = mmapDir.openInput("bytes", IOContext.DEFAULT);
IndexInput two = one.clone();
IndexInput three = two.clone(); // clone of clone
one.close();
assertAlreadyClosed(one, two, three, in -> in.readVInt());
assertAlreadyClosed(one, two, three, in -> in.readLongs(longs, 0, 2));
assertAlreadyClosed(one, two, three, in -> in.readInts(ints, 0, 2)); // read as int
assertAlreadyClosed(one, two, three, in -> in.readFloats(floats, 0, 2)); // read as float
two.close();
three.close();
// test double close of master:
Expand Down