LUCENE-9686: Fix read past EOF handling in DirectIODirectory (#2258)

This commit is contained in:
zacharymorn 2021-02-02 00:07:30 -08:00 committed by GitHub
parent 15aaec60d9
commit 3835cb4e95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 67 additions and 10 deletions

View File

@ -359,12 +359,8 @@ public class DirectIODirectory extends FilterDirectory {
filePos = alignedPos - buffer.capacity();
final int delta = (int) (pos - alignedPos);
refill();
try {
buffer.position(delta);
} catch (IllegalArgumentException iae) {
throw new EOFException("read past EOF: " + this);
}
refill(delta);
buffer.position(delta);
}
assert pos == getFilePointer();
}
@ -381,17 +377,18 @@ public class DirectIODirectory extends FilterDirectory {
@Override
public byte readByte() throws IOException {
if (!buffer.hasRemaining()) {
refill();
refill(1);
}
return buffer.get();
}
private void refill() throws IOException {
private void refill(int bytesToRead) throws IOException {
filePos += buffer.capacity();
// BaseDirectoryTestCase#testSeekPastEOF test for consecutive read past EOF,
// hence throwing EOFException early to maintain buffer state (position in particular)
if (filePos > channel.size()) {
if (filePos > channel.size() || (channel.size() - filePos < bytesToRead)) {
throw new EOFException("read past EOF: " + this);
}
@ -417,7 +414,7 @@ public class DirectIODirectory extends FilterDirectory {
buffer.get(dst, offset, left);
toRead -= left;
offset += left;
refill();
refill(toRead);
} else {
buffer.get(dst, offset, toRead);
break;

View File

@ -16,6 +16,8 @@
*/
package org.apache.lucene.misc.store;
import com.carrotsearch.randomizedtesting.RandomizedTest;
import java.io.EOFException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@ -77,10 +79,68 @@ public class TestDirectIODirectory extends BaseDirectoryTestCase {
o.close();
IndexInput i = dir.openInput("out", newIOContext(random()));
i.seek(fileSize);
// Seeking past EOF should always throw EOFException
expectThrows(
EOFException.class, () -> i.seek(fileSize + RandomizedTest.randomIntBetween(1, 2048)));
// Reading immediately after seeking past EOF should throw EOFException
expectThrows(EOFException.class, () -> i.readByte());
i.close();
}
}
public void testReadPastEOFShouldThrowEOFExceptionWithEmptyFile() throws Exception {
// fileSize needs to be 0 to test this condition. Do not randomized.
final int fileSize = 0;
try (Directory dir = getDirectory(createTempDir("testReadPastEOF"))) {
try (IndexOutput o = dir.createOutput("out", newIOContext(random()))) {
o.writeBytes(new byte[fileSize], 0, fileSize);
}
try (IndexInput i = dir.openInput("out", newIOContext(random()))) {
i.seek(fileSize);
expectThrows(EOFException.class, () -> i.readByte());
expectThrows(EOFException.class, () -> i.readBytes(new byte[1], 0, 1));
}
try (IndexInput i = dir.openInput("out", newIOContext(random()))) {
expectThrows(
EOFException.class, () -> i.seek(fileSize + RandomizedTest.randomIntBetween(1, 2048)));
expectThrows(EOFException.class, () -> i.readByte());
expectThrows(EOFException.class, () -> i.readBytes(new byte[1], 0, 1));
}
try (IndexInput i = dir.openInput("out", newIOContext(random()))) {
expectThrows(EOFException.class, () -> i.readByte());
}
try (IndexInput i = dir.openInput("out", newIOContext(random()))) {
expectThrows(EOFException.class, () -> i.readBytes(new byte[1], 0, 1));
}
}
}
public void testSeekPastEOFAndRead() throws Exception {
try (Directory dir = getDirectory(createTempDir("testSeekPastEOF"))) {
final int len = random().nextInt(2048);
try (IndexOutput o = dir.createOutput("out", newIOContext(random()))) {
byte[] b = new byte[len];
o.writeBytes(b, 0, len);
}
try (IndexInput i = dir.openInput("out", newIOContext(random()))) {
// Seeking past EOF should always throw EOFException
expectThrows(
EOFException.class, () -> i.seek(len + RandomizedTest.randomIntBetween(1, 2048)));
// Reading immediately after seeking past EOF should throw EOFException
expectThrows(EOFException.class, () -> i.readByte());
}
}
}
public void testUseDirectIODefaults() throws Exception {
Path path = createTempDir("testUseDirectIODefaults");
try (DirectIODirectory dir = new DirectIODirectory(FSDirectory.open(path))) {