Skip to content

Commit a79fd6e

Browse files
CNDB-16336: Handle failure in IndexSearcher::toMetaSortedIterator, close rowIdIterator (#2183)
### What is the issue Fixes: riptano/cndb#16336 ### What does this PR fix and why was it fixed In SAI, when we handle already created iterators, we need to close them on the exceptional path. This is not the most aesthetic practice, but it is the current one, so this change follows the current standard of catching throwable, calling close, and throwing the throwable. In this case, we hit an exception when reading from disk for `hasNext` but there are at a couple spots there could be exceptions in there, so I wrapped the whole block.
1 parent 1200c45 commit a79fd6e

File tree

2 files changed

+88
-16
lines changed

2 files changed

+88
-16
lines changed

src/java/org/apache/cassandra/index/sai/disk/v1/IndexSearcher.java

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -171,24 +171,32 @@ protected KeyRangeIterator toPrimaryKeyIterator(PostingList postingList, QueryCo
171171

172172
protected CloseableIterator<PrimaryKeyWithSortKey> toMetaSortedIterator(CloseableIterator<? extends RowIdWithMeta> rowIdIterator, QueryContext queryContext) throws IOException
173173
{
174-
if (rowIdIterator == null || !rowIdIterator.hasNext())
174+
try
175+
{
176+
if (rowIdIterator == null || !rowIdIterator.hasNext())
177+
{
178+
FileUtils.closeQuietly(rowIdIterator);
179+
return CloseableIterator.emptyIterator();
180+
}
181+
182+
IndexSearcherContext searcherContext = new IndexSearcherContext(metadata.minKey,
183+
metadata.maxKey,
184+
metadata.minSSTableRowId,
185+
metadata.maxSSTableRowId,
186+
metadata.segmentRowIdOffset,
187+
queryContext,
188+
null);
189+
var pkm = primaryKeyMapFactory.newPerSSTablePrimaryKeyMap();
190+
return new RowIdToPrimaryKeyWithSortKeyIterator(indexContext,
191+
pkm.getSSTableId(),
192+
rowIdIterator,
193+
pkm,
194+
searcherContext);
195+
}
196+
catch (Throwable t)
175197
{
176198
FileUtils.closeQuietly(rowIdIterator);
177-
return CloseableIterator.emptyIterator();
199+
throw t;
178200
}
179-
180-
IndexSearcherContext searcherContext = new IndexSearcherContext(metadata.minKey,
181-
metadata.maxKey,
182-
metadata.minSSTableRowId,
183-
metadata.maxSSTableRowId,
184-
metadata.segmentRowIdOffset,
185-
queryContext,
186-
null);
187-
var pkm = primaryKeyMapFactory.newPerSSTablePrimaryKeyMap();
188-
return new RowIdToPrimaryKeyWithSortKeyIterator(indexContext,
189-
pkm.getSSTableId(),
190-
rowIdIterator,
191-
pkm,
192-
searcherContext);
193201
}
194202
}

test/unit/org/apache/cassandra/index/sai/cql/VectorTypeTest.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@
3636
import org.apache.cassandra.index.sai.StorageAttachedIndex;
3737
import org.apache.cassandra.index.sai.disk.v1.IndexWriterConfig;
3838
import org.apache.cassandra.index.sai.disk.v1.SegmentBuilder;
39+
import org.apache.cassandra.index.sai.disk.vector.AutoResumingNodeScoreIterator;
3940
import org.apache.cassandra.index.sai.disk.vector.CassandraOnHeapGraph;
4041
import org.apache.cassandra.index.sai.disk.vector.VectorSourceModel;
4142
import org.apache.cassandra.index.sai.plan.QueryController;
4243
import org.apache.cassandra.inject.ActionBuilder;
4344
import org.apache.cassandra.inject.Expression;
45+
import org.apache.cassandra.inject.Injection;
4446
import org.apache.cassandra.inject.Injections;
4547
import org.apache.cassandra.inject.InvokePointBuilder;
4648
import org.apache.cassandra.service.ClientState;
@@ -1057,4 +1059,66 @@ public void testUpdateRowScoreToWorsePositionButIncludeInBatch()
10571059
// query with ANN only
10581060
assertRows(execute("SELECT c FROM %s ORDER BY r ANN OF [0.1, 0.1] LIMIT 10"), row(2), row(1));
10591061
}
1062+
1063+
@Test
1064+
public void testRowIdIteratorClosedOnHasNextFailure() throws Throwable
1065+
{
1066+
createTable("CREATE TABLE %s (pk int, vec vector<float, 2>, PRIMARY KEY(pk))");
1067+
createIndex("CREATE CUSTOM INDEX ON %s(vec) USING 'StorageAttachedIndex'");
1068+
1069+
// Track if the rowIdIterator's close method is called
1070+
Injections.Counter closeCounter = Injections.newCounter("rowIdIteratorCloseCounter")
1071+
.add(InvokePointBuilder.newInvokePoint()
1072+
.onClass(AutoResumingNodeScoreIterator.class)
1073+
.onMethod("close"))
1074+
.build();
1075+
1076+
// Inject failure at hasNext in toMetaSortedIterator
1077+
Injection hasNextFailure = Injections.newCustom("fail_on_hasNext")
1078+
.add(InvokePointBuilder.newInvokePoint()
1079+
.onClass(AutoResumingNodeScoreIterator.class)
1080+
.onMethod("computeNext")
1081+
.atEntry())
1082+
.add(ActionBuilder.newActionBuilder()
1083+
.actions()
1084+
.doThrow(RuntimeException.class, Expression.quote("Injected hasNext failure!")))
1085+
.build();
1086+
1087+
try
1088+
{
1089+
Injections.inject(closeCounter);
1090+
Injections.inject(hasNextFailure);
1091+
1092+
// Insert data
1093+
execute("INSERT INTO %s (pk, vec) VALUES (1, [1.0, 1.0])");
1094+
execute("INSERT INTO %s (pk, vec) VALUES (2, [2.0, 2.0])");
1095+
flush();
1096+
1097+
// Reset counter before the test
1098+
closeCounter.reset();
1099+
1100+
// Enable the failure injection
1101+
hasNextFailure.enable();
1102+
1103+
// Execute query that will trigger toMetaSortedIterator and fail at hasNext
1104+
assertThatThrownBy(() -> executeInternal("SELECT pk FROM %s ORDER BY vec ANN OF [1.5, 1.5] LIMIT 2"))
1105+
.hasMessageContaining("Injected hasNext failure!");
1106+
1107+
// Verify that close was called on the rowIdIterator despite the failure
1108+
// The close should be called in the catch block of toMetaSortedIterator (line 198)
1109+
assertThat(closeCounter.get()).as("rowIdIterator should be closed when hasNext fails")
1110+
.isGreaterThan(0);
1111+
1112+
// Remove failure and confirm we can still query
1113+
hasNextFailure.disable();
1114+
1115+
// Confrm subsequent queries succeed because we close the iterator and release the graph searcher
1116+
execute("SELECT pk FROM %s ORDER BY vec ANN OF [1.5, 1.5] LIMIT 2");
1117+
}
1118+
finally
1119+
{
1120+
hasNextFailure.disable();
1121+
closeCounter.disable();
1122+
}
1123+
}
10601124
}

0 commit comments

Comments
 (0)