From fe8351a0b1ad3cb0c7f3d60e05feb93fae357b8b Mon Sep 17 00:00:00 2001 From: chengliefeng Date: Mon, 19 Aug 2024 22:55:32 +0800 Subject: [PATCH 1/5] optimize: report the tcc fence transaction isolation level (#6679) --- .../org/apache/seata/common/Constants.java | 5 ++ .../TccActionInterceptorHandler.java | 10 ++++ .../seata/rm/tcc/utils/MethodUtils.java | 47 +++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 tcc/src/main/java/org/apache/seata/rm/tcc/utils/MethodUtils.java diff --git a/common/src/main/java/org/apache/seata/common/Constants.java b/common/src/main/java/org/apache/seata/common/Constants.java index 1c715b2dffe..d3a0ab512d2 100644 --- a/common/src/main/java/org/apache/seata/common/Constants.java +++ b/common/src/main/java/org/apache/seata/common/Constants.java @@ -101,6 +101,11 @@ public interface Constants { */ String TX_ACTION_CONTEXT = "actionContext"; + /** + * isolation + */ + String TX_ISOLATION = "isolation"; + /** * default charset name */ diff --git a/tcc/src/main/java/org/apache/seata/rm/tcc/interceptor/TccActionInterceptorHandler.java b/tcc/src/main/java/org/apache/seata/rm/tcc/interceptor/TccActionInterceptorHandler.java index acf84663c1d..3df7fdcbe92 100644 --- a/tcc/src/main/java/org/apache/seata/rm/tcc/interceptor/TccActionInterceptorHandler.java +++ b/tcc/src/main/java/org/apache/seata/rm/tcc/interceptor/TccActionInterceptorHandler.java @@ -38,7 +38,9 @@ import org.apache.seata.integration.tx.api.interceptor.TwoPhaseBusinessActionParam; import org.apache.seata.integration.tx.api.interceptor.handler.AbstractProxyInvocationHandler; import org.apache.seata.rm.tcc.api.TwoPhaseBusinessAction; +import org.apache.seata.rm.tcc.utils.MethodUtils; import org.slf4j.MDC; +import org.springframework.transaction.annotation.Transactional; import static org.apache.seata.common.ConfigurationKeys.TCC_ACTION_INTERCEPTOR_ORDER; @@ -82,6 +84,7 @@ protected Object doInvoke(InvocationWrapper invocation) throws Throwable { } try { TwoPhaseBusinessActionParam businessActionParam = createTwoPhaseBusinessActionParam(businessAction); + initTransactionalAnnotationContext(method, targetBean, businessActionParam.getBusinessActionContext()); //Handler the TCC Aspect, and return the business result return actionInterceptorHandler.proceed(method, invocation.getArguments(), xid, businessActionParam, invocation::proceed); @@ -99,6 +102,13 @@ protected Object doInvoke(InvocationWrapper invocation) throws Throwable { return invocation.proceed(); } + private void initTransactionalAnnotationContext(Method method, Object targetBean, Map businessActionContext) { + Transactional transactionalAnnotation = MethodUtils.getTransactionalAnnotationByMethod(method, targetBean); + if (transactionalAnnotation != null) { + businessActionContext.put(Constants.TX_ISOLATION, transactionalAnnotation.isolation().value()); + } + } + private Annotation parseAnnotation(Method methodKey) throws NoSuchMethodException { Annotation result = parseAnnotationCache.computeIfAbsent(methodKey, method -> { Annotation twoPhaseBusinessAction = method.getAnnotation(getAnnotationClass()); diff --git a/tcc/src/main/java/org/apache/seata/rm/tcc/utils/MethodUtils.java b/tcc/src/main/java/org/apache/seata/rm/tcc/utils/MethodUtils.java new file mode 100644 index 00000000000..a3c6a758e60 --- /dev/null +++ b/tcc/src/main/java/org/apache/seata/rm/tcc/utils/MethodUtils.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.rm.tcc.utils; + +import java.lang.reflect.Method; + +import org.apache.seata.common.exception.ShouldNeverHappenException; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.transaction.annotation.Transactional; + + + +public class MethodUtils { + /** + * Retrieve the Transactional annotation of a business method + * @param interfaceMethod interface method object + * @param targetTCCBean target tcc bean + * @return the @Transactional annotation + */ + public static Transactional getTransactionalAnnotationByMethod(Method interfaceMethod, Object targetTCCBean) { + String methodName = interfaceMethod.getName(); + Class[] parameterTypes = interfaceMethod.getParameterTypes(); + + Class clazz = targetTCCBean.getClass(); + Method implementationMethod; + try { + implementationMethod = clazz.getMethod(methodName, parameterTypes); + } catch (NoSuchMethodException e) { + throw new ShouldNeverHappenException(e); + } + return AnnotatedElementUtils.findMergedAnnotation(implementationMethod, Transactional.class); + } +} From 6d40cbe55897f622c9c1da5f8f8264dd61b64fb6 Mon Sep 17 00:00:00 2001 From: chengliefeng Date: Mon, 19 Aug 2024 23:03:10 +0800 Subject: [PATCH 2/5] optimize: report the tcc fence transaction isolation level (#6679) --- changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 1 + 2 files changed, 2 insertions(+) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 081e0757d81..bbc5cb504a7 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -59,6 +59,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6755](https://github.com/apache/incubator-seata/pull/6755)] optimize namingserver code logic - [[#6763](https://github.com/apache/incubator-seata/pull/6763)] optimize NacosConfiguration singleton reload - [[#6761](https://github.com/apache/incubator-seata/pull/6761)] optimize the namingserver code to improve readability +- [[#6768](https://github.com/apache/incubator-seata/pull/6768)] report the tcc fence transaction isolation level ### refactor: diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 0d7436294d3..0b98b0fd8a4 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -60,6 +60,7 @@ - [[#6755](https://github.com/apache/incubator-seata/pull/6755)] 优化namingserver代码逻辑 - [[#6763](https://github.com/apache/incubator-seata/pull/6763)] 优化 NacosConfiguration 单例加载 - [[#6761](https://github.com/apache/incubator-seata/pull/6761)] 提升namingserver manager代码可读性 +- [[#6768](https://github.com/apache/incubator-seata/pull/6768)] 上报tcc fence事务隔离级别 ### refactor: From b5c18012153b8933f0b44c3ee35ee55e7e5ccfa2 Mon Sep 17 00:00:00 2001 From: chengliefeng Date: Mon, 19 Aug 2024 23:09:44 +0800 Subject: [PATCH 3/5] optimize: report the tcc fence transaction isolation level (#6679) --- .../rm/tcc/interceptor/TccActionInterceptorHandler.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tcc/src/main/java/org/apache/seata/rm/tcc/interceptor/TccActionInterceptorHandler.java b/tcc/src/main/java/org/apache/seata/rm/tcc/interceptor/TccActionInterceptorHandler.java index 3df7fdcbe92..dc6a9d34248 100644 --- a/tcc/src/main/java/org/apache/seata/rm/tcc/interceptor/TccActionInterceptorHandler.java +++ b/tcc/src/main/java/org/apache/seata/rm/tcc/interceptor/TccActionInterceptorHandler.java @@ -102,6 +102,12 @@ protected Object doInvoke(InvocationWrapper invocation) throws Throwable { return invocation.proceed(); } + /** + * Initializes the transaction annotation context + * @param method the method + * @param targetBean the target bean + * @param businessActionContext the business action context + */ private void initTransactionalAnnotationContext(Method method, Object targetBean, Map businessActionContext) { Transactional transactionalAnnotation = MethodUtils.getTransactionalAnnotationByMethod(method, targetBean); if (transactionalAnnotation != null) { From 068cd80653141e6e107f4065e2d33114c8ad6e91 Mon Sep 17 00:00:00 2001 From: chengliefeng Date: Mon, 19 Aug 2024 23:30:46 +0800 Subject: [PATCH 4/5] bugfix: fix tcc fence deadLock (#6679) --- .../org/apache/seata/common/Constants.java | 10 ++++ .../seata/rm/fence/SpringFenceHandler.java | 53 +++++++++++++++++-- .../seata/rm/tcc/TCCResourceManager.java | 10 ++++ 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/org/apache/seata/common/Constants.java b/common/src/main/java/org/apache/seata/common/Constants.java index d3a0ab512d2..43da1827e05 100644 --- a/common/src/main/java/org/apache/seata/common/Constants.java +++ b/common/src/main/java/org/apache/seata/common/Constants.java @@ -225,4 +225,14 @@ public interface Constants { */ String JACKSON_JSON_TEXT_PREFIX = "{\"@class\":"; + /** + * The constant DEAD_LOCK_SQL_STATE + */ + String DEAD_LOCK_SQL_STATE = "40001"; + + /** + * The constant DEAD_LOCK_ERROR_CODE + */ + int DEAD_LOCK_ERROR_CODE = 1213; + } diff --git a/spring/src/main/java/org/apache/seata/rm/fence/SpringFenceHandler.java b/spring/src/main/java/org/apache/seata/rm/fence/SpringFenceHandler.java index 95ada3dc090..88003f6f4fb 100644 --- a/spring/src/main/java/org/apache/seata/rm/fence/SpringFenceHandler.java +++ b/spring/src/main/java/org/apache/seata/rm/fence/SpringFenceHandler.java @@ -18,8 +18,12 @@ import java.lang.reflect.Method; import java.sql.Connection; +import java.sql.SQLException; import java.util.ArrayList; import java.util.Date; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; @@ -28,6 +32,7 @@ import javax.sql.DataSource; +import org.apache.seata.common.Constants; import org.apache.seata.common.exception.ExceptionUtil; import org.apache.seata.common.exception.FrameworkErrorCode; import org.apache.seata.common.exception.SkipCallbackWrapperException; @@ -41,10 +46,14 @@ import org.apache.seata.integration.tx.api.fence.store.CommonFenceStore; import org.apache.seata.integration.tx.api.fence.store.db.CommonFenceStoreDataBaseDAO; import org.apache.seata.integration.tx.api.remoting.TwoPhaseResult; +import org.apache.seata.rm.tcc.api.BusinessActionContext; +import org.apache.seata.rm.tcc.api.BusinessActionContextUtil; +import org.apache.seata.rm.tcc.utils.MethodUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionTemplate; /** @@ -108,7 +117,8 @@ public static void setTransactionTemplate(TransactionTemplate transactionTemplat */ @Override public Object prepareFence(String xid, Long branchId, String actionName, Callback targetCallback) { - return transactionTemplate.execute(status -> { + TransactionTemplate template = createTransactionTemplateForTransactionalMethod(null); + return template.execute(status -> { try { Connection conn = DataSourceUtils.getConnection(dataSource); boolean result = insertCommonFenceLog(conn, xid, branchId, actionName, CommonFenceConstant.STATUS_TRIED); @@ -146,7 +156,8 @@ public Object prepareFence(String xid, Long branchId, String actionName, Callbac @Override public boolean commitFence(Method commitMethod, Object targetTCCBean, String xid, Long branchId, Object[] args) { - return transactionTemplate.execute(status -> { + TransactionTemplate template = createTransactionTemplateForTransactionalMethod(MethodUtils.getTransactionalAnnotationByMethod(commitMethod, targetTCCBean)); + return template.execute(status -> { try { Connection conn = DataSourceUtils.getConnection(dataSource); CommonFenceDO commonFenceDO = COMMON_FENCE_DAO.queryCommonFenceDO(conn, xid, branchId); @@ -188,7 +199,8 @@ public boolean commitFence(Method commitMethod, Object targetTCCBean, @Override public boolean rollbackFence(Method rollbackMethod, Object targetTCCBean, String xid, Long branchId, Object[] args, String actionName) { - return transactionTemplate.execute(status -> { + TransactionTemplate template = createTransactionTemplateForTransactionalMethod(MethodUtils.getTransactionalAnnotationByMethod(rollbackMethod, targetTCCBean)); + return template.execute(status -> { try { Connection conn = DataSourceUtils.getConnection(dataSource); CommonFenceDO commonFenceDO = COMMON_FENCE_DAO.queryCommonFenceDO(conn, xid, branchId); @@ -218,6 +230,16 @@ public boolean rollbackFence(Method rollbackMethod, Object targetTCCBean, return result; } catch (Throwable t) { status.setRollbackOnly(); + Throwable cause = t.getCause(); + if (cause != null && cause instanceof SQLException) { + SQLException sqlException = (SQLException) cause; + String sqlState = sqlException.getSQLState(); + int errorCode = sqlException.getErrorCode(); + if (Constants.DEAD_LOCK_SQL_STATE.equals(sqlState) && Constants.DEAD_LOCK_ERROR_CODE == errorCode) { + // MySQL deadlock exception + LOGGER.error("Common fence rollback fail. xid: {}, branchId: {}, This exception may be due to the deadlock caused by the transaction isolation level being Repeatable Read. The seata server will try to roll back again, so you can ignore this exception. (To avoid this exception, you can set transaction isolation to Read Committed.)", xid, branchId); + } + } throw new SkipCallbackWrapperException(t); } }); @@ -353,6 +375,31 @@ private static void addToLogCleanQueue(final String xid, final long branchId) { } } + /** + * Creating a transactionTemplate with business transactional attributes + * @param transactional Transactional annotation + * @return + */ + private TransactionTemplate createTransactionTemplateForTransactionalMethod(Transactional transactional) { + Map businessActionContext = Optional.ofNullable(BusinessActionContextUtil.getContext()).map(BusinessActionContext::getActionContext).orElse(null); + if (transactional == null && businessActionContext == null) { + return transactionTemplate; + } + if (transactional != null) { + TransactionTemplate template = new TransactionTemplate(Objects.requireNonNull(transactionTemplate.getTransactionManager())); + template.setIsolationLevel(transactional.isolation().value()); + return template; + } else { + boolean containIsolation = businessActionContext.containsKey(Constants.TX_ISOLATION); + if (!containIsolation) { + return transactionTemplate; + } + TransactionTemplate template = new TransactionTemplate(Objects.requireNonNull(transactionTemplate.getTransactionManager())); + template.setIsolationLevel((int) businessActionContext.get(Constants.TX_ISOLATION)); + return template; + } + } + /** * clean fence log that has the final status runnable. * diff --git a/tcc/src/main/java/org/apache/seata/rm/tcc/TCCResourceManager.java b/tcc/src/main/java/org/apache/seata/rm/tcc/TCCResourceManager.java index eaaa76494cd..2ad3c2b373f 100644 --- a/tcc/src/main/java/org/apache/seata/rm/tcc/TCCResourceManager.java +++ b/tcc/src/main/java/org/apache/seata/rm/tcc/TCCResourceManager.java @@ -119,6 +119,8 @@ public BranchStatus branchCommit(BranchType branchType, String xid, long branchI applicationData); Object[] args = this.getTwoPhaseCommitArgs(tccResource, businessActionContext); + //share actionContext implicitly + BusinessActionContextUtil.setContext(businessActionContext); Object ret; boolean result; // add idempotent and anti hanging @@ -146,6 +148,9 @@ public BranchStatus branchCommit(BranchType branchType, String xid, long branchI String msg = String.format("commit TCC resource error, resourceId: %s, xid: %s.", resourceId, xid); LOGGER.error(msg, ExceptionUtil.unwrap(t)); return BranchStatus.PhaseTwo_CommitFailed_Retryable; + } finally { + // clear the action context + BusinessActionContextUtil.clear(); } } @@ -177,6 +182,8 @@ public BranchStatus branchRollback(BranchType branchType, String xid, long branc BusinessActionContext businessActionContext = BusinessActionContextUtil.getBusinessActionContext(xid, branchId, resourceId, applicationData); Object[] args = this.getTwoPhaseRollbackArgs(tccResource, businessActionContext); + //share actionContext implicitly + BusinessActionContextUtil.setContext(businessActionContext); Object ret; boolean result; // add idempotent and anti hanging @@ -205,6 +212,9 @@ public BranchStatus branchRollback(BranchType branchType, String xid, long branc String msg = String.format("rollback TCC resource error, resourceId: %s, xid: %s.", resourceId, xid); LOGGER.error(msg, ExceptionUtil.unwrap(t)); return BranchStatus.PhaseTwo_RollbackFailed_Retryable; + } finally { + // clear the action context + BusinessActionContextUtil.clear(); } } From 9ffc3dbfaff184fda2f938ef6092f88cf778ed51 Mon Sep 17 00:00:00 2001 From: chengliefeng Date: Mon, 19 Aug 2024 23:39:00 +0800 Subject: [PATCH 5/5] bugfix: fix tcc fence deadLock (#6679) --- changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index bbc5cb504a7..899fc783568 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -24,6 +24,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6714](https://github.com/apache/incubator-seata/pull/6714)] fix dameng delete undo fail - [[#6701](https://github.com/apache/incubator-seata/pull/6728)] fix support serialization for dm.jdbc.driver.DmdbTimestamp - [[#6757](https://github.com/apache/incubator-seata/pull/6757)] the bug where multiple nodes cannot be retrieved from the naming server +- [[#6769](https://github.com/apache/incubator-seata/pull/6769)] fix tcc fence deadLock ### optimize: diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 0b98b0fd8a4..0371543adeb 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -25,7 +25,7 @@ - [[#6714](https://github.com/apache/incubator-seata/pull/6714)] 修复达梦数据库的delete sql回滚失败的问题 - [[#6701](https://github.com/apache/incubator-seata/pull/6728)] 修复达梦数据库的对dm.jdbc.driver.DmdbTimestamp的支持 - [[#6757](https://github.com/apache/incubator-seata/pull/6757)] 修复client通过namingserver只能获取到一个tc节点的bug - +- [[#6769](https://github.com/apache/incubator-seata/pull/6769)] 修复tcc fence死锁 ### optimize: - [[#6499](https://github.com/apache/incubator-seata/pull/6499)] 拆分 committing 和 rollbacking 状态的任务线程池