Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public KnnVectorsWriter fieldsWriter(SegmentWriteState state) throws IOException
acceleratedHNSWParams.getNumMergeWorkers(),
new TaskExecutor(acceleratedHNSWParams.getMergeExec()));
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
throw Utils.handleThrowable(e);
}
}
}
Expand All @@ -105,7 +105,7 @@ public KnnVectorsReader fieldsReader(SegmentReadState state) throws IOException
return LUCENE_PROVIDER.getLuceneHnswVectorsReaderInstance(
state, FLAT_VECTORS_FORMAT.fieldsReader(state));
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
throw Utils.handleThrowable(e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public KnnVectorsWriter fieldsWriter(SegmentWriteState state) throws IOException
acceleratedHNSWParams.getMaxConn(), acceleratedHNSWParams.getBeamWidth());
return fallbackFormat.fieldsWriter(state);
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
throw Utils.handleThrowable(e);
}
}
}
Expand All @@ -103,7 +103,7 @@ public KnnVectorsReader fieldsReader(SegmentReadState state) throws IOException
return LUCENE99_PROVIDER.getLuceneHnswVectorsReaderInstance(
state, FLAT_VECTORS_FORMAT.fieldsReader(state));
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
throw Utils.handleThrowable(e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public KnnVectorsWriter fieldsWriter(SegmentWriteState state) throws IOException
acceleratedHNSWParams.getBeamWidth(), acceleratedHNSWParams.getMaxConn());
return fallbackFormat.fieldsWriter(state);
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
throw Utils.handleThrowable(e);
}
}
}
Expand All @@ -95,7 +95,7 @@ public KnnVectorsReader fieldsReader(SegmentReadState state) throws IOException
return LUCENE_PROVIDER.getLuceneHnswVectorsReaderInstance(
state, FLAT_VECTORS_FORMAT.fieldsReader(state));
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
throw Utils.handleThrowable(e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,20 @@ public class Utils {
static final Logger log = Logger.getLogger(Utils.class.getName());

/**
* A utility method that throws specific types of throwable objects based on types.
* A utility method that rethrows known throwable types without changing their identity.
*
* <p>In particular, {@link Error} instances must not be converted to a {@link
* RuntimeException}; callers rely on errors retaining their original type and stack trace.
*
* <p>This method never returns normally; its return type exists solely so callers can write
* {@code throw handleThrowable(t);}, letting the compiler verify that the enclosing statement
* always completes abruptly.
*
* @param t the throwable object
* @return never returns; always throws
* @throws IOException
*/
static void handleThrowable(Throwable t) throws IOException {
static RuntimeException handleThrowable(Throwable t) throws IOException {
switch (t) {
case IOException ioe -> throw ioe;
case Error error -> throw error;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.nvidia.cuvs.lucene;

import java.io.IOException;
import java.util.Map;
import org.apache.lucene.codecs.Codec;
import org.apache.lucene.codecs.KnnVectorsFormat;
import org.apache.lucene.index.FieldInfos;
import org.apache.lucene.index.SegmentInfo;
import org.apache.lucene.index.SegmentReadState;
import org.apache.lucene.store.ByteBuffersDirectory;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FilterDirectory;
import org.apache.lucene.store.IOContext;
import org.apache.lucene.store.IndexInput;
import org.apache.lucene.tests.util.LuceneTestCase;
import org.apache.lucene.util.StringHelper;
import org.apache.lucene.util.Version;
import org.junit.Test;

public class TestAcceleratedHNSWVectorsFormatThrowableHandling extends LuceneTestCase {

@Test
public void testReadersRethrowIOExceptionUnchanged() throws Exception {
assertReaderFormatsRethrowUnchanged(new IOException("reader I/O failure"));
}

@Test
public void testReadersRethrowRuntimeExceptionUnchanged() throws Exception {
assertReaderFormatsRethrowUnchanged(new IllegalStateException("reader runtime failure"));
}

@Test
public void testReadersRethrowErrorUnchanged() throws Exception {
assertReaderFormatsRethrowUnchanged(new AssertionError("reader error"));
}

private void assertReaderFormatsRethrowUnchanged(Throwable failure) throws Exception {
for (KnnVectorsFormat format : readerFormats()) {
try (Directory directory = new ThrowingDirectory(failure)) {
SegmentReadState state = newSegmentReadState(directory);
Throwable thrown = assertThrows(failure.getClass(), () -> format.fieldsReader(state));
assertSame(format.getName(), failure, thrown);
}
}
}

private static KnnVectorsFormat[] readerFormats() {
return new KnnVectorsFormat[] {
new Lucene99AcceleratedHNSWVectorsFormat(),
new LuceneAcceleratedHNSWScalarQuantizedVectorsFormat(),
new LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat()
};
}

private static SegmentReadState newSegmentReadState(Directory directory) {
SegmentInfo segmentInfo =
new SegmentInfo(
directory,
Version.LATEST,
Version.LATEST,
"_0",
0,
false,
false,
Codec.getDefault(),
Map.of(),
StringHelper.randomId(),
Map.of(),
null);
return new SegmentReadState(directory, segmentInfo, FieldInfos.EMPTY, IOContext.DEFAULT);
}

private static final class ThrowingDirectory extends FilterDirectory {
private final Throwable failure;

private ThrowingDirectory(Throwable failure) {
super(new ByteBuffersDirectory());
this.failure = failure;
}

@Override
public IndexInput openInput(String name, IOContext context) throws IOException {
if (failure instanceof IOException ioe) {
throw ioe;
}
if (failure instanceof RuntimeException runtimeException) {
throw runtimeException;
}
if (failure instanceof Error error) {
throw error;
}
throw new AssertionError("unexpected test throwable", failure);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.nvidia.cuvs.lucene;

import java.io.IOException;
import org.apache.lucene.tests.util.LuceneTestCase;
import org.junit.Test;

public class TestUtilsThrowableHandling extends LuceneTestCase {

@Test
public void testHandleThrowableRethrowsIOExceptionUnchanged() {
IOException exception = new IOException("I/O failure");

IOException thrown = assertThrows(IOException.class, () -> Utils.handleThrowable(exception));

assertSame(exception, thrown);
}

@Test
public void testHandleThrowableRethrowsRuntimeExceptionUnchanged() {
RuntimeException exception = new IllegalStateException("runtime failure");

RuntimeException thrown =
assertThrows(RuntimeException.class, () -> Utils.handleThrowable(exception));

assertSame(exception, thrown);
}

@Test
public void testHandleThrowableRethrowsErrorUnchanged() {
Error error = new AssertionError("fatal failure");

Error thrown = assertThrows(Error.class, () -> Utils.handleThrowable(error));

assertSame(error, thrown);
}

@Test
public void testHandleThrowableWrapsCheckedExceptionWithCause() {
Exception exception = new Exception("checked failure");

RuntimeException thrown =
assertThrows(RuntimeException.class, () -> Utils.handleThrowable(exception));

assertSame(exception, thrown.getCause());
}
}
Loading