Skip to content
Closed
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
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@
<PackageVersion Include="System.Numerics.Tensors" Version="9.0.9" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.8" />
<PackageVersion Include="diskann-garnet" Version="1.0.20" />
<PackageVersion Include="diskann-garnet" Version="1.0.23" />
</ItemGroup>
</Project>
4 changes: 4 additions & 0 deletions libs/common/ExceptionInjectionType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,9 @@ public enum ExceptionInjectionType
/// During deletion of a Vector Set, leaving it partially deleted - at a particular point of execution.
/// </summary>
VectorSet_Interrupt_Delete_2,
/// <summary>
/// Force CreateIndex to simulate a null return, testing the defensive guard.
/// </summary>
VectorSet_CreateIndex_NullReturn,
}
}
4 changes: 2 additions & 2 deletions libs/server/Resp/Vector/RespServerSessionVectors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -299,13 +299,13 @@ private bool NetworkVADD<TGarnetApi>(ref TGarnetApi storageApi)
}

// Default unspecified options
quantType ??= VectorQuantType.Q8;
quantType ??= VectorQuantType.NoQuant;
buildExplorationFactor ??= 200;
attributes ??= default;
numLinks ??= 16;

// TODO: Distance metric specification is an extension - still needs to be implemented
distanceMetric ??= VectorDistanceMetricType.L2;
distanceMetric ??= VectorDistanceMetricType.Cosine;

// Validate that DiskANN is expected to succeed given data sizes
//
Expand Down
22 changes: 22 additions & 0 deletions libs/server/Resp/Vector/VectorManager.Locking.cs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,28 @@ out GarnetStatus status
newlyAllocatedIndex = Service.CreateIndex(indexContext, dims, reduceDims, quantizer, buildExplorationFactor, numLinks, distanceMetric, ReadCallbackPtr, WriteCallbackPtr, DeleteCallbackPtr, ReadModifyWriteCallbackPtr);
}

// Allow test injection to simulate CreateIndex failure
if (ExceptionInjectionHelper.TriggerCondition(ExceptionInjectionType.VectorSet_CreateIndex_NullReturn))
{
if (newlyAllocatedIndex != 0)
{
Service.DropIndex(indexContext, newlyAllocatedIndex);
}
newlyAllocatedIndex = 0;
}

if (newlyAllocatedIndex == 0)
{
if (!needsRecreate)
{
CleanupDroppedIndex(ref ActiveThreadSession.vectorContext, indexContext);
}

status = GarnetStatus.BADSTATE;
vectorSetLocks.ReleaseExclusiveLock(exclusiveLockToken);
return default;
}

input.parseState.EnsureCapacity(12);

// Save off for insertion
Expand Down
42 changes: 40 additions & 2 deletions test/Garnet.test/RespVectorSetTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,44 @@ private void InterruptedVectorSetDeleteRecovery(ExceptionInjectionType faultLoca
}
}

[Test]
public void CreateIndexNullReturnIsHandledGracefully()
{
#if !DEBUG
ClassicAssert.Ignore("Relies on ExceptionInjectionHelper, disabled in non-DEBUG");
#endif

using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig());
var db = redis.GetDatabase();

// Enable fault injection so that CreateIndex simulates a null (0) return
ExceptionInjectionHelper.EnableException(ExceptionInjectionType.VectorSet_CreateIndex_NullReturn);
try
{
// VADD should return an error rather than crashing the server
var exc = ClassicAssert.Throws<RedisServerException>(() =>
db.Execute("VADD", ["foo", "VALUES", "4", "1.0", "2.0", "3.0", "4.0", new byte[] { 0, 0, 0, 0 }, "Q8", "EF", "16", "M", "32"]));
ClassicAssert.IsTrue(exc.Message.Contains("partially deleted state"), $"Unexpected error: {exc.Message}");
}
finally
{
ExceptionInjectionHelper.DisableException(ExceptionInjectionType.VectorSet_CreateIndex_NullReturn);
}

// After the failed creation, the key should not exist - verify with VDIM
var vdimExc = ClassicAssert.Throws<RedisServerException>(() => db.Execute("VDIM", ["foo"]));
ClassicAssert.IsTrue(vdimExc.Message.Contains("Key not found"),
$"Expected key to not exist after failed CreateIndex, got: {vdimExc.Message}");

// Verify the server is still healthy - a subsequent VADD (without injection) should succeed
var res = (int)db.Execute("VADD", ["foo", "VALUES", "4", "1.0", "2.0", "3.0", "4.0", new byte[] { 0, 0, 0, 0 }, "Q8", "EF", "16", "M", "32"]);
ClassicAssert.AreEqual(1, res);

// Verify the index is usable
var dim = (int)db.Execute("VDIM", ["foo"]);
ClassicAssert.AreEqual(4, dim);
}

[Test]
public void RepeatedVectorSetDeletes()
{
Expand Down Expand Up @@ -1728,7 +1766,7 @@ public void VINFO()
ClassicAssert.AreEqual(14, vinfoRes.Length);
var values = BuildDictionaryFromResponse(vinfoRes);
ClassicAssert.AreEqual(values["quant-type"], expectedQuantType);
ClassicAssert.AreEqual(values["distance-metric"], "l2");
ClassicAssert.AreEqual(values["distance-metric"], "cosine");
ClassicAssert.AreEqual(values["input-vector-dimensions"], vectorDim.ToString());
ClassicAssert.AreEqual(values["reduced-dimensions"], reduceValueToUse.ToString());
ClassicAssert.AreEqual(values["build-exploration-factor"], expectedEf);
Expand All @@ -1746,7 +1784,7 @@ public void VINFO()
ClassicAssert.AreEqual(14, vinfoRes.Length);
values = BuildDictionaryFromResponse(vinfoRes);
ClassicAssert.AreEqual(values["quant-type"], expectedQuantType);
ClassicAssert.AreEqual(values["distance-metric"], "l2");
ClassicAssert.AreEqual(values["distance-metric"], "cosine");
ClassicAssert.AreEqual(values["input-vector-dimensions"], vectorDim.ToString());
ClassicAssert.AreEqual(values["reduced-dimensions"], reduceValueToUse.ToString());
ClassicAssert.AreEqual(values["build-exploration-factor"], expectedEf);
Expand Down
Loading