This is an automated email from the ASF dual-hosted git repository.
gangwu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-cpp.git
The following commit(s) were added to refs/heads/main by this push:
new 7b959525 fix: prevent double-commit in Transaction (#427)
7b959525 is described below
commit 7b9595256ee9ed21eaedb0dde38f75fe9ee6f947
Author: Xinli Shang <[email protected]>
AuthorDate: Mon Dec 22 17:44:43 2025 -0800
fix: prevent double-commit in Transaction (#427)
Prevents Transaction::Commit() from being called multiple times by:
- Adding committed_ flag to track transaction state
- Checking flag at start of Commit() and returning error if already
committed
- Setting flag after successful commit (both empty and non-empty
updates)
- Updating table_ reference after successful catalog update
This ensures transactions can only be committed once, preventing
unintended side effects and maintaining transaction semantics.
---------
Co-authored-by: Gang Wu <[email protected]>
---
src/iceberg/transaction.cc | 13 ++++++++++++-
src/iceberg/transaction.h | 2 ++
2 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/src/iceberg/transaction.cc b/src/iceberg/transaction.cc
index ca39ec04..c8cf42d3 100644
--- a/src/iceberg/transaction.cc
+++ b/src/iceberg/transaction.cc
@@ -75,6 +75,9 @@ Status
Transaction::Apply(std::vector<std::unique_ptr<TableUpdate>> updates) {
}
Result<std::shared_ptr<Table>> Transaction::Commit() {
+ if (committed_) {
+ return Invalid("Transaction already committed");
+ }
if (!last_update_committed_) {
return InvalidArgument(
"Cannot commit transaction when previous update is not committed");
@@ -82,6 +85,7 @@ Result<std::shared_ptr<Table>> Transaction::Commit() {
const auto& updates = metadata_builder_->changes();
if (updates.empty()) {
+ committed_ = true;
return table_;
}
@@ -98,7 +102,14 @@ Result<std::shared_ptr<Table>> Transaction::Commit() {
}
// XXX: we should handle commit failure and retry here.
- return table_->catalog()->UpdateTable(table_->name(), requirements, updates);
+ ICEBERG_ASSIGN_OR_RAISE(auto updated_table, table_->catalog()->UpdateTable(
+ table_->name(),
requirements, updates));
+
+ // Mark as committed and update table reference
+ committed_ = true;
+ table_ = std::move(updated_table);
+
+ return table_;
}
Result<std::shared_ptr<UpdateProperties>> Transaction::NewUpdateProperties() {
diff --git a/src/iceberg/transaction.h b/src/iceberg/transaction.h
index 36328026..73c34683 100644
--- a/src/iceberg/transaction.h
+++ b/src/iceberg/transaction.h
@@ -80,6 +80,8 @@ class ICEBERG_EXPORT Transaction : public
std::enable_shared_from_this<Transacti
const bool auto_commit_;
// To make the state simple, we require updates are added and committed in
order.
bool last_update_committed_ = true;
+ // Tracks if transaction has been committed to prevent double-commit
+ bool committed_ = false;
// Keep track of all created pending updates. Use weak_ptr to avoid circular
references.
// This is useful to retry failed updates.
std::vector<std::weak_ptr<PendingUpdate>> pending_updates_;