-
Type:
Bug
-
Resolution: Unresolved
-
Priority:
Critical - P2
-
None
-
Affects Version/s: None
-
Component/s: Cursors
-
Storage Engines - Transactions
-
SE Transactions - 2026-03-27, SE Transactions - 2026-04-10
-
8
Out-of-order reads caused by prepared conflict mid-walk
This is a cluster of related bugs all triggered by WT_PREPARE_CONFLICT during a cursor walk:
Bug A — Wrong fresh-start detection: __clayered_iterate_constituents used F_ISSET(&clayered->iface, WT_CURSTD_KEY_INT) to detect whether the cursor was at the start of a walk. But WT_PREPARE_CONFLICT clears KEY_INT on the btree cursor while leaving the page ref set (for retry). This caused a mid-walk conflict to look like a fresh start, so both cursors were advanced from the beginning of the table, skipping keys already passed.
Bug B — Reset cursors missed ref-positioned cursors: __clayered_reset_cursors only checked KEY_SET to decide whether constituent cursors needed resetting. After a prepare conflict, KEY_SET is cleared but ref remains set, so the fast-path in __clayered_reset_cursors incorrectly skipped the reset, leaving the btree cursor in a half-positioned state that later triggered the "constituent cursor already positioned" assertion.
Bug C — Error handler preserved position on WT_PREPARE_CONFLICT: The err: label in __clayered_iterate called __clayered_reset_cursors only when ret != WT_PREPARE_CONFLICT, intending to preserve position for retry. But this interacted badly with the above bugs — it left stale ref/flag state on the constituents.
Bug D — __cursor_needkey in __clayered_open_cursors corrupted position state: When __clayered_open_cursors was called on a cursor with KEY_INT set (e.g., after a prepare conflict that cleared KEY_INT on the btree but not the iface), it called __cursor_needkey which copied the key to external storage and changed flag state without resetting the constituents, leaving them inconsistent.