Skip to content

Commit 65ac9f1

Browse files
committed
Roaring Bitmap: address PR review feedback
1. RoaringBitmapDataTests: cast actual0 to int when indexing bool[]; add an explicit non-negative guard for clarity. 2. RoaringBitmap.ByteSize: docstring now states the per-entry SortedDictionary node overhead is included (the implementation already accounted for it). 3. RSetBit.NeedInitialUpdate: validate offset/value against a copy of the input before the framework allocates an empty object; write the error and return false on bad input so malformed R.SETBIT no longer leaves an empty key behind. 4. RoaringBitmapObject default ctor: align Size with the deserialized ctor by setting Size = ObjectOverhead + bitmap.ByteSize so freshly-created and round-tripped objects report identical memory baselines and later ByteSize-based deltas don't double-count. 5. (Reviewer suggested removing 'using Garnet.common;' as unused; that namespace owns RespMemoryWriter and is required, so the using stays.) All 27 data-structure + 14 RESP integration tests still pass.
1 parent facd8fe commit 65ac9f1

4 files changed

Lines changed: 35 additions & 5 deletions

File tree

main/GarnetServer/Extensions/RoaringBitmap/RoaringBitmap.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,15 +178,20 @@ public long BitPos(int bit, uint from = 0)
178178
}
179179
}
180180

181-
/// <summary>Estimated heap byte cost. Excludes the .NET object header overhead and the SortedDictionary node overhead.</summary>
181+
/// <summary>
182+
/// Estimated heap byte cost, including an approximate base-object cost and
183+
/// approximate per-entry <see cref="SortedDictionary{TKey, TValue}"/> node
184+
/// overhead. Excludes the .NET object header overhead.
185+
/// </summary>
182186
public long ByteSize
183187
{
184188
get
185189
{
186-
long sum = 24; // base object overhead estimate
190+
long sum = 24; // approximate RoaringBitmap instance/base-object cost
187191
foreach (var kv in chunks)
188192
{
189-
// Per-entry: key (2B), reference (8B), red-black tree node overhead (~40B), and container.
193+
// Per-entry estimate: key (2B), reference (8B), red-black tree
194+
// node overhead (~40B), and the container's own footprint.
190195
sum += 50 + kv.Value.ByteSize;
191196
}
192197
return sum;

main/GarnetServer/Extensions/RoaringBitmap/RoaringBitmapCommands.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,28 @@ public sealed class RSetBit : CustomObjectFunctions
4747
private static ReadOnlySpan<byte> ErrOffset => "ERR bit offset is not an unsigned 32-bit integer"u8;
4848
private static ReadOnlySpan<byte> ErrValue => "ERR bit value must be 0 or 1"u8;
4949

50-
public override bool NeedInitialUpdate(ReadOnlyMemory<byte> key, ref ObjectInput input, ref RespMemoryWriter writer) => true;
50+
public override bool NeedInitialUpdate(ReadOnlyMemory<byte> key, ref ObjectInput input, ref RespMemoryWriter writer)
51+
{
52+
// Validate args BEFORE the framework allocates the empty object so that
53+
// a malformed R.SETBIT does not leave a tombstone-style empty key behind.
54+
// Walk a copy of the input so Updater can re-parse from offset 0.
55+
var validation = input;
56+
int offset = 0;
57+
var offsetArg = GetNextArg(ref validation, ref offset);
58+
var bitArg = GetNextArg(ref validation, ref offset);
59+
60+
if (!RoaringBitmapArgs.TryParseUInt32(offsetArg, out _))
61+
{
62+
writer.WriteError(ErrOffset);
63+
return false;
64+
}
65+
if (!RoaringBitmapArgs.TryParseBit(bitArg, out _))
66+
{
67+
writer.WriteError(ErrValue);
68+
return false;
69+
}
70+
return true;
71+
}
5172

5273
public override bool Updater(ReadOnlyMemory<byte> key, ref ObjectInput input, IGarnetObject value, ref RespMemoryWriter writer, ref RMWInfo rmwInfo)
5374
{

main/GarnetServer/Extensions/RoaringBitmap/RoaringBitmapObject.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ public RoaringBitmapObject(byte type)
2727
: base(type, expiration: 0, size: ObjectOverhead)
2828
{
2929
bitmap = new RoaringBitmap();
30+
// Keep size accounting consistent with the deserialized constructor and
31+
// with later mutation deltas based on bitmap.ByteSize so the empty-bitmap
32+
// baseline is counted exactly once.
33+
this.Size = ObjectOverhead + bitmap.ByteSize;
3034
}
3135

3236
public RoaringBitmapObject(byte type, BinaryReader reader)

test/Garnet.test/RoaringBitmapDataTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ public void SmallUniverse_BitPos_MatchesOracle()
172172
// uint32 universe — past the set range any value qualifies, so
173173
// accept any value >= from that's not in the bits array.
174174
ClassicAssert.GreaterOrEqual(actual0, from);
175-
ClassicAssert.IsTrue(actual0 == 0 || (actual0 < bits.LongLength ? !bits[actual0] : true));
175+
ClassicAssert.IsTrue(actual0 == 0 || (actual0 >= 0 && actual0 < bits.LongLength ? !bits[(int)actual0] : true));
176176
}
177177
}
178178

0 commit comments

Comments
 (0)