Skip to content

fix: release START_TXN nesting level on SQLException paths in GenericDaoBase sibling methods - #13926

Open
waterWang wants to merge 1 commit into
apache:mainfrom
waterWang:fix-txn-nesting-sibling-methods
Open

fix: release START_TXN nesting level on SQLException paths in GenericDaoBase sibling methods#13926
waterWang wants to merge 1 commit into
apache:mainfrom
waterWang:fix-txn-nesting-sibling-methods

Conversation

@waterWang

Copy link
Copy Markdown

Bug

GenericDaoBase contains seven sibling methods that leak a transaction nesting level on SQLException paths, same root cause as #13905 (persist() — fixed in #13925):

  • update(ID, UpdateBuilder, T)
  • update(UpdateBuilder, SearchCriteria, Integer)
  • expunge(ID)
  • insertElementCollection(...)
  • expunge()
  • unremove(ID)
  • remove(ID)

Root cause

TransactionLegacy.start() pushes a START_TXN stack element onto the caller's transaction stack and sets _txn = true. Each of these methods calls txn.start(), but when the SQLException catch block throws, control never reaches txn.commit(). Because there is no finally rollback, the pushed START_TXN stays on the stack forever.

The caller's own commit() then finds the transaction stack still balanced with a START_TXN entry (hasTxnInStack() == true) and silently returns false, logging only Not committing because transaction started elsewhere. Every change made inside the caller's transaction is silently discarded while the caller believes it committed.

For Example, remove(ID) on an entity with a removed column:

  • silently loses the delete,
  • or if the DAO method is the outermost transaction, the update is discarded.

Fix

Mirror the persist() fix from #13925: add a boolean committed flag, set it to true right after txn.commit(), and roll back in a finally block when commit was never reached:

boolean committed = false;
try {
    txn.start();
    ...
    txn.commit();
    committed = true;
    ...
} catch (final SQLException e) {
    ...
    throw new CloudRuntimeException(...);
} finally {
    if (!committed) {
        txn.rollback();
    }
}

rollback() pops the START_TXN level and, when it was the outermost one, performs a real DB rollback — so the caller receives a genuine failure instead of a silent no-op.

Signed-off-by: waterWang waterwang@proton.me

…DaoBase sibling methods

update(ID,...), update(ub,sc,rows), expunge(ID), insertElementCollection,
expunge(), unremove(ID) and remove(ID) call txn.start() which pushes a
START_TXN nesting level onto the caller's transaction stack, but their
SQLException catch blocks throw without reaching txn.commit() and without
a finally rollback. The leaked nesting level makes the caller's commit()
silently no-op, discarding all work done in the transaction.

Add a committed flag and roll back in finally when commit was not reached,
mirroring the fix for apache#13905 in persist().

Signed-off-by: waterWang <waterwang@proton.me>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants