-
Type:
Improvement
-
Resolution: Unresolved
-
Priority:
Major - P3
-
None
-
Affects Version/s: None
-
Component/s: None
-
None
-
Storage Engines - Foundations
-
462.063
-
None
-
8
Prototype: layered-cursors-ops-isolation-attempt (the two most recent commits; the older ones have already been merged to develop)
Background
Today the layered cursor owns all of its state all of the time: the constituent cursors, the current-cursor selection, the iteration flags and the public key/value state. Operation bodies read and write this state directly at arbitrary points during execution, so any intermediate step (a constituent reopen, a flag update, an early error return) is immediately visible on the cursor. The cursor's durable state therefore reflects half-finished operations, and every body must be careful to leave it consistent on every exit path – which is where several past bugs lived.
Proposal
Split the state into two owners with a hand-off at the operation boundary:
- On entry, the layered cursor hands responsibility for the constituents over to the op structure. The op captures everything the operation will work with: the resolved ingest and stable cursors, the current-cursor selection, the walk direction established by the previous call, the operation mode and the role, plus a read-only view of the public cursor (const WT_CURSOR *iface) for the application's key/value inputs and the session. From this point the durable flags on the layered cursor are cleared: mid-operation, the cursor does not pretend to know where its constituents stand.
- During the operation, the op owns the constituents outright. Bodies position, advance and reset them through the op only; they cannot touch the layered cursor (there is no back-pointer) and cannot mutate the public cursor (the iface is const). Whatever intermediate states the constituents pass through are the op's private business.
- On exit, responsibility is propagated back to the layered cursor, and how it propagates depends on the outcome. The body records its verdict in the op: positioned (key and value live on the cursor), key-only (remove left a position but no value), external key (largest_key), or unpositioned (insert success, WT_NOTFOUND, any error). A single function translates that verdict into the layered cursor's durable state: which constituent is current, whether the public cursor's key/value flags are set, and whether a walk direction is re-established for the next call. A successful next/prev hands back "walk in progress"; a failure hands back a cleanly unpositioned cursor – automatically, because unpositioned is the default verdict.
The result is that each structure is responsible for exactly its own state: the op owns the transient, intra-operation state and is fully described by its fields (so a body can be exercised in isolation by constructing an op by hand), while the layered cursor owns only the durable, between-operations state and can only be changed at one place, by the verdict application. Consistency on error paths stops being a per-body obligation and becomes a property of the boundary.
Goal
Evaluate whether this ownership split is a genuine simplification of the layered cursor implementation on current develop, and whether it should be the target interface for the post-PuP rework. Expected value: op bodies unit-testable against hand-built op structures and plain table cursors (unblocks grey-box unit testing), compiler-enforced read-only inputs, and a single mutation point for all durable cursor state. Expected friction to examine honestly: operations whose effects cannot wait for the exit boundary (the write path must reset the non-target constituent before the write), interaction with the in-review iteration-flag centralization, and the safety of the window between role capture at entry and constituent capture after the cursors settle.
Deliverables
- Design note: adopt / adopt partially / reject, listing any operations whose semantics do not fit the entry/exit hand-off model.
- A proof-of-concept unit test driving one op body with a hand-constructed op and plain cursors, no WT_CURSOR_LAYERED, if the verdict is positive.
- Scoped implementation tickets; the mechanical change is small (two commits' worth) once the design is confirmed.
Out of scope
Landing the change. Any prototype used during the investigation must pass the layered cursor differential stress test.