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.

Reply via email to