This is an automated email from the ASF dual-hosted git repository.
funky-eyes pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/incubator-seata.git
The following commit(s) were added to refs/heads/2.x by this push:
new d9eb22d85d bugfix:Branch logic is prepare Failed at phase one, need to
notify TC of global rollback (#7997)
d9eb22d85d is described below
commit d9eb22d85d5637fa7b452b7b020d2406d88ac732
Author: shukai <[email protected]>
AuthorDate: Mon Jun 8 10:11:07 2026 +0800
bugfix:Branch logic is prepare Failed at phase one, need to notify TC of
global rollback (#7997)
---
.../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 ++++++++++-----
8 files changed, 78 insertions(+), 23 deletions(-)
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]