Improve handling of NullPointerException in MMapDirectory's IndexInputs (check the "closed" condition) (#12705)

This commit is contained in:
Uwe Schindler 2023-10-22 10:58:44 +02:00 committed by GitHub
parent ad06ee57b8
commit 9729123369
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 64 additions and 13 deletions

View File

@ -183,6 +183,9 @@ Improvements
* GITHUB#12586: Remove over-counting of deleted terms. (Guo Feng)
* GITHUB#12705: Improve handling of NullPointerException in MMapDirectory's IndexInputs.
(Uwe Schindler, Michael Sokolov)
Optimizations
---------------------
* GITHUB#12183: Make TermStates#build concurrent. (Shubham Chaudhary)

View File

@ -78,6 +78,10 @@ final class ByteBufferGuard {
}
}
public boolean isInvalidated() {
return invalidated;
}
private void ensureValid() {
if (invalidated) {
// this triggers an AlreadyClosedException in ByteBufferIndexInput:

View File

@ -99,9 +99,16 @@ public abstract class ByteBufferIndexInput extends IndexInput implements RandomA
}
}
// the unused parameter is just to silence javac about unused variables
AlreadyClosedException alreadyClosed(RuntimeException unused) {
return new AlreadyClosedException("Already closed: " + this);
AlreadyClosedException alreadyClosed(NullPointerException npe) {
// we use NPE to signal if this input is closed (to not have checks everywhere). If NPE happens,
// we check the "is closed" condition explicitly by checking that our "buffers" are null or
// the guard was invalidated.
if (this.buffers == null || this.curBuf == null || guard.isInvalidated()) {
return new AlreadyClosedException("Already closed: " + this);
}
// otherwise rethrow unmodified NPE (as it possibly a bug with passing a null parameter to the
// IndexInput method):
throw npe;
}
@Override
@ -459,7 +466,7 @@ public abstract class ByteBufferIndexInput extends IndexInput implements RandomA
/** Builds the actual sliced IndexInput (may apply extra offset in subclasses). * */
protected ByteBufferIndexInput buildSlice(String sliceDescription, long offset, long length) {
if (buffers == null) {
if (buffers == null || guard.isInvalidated()) {
throw alreadyClosed(null);
}

View File

@ -102,9 +102,17 @@ abstract class MemorySegmentIndexInput extends IndexInput implements RandomAcces
}
}
// the unused parameter is just to silence javac about unused variables
AlreadyClosedException alreadyClosed(RuntimeException unused) {
return new AlreadyClosedException("Already closed: " + this);
AlreadyClosedException alreadyClosed(RuntimeException e) {
// we use NPE to signal if this input is closed (to not have checks everywhere). If NPE happens,
// we check the "is closed" condition explicitly by checking that our "curSegment" is null.
// if it is an IllegalStateException, it can only come from MemorySegment API (other thread has
// closed input)
if (this.curSegment == null || e instanceof IllegalStateException) {
return new AlreadyClosedException("Already closed: " + this);
}
// otherwise rethrow unmodified NPE/ISE (as it possibly a bug with passing a null parameter to
// the IndexInput method):
throw e;
}
@Override

View File

@ -100,9 +100,17 @@ abstract class MemorySegmentIndexInput extends IndexInput implements RandomAcces
}
}
// the unused parameter is just to silence javac about unused variables
AlreadyClosedException alreadyClosed(RuntimeException unused) {
return new AlreadyClosedException("Already closed: " + this);
AlreadyClosedException alreadyClosed(RuntimeException e) {
// we use NPE to signal if this input is closed (to not have checks everywhere). If NPE happens,
// we check the "is closed" condition explicitly by checking that our "curSegment" is null.
// if it is an IllegalStateException, it can only come from MemorySegment API (other thread has
// closed input)
if (this.curSegment == null || e instanceof IllegalStateException) {
return new AlreadyClosedException("Already closed: " + this);
}
// otherwise rethrow unmodified NPE/ISE (as it possibly a bug with passing a null parameter to
// the IndexInput method):
throw e;
}
@Override

View File

@ -100,9 +100,17 @@ abstract class MemorySegmentIndexInput extends IndexInput implements RandomAcces
}
}
// the unused parameter is just to silence javac about unused variables
AlreadyClosedException alreadyClosed(RuntimeException unused) {
return new AlreadyClosedException("Already closed: " + this);
AlreadyClosedException alreadyClosed(RuntimeException e) {
// we use NPE to signal if this input is closed (to not have checks everywhere). If NPE happens,
// we check the "is closed" condition explicitly by checking that our "curSegment" is null.
// if it is an IllegalStateException, it can only come from MemorySegment API (other thread has
// closed input)
if (this.curSegment == null || e instanceof IllegalStateException) {
return new AlreadyClosedException("Already closed: " + this);
}
// otherwise rethrow unmodified NPE/ISE (as it possibly a bug with passing a null parameter to
// the IndexInput method):
throw e;
}
@Override

View File

@ -104,4 +104,17 @@ public class TestMmapDirectory extends BaseDirectoryTestCase {
dir.close();
}
}
public void testNullParamsIndexInput() throws Exception {
try (Directory mmapDir = getDirectory(createTempDir("testNullParamsIndexInput"))) {
try (IndexOutput out = mmapDir.createOutput("bytes", newIOContext(random()))) {
out.alignFilePointer(16);
}
try (IndexInput in = mmapDir.openInput("bytes", IOContext.DEFAULT)) {
assertThrows(NullPointerException.class, () -> in.readBytes(null, 0, 1));
assertThrows(NullPointerException.class, () -> in.readFloats(null, 0, 1));
assertThrows(NullPointerException.class, () -> in.readLongs(null, 0, 1));
}
}
}
}