You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
GenericDaoBase.persist() does not own a transaction, it joins the caller's via TransactionLegacy.currentTxn() and calls txn.start(), which pushes a START_TXN nesting level. On the SQLException path it never reaches txn.commit(), and there is no finally, so that nesting level is leaked:
finalTransactionLegacytxn = TransactionLegacy.currentTxn(); // the CALLER's transactiontry {
txn.start(); // pushes a START_TXN nesting level
...
pstmt.executeUpdate(); // throws
...
txn.commit(); // never reached
} catch (finalSQLExceptione) {
logger.error("DB Exception on: " + pstmt, e);
handleEntityExistsException(e); // throws EntityExistsExceptionthrownewCloudRuntimeException("Unable to persist on DB, due to: " + e.getLocalizedMessage());
}
// no finally, the pushed nesting level is never released
Consequence: the caller's own commit() then finds the transaction unbalanced and silently no-ops, logging only:
WARN [db.Transaction.Transaction] txn: Commit called when it is not a transaction:
(TransactionLegacy.commit() — if (!_txn) { LOGGER.warn(...); return false; })
Everything in that transaction is discarded while the caller believes it committed.
Why it matters beyond one call site: callers that deliberately catch EntityExistsException in order to log-and-continue cannot actually continue, because the enclosing transaction is already unrecoverable. UsageManagerImpl.createHelperRecord() is one such caller, and in #13399 this is what converts a single constraint violation into permanent usage-aggregation failure rather than one skipped record.
versions
Observed on CloudStack 4.22.1.0 (EL9 packages), MySQL 8.x / InnoDB.
This is a code-level defect in framework/db rather than an environment-specific one; the code path is not version-specific and hypervisor/storage/network are not relevant.
The steps to reproduce the bug
On 4.22.1.0 with the Usage Server enabled, deploy an instance. Its ROOT volume produces a VOLUME.CREATE usage event carrying vm_id.
createHelperRecord() catches the resulting EntityExistsException and logs a warning, intending to continue.
Observe txn: Commit called when it is not a transaction shortly afterwards, and that the processed flags set for that batch of events in cloud_usage.usage_event were never committed.
Any caller that hits a constraint violation inside a transaction it owns should show the same behaviour, #13399 is simply a case where it happens on every VM deployment.
What to do about it?
Release the nesting level in a finally, and/or mark the transaction rollback-only so callers receive a real failure instead of a silent no-op.
Either way this needs someone familiar with TransactionLegacy's nesting semantics, since GenericDaoBase backs every DAO in the codebase. I'm raising it rather than proposing a patch.
problem
Split out of #13399 at @DaanHoogland's request.
GenericDaoBase.persist()does not own a transaction, it joins the caller's viaTransactionLegacy.currentTxn()and callstxn.start(), which pushes aSTART_TXNnesting level. On theSQLExceptionpath it never reachestxn.commit(), and there is nofinally, so that nesting level is leaked:Consequence: the caller's own
commit()then finds the transaction unbalanced and silently no-ops, logging only:(
TransactionLegacy.commit()—if (!_txn) { LOGGER.warn(...); return false; })Everything in that transaction is discarded while the caller believes it committed.
Why it matters beyond one call site: callers that deliberately catch
EntityExistsExceptionin order to log-and-continue cannot actually continue, because the enclosing transaction is already unrecoverable.UsageManagerImpl.createHelperRecord()is one such caller, and in #13399 this is what converts a single constraint violation into permanent usage-aggregation failure rather than one skipped record.versions
Observed on CloudStack 4.22.1.0 (EL9 packages), MySQL 8.x / InnoDB.
This is a code-level defect in
framework/dbrather than an environment-specific one; the code path is not version-specific and hypervisor/storage/network are not relevant.The steps to reproduce the bug
VOLUME.CREATEusage event carryingvm_id.UsageManagerImpl.createVolumeHelperEvent()performs twopersist()calls sharing(volume_id, created); the second violatesusage_volume's unique key (see Usage Server repeatedly reprocesses historical usage and creates duplicate cloud_usage records for usage_type=1 #13399).createHelperRecord()catches the resultingEntityExistsExceptionand logs a warning, intending to continue.txn: Commit called when it is not a transactionshortly afterwards, and that theprocessedflags set for that batch of events incloud_usage.usage_eventwere never committed.Any caller that hits a constraint violation inside a transaction it owns should show the same behaviour, #13399 is simply a case where it happens on every VM deployment.
What to do about it?
Release the nesting level in a
finally, and/or mark the transaction rollback-only so callers receive a real failure instead of a silent no-op.Either way this needs someone familiar with
TransactionLegacy's nesting semantics, sinceGenericDaoBasebacks every DAO in the codebase. I'm raising it rather than proposing a patch.