This is an automated email from the ASF dual-hosted git repository.

funky-eyes pushed a commit to branch 2.7.0
in repository https://gitbox.apache.org/repos/asf/incubator-seata.git


The following commit(s) were added to refs/heads/2.7.0 by this push:
     new 5b677b8398 bugfix:Branch logic is prepare Failed at phase one, need to 
notify TC of global rollback
5b677b8398 is described below

commit 5b677b839887daa0d04a055eb884eb3e3199a1b5
Author: jianbin <[email protected]>
AuthorDate: Mon Jun 8 10:17:17 2026 +0800

    bugfix:Branch logic is prepare Failed at phase one, need to notify TC of 
global rollback
---
 changes/en-us/2.7.0.md                             |  1 +
 changes/zh-cn/2.7.0.md                             |  2 +-
 .../org/apache/seata/core/model/GlobalStatus.java  | 13 ++++++++++
 .../core/exception/TransactionExceptionCode.java   |  7 ++++-
 .../org/apache/seata/core/model/BranchStatus.java  |  8 +++++-
 .../seata/rm/datasource/xa/ConnectionProxyXA.java  | 30 ++++++++++++++--------
 .../rm/datasource/xa/ConnectionProxyXATest.java    |  2 +-
 .../seata/server/coordinator/DefaultCore.java      |  6 +++--
 .../apache/seata/server/transaction/xa/XACore.java | 15 ++++++++++-
 .../apache/seata/tm/api/TransactionalTemplate.java | 20 ++++++++++-----
 10 files changed, 80 insertions(+), 24 deletions(-)

diff --git a/changes/en-us/2.7.0.md b/changes/en-us/2.7.0.md
index fae3ef11bb..435ec1c935 100644
--- a/changes/en-us/2.7.0.md
+++ b/changes/en-us/2.7.0.md
@@ -51,6 +51,7 @@ Add changes here for all PR submitted to the 2.x branch.
 - [[#8113](https://github.com/apache/incubator-seata/pull/8113)] fix console 
export JSON consistency and download issues
 - [[#8118](https://github.com/apache/incubator-seata/pull/8118)] Use explicit 
columns in rollback validation query
 - [[#8124](https://github.com/apache/incubator-seata/pull/8124)] Fix PK 
extraction for batch inserts with SQL functions
+- [[#7997](https://github.com/apache/incubator-seata/pull/7997)] Branch logic 
is prepare Failed at phase one, need to notify TC of global rollback
 
 ### optimize:
 
diff --git a/changes/zh-cn/2.7.0.md b/changes/zh-cn/2.7.0.md
index 08af1db06b..0315ce3c20 100644
--- a/changes/zh-cn/2.7.0.md
+++ b/changes/zh-cn/2.7.0.md
@@ -51,7 +51,7 @@
 - [[#8078](https://github.com/apache/incubator-seata/pull/8078)] 修复 MySQL undo 
log 序列化异常
 - [[#8106](https://github.com/apache/incubator-seata/pull/8106)] 修复 AOT 
代理创建时的空指针异常
 - [[#8113](https://github.com/apache/incubator-seata/pull/8113)] 修复控制台导出 JSON 
一致性和下载问题
-
+- [[#7997](https://github.com/apache/incubator-seata/pull/7997)] 
分支逻辑在第一阶段准备失败,需要通知 TC 进行全局回滚
 
 ### optimize:
 
diff --git a/common/src/main/java/org/apache/seata/core/model/GlobalStatus.java 
b/common/src/main/java/org/apache/seata/core/model/GlobalStatus.java
index fcb11c610f..b4d92fc459 100644
--- a/common/src/main/java/org/apache/seata/core/model/GlobalStatus.java
+++ b/common/src/main/java/org/apache/seata/core/model/GlobalStatus.java
@@ -235,4 +235,17 @@ public enum GlobalStatus {
         }
         return false;
     }
+
+    /**
+     * Is one phase prepare failed boolean.
+     *
+     * @param status the status
+     * @return the boolean
+     */
+    public static boolean isOnePhasePrepareFailed(GlobalStatus status) {
+        if (status == GlobalStatus.RollbackRetrying) {
+            return true;
+        }
+        return false;
+    }
 }
diff --git 
a/core/src/main/java/org/apache/seata/core/exception/TransactionExceptionCode.java
 
b/core/src/main/java/org/apache/seata/core/exception/TransactionExceptionCode.java
index 52702ce46b..3bb57d2ea3 100644
--- 
a/core/src/main/java/org/apache/seata/core/exception/TransactionExceptionCode.java
+++ 
b/core/src/main/java/org/apache/seata/core/exception/TransactionExceptionCode.java
@@ -139,7 +139,12 @@ public enum TransactionExceptionCode {
     /**
      * Broken transaction exception code.
      */
-    Broken;
+    Broken,
+
+    /**
+     * Branch prepare failed transaction exception code.
+     */
+    BranchPrepareFailed;
 
     /**
      * Get transaction exception code.
diff --git a/core/src/main/java/org/apache/seata/core/model/BranchStatus.java 
b/core/src/main/java/org/apache/seata/core/model/BranchStatus.java
index 34c08387e2..b28a906e24 100644
--- a/core/src/main/java/org/apache/seata/core/model/BranchStatus.java
+++ b/core/src/main/java/org/apache/seata/core/model/BranchStatus.java
@@ -112,7 +112,13 @@ public enum BranchStatus {
      * Stop retry
      * description:user operate to stop retry
      */
-    STOP_RETRY(14);
+    STOP_RETRY(14),
+
+    /**
+     * The Phase one prepare failed.
+     * description:Branch logic is prepare Failed at phase one, need to notify 
TC of global rollback
+     */
+    PhaseOne_PrepareFailed(15);
 
     private int code;
 
diff --git 
a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/xa/ConnectionProxyXA.java
 
b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/xa/ConnectionProxyXA.java
index 6c2c670e97..041377b13c 100644
--- 
a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/xa/ConnectionProxyXA.java
+++ 
b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/xa/ConnectionProxyXA.java
@@ -243,6 +243,10 @@ public class ConnectionProxyXA extends 
AbstractConnectionProxyXA implements Hold
 
     @Override
     public void rollback() throws SQLException {
+        rollback(BranchStatus.PhaseOne_Failed);
+    }
+
+    private void rollback(BranchStatus branchStatus) throws SQLException {
         if (combine) {
             return;
         }
@@ -250,7 +254,7 @@ public class ConnectionProxyXA extends 
AbstractConnectionProxyXA implements Hold
             // Ignore the committing on an autocommit session and read-only 
transaction.
             return;
         }
-        if (!xaActive || this.xaBranchXid == null) {
+        if ((!xaActive && branchStatus != BranchStatus.PhaseOne_PrepareFailed) 
|| this.xaBranchXid == null) {
             throw new SQLException("should NOT rollback on an inactive 
session");
         }
         try {
@@ -260,7 +264,7 @@ public class ConnectionProxyXA extends 
AbstractConnectionProxyXA implements Hold
                 xaRollback(xaBranchXid);
             }
             // Branch Report to TC
-            reportStatusToTC(BranchStatus.PhaseOne_Failed);
+            reportStatusToTC(branchStatus);
             LOGGER.info("{} was rollbacked", xaBranchXid);
         } catch (XAException xe) {
             throw new SQLException(
@@ -311,7 +315,6 @@ public class ConnectionProxyXA extends 
AbstractConnectionProxyXA implements Hold
 
     private void checkTimeout(Long now) throws XAException {
         if (now - branchRegisterTime > TIMEOUT) {
-            xaRollback(xaBranchXid);
             throw new XAException("XA branch timeout error");
         }
     }
@@ -322,6 +325,7 @@ public class ConnectionProxyXA extends 
AbstractConnectionProxyXA implements Hold
             if (combine) {
                 return;
             }
+            boolean isException = false;
             try {
                 if (xaActive && this.xaBranchXid != null) {
                     // XA End: Success
@@ -330,7 +334,6 @@ public class ConnectionProxyXA extends 
AbstractConnectionProxyXA implements Hold
                     } catch (SQLException sqle) {
                         // Rollback immediately before the XA Branch Context 
is deleted.
                         String xaBranchXid = this.xaBranchXid.toString();
-                        rollback();
                         throw new SQLException(
                                 "Branch " + xaBranchXid + " was rollbacked on 
committing since " + sqle.getMessage(),
                                 SQLSTATE_XA_NOT_END,
@@ -348,19 +351,24 @@ public class ConnectionProxyXA extends 
AbstractConnectionProxyXA implements Hold
                         reportStatusToTC(BranchStatus.PhaseOne_RDONLY);
                     }
                 }
+            } catch (SQLException xe) {
+                isException = true;
+                // Rollback and Branch Report to TC: Exception
+                rollback(BranchStatus.PhaseOne_PrepareFailed);
+                throw xe;
             } catch (XAException xe) {
-                // Branch Report to TC: Failed
-                reportStatusToTC(BranchStatus.PhaseOne_Failed);
+                isException = true;
+                long branchId = xaBranchXid.getBranchId();
+                // Rollback and Branch Report to TC: Exception
+                rollback(BranchStatus.PhaseOne_PrepareFailed);
                 throw new SQLException(
-                        "Failed to end(TMSUCCESS)/prepare xa branch on " + xid 
+ "-" + xaBranchXid.getBranchId()
-                                + " since " + xe.getMessage(),
+                        "Failed to end(TMSUCCESS)/prepare xa branch on " + xid 
+ "-" + branchId + " since "
+                                + xe.getMessage(),
                         xe);
             } finally {
                 cleanXABranchContext();
                 rollBacked = false;
-                if (isHeld() && shouldBeHeld()) {
-                    // if kept by a keeper, just hold the connection.
-                } else {
+                if (!(isHeld() && shouldBeHeld() && !isException)) {
                     originalConnection.close();
                 }
             }
diff --git 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/xa/ConnectionProxyXATest.java
 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/xa/ConnectionProxyXATest.java
index 8237bc9b41..d656c9f861 100644
--- 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/xa/ConnectionProxyXATest.java
+++ 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/xa/ConnectionProxyXATest.java
@@ -700,7 +700,7 @@ public class ConnectionProxyXATest {
 
         // Verify branch report was called with Failed status
         Mockito.verify(mockResourceManager)
-                .branchReport(eq(BranchType.XA), eq(xid), anyLong(), 
eq(BranchStatus.PhaseOne_Failed), any());
+                .branchReport(eq(BranchType.XA), eq(xid), anyLong(), 
eq(BranchStatus.PhaseOne_PrepareFailed), any());
     }
 
     @AfterAll
diff --git 
a/server/src/main/java/org/apache/seata/server/coordinator/DefaultCore.java 
b/server/src/main/java/org/apache/seata/server/coordinator/DefaultCore.java
index 1378725bdd..4e6f4a0575 100644
--- a/server/src/main/java/org/apache/seata/server/coordinator/DefaultCore.java
+++ b/server/src/main/java/org/apache/seata/server/coordinator/DefaultCore.java
@@ -299,7 +299,8 @@ public class DefaultCore implements Core {
                         }
 
                         BranchStatus currentStatus = branchSession.getStatus();
-                        if (currentStatus == BranchStatus.PhaseOne_Failed) {
+                        if (currentStatus == BranchStatus.PhaseOne_Failed
+                                || currentStatus == 
BranchStatus.PhaseOne_PrepareFailed) {
                             SessionHelper.removeBranch(globalSession, 
branchSession, !retrying);
                             return CONTINUE;
                         }
@@ -433,7 +434,8 @@ public class DefaultCore implements Core {
                     branchSessions,
                     branchSession -> {
                         BranchStatus currentBranchStatus = 
branchSession.getStatus();
-                        if (currentBranchStatus == 
BranchStatus.PhaseOne_Failed) {
+                        if (currentBranchStatus == BranchStatus.PhaseOne_Failed
+                                || currentBranchStatus == 
BranchStatus.PhaseOne_PrepareFailed) {
                             SessionHelper.removeBranch(globalSession, 
branchSession, !retrying);
                             return CONTINUE;
                         }
diff --git 
a/server/src/main/java/org/apache/seata/server/transaction/xa/XACore.java 
b/server/src/main/java/org/apache/seata/server/transaction/xa/XACore.java
index 5bd6c98aa1..a4505ebf6f 100644
--- a/server/src/main/java/org/apache/seata/server/transaction/xa/XACore.java
+++ b/server/src/main/java/org/apache/seata/server/transaction/xa/XACore.java
@@ -19,10 +19,12 @@ package org.apache.seata.server.transaction.xa;
 import org.apache.seata.core.exception.TransactionException;
 import org.apache.seata.core.model.BranchStatus;
 import org.apache.seata.core.model.BranchType;
+import org.apache.seata.core.model.GlobalStatus;
 import org.apache.seata.core.rpc.RemotingServer;
 import org.apache.seata.server.coordinator.AbstractCore;
 import org.apache.seata.server.session.BranchSession;
 import org.apache.seata.server.session.GlobalSession;
+import org.apache.seata.server.session.SessionHolder;
 
 /**
  * The type XA core.
@@ -44,7 +46,18 @@ public class XACore extends AbstractCore {
             BranchType branchType, String xid, long branchId, BranchStatus 
status, String applicationData)
             throws TransactionException {
         super.branchReport(branchType, xid, branchId, status, applicationData);
-        if (BranchStatus.PhaseOne_Failed == status) {}
+        if (BranchStatus.PhaseOne_PrepareFailed == status) {
+            GlobalSession globalSession = SessionHolder.findGlobalSession(xid);
+            // just lock changeStatus
+            SessionHolder.lockAndExecute(globalSession, () -> {
+                globalSession.close(); // Highlight: Firstly, close the 
session, then no more branch can be registered.
+                if (globalSession.getStatus() == GlobalStatus.Begin) {
+                    
globalSession.changeGlobalStatus(GlobalStatus.RollbackRetrying);
+                    return true;
+                }
+                return false;
+            });
+        }
     }
 
     @Override
diff --git 
a/tm/src/main/java/org/apache/seata/tm/api/TransactionalTemplate.java 
b/tm/src/main/java/org/apache/seata/tm/api/TransactionalTemplate.java
index 6afbe84f0c..9cb732879a 100644
--- a/tm/src/main/java/org/apache/seata/tm/api/TransactionalTemplate.java
+++ b/tm/src/main/java/org/apache/seata/tm/api/TransactionalTemplate.java
@@ -169,11 +169,10 @@ public class TransactionalTemplate {
      *
      * @param business the business executor containing logic and transaction 
configuration
      * @return the result returned by business logic execution
-     * @throws Throwable any exception thrown by business logic (after 
transaction handling)
+     * @throws Throwable                                any exception thrown 
by business logic (after transaction handling)
      * @throws TransactionalExecutor.ExecutionException for transaction 
infrastructure failures
-     * @throws TransactionException for transaction operation failures
-     * @throws IllegalStateException for invalid transaction states
-     *
+     * @throws TransactionException                     for transaction 
operation failures
+     * @throws IllegalStateException                    for invalid 
transaction states
      * @see TransactionalExecutor#execute()
      * @see TransactionalExecutor#getTransactionInfo()
      * @see org.apache.seata.tm.api.transaction.Propagation
@@ -284,7 +283,7 @@ public class TransactionalTemplate {
      * Judge whether timeout
      *
      * @param beginTime the beginTime
-     * @param txInfo          the transaction info
+     * @param txInfo    the transaction info
      * @return is timeout
      */
     private boolean isTimeout(long beginTime, TransactionInfo txInfo) {
@@ -362,6 +361,9 @@ public class TransactionalTemplate {
                 case Finished:
                     code = TransactionalExecutor.Code.CommitFailure;
                     break;
+                case RollbackRetrying:
+                    code = TransactionalExecutor.Code.Rollbacking;
+                    break;
                 default:
             }
             Exception statusException = null;
@@ -373,9 +375,15 @@ public class TransactionalTemplate {
                 statusException = new TmTransactionException(
                         TransactionExceptionCode.TransactionTimeout,
                         String.format("Global transaction[%s] is timeout and 
will be rollback[TC].", tx.getXid()));
+            } else if 
(GlobalStatus.isOnePhasePrepareFailed(afterCommitStatus)) {
+                statusException = new TmTransactionException(
+                        TransactionExceptionCode.BranchPrepareFailed,
+                        String.format(
+                                "Global transaction[%s] is branch prepare 
failure and will be rollback[TC].",
+                                tx.getXid()));
             }
             if (null != statusException) {
-                throw new TransactionalExecutor.ExecutionException(tx, 
statusException, code);
+                throw new TransactionalExecutor.ExecutionException(tx, 
statusException, code, statusException);
             }
             triggerAfterCommit();
         } catch (TransactionException txe) {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to