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_;

Reply via email to