fix(pvtdatastorage): close collItr before releasing purgerLock in processCollElgEvents - #5424
Conversation
d568539 to
6c7dfac
Compare
…cessCollElgEvents When processCollElgEvents flushes an oversized batch, it releases purgerLock to sleep between writes but leaves the collItr LevelDB snapshot iterator open. LevelDB snapshots freeze DB state at creation time, so while the lock is dropped the purger goroutine can wake up and delete InelgMissingData entries that collItr hasn't processed yet. When the lock is re-acquired, the stale iterator still yields those deleted keys, and the loop re-inserts them as ElgPrioMissingData. The result is that expired private data gets written back into the store and the reconciler spins trying to fetch it from peers that have already purged it. Fix: release collItr before dropping the lock, then reopen a fresh iterator from the last-processed key after re-acquiring it. Since that key was already deleted by the flushed batch, the new iterator skips it and picks up from the next real entry, now reflecting whatever the purger cleaned up during the sleep. Signed-off-by: Shridhar Panigrahi <sridharpanigrahi2006@gmail.com>
6c7dfac to
105a7f0
Compare
|
@pfi79 , please let me know your thoughts on this ! |
A magnificent find. Thank you for your hard work. |
… race Add TestCollElgEnabled_PurgerDeletesDuringBatchSleep which verifies that InelgMissingData entries deleted by the purger during the inter-batch sleep window in processCollElgEvents are not re-inserted as ElgPrioMissingData. The test uses MaxBatchSize=1 to force a batch flush after the first entry, then a goroutine acquires purgerLock during the sleep and deletes remaining InelgMissing keys (simulating the purger). Without the fix the stale LevelDB snapshot iterator still yields the deleted keys and the test fails; with the fix the fresh iterator reflects the deletions and the test passes. Signed-off-by: Shridhar Panigrahi <sridharpanigrahi2006@gmail.com>
|
Thanks @pfi79! I've added a regression test ( It works by setting |
|
Hey @pfi79, the failing All pvtdatastorage unit tests pass, including the new regression test. Could you re-run the failed job when you get a chance? Thanks! |
I found a bug in
processCollElgEventswhere a stale LevelDB iterator can cause expired private data to be silently re-added to the store, making the reconciler loop forever trying to fetch data that no longer exists.What's happening
When converting ineligible missing data entries to eligible ones, the function batches up writes and sleeps between batches to avoid hammering the DB. Before sleeping it drops
purgerLock:The problem is that
collItr— a LevelDB snapshot iterator over the ineligible missing data range — is still open when the lock is released. LevelDB snapshot iterators capture the DB state at the moment they're created and keep returning those keys regardless of what happens to the DB afterwards.So while we're sleeping, the purger goroutine wakes up (it was blocked waiting for
purgerLock), runspurgeExpiredData, and deletes both the eligible and ineligible missing data entries for any BTL-expired collections. When we re-acquire the lock and keep iterating, the stale snapshot still hands us those deleted keys, and we write them back to the DB as eligible missing data:The reconciler then picks these up, asks every peer for the data, and every peer says no — because it's been purged everywhere. This repeats until the purger happens to run again and cleans up the re-inserted entries.
The fix
Close
collItrbefore releasing the lock. After re-acquiring the lock, reopen a fresh iterator starting from the last-processed key. Since that key was already deleted by the batch we just flushed, the new iterator skips it and starts from the next real entry — and this time it reflects the actual DB state, including whatever the purger deleted during the sleep.Why it's hard to notice
There's no crash and the peer keeps running normally. The only signal is the reconciler logging failures to fetch private data, which happens in normal operation too (e.g. when peers are temporarily unreachable). You'd have to specifically correlate those warnings with collection eligibility events and purge intervals to suspect this. In practice it just looks like slow or noisy reconciliation.