Describe the bug
Summary
In v2.0beta3, calling IterateLookup after a Upsert → Delete → Upsert sequence causes either a freeze or missing records, depending on the device type. The same logic works correctly in v1.1.
Steps to Reproduce
- Upsert keys 0–252 (253 records total)
- Delete all keys 0–252
- Upsert keys 0–252 again
- Call
IterateLookup in a loop with resetCursor:false; in the Reader callback, return CursorRecordResult.Accept | CursorRecordResult.EndBatch when the batch count reaches 64 (MaxCount)
Expected Behavior (v1.1)
All 253 records (keys 0–252) are returned. The loop exits naturally with TotalCount == 253.
Actual Behavior (v2.0beta3)
Two distinct failure modes are observed depending on the device type.
① LocalMemoryDevice: freeze / infinite loop
IterateLookup never completes. The scan log shows the following repeating pattern, with OnStop firing repeatedly and OnStart never called between batches:
OnStop → Accept... → OnStop
Accept...
OnStop → Accept... → OnStop
OnStop → Accept... → OnStop
(repeats indefinitely)
② Other devices (e.g. ManagedLocalStorageDevice): missing records
The loop terminates, but keys 0–66 (67 records) are never returned. Only keys 67–252 (186 records) are retrieved. Assert.Equal(253, TotalCount) fails.
Comparison with v1.1
| v1.1 | v2.0beta3
-- | -- | --
Interface | IScanIteratorFunctions | IScanIteratorFunctions
Allocator | SpanByteAllocator | ObjectAllocator
LocalMemoryDevice | Works correctly | Freezes
Other devices | Works correctly | Keys 0–66 missing
Root Cause Hypothesis
The bug appears to be in how v2.0beta3's IterateLookup handles cursor resumption across batch boundaries (EndBatch) when deleted (tombstoned) records are physically present in the log. Specifically, the resume address calculation may not correctly account for skipped delete records, causing the scan to either loop back to an already-visited region (LocalMemoryDevice) or skip the initial segment of live records entirely (other devices). The absence of OnStart between batches in the LocalMemoryDevice log strongly suggests the cursor state is not being preserved correctly when re-entering the scan after EndBatch.
A minimal reproduction is provided in the attached test code.
Describe the bug
Summary
In v2.0beta3, calling
IterateLookupafter aUpsert → Delete → Upsertsequence causes either a freeze or missing records, depending on the device type. The same logic works correctly in v1.1.Steps to Reproduce
IterateLookupin a loop withresetCursor:false; in theReadercallback, returnCursorRecordResult.Accept | CursorRecordResult.EndBatchwhen the batch count reaches 64 (MaxCount)Expected Behavior (v1.1)
All 253 records (keys 0–252) are returned. The loop exits naturally with
TotalCount == 253.Actual Behavior (v2.0beta3)
Two distinct failure modes are observed depending on the device type.
①
LocalMemoryDevice: freeze / infinite loopIterateLookupnever completes. The scan log shows the following repeating pattern, withOnStopfiring repeatedly andOnStartnever called between batches:② Other devices (e.g.
ManagedLocalStorageDevice): missing recordsThe loop terminates, but keys 0–66 (67 records) are never returned. Only keys 67–252 (186 records) are retrieved.
Assert.Equal(253, TotalCount)fails.Comparison with v1.1
Root Cause Hypothesis
The bug appears to be in how v2.0beta3's
IterateLookuphandles cursor resumption across batch boundaries (EndBatch) when deleted (tombstoned) records are physically present in the log. Specifically, the resume address calculation may not correctly account for skipped delete records, causing the scan to either loop back to an already-visited region (LocalMemoryDevice) or skip the initial segment of live records entirely (other devices). The absence ofOnStartbetween batches in theLocalMemoryDevicelog strongly suggests the cursor state is not being preserved correctly when re-entering the scan afterEndBatch.A minimal reproduction is provided in the attached test code.