https://bugs.openldap.org/show_bug.cgi?id=10462
--- Comment #6 from [email protected] <[email protected]> --- Attached are the two requested test cases. Infinite Loop (single-key DUPSORT db): The original reproducer only called MDB_NEXT_NODUP once after the del+put. The infinite loop occurs when del+put happens inside a traversal loop -- each iteration empties and refills the tree, so every MDB_NEXT_NODUP falls back to mdb_cursor_first() and the loop never terminates. See attached repro1.c. Output: start: key -> val1 BUG: NEXT_NODUP returned SUCCESS -- key=key val=newval (iteration 1) BUG: NEXT_NODUP returned SUCCESS -- key=key val=newval (iteration 2) BUG: NEXT_NODUP returned SUCCESS -- key=key val=newval (iteration 3) BUG: NEXT_NODUP returned SUCCESS -- key=key val=newval (iteration 4) BUG: NEXT_NODUP returned SUCCESS -- key=key val=newval (iteration 5) BUG: NEXT_NODUP returned SUCCESS -- key=key val=newval (iteration 6) BUG: NEXT_NODUP returned SUCCESS -- key=key val=newval (iteration 7) BUG: NEXT_NODUP returned SUCCESS -- key=key val=newval (iteration 8) BUG: NEXT_NODUP returned SUCCESS -- key=key val=newval (iteration 9) BUG: NEXT_NODUP returned SUCCESS -- key=key val=newval (iteration 10) BUG CONFIRMED: infinite loop, aborting. Working case (multi-key DUPSORT db): Same traversal pattern but with two keys. The delete never empties the tree, so C_INITIALIZED stays set and MDB_NEXT_NODUP follows the normal code path. No infinite loop. See attached repro_multi.c. Output: visit 1: aaa -> val1 visit 2: bbb -> val2 done after 2 keys (expected 2) Explanation: The ~C_INITIALIZED -> MDB_FIRST fallback is the right default for a freshly opened cursor. But after mdb_del empties the tree, mdb_cursor_del0 sets C_DEL on other cursors at the same position to signal that their entry was deleted. mdb_rebalance then clears C_INITIALIZED because the tree is now empty. The cursor now has C_DEL set but C_INITIALIZED cleared. The C_DEL flag carries meaningful state -- it tells mdb_cursor_next that the cursor's record was deleted and certain advances should be suppressed (this is already checked at line 5964 for MDB_NEXT_DUP). But the !C_INITIALIZED branch at line 5967 ignores C_DEL entirely, discarding that signal. The result: MDB_NEXT_NODUP on a cursor whose entry was deleted behaves identically to MDB_FIRST, which is incorrect. In a traversal loop where each iteration deletes and re-inserts the sole key, this creates an infinite loop that is inescapable from user code. The inconsistency is that deleting one of two keys works correctly (cursor advances to the next key), but deleting the only key does not (cursor rewinds to the beginning). The difference is solely whether the tree happened to empty, which is an internal detail invisible to the caller. -- You are receiving this mail because: You are on the CC list for the issue.
