Skip to content

feat(transaction): Introduce stateful TransactionAction trait - #3265

Open
CTTY wants to merge 2 commits into
apache:mainfrom
CTTY:ctty/tx-state
Open

CTTY wants to merge 2 commits into
apache:mainfrom
CTTY:ctty/tx-state

Conversation

@CTTY

@CTTY CTTY commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator

Which issue does this PR close?

Part of epic #3266 (milestone 1 of RFC-0003, introduced in #2620).

What changes are included in this PR?

  • New TransactionAction trait: Clone + Send + Sync + 'static supertraits, associated State type, new_state(), per-attempt commit(&self, &mut State, &Table), and a consuming cleanup(Box<Self>, State, &Table, CommitStatus) (defined but not yet invoked; wiring lands with milestone 3).
  • CommitStatus (Committed / Failed / Unknown) carrying the RFC §6 terminal-cleanup semantics.
  • ActionEntry<A> pairs action intent with its state by construction, erased behind the object-safe ErasedActionEntry adapter; Transaction stores Vec<TransactionActionEntry>.
  • Manual Clone for Transaction via fresh_clone(): clones duplicate intent and create fresh state, never sharing retry state between executions.
  • All eight existing actions migrate with State = () and explicit no-op cleanup; no public API changes other than the actions now implementing Clone.

Are these changes tested?

Covered by the existing transaction test suite (updated for the new trait shape), plus a new test asserting that cloning a transaction produces independent entries. Full iceberg lib suite passes.

AI Disclosure

Used Claude for the development

Rework the TransactionAction trait per the stateful transaction RFC
(milestone 1): each action declares an associated retry-persistent
State type created via new_state(), commit() borrows the action
immutably and its state mutably per attempt, and a consuming cleanup()
plus CommitStatus define terminal-cleanup semantics (not yet invoked;
wired in a later milestone).

Transactions now store one entry per action, pairing immutable intent
with exclusively-owned execution state behind an object-safe
ErasedActionEntry adapter. Cloning a transaction creates a new
execution of the action plan: actions are duplicated and paired with
fresh state, so retry state is never shared between executions.

All existing actions migrate with State = () and no-op cleanup.
@CTTY
CTTY marked this pull request as ready for review September 22, 2026 22:19

@JanKaul JanKaul left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a minor comment. Generally, looks good to me.

/// The pairing is preserved by construction: the entry is created with fresh
/// state and owns both exclusively, so terminal cleanup can consume them together.
pub(crate) struct ActionEntry<A: TransactionAction> {
action: Box<A>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the inner Box<A> here necessary? Since the entry is already stored as Box<dyn ErasedActionEntry>, this looks like a second nested allocation — and as far as I can tell it only exists to satisfy the self: Box<Self> receiver on TransactionAction::cleanup.

Given that TransactionAction is never used as a trait object (erasure happens at the ErasedActionEntry layer), could cleanup take self by value instead of Box<Self>? That would let ActionEntry hold action: A directly and drop the extra allocation:

async fn cleanup(self, state: Self::State, table: &Table, status: CommitStatus);

Or was the Box<Self> receiver a deliberate choice for something later in the RFC?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants