diff --git a/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java b/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java index 840e0298004..c3d0f4b4441 100644 --- a/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java +++ b/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java @@ -299,6 +299,16 @@ public interface ConfigurationKeys { */ String DISTRIBUTED_LOCK_DB_TABLE = STORE_DB_PREFIX + "distributedLockTable"; + /** + * the constant AUTO_RESTART_TIME + */ + String AUTO_RESTART_TIME = SERVER_PREFIX + "autoRestartTime"; + + /** + * the constant AUTO_RESTART_PERIOD + */ + String AUTO_RESTART_PERIOD = SERVER_PREFIX + "autoRestartPeriod"; + /** * The constant STORE_DB_DATASOURCE_TYPE. */ 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 43da1827e05..cb2a6bc4fb6 100644 --- a/common/src/main/java/org/apache/seata/common/Constants.java +++ b/common/src/main/java/org/apache/seata/common/Constants.java @@ -185,6 +185,11 @@ public interface Constants { */ String AUTO_COMMIT = "autoCommit"; + /** + * The constant AUTO_RESTART_SESSION + */ + String AUTO_RESTART_SESSION = "autoRestartSession"; + /** * The constant SKIP_CHECK_LOCK */ diff --git a/common/src/main/java/org/apache/seata/common/DefaultValues.java b/common/src/main/java/org/apache/seata/common/DefaultValues.java index 85ed496430d..5f7de33d12e 100644 --- a/common/src/main/java/org/apache/seata/common/DefaultValues.java +++ b/common/src/main/java/org/apache/seata/common/DefaultValues.java @@ -501,4 +501,14 @@ public interface DefaultValues { * The constant DEFAULT_RAFT_SSL_ENABLED. */ boolean DEFAULT_RAFT_SSL_ENABLED = false; + + /** + * The constant DEFAULT_AUTO_RESTART_TIME + */ + long DEFAULT_AUTO_RESTART_TIME = 24 * 60 * 60 * 1000; + + /** + * The constant DEFAULT_AUTO_RESTART_PERIOD + */ + long DEFAULT_AUTO_RESTART_PERIOD = 60 * 60 * 1000; } diff --git a/common/src/main/java/org/apache/seata/common/result/Result.java b/common/src/main/java/org/apache/seata/common/result/Result.java index 39eb93ee6a4..7bb8254fed4 100644 --- a/common/src/main/java/org/apache/seata/common/result/Result.java +++ b/common/src/main/java/org/apache/seata/common/result/Result.java @@ -26,6 +26,8 @@ public class Result implements Serializable { public static final String SUCCESS_CODE = "200"; public static final String SUCCESS_MSG = "success"; + public static final String FAIL_CODE = "400"; + private String code = SUCCESS_CODE; private String message = SUCCESS_MSG; diff --git a/common/src/main/java/org/apache/seata/common/result/SingleResult.java b/common/src/main/java/org/apache/seata/common/result/SingleResult.java index 97394053fab..a9f1b8b1bb0 100644 --- a/common/src/main/java/org/apache/seata/common/result/SingleResult.java +++ b/common/src/main/java/org/apache/seata/common/result/SingleResult.java @@ -47,10 +47,18 @@ public static SingleResult failure(Code errorCode) { return new SingleResult(errorCode.getCode(), errorCode.getMsg()); } + public static SingleResult failure(String msg) { + return new SingleResult<>(FAIL_CODE, msg); + } + public static SingleResult success(T data) { return new SingleResult<>(SUCCESS_CODE, SUCCESS_MSG,data); } + public static SingleResult success() { + return new SingleResult<>(SUCCESS_CODE, SUCCESS_MSG, null); + } + public T getData() { return data; } diff --git a/console/src/main/resources/static/console-fe/src/locales/en-us.ts b/console/src/main/resources/static/console-fe/src/locales/en-us.ts index 58e3a8fba06..699e4e0b9f3 100644 --- a/console/src/main/resources/static/console-fe/src/locales/en-us.ts +++ b/console/src/main/resources/static/console-fe/src/locales/en-us.ts @@ -61,6 +61,14 @@ const enUs: ILocale = { showBranchSessionTitle: 'View branch session', showGlobalLockTitle: 'View global lock', branchSessionDialogTitle: 'Branch session info', + deleteGlobalSessionTitle: 'Delete global session', + stopGlobalSessionTitle: 'Stop global session retry', + startGlobalSessionTitle: 'Start global session retry', + sendGlobalSessionTitle: 'Commit or rollback global session', + changeGlobalSessionTitle: 'Change global session status', + deleteBranchSessionTitle: 'Delete branch session', + stopBranchSessionTitle: 'Stop branch session retry', + startBranchSessionTitle: 'Start branch session retry', }, GlobalLockInfo: { title: 'GlobalLockInfo', @@ -69,6 +77,8 @@ const enUs: ILocale = { inputFilterPlaceholder: 'Please enter filter criteria', resetButtonLabel: 'Reset', searchButtonLabel: 'Search', + operateTitle: 'operate', + deleteGlobalLockTitle: 'Delete global lock', }, }; diff --git a/console/src/main/resources/static/console-fe/src/locales/zh-cn.ts b/console/src/main/resources/static/console-fe/src/locales/zh-cn.ts index e2ed430d514..4d522f7a941 100644 --- a/console/src/main/resources/static/console-fe/src/locales/zh-cn.ts +++ b/console/src/main/resources/static/console-fe/src/locales/zh-cn.ts @@ -61,6 +61,14 @@ const zhCn: ILocale = { showBranchSessionTitle: '查看分支信息', showGlobalLockTitle: '查看全局锁', branchSessionDialogTitle: '分支事务信息', + deleteGlobalSessionTitle: '删除全局事务', + stopGlobalSessionTitle: '停止全局事务重试', + startGlobalSessionTitle: '开启全局事务重试', + sendGlobalSessionTitle: '提交或回滚全局事务', + changeGlobalSessionTitle: '更新全局事务状态', + deleteBranchSessionTitle: '删除分支事务', + stopBranchSessionTitle: '停止分支事务重启', + startBranchSessionTitle: '开启分支事务重试', }, GlobalLockInfo: { title: '全局锁信息', @@ -69,6 +77,8 @@ const zhCn: ILocale = { inputFilterPlaceholder: '请输入筛选条件', resetButtonLabel: '重置', searchButtonLabel: '搜索', + operateTitle: '操作', + deleteGlobalLockTitle: '删除全局锁', }, }; diff --git a/console/src/main/resources/static/console-fe/src/pages/GlobalLockInfo/GlobalLockInfo.tsx b/console/src/main/resources/static/console-fe/src/pages/GlobalLockInfo/GlobalLockInfo.tsx index 5a0c6362011..31e235769f8 100644 --- a/console/src/main/resources/static/console-fe/src/pages/GlobalLockInfo/GlobalLockInfo.tsx +++ b/console/src/main/resources/static/console-fe/src/pages/GlobalLockInfo/GlobalLockInfo.tsx @@ -15,17 +15,19 @@ * limitations under the License. */ import React from 'react'; -import { ConfigProvider, Table, Button, DatePicker, Form, Icon, Pagination, Input } from '@alicloud/console-components'; +import { ConfigProvider, Table, Button, DatePicker, Form, Icon, Pagination, Input, Dialog, Message } from '@alicloud/console-components'; import Actions, { LinkButton } from '@alicloud/console-components-actions'; import { withRouter } from 'react-router-dom'; import Page from '@/components/Page'; import { GlobalProps } from '@/module'; import styled, { css } from 'styled-components'; -import getData, { GlobalLockParam } from '@/service/globalLockInfo'; +import getData, {checkData, deleteData, GlobalLockParam } from '@/service/globalLockInfo'; import PropTypes from 'prop-types'; import moment from 'moment'; import './index.scss'; +import {get} from "lodash"; +import {enUsKey, getCurrentLanguage} from "@/reducers/locale"; const { RangePicker } = DatePicker; const FormItem = Form.Item; @@ -37,7 +39,7 @@ type GlobalLockInfoState = { globalLockParam: GlobalLockParam; } - class GlobalLockInfo extends React.Component { +class GlobalLockInfo extends React.Component { static displayName = 'GlobalLockInfo'; static propTypes = { @@ -148,12 +150,53 @@ type GlobalLockInfoState = { this.search(); } + deleteCell = (val: string, index: number, record: any) => { + const {locale = {}} = this.props; + const { + deleteGlobalLockTitle + } = locale; + let width = getCurrentLanguage() === enUsKey ? '120px' : '80px' + return ( + + + ) + } + + render() { const { locale = {} } = this.props; const { title, subTitle, createTimeLabel, inputFilterPlaceholder, searchButtonLabel, resetButtonLabel, + operateTitle, } = locale; return ( {/* global lock table */}
- - - - - - - - - - -
- + + + + + + + + + + + +
+
); diff --git a/console/src/main/resources/static/console-fe/src/pages/TransactionInfo/TransactionInfo.tsx b/console/src/main/resources/static/console-fe/src/pages/TransactionInfo/TransactionInfo.tsx index f6ae8c26ea4..090a4cc6af5 100644 --- a/console/src/main/resources/static/console-fe/src/pages/TransactionInfo/TransactionInfo.tsx +++ b/console/src/main/resources/static/console-fe/src/pages/TransactionInfo/TransactionInfo.tsx @@ -15,17 +15,19 @@ * limitations under the License. */ import React from 'react'; -import { ConfigProvider, Table, Button, DatePicker, Form, Icon, Switch, Pagination, Dialog, Input, Select } from '@alicloud/console-components'; +import { ConfigProvider, Table, Button, DatePicker, Form, Icon, Switch, Pagination, Dialog, Input, Select, Message } from '@alicloud/console-components'; import Actions, { LinkButton } from '@alicloud/console-components-actions'; import { withRouter } from 'react-router-dom'; import Page from '@/components/Page'; import { GlobalProps } from '@/module'; import styled, { css } from 'styled-components'; -import getData, { GlobalSessionParam } from '@/service/transactionInfo'; +import getData, { changeGlobalData, deleteBranchData, deleteGlobalData, GlobalSessionParam, sendGlobalCommitOrRollback, startBranchData, startGlobalData, stopBranchData, stopGlobalData } from '@/service/transactionInfo'; import PropTypes from 'prop-types'; import moment from 'moment'; import './index.scss'; +import { get as lodashGet} from "lodash"; +import {enUsKey, getCurrentLanguage} from "@/reducers/locale"; const { RangePicker } = DatePicker; const FormItem = Form.Item; @@ -42,6 +44,7 @@ type TransactionInfoState = { total: number; loading: boolean; branchSessionDialogVisible: boolean; + xid : string; currentBranchSession: Array; globalSessionParam : GlobalSessionParam; } @@ -155,6 +158,24 @@ const statusList:Array = [ iconType: 'warning', iconColor: '#FFA003', }, + { + label: 'Deleting', + value: 18, + iconType: 'warning', + iconColor: '#FFA003', + }, + { + label: 'StopCommitRetry', + value: 19, + iconType: 'ellipsis', + iconColor: 'rgb(3, 193, 253)', + }, + { + label: 'StopRollbackRetry', + value: 20, + iconType: 'ellipsis', + iconColor: 'rgb(3, 193, 253)', + }, ]; const branchSessionStatusList:Array = [ @@ -225,13 +246,23 @@ const branchSessionStatusList:Array = [ iconColor: '#FF3333', }, { - label: 'PhaseOne_RDONLY', + label: 'STOP_RETRY', value: 13, iconType: 'ellipsis', iconColor: 'rgb(3, 193, 253)', }, ]; +const commonWarnning = 'Global transaction commit or rollback inconsistency problem exists.' +const warnning = new Map([ + ['stopBranchSession', new Map([['AT', ''], ['XA', ''], + ['TCC', 'Please check if this may affect the logic of other branches.'], ['SAGA', '']])], + ['deleteBranchSession', + new Map([['AT', 'The global lock and undo log will be deleted too, dirty write problem exists.'], + ['XA', 'The xa branch will rollback'], ['TCC', ''], ['SAGA', '']])], + ['deleteGlobalSession', new Map([['AT', ''], ['XA', ''], ['TCC', ''], ['SAGA', '']])], +]) + class TransactionInfo extends React.Component { static displayName = 'TransactionInfo'; @@ -245,6 +276,7 @@ class TransactionInfo extends React.Component total: 0, loading: false, branchSessionDialogVisible: false, + xid: '', currentBranchSession: [], globalSessionParam: { withBranch: false, @@ -288,6 +320,13 @@ class TransactionInfo extends React.Component element.beginTime = (element.beginTime == null || element.beginTime === '') ? null : moment(Number(element.beginTime)).format('YYYY-MM-DD HH:mm:ss'); }); + if (this.state.branchSessionDialogVisible) { + data.data.forEach((item:any) => { + if (item.xid == this.state.xid) { + this.state.currentBranchSession = item.branchSessionVOs + } + }) + } this.setState({ list: data.data, total: data.total, @@ -299,10 +338,10 @@ class TransactionInfo extends React.Component } searchFilterOnChange = (key:string, val:string) => { - this.setState({ - globalSessionParam: Object.assign(this.state.globalSessionParam, - { [key]: val }), - }); + this.setState({ + globalSessionParam: Object.assign(this.state.globalSessionParam, + { [key]: val }), + }); } branchSessionSwitchOnChange = (checked: boolean, e: any) => { @@ -363,15 +402,21 @@ class TransactionInfo extends React.Component const { showBranchSessionTitle, showGlobalLockTitle, + deleteGlobalSessionTitle, + stopGlobalSessionTitle, + startGlobalSessionTitle, + sendGlobalSessionTitle, + changeGlobalSessionTitle, } = locale; + let width = getCurrentLanguage() === enUsKey ? '450px' : '350px' return ( - + {/* {when withBranch false, hide 'View branch session' button} */} {this.state.globalSessionParam.withBranch ? ( - {showBranchSessionTitle} + {showBranchSessionTitle} ) : null} @@ -386,6 +431,118 @@ class TransactionInfo extends React.Component > {showGlobalLockTitle} + + + + {record.status == 19 || record.status == 20 ? ( + + ) : + } + + + + ); } @@ -393,9 +550,13 @@ class TransactionInfo extends React.Component const { locale = {}, history } = this.props; const { showGlobalLockTitle, + deleteBranchSessionTitle, + stopBranchSessionTitle, + startBranchSessionTitle, } = locale; + let width = getCurrentLanguage() === enUsKey ? '450px' : '350px' return ( - + { history.push({ @@ -407,6 +568,79 @@ class TransactionInfo extends React.Component > {showGlobalLockTitle} + + + + + {record.status == 13 ? ( + + ) : } + ); } @@ -427,16 +661,18 @@ class TransactionInfo extends React.Component } showBranchSessionDialog = (val: string, index: number, record: any) => () => { - this.setState({ - branchSessionDialogVisible: true, - currentBranchSession: record.branchSessionVOs, - }); + this.setState({ + branchSessionDialogVisible: true, + currentBranchSession: record.branchSessionVOs, + xid: record.xid, + }); } closeBranchSessionDialog = () => { this.setState({ branchSessionDialogVisible: false, currentBranchSession: [], + xid: '', }); } @@ -455,14 +691,14 @@ class TransactionInfo extends React.Component {/* search form */}
@@ -519,40 +755,40 @@ class TransactionInfo extends React.Component {/* global session table */}
- - - - - - - - - - - + + + + + + + + + + +
+ - -
{/* branch session dialog */} - +
diff --git a/console/src/main/resources/static/console-fe/src/service/globalLockInfo.ts b/console/src/main/resources/static/console-fe/src/service/globalLockInfo.ts index 3c16bf15255..c7c909b25bc 100644 --- a/console/src/main/resources/static/console-fe/src/service/globalLockInfo.ts +++ b/console/src/main/resources/static/console-fe/src/service/globalLockInfo.ts @@ -29,7 +29,7 @@ export type GlobalLockParam = { timeEnd?: number }; - export default async function fetchData(params:GlobalLockParam):Promise { +export default async function fetchData(params:GlobalLockParam):Promise { let result = await request('/console/globalLock/query', { method: 'get', params, @@ -37,3 +37,25 @@ export type GlobalLockParam = { return result; } + +export async function deleteData(params: GlobalLockParam): Promise { + let result = await request('/console/globalLock/delete', { + method: 'delete', + params, + }); + return result; +} + +export async function checkData(params: GlobalLockParam): Promise { + const xid = params.xid + const branchId = params.branchId + + let result = await request('/console/globalLock/check', { + method: 'get', + params: { + xid, + branchId + }, + }); + return result; +} diff --git a/console/src/main/resources/static/console-fe/src/service/transactionInfo.ts b/console/src/main/resources/static/console-fe/src/service/transactionInfo.ts index 8e953ac849c..b3840e29842 100644 --- a/console/src/main/resources/static/console-fe/src/service/transactionInfo.ts +++ b/console/src/main/resources/static/console-fe/src/service/transactionInfo.ts @@ -28,6 +28,14 @@ export type GlobalSessionParam = { timeEnd?: number }; +export type BranchSessionParam = { + xid?: string, + branchId?: string, + applicationId?: string, + status?: number, + transactionName?: string, +}; + export default async function fetchData(params:GlobalSessionParam):Promise { let result = await request('/console/globalSession/query', { method: 'get', @@ -36,3 +44,97 @@ export default async function fetchData(params:GlobalSessionParam):Promise return result; } + +export async function deleteGlobalData(params: GlobalSessionParam): Promise { + const xid = params.xid + let result = await request('/console/globalSession/deleteGlobalSession', { + method: 'delete', + params: { + xid + }, + }); + return result; +} + +export async function stopGlobalData(params: GlobalSessionParam): Promise { + const xid = params.xid + let result = await request('/console/globalSession/stopGlobalSession', { + method: 'PUT', + params: { + xid + }, + }); + return result; +} + +export async function startGlobalData(params: GlobalSessionParam): Promise { + const xid = params.xid + let result = await request('/console/globalSession/startGlobalSession', { + method: 'PUT', + params: { + xid + }, + }); + return result; +} + +export async function sendGlobalCommitOrRollback(params: BranchSessionParam): Promise { + const xid = params.xid + let result = await request('/console/globalSession/sendCommitOrRollback', { + method: 'PUT', + params: { + xid + }, + }); + return result; +} + +export async function changeGlobalData(params: GlobalSessionParam): Promise { + const xid = params.xid + let result = await request('/console/globalSession/changeGlobalStatus', { + method: 'PUT', + params: { + xid + }, + }); + return result; +} + +export async function deleteBranchData(params: BranchSessionParam): Promise { + const xid = params.xid + const branchId = params.branchId + let result = await request('/console/branchSession/deleteBranchSession', { + method: 'delete', + params: { + xid, + branchId + }, + }); + return result; +} + +export async function stopBranchData(params: BranchSessionParam): Promise { + const xid = params.xid + const branchId = params.branchId + let result = await request('/console/branchSession/stopBranchSession', { + method: 'PUT', + params: { + xid, + branchId + }, + }); + return result; +} + +export async function startBranchData(params: BranchSessionParam): Promise { + const xid = params.xid + const branchId = params.branchId + let result = await request('/console/branchSession/startBranchSession', { + method: 'PUT', + params: { + xid, + branchId + }, + }); + return result; +} 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 16a2e899dc6..2c50e1ebbc8 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 @@ -140,8 +140,12 @@ public enum TransactionExceptionCode { /** * Broken transaction exception code. */ - Broken; + Broken, + /** + * Failed to send branch rollback request transaction exception code. + */ + FailedToSendBranchDeleteRequest; /** * 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 c728670d742..9dc4313e4c6 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 @@ -108,7 +108,13 @@ public enum BranchStatus { * The results of the Phase one are read-only. * Description: After the branch prepare in the Oracle database, only purely read-only query statements were executed. */ - PhaseOne_RDONLY(13); + PhaseOne_RDONLY(13), + + /** + * Stop retry + * description:user operate to stop retry + */ + STOP_RETRY(14); private int code; diff --git a/core/src/main/java/org/apache/seata/core/model/GlobalStatus.java b/core/src/main/java/org/apache/seata/core/model/GlobalStatus.java index 655328f57bd..76371437ca4 100644 --- a/core/src/main/java/org/apache/seata/core/model/GlobalStatus.java +++ b/core/src/main/java/org/apache/seata/core/model/GlobalStatus.java @@ -128,7 +128,25 @@ public enum GlobalStatus { * The rollback retry Timeout . */ // Finally: failed to rollback since retry timeout - RollbackRetryTimeout(17, "global transaction still failed after commit failure and retries for some time"); + RollbackRetryTimeout(17, "global transaction still failed after commit failure and retries for some time"), + + /** + * Deleting . + */ + // Deleting global transaction + Deleting(18, "global transaction is deleting"), + + /** + * Stop commit or commit retry . + */ + // stop commit or commit retry + StopCommitOrCommitRetry(19,"global transaction is commit or retry commit but stop now"), + + /** + * Stop rollback or rollback retry . + */ + // stop rollback or rollback retry + StopRollbackOrRollbackRetry(20,"global transaction is rollback or retry rollback but stop now"); private final int code; private final String desc; @@ -194,7 +212,7 @@ public static boolean isOnePhaseTimeout(GlobalStatus status) { */ public static boolean isTwoPhaseSuccess(GlobalStatus status) { if (status == GlobalStatus.Committed || status == GlobalStatus.Rollbacked - || status == GlobalStatus.TimeoutRollbacked) { + || status == GlobalStatus.TimeoutRollbacked || status == GlobalStatus.Deleting) { return true; } return false; diff --git a/core/src/main/java/org/apache/seata/core/protocol/MessageType.java b/core/src/main/java/org/apache/seata/core/protocol/MessageType.java index 58adbfbce6f..63c8a536f94 100644 --- a/core/src/main/java/org/apache/seata/core/protocol/MessageType.java +++ b/core/src/main/java/org/apache/seata/core/protocol/MessageType.java @@ -70,6 +70,14 @@ public interface MessageType { * The constant TYPE_GLOBAL_LOCK_QUERY_RESULT. */ short TYPE_GLOBAL_LOCK_QUERY_RESULT = 22; + /** + * The constant TYPE_BRANCH_DELETE. + */ + short TYPE_BRANCH_DELETE = 23; + /** + * The constant TYPE_BRANCH_DELETE_RESULT. + */ + short TYPE_BRANCH_DELETE_RESULT = 24; /** * The constant TYPE_BRANCH_COMMIT. diff --git a/core/src/main/java/org/apache/seata/core/protocol/transaction/BranchDeleteRequest.java b/core/src/main/java/org/apache/seata/core/protocol/transaction/BranchDeleteRequest.java new file mode 100644 index 00000000000..a815bc32a77 --- /dev/null +++ b/core/src/main/java/org/apache/seata/core/protocol/transaction/BranchDeleteRequest.java @@ -0,0 +1,92 @@ +/* + * 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.core.protocol.transaction; + +import org.apache.seata.core.model.BranchType; +import org.apache.seata.core.protocol.MessageType; +import org.apache.seata.core.rpc.RpcContext; + +import java.io.Serializable; + +/** + * BranchDeleteRequest + */ +public class BranchDeleteRequest extends AbstractTransactionRequestToRM implements Serializable { + private static final long serialVersionUID = 7134732523612364742L; + + /** + * The Xid. + */ + protected String xid; + + /** + * The Branch id. + */ + protected long branchId; + + /** + * The Branch type. + */ + protected BranchType branchType = BranchType.AT; + + /** + * The resource id. + */ + private String resourceId; + + @Override + public short getTypeCode() { + return MessageType.TYPE_BRANCH_DELETE; + } + + @Override + public AbstractTransactionResponse handle(RpcContext rpcContext) { + return handler.handle(this); + } + + public String getXid() { + return xid; + } + + public void setXid(String xid) { + this.xid = xid; + } + + public long getBranchId() { + return branchId; + } + + public void setBranchId(long branchId) { + this.branchId = branchId; + } + + public BranchType getBranchType() { + return branchType; + } + + public void setBranchType(BranchType branchType) { + this.branchType = branchType; + } + + public String getResourceId() { + return resourceId; + } + + public void setResourceId(String resourceId) { + this.resourceId = resourceId; + } +} diff --git a/core/src/main/java/org/apache/seata/core/protocol/transaction/BranchDeleteResponse.java b/core/src/main/java/org/apache/seata/core/protocol/transaction/BranchDeleteResponse.java new file mode 100644 index 00000000000..1ba23f19b11 --- /dev/null +++ b/core/src/main/java/org/apache/seata/core/protocol/transaction/BranchDeleteResponse.java @@ -0,0 +1,29 @@ +/* + * 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.core.protocol.transaction; + +import org.apache.seata.core.protocol.MessageType; + +/** + * BranchDeleteResponse + */ +public class BranchDeleteResponse extends AbstractBranchEndResponse { + @Override + public short getTypeCode() { + return MessageType.TYPE_BRANCH_DELETE_RESULT; + } +} diff --git a/core/src/main/java/org/apache/seata/core/protocol/transaction/RMInboundHandler.java b/core/src/main/java/org/apache/seata/core/protocol/transaction/RMInboundHandler.java index 0288f0a6a87..5b17fa95fc9 100644 --- a/core/src/main/java/org/apache/seata/core/protocol/transaction/RMInboundHandler.java +++ b/core/src/main/java/org/apache/seata/core/protocol/transaction/RMInboundHandler.java @@ -45,4 +45,11 @@ public interface RMInboundHandler { */ void handle(UndoLogDeleteRequest request); + /** + * Handle branch delete . + * + * @param request the request + * @return the branch delete response + */ + BranchDeleteResponse handle(BranchDeleteRequest request); } diff --git a/core/src/main/java/org/apache/seata/core/rpc/netty/NettyRemotingServer.java b/core/src/main/java/org/apache/seata/core/rpc/netty/NettyRemotingServer.java index 3010efc4ae2..19b84aeb741 100644 --- a/core/src/main/java/org/apache/seata/core/rpc/netty/NettyRemotingServer.java +++ b/core/src/main/java/org/apache/seata/core/rpc/netty/NettyRemotingServer.java @@ -111,6 +111,7 @@ private void registerProcessor() { // 2. registry on response message processor ServerOnResponseProcessor onResponseProcessor = new ServerOnResponseProcessor(getHandler(), getFutures()); + super.registerProcessor(MessageType.TYPE_BRANCH_DELETE_RESULT, onResponseProcessor, branchResultMessageExecutor); super.registerProcessor(MessageType.TYPE_BRANCH_COMMIT_RESULT, onResponseProcessor, branchResultMessageExecutor); super.registerProcessor(MessageType.TYPE_BRANCH_ROLLBACK_RESULT, onResponseProcessor, branchResultMessageExecutor); // 3. registry rm message processor diff --git a/core/src/main/java/org/apache/seata/core/rpc/netty/RmNettyRemotingClient.java b/core/src/main/java/org/apache/seata/core/rpc/netty/RmNettyRemotingClient.java index 0b9fd0e12bc..26114cdb54c 100644 --- a/core/src/main/java/org/apache/seata/core/rpc/netty/RmNettyRemotingClient.java +++ b/core/src/main/java/org/apache/seata/core/rpc/netty/RmNettyRemotingClient.java @@ -46,6 +46,7 @@ import org.apache.seata.core.rpc.processor.client.ClientHeartbeatProcessor; import org.apache.seata.core.rpc.processor.client.ClientOnResponseProcessor; import org.apache.seata.core.rpc.processor.client.RmBranchCommitProcessor; +import org.apache.seata.core.rpc.processor.client.RmBranchDeleteProcessor; import org.apache.seata.core.rpc.processor.client.RmBranchRollbackProcessor; import org.apache.seata.core.rpc.processor.client.RmUndoLogProcessor; import org.slf4j.Logger; @@ -333,5 +334,8 @@ private void registerProcessor() { // 5.registry heartbeat message processor ClientHeartbeatProcessor clientHeartbeatProcessor = new ClientHeartbeatProcessor(); super.registerProcessor(MessageType.TYPE_HEARTBEAT_MSG, clientHeartbeatProcessor, null); + // 6.registry rm handler branch delete processor + RmBranchDeleteProcessor rmBranchDeleteProcessor = new RmBranchDeleteProcessor(getTransactionMessageHandler(), this); + super.registerProcessor(MessageType.TYPE_BRANCH_DELETE, rmBranchDeleteProcessor, messageExecutor); } } diff --git a/core/src/main/java/org/apache/seata/core/rpc/processor/client/RmBranchDeleteProcessor.java b/core/src/main/java/org/apache/seata/core/rpc/processor/client/RmBranchDeleteProcessor.java new file mode 100644 index 00000000000..d0694d85063 --- /dev/null +++ b/core/src/main/java/org/apache/seata/core/rpc/processor/client/RmBranchDeleteProcessor.java @@ -0,0 +1,68 @@ +/* + * 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.core.rpc.processor.client; + +import io.netty.channel.ChannelHandlerContext; +import org.apache.seata.common.util.NetUtil; +import org.apache.seata.core.protocol.RpcMessage; +import org.apache.seata.core.protocol.transaction.BranchDeleteRequest; +import org.apache.seata.core.protocol.transaction.BranchDeleteResponse; +import org.apache.seata.core.rpc.RemotingClient; +import org.apache.seata.core.rpc.TransactionMessageHandler; +import org.apache.seata.core.rpc.processor.RemotingProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The type Rm branch delete processor. + */ +public class RmBranchDeleteProcessor implements RemotingProcessor { + + private static final Logger LOGGER = LoggerFactory.getLogger(RmBranchDeleteProcessor.class); + + private TransactionMessageHandler handler; + + private RemotingClient remotingClient; + + public RmBranchDeleteProcessor(TransactionMessageHandler handler, RemotingClient remotingClient) { + this.handler = handler; + this.remotingClient = remotingClient; + } + + @Override + public void process(ChannelHandlerContext ctx, RpcMessage rpcMessage) throws Exception { + String remoteAddress = NetUtil.toStringAddress(ctx.channel().remoteAddress()); + Object msg = rpcMessage.getBody(); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("rm handle branch rollback process: {}", msg); + } + handleBranchDelete(rpcMessage, remoteAddress, (BranchDeleteRequest) msg); + } + + private void handleBranchDelete(RpcMessage request, String serverAddress, BranchDeleteRequest branchDeleteRequest) { + BranchDeleteResponse resultMessage; + resultMessage = (BranchDeleteResponse) handler.onRequest(branchDeleteRequest, null); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("branch delete result: {}", resultMessage); + } + try { + this.remotingClient.sendAsyncResponse(serverAddress, request, resultMessage); + } catch (Throwable throwable) { + LOGGER.error("branch delete error: {}", throwable.getMessage(), throwable); + } + } +} diff --git a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/DefaultCommonFenceHandler.java b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/DefaultCommonFenceHandler.java index 3e8307628d7..52a2e7313d2 100644 --- a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/DefaultCommonFenceHandler.java +++ b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/DefaultCommonFenceHandler.java @@ -67,4 +67,11 @@ public int deleteFenceByDate(Date datetime) { check(); return fenceHandler.deleteFenceByDate(datetime); } + + + @Override + public boolean deleteFenceByXidAndBranchId(String xid, Long branchId) { + check(); + return fenceHandler.deleteFenceByXidAndBranchId(xid, branchId); + } } diff --git a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/FenceHandler.java b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/FenceHandler.java index 11978290bff..fbaabc5e57c 100644 --- a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/FenceHandler.java +++ b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/FenceHandler.java @@ -32,4 +32,5 @@ public interface FenceHandler { int deleteFenceByDate(Date datetime); + boolean deleteFenceByXidAndBranchId(String xid, Long branchId); } diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/RMHandlerAT.java b/rm-datasource/src/main/java/org/apache/seata/rm/RMHandlerAT.java index c199c284ff0..08d231a9448 100644 --- a/rm-datasource/src/main/java/org/apache/seata/rm/RMHandlerAT.java +++ b/rm-datasource/src/main/java/org/apache/seata/rm/RMHandlerAT.java @@ -22,8 +22,12 @@ import java.util.Date; import org.apache.seata.common.util.DateUtil; +import org.apache.seata.core.model.BranchStatus; import org.apache.seata.core.model.BranchType; import org.apache.seata.core.model.ResourceManager; +import org.apache.seata.core.protocol.ResultCode; +import org.apache.seata.core.protocol.transaction.BranchDeleteRequest; +import org.apache.seata.core.protocol.transaction.BranchDeleteResponse; import org.apache.seata.core.protocol.transaction.UndoLogDeleteRequest; import org.apache.seata.rm.datasource.DataSourceManager; import org.apache.seata.rm.datasource.DataSourceProxy; @@ -70,6 +74,29 @@ public void handle(UndoLogDeleteRequest request) { } } + @Override + public BranchDeleteResponse handle(BranchDeleteRequest request) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Start at delete undo log, xid:{}, branchId:{}.", request.getXid(), request.getBranchId()); + } + BranchDeleteResponse branchDeleteResponse = new BranchDeleteResponse(); + DataSourceManager dataSourceManager = (DataSourceManager) getResourceManager(); + try { + // use commit to delete undo log + dataSourceManager.branchCommit(BranchType.AT, request.getXid(), request.getBranchId(), + request.getResourceId(), ""); + branchDeleteResponse.setResultCode(ResultCode.Success); + } catch (Exception e) { + branchDeleteResponse.setResultCode(ResultCode.Failed); + LOGGER.error("delete undo log fail, xid:{}, branchId:{}, ",request.getXid(), request.getBranchId(), e); + } + branchDeleteResponse.setXid(request.getXid()); + branchDeleteResponse.setBranchId(request.getBranchId()); + // this branch status is no importance + branchDeleteResponse.setBranchStatus(BranchStatus.Unknown); + return branchDeleteResponse; + } + Connection getConnection(DataSourceProxy dataSourceProxy) { try { return dataSourceProxy.getPlainConnection(); diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/RMHandlerXA.java b/rm-datasource/src/main/java/org/apache/seata/rm/RMHandlerXA.java index a77092e3c67..cae8f5bf128 100644 --- a/rm-datasource/src/main/java/org/apache/seata/rm/RMHandlerXA.java +++ b/rm-datasource/src/main/java/org/apache/seata/rm/RMHandlerXA.java @@ -16,20 +16,49 @@ */ package org.apache.seata.rm; +import org.apache.seata.core.model.BranchStatus; import org.apache.seata.core.model.BranchType; import org.apache.seata.core.model.ResourceManager; +import org.apache.seata.core.protocol.ResultCode; +import org.apache.seata.core.protocol.transaction.BranchDeleteRequest; +import org.apache.seata.core.protocol.transaction.BranchDeleteResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The type RM handler XA. * */ public class RMHandlerXA extends AbstractRMHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(RMHandlerXA.class); @Override protected ResourceManager getResourceManager() { return DefaultResourceManager.get().getResourceManager(BranchType.XA); } + @Override + public BranchDeleteResponse handle(BranchDeleteRequest request) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Start xa delete branch, xid:{}, branchId:{}", request.getXid(), request.getBranchId()); + } + BranchDeleteResponse branchDeleteResponse = new BranchDeleteResponse(); + try { + // use rollback to make date correct in xa mode + BranchStatus branchStatus = getResourceManager().branchRollback(request.getBranchType(), request.getXid(), + request.getBranchId(), request.getResourceId(), ""); + ResultCode code = branchStatus == BranchStatus.PhaseTwo_Rollbacked ? ResultCode.Success : ResultCode.Failed; + branchDeleteResponse.setResultCode(code); + branchDeleteResponse.setBranchStatus(branchStatus); + } catch (Exception e) { + branchDeleteResponse.setResultCode(ResultCode.Failed); + LOGGER.error("XA branch delete fail, reason: {}", e.getMessage(), e); + } + branchDeleteResponse.setXid(request.getXid()); + branchDeleteResponse.setBranchId(request.getBranchId()); + return branchDeleteResponse; + } + @Override public BranchType getBranchType() { return BranchType.XA; diff --git a/rm/src/main/java/org/apache/seata/rm/DefaultRMHandler.java b/rm/src/main/java/org/apache/seata/rm/DefaultRMHandler.java index 3e56358b598..7211d5b7999 100644 --- a/rm/src/main/java/org/apache/seata/rm/DefaultRMHandler.java +++ b/rm/src/main/java/org/apache/seata/rm/DefaultRMHandler.java @@ -24,6 +24,8 @@ import org.apache.seata.core.model.ResourceManager; import org.apache.seata.core.protocol.transaction.BranchCommitRequest; import org.apache.seata.core.protocol.transaction.BranchCommitResponse; +import org.apache.seata.core.protocol.transaction.BranchDeleteRequest; +import org.apache.seata.core.protocol.transaction.BranchDeleteResponse; import org.apache.seata.core.protocol.transaction.BranchRollbackRequest; import org.apache.seata.core.protocol.transaction.BranchRollbackResponse; import org.apache.seata.core.protocol.transaction.UndoLogDeleteRequest; @@ -73,6 +75,11 @@ public void handle(UndoLogDeleteRequest request) { getRMHandler(request.getBranchType()).handle(request); } + @Override + public BranchDeleteResponse handle(BranchDeleteRequest request) { + return getRMHandler(request.getBranchType()).handle(request); + } + protected AbstractRMHandler getRMHandler(BranchType branchType) { return allRMHandlersMap.get(branchType); } diff --git a/saga/seata-saga-annotation/src/main/java/org/apache/seata/saga/rm/RMHandlerSagaAnnotation.java b/saga/seata-saga-annotation/src/main/java/org/apache/seata/saga/rm/RMHandlerSagaAnnotation.java index 21dad9e5b47..eeb0be6611f 100644 --- a/saga/seata-saga-annotation/src/main/java/org/apache/seata/saga/rm/RMHandlerSagaAnnotation.java +++ b/saga/seata-saga-annotation/src/main/java/org/apache/seata/saga/rm/RMHandlerSagaAnnotation.java @@ -16,8 +16,11 @@ */ package org.apache.seata.saga.rm; +import org.apache.seata.common.exception.ShouldNeverHappenException; import org.apache.seata.core.model.BranchType; import org.apache.seata.core.model.ResourceManager; +import org.apache.seata.core.protocol.transaction.BranchDeleteRequest; +import org.apache.seata.core.protocol.transaction.BranchDeleteResponse; import org.apache.seata.rm.AbstractRMHandler; import org.apache.seata.rm.DefaultResourceManager; @@ -36,4 +39,8 @@ public BranchType getBranchType() { return BranchType.SAGA_ANNOTATION; } + @Override + public BranchDeleteResponse handle(BranchDeleteRequest request) { + throw new ShouldNeverHappenException("saga mode rm handler not support BranchDeleteRequest"); + } } diff --git a/saga/seata-saga-rm/src/main/java/org/apache/seata/saga/rm/RMHandlerSaga.java b/saga/seata-saga-rm/src/main/java/org/apache/seata/saga/rm/RMHandlerSaga.java index dbf71bef795..8e97fbd6e91 100644 --- a/saga/seata-saga-rm/src/main/java/org/apache/seata/saga/rm/RMHandlerSaga.java +++ b/saga/seata-saga-rm/src/main/java/org/apache/seata/saga/rm/RMHandlerSaga.java @@ -16,8 +16,11 @@ */ package org.apache.seata.saga.rm; +import org.apache.seata.common.exception.ShouldNeverHappenException; import org.apache.seata.core.model.BranchType; import org.apache.seata.core.model.ResourceManager; +import org.apache.seata.core.protocol.transaction.BranchDeleteRequest; +import org.apache.seata.core.protocol.transaction.BranchDeleteResponse; import org.apache.seata.core.protocol.transaction.UndoLogDeleteRequest; import org.apache.seata.rm.AbstractRMHandler; import org.apache.seata.rm.DefaultResourceManager; @@ -33,6 +36,11 @@ public void handle(UndoLogDeleteRequest request) { //DO nothing } + @Override + public BranchDeleteResponse handle(BranchDeleteRequest request) { + throw new ShouldNeverHappenException("saga mode rm handler not support BranchDeleteRequest"); + } + /** * get SAGA resource manager * diff --git a/serializer/seata-serializer-protobuf/src/main/java/org/apache/seata/serializer/protobuf/convertor/BranchDeleteRequestConvertor.java b/serializer/seata-serializer-protobuf/src/main/java/org/apache/seata/serializer/protobuf/convertor/BranchDeleteRequestConvertor.java new file mode 100644 index 00000000000..0604304eaa9 --- /dev/null +++ b/serializer/seata-serializer-protobuf/src/main/java/org/apache/seata/serializer/protobuf/convertor/BranchDeleteRequestConvertor.java @@ -0,0 +1,59 @@ +/* + * 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.serializer.protobuf.convertor; + +import org.apache.seata.core.model.BranchType; +import org.apache.seata.core.protocol.transaction.BranchDeleteRequest; +import org.apache.seata.serializer.protobuf.generated.AbstractMessageProto; +import org.apache.seata.serializer.protobuf.generated.AbstractTransactionRequestProto; +import org.apache.seata.serializer.protobuf.generated.BranchDeleteRequestProto; +import org.apache.seata.serializer.protobuf.generated.BranchTypeProto; +import org.apache.seata.serializer.protobuf.generated.MessageTypeProto; + +/** + * BranchDeleteRequestConvertor + */ +public class BranchDeleteRequestConvertor implements PbConvertor { + @Override + public BranchDeleteRequestProto convert2Proto(BranchDeleteRequest branchDeleteRequest) { + final short typeCode = branchDeleteRequest.getTypeCode(); + + final AbstractMessageProto abstractMessage = AbstractMessageProto.newBuilder().setMessageType( + MessageTypeProto.forNumber(typeCode)).build(); + + final AbstractTransactionRequestProto abstractTransactionRequestProto = AbstractTransactionRequestProto + .newBuilder().setAbstractMessage(abstractMessage).build(); + + final String resourceId = branchDeleteRequest.getResourceId(); + return BranchDeleteRequestProto.newBuilder().setAbstractTransactionRequest( + abstractTransactionRequestProto).setXid(branchDeleteRequest.getXid()).setBranchId( + branchDeleteRequest.getBranchId()).setBranchType(BranchTypeProto.valueOf( + branchDeleteRequest.getBranchType().name())).setResourceId(resourceId == null ? "" : resourceId).build(); + } + + @Override + public BranchDeleteRequest convert2Model(BranchDeleteRequestProto branchDeleteRequestProto) { + BranchDeleteRequest branchDeleteRequest = new BranchDeleteRequest(); + branchDeleteRequest.setBranchId(branchDeleteRequestProto.getBranchId()); + branchDeleteRequest.setResourceId(branchDeleteRequestProto.getResourceId()); + branchDeleteRequest.setXid(branchDeleteRequestProto.getXid()); + branchDeleteRequest.setBranchType( + BranchType.valueOf(branchDeleteRequestProto.getBranchType().name())); + return branchDeleteRequest; + } +} + diff --git a/serializer/seata-serializer-protobuf/src/main/java/org/apache/seata/serializer/protobuf/convertor/BranchDeleteResponseConvertor.java b/serializer/seata-serializer-protobuf/src/main/java/org/apache/seata/serializer/protobuf/convertor/BranchDeleteResponseConvertor.java new file mode 100644 index 00000000000..ab60b257ddb --- /dev/null +++ b/serializer/seata-serializer-protobuf/src/main/java/org/apache/seata/serializer/protobuf/convertor/BranchDeleteResponseConvertor.java @@ -0,0 +1,79 @@ +/* + * 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.serializer.protobuf.convertor; + +import org.apache.seata.core.exception.TransactionExceptionCode; +import org.apache.seata.core.model.BranchStatus; +import org.apache.seata.core.protocol.ResultCode; +import org.apache.seata.core.protocol.transaction.BranchDeleteResponse; +import org.apache.seata.serializer.protobuf.generated.AbstractBranchEndResponseProto; +import org.apache.seata.serializer.protobuf.generated.AbstractMessageProto; +import org.apache.seata.serializer.protobuf.generated.AbstractResultMessageProto; +import org.apache.seata.serializer.protobuf.generated.AbstractTransactionResponseProto; +import org.apache.seata.serializer.protobuf.generated.BranchDeleteResponseProto; +import org.apache.seata.serializer.protobuf.generated.BranchStatusProto; +import org.apache.seata.serializer.protobuf.generated.MessageTypeProto; +import org.apache.seata.serializer.protobuf.generated.ResultCodeProto; +import org.apache.seata.serializer.protobuf.generated.TransactionExceptionCodeProto; + +/** + * BranchDeleteResponseConvertor + */ +public class BranchDeleteResponseConvertor implements PbConvertor { + @Override + public BranchDeleteResponseProto convert2Proto(BranchDeleteResponse branchDeleteResponse) { + final short typeCode = branchDeleteResponse.getTypeCode(); + + final AbstractMessageProto abstractMessage = AbstractMessageProto.newBuilder().setMessageType( + MessageTypeProto.forNumber(typeCode)).build(); + + final String msg = branchDeleteResponse.getMsg(); + final AbstractResultMessageProto abstractResultMessageProto = AbstractResultMessageProto.newBuilder().setMsg( + msg == null ? "" : msg).setResultCode(ResultCodeProto.valueOf(branchDeleteResponse.getResultCode().name())) + .setAbstractMessage(abstractMessage).build(); + + AbstractTransactionResponseProto abstractTransactionResponseProto = AbstractTransactionResponseProto + .newBuilder().setAbstractResultMessage(abstractResultMessageProto).setTransactionExceptionCode( + TransactionExceptionCodeProto.valueOf(branchDeleteResponse.getTransactionExceptionCode().name())) + .build(); + + final AbstractBranchEndResponseProto abstractBranchEndResponse = AbstractBranchEndResponseProto.newBuilder(). + setAbstractTransactionResponse(abstractTransactionResponseProto).setXid(branchDeleteResponse.getXid()) + .setBranchId(branchDeleteResponse.getBranchId()).setBranchStatus( + BranchStatusProto.forNumber(branchDeleteResponse.getBranchStatus().getCode())).build(); + + return BranchDeleteResponseProto.newBuilder().setAbstractBranchEndResponse( + abstractBranchEndResponse).build(); + } + + @Override + public BranchDeleteResponse convert2Model(BranchDeleteResponseProto branchDeleteResponseProto) { + BranchDeleteResponse branchDeleteResponse = new BranchDeleteResponse(); + final AbstractBranchEndResponseProto abstractResultMessage = branchDeleteResponseProto.getAbstractBranchEndResponse(); + branchDeleteResponse.setBranchId(branchDeleteResponseProto.getAbstractBranchEndResponse().getBranchId()); + branchDeleteResponse.setBranchStatus( + BranchStatus.get(branchDeleteResponseProto.getAbstractBranchEndResponse().getBranchStatusValue())); + branchDeleteResponse.setXid(branchDeleteResponseProto.getAbstractBranchEndResponse().getXid()); + + branchDeleteResponse.setMsg(abstractResultMessage.getAbstractTransactionResponse().getAbstractResultMessage().getMsg()); + branchDeleteResponse.setResultCode(ResultCode.valueOf(abstractResultMessage.getAbstractTransactionResponse().getAbstractResultMessage().getResultCode().name())); + branchDeleteResponse.setTransactionExceptionCode(TransactionExceptionCode.valueOf( + abstractResultMessage.getAbstractTransactionResponse().getTransactionExceptionCode().name())); + return branchDeleteResponse; + } +} + diff --git a/serializer/seata-serializer-protobuf/src/main/java/org/apache/seata/serializer/protobuf/manager/ProtobufConvertManager.java b/serializer/seata-serializer-protobuf/src/main/java/org/apache/seata/serializer/protobuf/manager/ProtobufConvertManager.java index 4ce861b155a..0d85cee72a2 100644 --- a/serializer/seata-serializer-protobuf/src/main/java/org/apache/seata/serializer/protobuf/manager/ProtobufConvertManager.java +++ b/serializer/seata-serializer-protobuf/src/main/java/org/apache/seata/serializer/protobuf/manager/ProtobufConvertManager.java @@ -17,9 +17,13 @@ package org.apache.seata.serializer.protobuf.manager; import org.apache.seata.core.protocol.BatchResultMessage; +import org.apache.seata.core.protocol.transaction.BranchDeleteRequest; +import org.apache.seata.core.protocol.transaction.BranchDeleteResponse; import org.apache.seata.serializer.protobuf.convertor.BatchResultMessageConvertor; import org.apache.seata.serializer.protobuf.convertor.BranchCommitRequestConvertor; import org.apache.seata.serializer.protobuf.convertor.BranchCommitResponseConvertor; +import org.apache.seata.serializer.protobuf.convertor.BranchDeleteRequestConvertor; +import org.apache.seata.serializer.protobuf.convertor.BranchDeleteResponseConvertor; import org.apache.seata.serializer.protobuf.convertor.BranchRegisterRequestConvertor; import org.apache.seata.serializer.protobuf.convertor.BranchRegisterResponseConvertor; import org.apache.seata.serializer.protobuf.convertor.BranchReportRequestConvertor; @@ -47,6 +51,8 @@ import org.apache.seata.serializer.protobuf.convertor.RegisterTMRequestConvertor; import org.apache.seata.serializer.protobuf.convertor.RegisterTMResponseConvertor; import org.apache.seata.serializer.protobuf.generated.BatchResultMessageProto; +import org.apache.seata.serializer.protobuf.generated.BranchDeleteRequestProto; +import org.apache.seata.serializer.protobuf.generated.BranchDeleteResponseProto; import org.apache.seata.serializer.protobuf.generated.GlobalReportRequestProto; import org.apache.seata.serializer.protobuf.generated.GlobalReportResponseProto; import org.apache.seata.core.protocol.HeartbeatMessage; @@ -164,6 +170,10 @@ private static class SingletonHolder { new GlobalReportResponseConvertor()); protobufConvertManager.convertorMap.put(UndoLogDeleteRequest.class.getName(), new UndoLogDeleteRequestConvertor()); + protobufConvertManager.convertorMap.put(BranchDeleteRequest.class.getName(), + new BranchDeleteRequestConvertor()); + protobufConvertManager.convertorMap.put(BranchDeleteResponse.class.getName(), + new BranchDeleteResponseConvertor()); protobufConvertManager.convertorMap.put(MergedWarpMessage.class.getName(), new MergedWarpMessageConvertor()); @@ -223,6 +233,10 @@ private static class SingletonHolder { GlobalReportResponseProto.class); protobufConvertManager.protoClazzMap.put(UndoLogDeleteRequestProto.getDescriptor().getFullName(), UndoLogDeleteRequestProto.class); + protobufConvertManager.protoClazzMap.put(BranchDeleteResponseProto.getDescriptor().getFullName(), + BranchDeleteResponseProto.class); + protobufConvertManager.protoClazzMap.put(BranchDeleteRequestProto.getDescriptor().getFullName(), + BranchDeleteRequestProto.class); protobufConvertManager.protoClazzMap.put(MergedWarpMessageProto.getDescriptor().getFullName(), MergedWarpMessageProto.class); diff --git a/serializer/seata-serializer-protobuf/src/main/resources/protobuf/org/apache/seata/protocol/transcation/branchDeleteRequest.proto b/serializer/seata-serializer-protobuf/src/main/resources/protobuf/org/apache/seata/protocol/transcation/branchDeleteRequest.proto new file mode 100644 index 00000000000..d4520d7ff47 --- /dev/null +++ b/serializer/seata-serializer-protobuf/src/main/resources/protobuf/org/apache/seata/protocol/transcation/branchDeleteRequest.proto @@ -0,0 +1,39 @@ +/* + * 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. + */ +syntax = "proto3"; + +package org.apache.seata.protocol.protobuf; + +import "branchType.proto"; +import "abstractTransactionRequest.proto"; + +option java_multiple_files = true; +option java_outer_classname = "BranchDeleteRequest"; +option java_package = "org.apache.seata.serializer.protobuf.generated"; + + +// PublishRequest is a publish request. +message BranchDeleteRequestProto { + AbstractTransactionRequestProto abstractTransactionRequest = 1; + string xid = 2; + + int64 branchId = 3; + + BranchTypeProto branchType = 4; + + string resourceId = 5; +} diff --git a/serializer/seata-serializer-protobuf/src/main/resources/protobuf/org/apache/seata/protocol/transcation/branchDeleteResponse.proto b/serializer/seata-serializer-protobuf/src/main/resources/protobuf/org/apache/seata/protocol/transcation/branchDeleteResponse.proto new file mode 100644 index 00000000000..b9cd45a95e5 --- /dev/null +++ b/serializer/seata-serializer-protobuf/src/main/resources/protobuf/org/apache/seata/protocol/transcation/branchDeleteResponse.proto @@ -0,0 +1,30 @@ +/* + * 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. + */ +syntax = "proto3"; + +package org.apache.seata.protocol.protobuf; + +import "abstractBranchEndResponse.proto"; + +option java_multiple_files = true; +option java_outer_classname = "BranchDeleteResponse"; +option java_package = "org.apache.seata.serializer.protobuf.generated"; + +// PublishRequest is a publish request. +message BranchDeleteResponseProto { + AbstractBranchEndResponseProto abstractBranchEndResponse = 1; +} \ No newline at end of file diff --git a/serializer/seata-serializer-protobuf/src/main/resources/protobuf/org/apache/seata/protocol/transcation/messageType.proto b/serializer/seata-serializer-protobuf/src/main/resources/protobuf/org/apache/seata/protocol/transcation/messageType.proto index 754ce1173c0..7652ef5cdbd 100644 --- a/serializer/seata-serializer-protobuf/src/main/resources/protobuf/org/apache/seata/protocol/transcation/messageType.proto +++ b/serializer/seata-serializer-protobuf/src/main/resources/protobuf/org/apache/seata/protocol/transcation/messageType.proto @@ -70,6 +70,14 @@ enum MessageTypeProto { * The constant TYPE_GLOBAL_LOCK_QUERY_RESULT. */ TYPE_GLOBAL_LOCK_QUERY_RESULT = 22; + /** + * The constant TYPE_BRANCH_DELETE. + */ + TYPE_BRANCH_DELETE = 23; + /** + * The constant TYPE_BRANCH_DELETE_RESULT. + */ + TYPE_BRANCH_DELETE_RESULT = 24; /** * The constant TYPE_BRANCH_COMMIT. diff --git a/serializer/seata-serializer-protobuf/src/test/java/org/apache/seata/serializer/protobuf/convertor/BranchDeleteRequestConvertorTest.java b/serializer/seata-serializer-protobuf/src/test/java/org/apache/seata/serializer/protobuf/convertor/BranchDeleteRequestConvertorTest.java new file mode 100644 index 00000000000..6d314e9c94f --- /dev/null +++ b/serializer/seata-serializer-protobuf/src/test/java/org/apache/seata/serializer/protobuf/convertor/BranchDeleteRequestConvertorTest.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.serializer.protobuf.convertor; + +import org.apache.seata.core.model.BranchType; +import org.apache.seata.core.protocol.transaction.BranchDeleteRequest; +import org.apache.seata.serializer.protobuf.generated.BranchDeleteRequestProto; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BranchDeleteRequestConvertorTest { + @Test + public void convert2Proto() { + BranchDeleteRequest branchDeleteRequest = new BranchDeleteRequest(); + branchDeleteRequest.setBranchType(BranchType.AT); + branchDeleteRequest.setXid("xid"); + branchDeleteRequest.setResourceId("resourceId"); + branchDeleteRequest.setBranchId(123); + + BranchDeleteRequestConvertor branchDeleteRequestConvertor = new BranchDeleteRequestConvertor(); + BranchDeleteRequestProto proto = branchDeleteRequestConvertor.convert2Proto( + branchDeleteRequest); + BranchDeleteRequest realRequest = branchDeleteRequestConvertor.convert2Model(proto); + + assertThat(realRequest.getTypeCode()).isEqualTo(branchDeleteRequest.getTypeCode()); + assertThat(realRequest.getBranchType()).isEqualTo(branchDeleteRequest.getBranchType()); + assertThat(realRequest.getXid()).isEqualTo(branchDeleteRequest.getXid()); + assertThat(realRequest.getResourceId()).isEqualTo(branchDeleteRequest.getResourceId()); + assertThat(realRequest.getBranchId()).isEqualTo(branchDeleteRequest.getBranchId()); + + } +} diff --git a/serializer/seata-serializer-protobuf/src/test/java/org/apache/seata/serializer/protobuf/convertor/BranchDeleteResponseConvertorTest.java b/serializer/seata-serializer-protobuf/src/test/java/org/apache/seata/serializer/protobuf/convertor/BranchDeleteResponseConvertorTest.java new file mode 100644 index 00000000000..9f5f9c8b0b0 --- /dev/null +++ b/serializer/seata-serializer-protobuf/src/test/java/org/apache/seata/serializer/protobuf/convertor/BranchDeleteResponseConvertorTest.java @@ -0,0 +1,53 @@ +/* + * 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.serializer.protobuf.convertor; + +import org.apache.seata.core.exception.TransactionExceptionCode; +import org.apache.seata.core.model.BranchStatus; +import org.apache.seata.core.protocol.ResultCode; +import org.apache.seata.core.protocol.transaction.BranchDeleteResponse; +import org.apache.seata.serializer.protobuf.generated.BranchDeleteResponseProto; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The type Branch delete response convertor test. + */ +public class BranchDeleteResponseConvertorTest { + @Test + public void convert2Proto() { + BranchDeleteResponse branchDeleteResponse = new BranchDeleteResponse(); + branchDeleteResponse.setMsg("msg"); + branchDeleteResponse.setResultCode(ResultCode.Failed); + branchDeleteResponse.setTransactionExceptionCode(TransactionExceptionCode.GlobalTransactionNotExist); + branchDeleteResponse.setXid("xid"); + branchDeleteResponse.setBranchStatus(BranchStatus.PhaseTwo_Rollbacked); + branchDeleteResponse.setBranchId(123); + + BranchDeleteResponseConvertor convertor = new BranchDeleteResponseConvertor(); + BranchDeleteResponseProto proto = convertor.convert2Proto(branchDeleteResponse); + BranchDeleteResponse real = convertor.convert2Model(proto); + assertThat((real.getTypeCode())).isEqualTo(branchDeleteResponse.getTypeCode()); + assertThat((real.getMsg())).isEqualTo(branchDeleteResponse.getMsg()); + assertThat((real.getResultCode())).isEqualTo(branchDeleteResponse.getResultCode()); + assertThat((real.getTransactionExceptionCode())).isEqualTo(branchDeleteResponse.getTransactionExceptionCode()); + assertThat((real.getBranchId())).isEqualTo(branchDeleteResponse.getBranchId()); + assertThat((real.getXid())).isEqualTo(branchDeleteResponse.getXid()); + assertThat((real.getBranchStatus())).isEqualTo(branchDeleteResponse.getBranchStatus()); + } +} diff --git a/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/MessageCodecFactory.java b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/MessageCodecFactory.java index 6231c72bbde..65169eac513 100644 --- a/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/MessageCodecFactory.java +++ b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/MessageCodecFactory.java @@ -19,6 +19,8 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import org.apache.seata.core.protocol.transaction.BranchDeleteRequest; +import org.apache.seata.core.protocol.transaction.BranchDeleteResponse; import org.apache.seata.serializer.seata.protocol.BatchResultMessageCodec; import org.apache.seata.serializer.seata.protocol.MergeResultMessageCodec; import org.apache.seata.serializer.seata.protocol.MergedWarpMessageCodec; @@ -28,6 +30,8 @@ import org.apache.seata.serializer.seata.protocol.RegisterTMResponseCodec; import org.apache.seata.serializer.seata.protocol.transaction.BranchCommitRequestCodec; import org.apache.seata.serializer.seata.protocol.transaction.BranchCommitResponseCodec; +import org.apache.seata.serializer.seata.protocol.transaction.BranchDeleteRequestCodec; +import org.apache.seata.serializer.seata.protocol.transaction.BranchDeleteResponseCodec; import org.apache.seata.serializer.seata.protocol.transaction.BranchRegisterRequestCodec; import org.apache.seata.serializer.seata.protocol.transaction.BranchRegisterResponseCodec; import org.apache.seata.serializer.seata.protocol.transaction.BranchReportRequestCodec; @@ -137,6 +141,12 @@ public static MessageSeataCodec getMessageCodec(short typeCode, byte version) { case MessageType.TYPE_BATCH_RESULT_MSG: msgCodec = new BatchResultMessageCodec(version); break; + case MessageType.TYPE_BRANCH_DELETE: + msgCodec = new BranchDeleteRequestCodec(); + break; + case MessageType.TYPE_BRANCH_DELETE_RESULT: + msgCodec = new BranchDeleteResponseCodec(); + break; case MessageType.TYPE_GLOBAL_BEGIN: msgCodec = new GlobalBeginRequestCodec(); break; @@ -295,6 +305,12 @@ public static AbstractMessage getMessage(short typeCode) { case MessageType.TYPE_BRANCH_ROLLBACK_RESULT: abstractMessage = new BranchRollbackResponse(); break; + case MessageType.TYPE_BRANCH_DELETE: + abstractMessage = new BranchDeleteRequest(); + break; + case MessageType.TYPE_BRANCH_DELETE_RESULT: + abstractMessage = new BranchDeleteResponse(); + break; default: break; } diff --git a/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/transaction/BranchDeleteRequestCodec.java b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/transaction/BranchDeleteRequestCodec.java new file mode 100644 index 00000000000..6fef1352f68 --- /dev/null +++ b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/transaction/BranchDeleteRequestCodec.java @@ -0,0 +1,108 @@ +/* + * 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.serializer.seata.protocol.transaction; + +import io.netty.buffer.ByteBuf; +import org.apache.seata.core.model.BranchType; +import org.apache.seata.core.protocol.transaction.BranchDeleteRequest; + +import java.nio.ByteBuffer; + +/** + * BranchDeleteRequestCodec + */ +public class BranchDeleteRequestCodec extends AbstractTransactionRequestToRMCodec { + @Override + public void encode(T t, ByteBuf out) { + BranchDeleteRequest branchDeleteRequest = (BranchDeleteRequest) t; + String xid = branchDeleteRequest.getXid(); + long branchId = branchDeleteRequest.getBranchId(); + BranchType branchType = branchDeleteRequest.getBranchType(); + String resourceId = branchDeleteRequest.getResourceId(); + + // 1. xid + if (xid != null) { + byte[] bs = xid.getBytes(UTF8); + out.writeShort((short) bs.length); + if (bs.length > 0) { + out.writeBytes(bs); + } + } else { + out.writeShort((short) 0); + } + // 2. Branch Id + out.writeLong(branchId); + // 3. Branch Type + out.writeByte(branchType.ordinal()); + // 4. Resource Id + if (resourceId != null) { + byte[] bs = resourceId.getBytes(UTF8); + out.writeShort((short) bs.length); + if (bs.length > 0) { + out.writeBytes(bs); + } + } else { + out.writeShort((short) 0); + } + } + + @Override + public void decode(T t, ByteBuffer in) { + BranchDeleteRequest branchDeleteRequest = (BranchDeleteRequest) t; + + int xidLen = 0; + if (in.remaining() >= 2) { + xidLen = in.getShort(); + } + if (xidLen <= 0) { + return; + } + if (in.remaining() < xidLen) { + return; + } + byte[] bs = new byte[xidLen]; + in.get(bs); + branchDeleteRequest.setXid(new String(bs, UTF8)); + + if (in.remaining() < 8) { + return; + } + branchDeleteRequest.setBranchId(in.getLong()); + + if (in.remaining() < 1) { + return; + } + branchDeleteRequest.setBranchType(BranchType.get(in.get())); + + int resourceIdLen = 0; + if (in.remaining() < 2) { + return; + } + resourceIdLen = in.getShort(); + + if (resourceIdLen <= 0) { + return; + } + if (in.remaining() < resourceIdLen) { + return; + } + bs = new byte[resourceIdLen]; + in.get(bs); + branchDeleteRequest.setResourceId(new String(bs, UTF8)); + } +} + diff --git a/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/transaction/BranchDeleteResponseCodec.java b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/transaction/BranchDeleteResponseCodec.java new file mode 100644 index 00000000000..70e203ef6f2 --- /dev/null +++ b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/transaction/BranchDeleteResponseCodec.java @@ -0,0 +1,32 @@ +/* + * 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.serializer.seata.protocol.transaction; + +import org.apache.seata.core.protocol.transaction.BranchDeleteResponse; + +import java.io.Serializable; + +/** + * BranchDeleteResponseCodec + */ +public class BranchDeleteResponseCodec extends AbstractBranchEndResponseCodec implements Serializable { + @Override + public Class getMessageClassType() { + return BranchDeleteResponse.class; + } +} + diff --git a/serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/protocol/transaction/BranchDeleteRequestCodecTest.java b/serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/protocol/transaction/BranchDeleteRequestCodecTest.java new file mode 100644 index 00000000000..c0b2ba89bf2 --- /dev/null +++ b/serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/protocol/transaction/BranchDeleteRequestCodecTest.java @@ -0,0 +1,57 @@ +/* + * 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.serializer.seata.protocol.transaction; + +import org.apache.seata.core.model.BranchType; +import org.apache.seata.core.protocol.ProtocolConstants; +import org.apache.seata.core.protocol.transaction.BranchDeleteRequest; +import org.apache.seata.serializer.seata.SeataSerializer; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The type Branch delete request codec test. + */ +public class BranchDeleteRequestCodecTest { + + /** + * The Seata codec. + */ + SeataSerializer seataSerializer = new SeataSerializer(ProtocolConstants.VERSION); + + /** + * Test codec. + */ + @Test + public void test_codec(){ + BranchDeleteRequest branchDeleteRequest = new BranchDeleteRequest(); + branchDeleteRequest.setBranchId(112232); + branchDeleteRequest.setBranchType(BranchType.TCC); + branchDeleteRequest.setResourceId("343"); + branchDeleteRequest.setXid("123"); + + byte[] bytes = seataSerializer.serialize(branchDeleteRequest); + + BranchDeleteRequest branchDeleteRequest2 = seataSerializer.deserialize(bytes); + + assertThat(branchDeleteRequest2.getBranchId()).isEqualTo(branchDeleteRequest.getBranchId()); + assertThat(branchDeleteRequest2.getBranchType()).isEqualTo(branchDeleteRequest.getBranchType()); + assertThat(branchDeleteRequest2.getResourceId()).isEqualTo(branchDeleteRequest.getResourceId()); + assertThat(branchDeleteRequest2.getXid()).isEqualTo(branchDeleteRequest.getXid()); + } +} diff --git a/serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/protocol/transaction/BranchDeleteResponseCodecTest.java b/serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/protocol/transaction/BranchDeleteResponseCodecTest.java new file mode 100644 index 00000000000..d3ccf3d1160 --- /dev/null +++ b/serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/protocol/transaction/BranchDeleteResponseCodecTest.java @@ -0,0 +1,59 @@ +/* + * 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.serializer.seata.protocol.transaction; + +import org.apache.seata.core.exception.TransactionExceptionCode; +import org.apache.seata.core.model.BranchStatus; +import org.apache.seata.core.protocol.ProtocolConstants; +import org.apache.seata.core.protocol.ResultCode; +import org.apache.seata.core.protocol.transaction.BranchDeleteResponse; +import org.apache.seata.serializer.seata.SeataSerializer; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The type Branch delete response codec test. + */ +public class BranchDeleteResponseCodecTest { + + /** + * The Seata codec. + */ + SeataSerializer seataSerializer = new SeataSerializer(ProtocolConstants.VERSION); + + /** + * Test codec. + */ + @Test + public void test_codec(){ + BranchDeleteResponse branchDeleteResponse = new BranchDeleteResponse(); + branchDeleteResponse.setMsg("test"); + branchDeleteResponse.setResultCode(ResultCode.Failed); + branchDeleteResponse.setTransactionExceptionCode(TransactionExceptionCode.BranchTransactionNotExist); + branchDeleteResponse.setBranchStatus(BranchStatus.PhaseTwo_CommitFailed_XAER_NOTA_Retryable); + + byte[] bytes = seataSerializer.serialize(branchDeleteResponse); + + BranchDeleteResponse branchDeleteResponse2 = seataSerializer.deserialize(bytes); + + assertThat(branchDeleteResponse2.getMsg()).isEqualTo(branchDeleteResponse.getMsg()); + assertThat(branchDeleteResponse2.getResultCode()).isEqualTo(branchDeleteResponse.getResultCode()); + assertThat(branchDeleteResponse2.getTransactionExceptionCode()).isEqualTo(branchDeleteResponse.getTransactionExceptionCode()); + assertThat(branchDeleteResponse2.getBranchStatus()).isEqualTo(branchDeleteResponse.getBranchStatus()); + } +} diff --git a/server/src/main/java/org/apache/seata/server/console/aop/GlobalExceptionHandlerAdvice.java b/server/src/main/java/org/apache/seata/server/console/aop/GlobalExceptionHandlerAdvice.java new file mode 100644 index 00000000000..4ac21f659cd --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/console/aop/GlobalExceptionHandlerAdvice.java @@ -0,0 +1,76 @@ +/* + * 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.server.console.aop; + +import org.apache.seata.common.exception.FrameworkException; +import org.apache.seata.common.exception.ShouldNeverHappenException; +import org.apache.seata.common.result.SingleResult; +import org.apache.seata.server.console.exception.ConsoleException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; + +@ControllerAdvice +@Component +public class GlobalExceptionHandlerAdvice { + private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandlerAdvice.class); + + @ExceptionHandler(ConsoleException.class) + @ResponseBody + public SingleResult handlerConsoleException(ConsoleException ex) { + LOGGER.error("console error, reason: {}", ex.getLogMessage(), ex); + return SingleResult.failure(ex.getMessage()); + } + + @ExceptionHandler(IllegalArgumentException.class) + @ResponseBody + public SingleResult handlerIllegalArgumentException(IllegalArgumentException ex) { + LOGGER.error("IllegalArgumentException: ", ex); + return SingleResult.failure(ex.getMessage()); + } + + @ExceptionHandler(IllegalStateException.class) + @ResponseBody + public SingleResult handlerIllegalStateException(IllegalStateException ex) { + LOGGER.error("IllegalStateException: ", ex); + return SingleResult.failure(ex.getMessage()); + } + + @ExceptionHandler(ShouldNeverHappenException.class) + @ResponseBody + public SingleResult handlerShouldNeverHappenException(ShouldNeverHappenException ex) { + LOGGER.error("ShouldNeverHappenException: ", ex); + return SingleResult.failure(ex.getMessage()); + } + + @ExceptionHandler(FrameworkException.class) + @ResponseBody + public SingleResult handlerFrameworkException(FrameworkException ex) { + LOGGER.error("FrameworkException: ", ex); + return SingleResult.failure(ex.getMessage()); + } + + @ExceptionHandler(Exception.class) + @ResponseBody + public SingleResult handleException(Exception ex) { + LOGGER.error("Exception: ", ex); + return SingleResult.failure(ex.getMessage()); + } +} diff --git a/server/src/main/java/org/apache/seata/server/console/controller/BranchSessionController.java b/server/src/main/java/org/apache/seata/server/console/controller/BranchSessionController.java index 1a194b17122..12c509e78a3 100644 --- a/server/src/main/java/org/apache/seata/server/console/controller/BranchSessionController.java +++ b/server/src/main/java/org/apache/seata/server/console/controller/BranchSessionController.java @@ -17,7 +17,13 @@ package org.apache.seata.server.console.controller; import javax.annotation.Resource; + +import org.apache.seata.common.result.SingleResult; import org.apache.seata.server.console.service.BranchSessionService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -25,11 +31,55 @@ * Branch Session Controller */ @RestController -@RequestMapping("console/branchSession") +@RequestMapping("/api/v1/console/branchSession") public class BranchSessionController { + private static final Logger LOGGER = LoggerFactory.getLogger(BranchSessionController.class); @Resource(type = BranchSessionService.class) private BranchSessionService branchSessionService; + /** + * Delete branch transaction + * + * @param xid the branch of xid + * @param branchId the branch id + * @return SingleResult + */ + @DeleteMapping("deleteBranchSession") + public SingleResult deleteBranchSession(String xid, String branchId) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("manual operation to delete the branch session, xid: {} branchId: {}", xid, branchId); + } + return branchSessionService.deleteBranchSession(xid, branchId); + } + + /** + * Stop branch transaction retry + * + * @param xid the branch of xid + * @param branchId the branch id + * @return SingleResult + */ + @PutMapping("stopBranchSession") + public SingleResult stopBranchSession(String xid, String branchId) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("manual operation to stop the branch session, xid: {} branchId: {}", xid, branchId); + } + return branchSessionService.stopBranchRetry(xid, branchId); + } + /** + * Start branch transaction retry + * + * @param xid the branch of xid + * @param branchId the branch id + * @return SingleResult + */ + @PutMapping("startBranchSession") + public SingleResult startBranchRetry(String xid, String branchId) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("manual operation to start the branch session, xid: {} branchId: {}", xid, branchId); + } + return branchSessionService.startBranchRetry(xid, branchId); + } } diff --git a/server/src/main/java/org/apache/seata/server/console/controller/GlobalLockController.java b/server/src/main/java/org/apache/seata/server/console/controller/GlobalLockController.java index 95d7cabf55c..9eaeaa24995 100644 --- a/server/src/main/java/org/apache/seata/server/console/controller/GlobalLockController.java +++ b/server/src/main/java/org/apache/seata/server/console/controller/GlobalLockController.java @@ -19,9 +19,13 @@ import javax.annotation.Resource; import org.apache.seata.common.result.PageResult; +import org.apache.seata.common.result.SingleResult; import org.apache.seata.server.console.param.GlobalLockParam; import org.apache.seata.server.console.vo.GlobalLockVO; import org.apache.seata.server.console.service.GlobalLockService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; @@ -34,6 +38,7 @@ @RestController @RequestMapping("/api/v1/console/globalLock") public class GlobalLockController { + private static final Logger LOGGER = LoggerFactory.getLogger(GlobalLockController.class); @Resource(type = GlobalLockService.class) private GlobalLockService globalLockService; @@ -48,4 +53,29 @@ public PageResult query(@ModelAttribute GlobalLockParam param) { return globalLockService.query(param); } + /** + * Delete global locks + * + * @param param the param + * @return SingleResult + */ + @DeleteMapping("delete") + public SingleResult delete(@ModelAttribute GlobalLockParam param) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("manual operation to delete the global lock, param: {}", param); + } + return globalLockService.deleteLock(param); + } + + /** + * Check if the lock exist the branch session + * + * @param xid xid + * @param branchId branch id + * @return the list of GlobalLockVO + */ + @GetMapping("check") + public SingleResult check(String xid, String branchId) { + return globalLockService.check(xid, branchId); + } } diff --git a/server/src/main/java/org/apache/seata/server/console/controller/GlobalSessionController.java b/server/src/main/java/org/apache/seata/server/console/controller/GlobalSessionController.java index aa039a663b6..8c1ff6aa51f 100644 --- a/server/src/main/java/org/apache/seata/server/console/controller/GlobalSessionController.java +++ b/server/src/main/java/org/apache/seata/server/console/controller/GlobalSessionController.java @@ -18,12 +18,17 @@ import javax.annotation.Resource; +import org.apache.seata.common.result.SingleResult; import org.apache.seata.server.console.param.GlobalSessionParam; import org.apache.seata.common.result.PageResult; import org.apache.seata.server.console.vo.GlobalSessionVO; import org.apache.seata.server.console.service.GlobalSessionService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -33,6 +38,7 @@ @RestController @RequestMapping("/api/v1/console/globalSession") public class GlobalSessionController { + private static final Logger LOGGER = LoggerFactory.getLogger(GlobalSessionController.class); @Resource(type = GlobalSessionService.class) private GlobalSessionService globalSessionService; @@ -47,4 +53,73 @@ public PageResult query(@ModelAttribute GlobalSessionParam para return globalSessionService.query(param); } + /** + * Delete the global session + * + * @param xid The xid + * @return SingleResult + */ + @DeleteMapping("deleteGlobalSession") + public SingleResult deleteGlobalSession(String xid) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("manual operation to delete the global session, xid: {}", xid); + } + return globalSessionService.deleteGlobalSession(xid); + } + + /** + * Stop the global session retry + * + * @param xid The xid + * @return SingleResult + */ + @PutMapping("stopGlobalSession") + public SingleResult stopGlobalSession(String xid) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("manual operation to stop the global session, xid: {}", xid); + } + return globalSessionService.stopGlobalRetry(xid); + } + + /** + * Start the global session retry + * + * @param xid The xid + * @return SingleResult + */ + @PutMapping("startGlobalSession") + public SingleResult startGlobalSession(String xid) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("manual operation to start the global session, xid: {}", xid); + } + return globalSessionService.startGlobalRetry(xid); + } + + /** + * Send global session to commit or rollback to rm + * + * @param xid The xid + * @return SingleResult + */ + @PutMapping("sendCommitOrRollback") + public SingleResult sendCommitOrRollback(String xid) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("manual operation to commit or rollback the global session, xid: {}", xid); + } + return globalSessionService.sendCommitOrRollback(xid); + } + + /** + * Change the global session status + * + * @param xid The xid + * @return SingleResult + */ + @PutMapping("changeGlobalStatus") + public SingleResult changeGlobalStatus(String xid) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("manual operation to change the global session, xid: {}", xid); + } + return globalSessionService.changeGlobalStatus(xid); + } } diff --git a/server/src/main/java/org/apache/seata/server/console/exception/ConsoleException.java b/server/src/main/java/org/apache/seata/server/console/exception/ConsoleException.java new file mode 100644 index 00000000000..50a65268efe --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/console/exception/ConsoleException.java @@ -0,0 +1,39 @@ +/* + * 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.server.console.exception; + +public class ConsoleException extends RuntimeException { + /** + * use for globalExceptionHandlerAdvice + * + * @see org.apache.seata.server.console.aop.GlobalExceptionHandlerAdvice + */ + private String logMessage; + + public ConsoleException(Throwable cause, String logMessage) { + super(cause); + this.logMessage = logMessage; + } + + public String getLogMessage() { + return logMessage; + } + + public void setLogMessage(String logMessage) { + this.logMessage = logMessage; + } +} diff --git a/server/src/main/java/org/apache/seata/server/console/impl/AbstractBranchService.java b/server/src/main/java/org/apache/seata/server/console/impl/AbstractBranchService.java new file mode 100644 index 00000000000..999520eb4b2 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/console/impl/AbstractBranchService.java @@ -0,0 +1,109 @@ +/* + * 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.server.console.impl; + +import org.apache.seata.common.result.SingleResult; +import org.apache.seata.core.model.BranchStatus; +import org.apache.seata.core.model.GlobalStatus; +import org.apache.seata.server.console.exception.ConsoleException; +import org.apache.seata.server.console.service.BranchSessionService; +import org.apache.seata.server.session.BranchSession; +import org.apache.seata.server.session.GlobalSession; + +public abstract class AbstractBranchService extends AbstractService implements BranchSessionService { + @Override + public SingleResult stopBranchRetry(String xid, String branchId) { + CheckResult checkResult = commonCheckAndGetGlobalStatus(xid, branchId); + GlobalSession globalSession = checkResult.getGlobalSession(); + // saga is not support to operate + if (globalSession.isSaga()) { + throw new IllegalArgumentException("saga can not operate branch transactions because it have no determinative role"); + } + BranchSession branchSession = checkResult.getBranchSession(); + BranchStatus branchStatus = branchSession.getStatus(); + if (branchStatus.getCode() == BranchStatus.STOP_RETRY.getCode()) { + throw new IllegalArgumentException("current branch session is already stop"); + } + // For BranchStatus.PhaseOne_Done is finished, will remove soon, thus no support + if (branchStatus != BranchStatus.Unknown && branchStatus != BranchStatus.Registered && + branchStatus != BranchStatus.PhaseOne_Done) { + throw new IllegalArgumentException("current branch session is not support to stop"); + } + GlobalStatus status = globalSession.getStatus(); + BranchStatus newStatus = RETRY_STATUS.contains(status) || GlobalStatus.Rollbacking.equals(status) || + GlobalStatus.Committing.equals(status) || GlobalStatus.StopRollbackOrRollbackRetry.equals(status) || + GlobalStatus.StopCommitOrCommitRetry.equals(status) ? BranchStatus.STOP_RETRY : null; + if (newStatus == null) { + throw new IllegalArgumentException("wrong status for global status"); + } + branchSession.setStatus(newStatus); + try { + globalSession.changeBranchStatus(branchSession, newStatus); + } catch (Exception e) { + throw new ConsoleException(e, String.format("stop branch session retry fail, xid:%s, branchId:%s", xid, branchId)); + } + return SingleResult.success(); + } + + @Override + public SingleResult startBranchRetry(String xid, String branchId) { + CheckResult checkResult = commonCheckAndGetGlobalStatus(xid, branchId); + GlobalSession globalSession = checkResult.getGlobalSession(); + // saga is not support to operate + if (globalSession.isSaga()) { + throw new IllegalArgumentException("saga can not operate branch transactions because it have no determinative role"); + } + BranchSession branchSession = checkResult.getBranchSession(); + BranchStatus branchStatus = branchSession.getStatus(); + if (!BranchStatus.STOP_RETRY.equals(branchStatus)) { + throw new IllegalArgumentException("current branch transactions status is not support to start retry"); + } + // BranchStatus.PhaseOne_Done and BranchStatus.Registered will become BranchStatus.Registered + BranchStatus newStatus = BranchStatus.Registered; + branchSession.setStatus(newStatus); + try { + globalSession.changeBranchStatus(branchSession, newStatus); + } catch (Exception e) { + throw new ConsoleException(e, String.format("start branch session retry fail, xid:%s, branchId:%s", xid, branchId)); + } + return SingleResult.success(); + } + + @Override + public SingleResult deleteBranchSession(String xid, String branchId) { + CheckResult checkResult = commonCheckAndGetGlobalStatus(xid, branchId); + GlobalSession globalSession = checkResult.getGlobalSession(); + // saga is not support to operate + if (globalSession.isSaga()) { + throw new IllegalArgumentException("saga can not operate branch transactions because it have no determinative role"); + } + GlobalStatus globalStatus = globalSession.getStatus(); + BranchSession branchSession = checkResult.getBranchSession(); + if (FAIL_STATUS.contains(globalStatus) || RETRY_STATUS.contains(globalStatus) + || FINISH_STATUS.contains(globalStatus) || GlobalStatus.StopRollbackOrRollbackRetry == globalStatus + || GlobalStatus.StopCommitOrCommitRetry == globalStatus || GlobalStatus.Deleting == globalStatus) { + try { + boolean deleted = doDeleteBranch(globalSession, branchSession); + return deleted ? SingleResult.success() : + SingleResult.failure("delete branch fail, please retry again later"); + } catch (Exception e) { + throw new ConsoleException(e, String.format("delete branch session fail, xid:%s, branchId:%s", xid, branchId)); + } + } + throw new IllegalArgumentException("current global transaction is not support delete branch transaction"); + } +} diff --git a/server/src/main/java/org/apache/seata/server/console/impl/AbstractGlobalService.java b/server/src/main/java/org/apache/seata/server/console/impl/AbstractGlobalService.java new file mode 100644 index 00000000000..7fc2cb83011 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/console/impl/AbstractGlobalService.java @@ -0,0 +1,143 @@ +/* + * 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.server.console.impl; + +import org.apache.seata.common.result.SingleResult; +import org.apache.seata.core.model.GlobalStatus; +import org.apache.seata.server.console.exception.ConsoleException; +import org.apache.seata.server.console.service.GlobalSessionService; +import org.apache.seata.server.coordinator.DefaultCoordinator; +import org.apache.seata.server.session.BranchSession; +import org.apache.seata.server.session.GlobalSession; +import org.apache.seata.server.session.SessionHolder; + +import java.util.ArrayList; +import java.util.List; + +public abstract class AbstractGlobalService extends AbstractService implements GlobalSessionService { + @Override + public SingleResult deleteGlobalSession(String xid) { + GlobalSession globalSession = checkGlobalSession(xid); + GlobalStatus globalStatus = globalSession.getStatus(); + if (FAIL_STATUS.contains(globalStatus) || RETRY_STATUS.contains(globalStatus) || FINISH_STATUS.contains(globalStatus) + || GlobalStatus.Deleting.equals(globalStatus) || GlobalStatus.StopCommitOrCommitRetry.equals(globalStatus) + || GlobalStatus.StopRollbackOrRollbackRetry.equals(globalStatus)) { + try { + if (!GlobalStatus.Deleting.equals(globalStatus)) { + globalSession.changeGlobalStatus(GlobalStatus.Deleting); + } + List branchSessions = globalSession.getBranchSessions(); + List iteratorBranchSessions = new ArrayList<>(branchSessions); + for (BranchSession branchSession : iteratorBranchSessions) { + if (!doDeleteBranch(globalSession, branchSession)) { + return SingleResult.failure("Delete branch fail, please try again"); + } + } + globalSession.end(); + return SingleResult.success(null); + } catch (Exception e) { + throw new ConsoleException(e, String.format("delete global session fail, xid:%s", xid)); + } + } + throw new IllegalArgumentException("current global transaction status is not support deleted"); + } + + @Override + public SingleResult stopGlobalRetry(String xid) { + GlobalSession globalSession = checkGlobalSession(xid); + GlobalStatus globalStatus = globalSession.getStatus(); + GlobalStatus newStatus = COMMIT_ING_STATUS.contains(globalStatus) ? GlobalStatus.StopCommitOrCommitRetry : + RETRY_ROLLBACK_STATUS.contains(globalStatus) || ROLLBACK_ING_STATUS.contains(globalStatus) + ? GlobalStatus.StopRollbackOrRollbackRetry : null; + if (newStatus == null) { + throw new IllegalArgumentException("current global transaction status is not support stop"); + } + try { + globalSession.changeGlobalStatus(newStatus); + return SingleResult.success(); + } catch (Exception e) { + throw new ConsoleException(e, String.format("Stop global session retry fail, xid:%s", xid)); + } + } + + @Override + public SingleResult startGlobalRetry(String xid) { + GlobalSession globalSession = checkGlobalSession(xid); + GlobalStatus globalStatus = globalSession.getStatus(); + GlobalStatus newStatus = GlobalStatus.StopCommitOrCommitRetry.equals(globalStatus) ? GlobalStatus.CommitRetrying : + GlobalStatus.StopRollbackOrRollbackRetry.equals(globalStatus) ? GlobalStatus.RollbackRetrying : null; + if (newStatus == null) { + throw new IllegalArgumentException("current global transaction status is not support start"); + } + try { + globalSession.changeGlobalStatus(newStatus); + return SingleResult.success(); + } catch (Exception e) { + throw new ConsoleException(e, String.format("Start global session retry fail, xid:%s", xid)); + } + } + + @Override + public SingleResult sendCommitOrRollback(String xid) { + GlobalSession globalSession = checkGlobalSession(xid); + GlobalStatus globalStatus = globalSession.getStatus(); + try { + boolean res; + if (RETRY_COMMIT_STATUS.contains(globalStatus) || GlobalStatus.Committing.equals(globalStatus) + || GlobalStatus.StopCommitOrCommitRetry.equals(globalStatus)) { + res = DefaultCoordinator.getInstance().getCore().doGlobalCommit(globalSession, false); + if (res && globalSession.hasBranch() && globalSession.hasATBranch()) { + globalSession.clean(); + globalSession.asyncCommit(); + } else if (res && SessionHolder.findGlobalSession(xid) != null) { + globalSession.end(); + } + } else if (RETRY_ROLLBACK_STATUS.contains(globalStatus) || GlobalStatus.Rollbacking.equals(globalStatus) + || GlobalStatus.StopRollbackOrRollbackRetry.equals(globalStatus)) { + res = DefaultCoordinator.getInstance().getCore().doGlobalRollback(globalSession, false); + // the record is not deleted + if (res && SessionHolder.findGlobalSession(xid) != null) { + globalSession.changeGlobalStatus(GlobalStatus.Rollbacked); + globalSession.end(); + } + } else { + throw new IllegalArgumentException("current global transaction status is not support to do"); + } + return res ? SingleResult.success() : + SingleResult.failure("Commit or rollback fail, please try again"); + } catch (Exception e) { + throw new ConsoleException(e, String.format("send commit or rollback to rm fail, xid:%s", xid)); + } + } + + @Override + public SingleResult changeGlobalStatus(String xid) { + GlobalSession globalSession = checkGlobalSession(xid); + GlobalStatus globalStatus = globalSession.getStatus(); + GlobalStatus newStatus = FAIL_COMMIT_STATUS.contains(globalStatus) ? GlobalStatus.CommitRetrying : + FAIL_ROLLBACK_STATUS.contains(globalStatus) ? GlobalStatus.RollbackRetrying : null; + if (newStatus == null) { + throw new IllegalArgumentException("current global transaction status is not support to change"); + } + try { + globalSession.changeGlobalStatus(newStatus); + return SingleResult.success(); + } catch (Exception e) { + throw new ConsoleException(e, String.format("change global status fail, xid:%s", xid)); + } + } +} diff --git a/server/src/main/java/org/apache/seata/server/console/impl/AbstractLockService.java b/server/src/main/java/org/apache/seata/server/console/impl/AbstractLockService.java new file mode 100644 index 00000000000..3b3f7634ae4 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/console/impl/AbstractLockService.java @@ -0,0 +1,44 @@ +/* + * 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.server.console.impl; + +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.common.result.SingleResult; +import org.apache.seata.server.console.param.GlobalLockParam; +import org.apache.seata.server.console.service.GlobalLockService; + +public abstract class AbstractLockService extends AbstractService implements GlobalLockService { + + @Override + public SingleResult check(String xid, String branchId) { + try { + commonCheckAndGetGlobalStatus(xid, branchId); + } catch (IllegalArgumentException e) { + return SingleResult.success(Boolean.FALSE); + } + return SingleResult.success(Boolean.TRUE); + } + + protected void checkDeleteLock(GlobalLockParam param) { + commonCheck(param.getXid(), param.getBranchId()); + if (StringUtils.isBlank(param.getTableName()) || StringUtils.isBlank(param.getPk()) + || StringUtils.isBlank(param.getResourceId())) { + throw new IllegalArgumentException("tableName or resourceId or pk can not be empty"); + } + } +} + diff --git a/server/src/main/java/org/apache/seata/server/console/impl/AbstractService.java b/server/src/main/java/org/apache/seata/server/console/impl/AbstractService.java new file mode 100644 index 00000000000..8480ee8b4f2 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/console/impl/AbstractService.java @@ -0,0 +1,169 @@ +/* + * 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.server.console.impl; + +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.core.exception.TransactionException; +import org.apache.seata.core.model.BranchStatus; +import org.apache.seata.core.model.GlobalStatus; +import org.apache.seata.server.coordinator.DefaultCoordinator; +import org.apache.seata.server.lock.LockManager; +import org.apache.seata.server.lock.LockerManagerFactory; +import org.apache.seata.server.session.BranchSession; +import org.apache.seata.server.session.GlobalSession; +import org.apache.seata.server.session.SessionHolder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * The abstract service. + */ +public abstract class AbstractService { + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractService.class); + + protected final LockManager lockManager = LockerManagerFactory.getLockManager(); + + protected static final List RETRY_COMMIT_STATUS = Arrays.asList(GlobalStatus.CommitRetrying); + + protected static final List RETRY_ROLLBACK_STATUS = Arrays.asList(GlobalStatus.RollbackRetrying, + GlobalStatus.TimeoutRollbackRetrying, GlobalStatus.TimeoutRollbacking); + + protected static final List COMMIT_ING_STATUS = Stream.concat(RETRY_COMMIT_STATUS.stream(), + Collections.singletonList(GlobalStatus.Committing).stream()).collect(Collectors.toList()); + + protected static final List ROLLBACK_ING_STATUS = Stream.concat(RETRY_ROLLBACK_STATUS.stream(), + Collections.singletonList(GlobalStatus.Rollbacking).stream()).collect(Collectors.toList()); + + protected static final List RETRY_STATUS = Stream.concat(RETRY_COMMIT_STATUS.stream(), + RETRY_ROLLBACK_STATUS.stream()).collect(Collectors.toList()); + + protected static final List FAIL_COMMIT_STATUS = Arrays.asList(GlobalStatus.CommitFailed, + GlobalStatus.CommitRetryTimeout); + + protected static final List FAIL_ROLLBACK_STATUS = Arrays.asList(GlobalStatus.TimeoutRollbacked, + GlobalStatus.RollbackFailed, GlobalStatus.RollbackRetryTimeout); + + protected static final List FAIL_STATUS = Stream.concat(FAIL_COMMIT_STATUS.stream(), + FAIL_ROLLBACK_STATUS.stream()).collect(Collectors.toList()); + + protected static final List FINISH_STATUS = Arrays.asList(GlobalStatus.Committed, + GlobalStatus.Finished, GlobalStatus.Rollbacked); + + protected void commonCheck(String xid, String branchId) { + if (StringUtils.isBlank(xid)) { + throw new IllegalArgumentException("Wrong parameter for xid"); + } + if (StringUtils.isBlank(branchId)) { + throw new IllegalArgumentException("Wrong parameter for branchId"); + } + try { + Long.parseLong(branchId); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Wrong parameter for branchId, branch Id is not number"); + } + } + + protected GlobalSession checkGlobalSession(String xid) { + if (StringUtils.isBlank(xid)) { + throw new IllegalArgumentException("Wrong parameter for xid"); + } + GlobalSession globalSession = SessionHolder.findGlobalSession(xid); + if (Objects.isNull(globalSession)) { + throw new IllegalArgumentException("Global session is not exist, may be finished"); + } + return globalSession; + } + + /** + * check if exist global transaction and branch transaction + * + * @param xid xid + * @param branchId branchId + * @return CheckResult, throw IllegalArgumentException if not exist + */ + protected CheckResult commonCheckAndGetGlobalStatus(String xid, String branchId) { + commonCheck(xid, branchId); + GlobalSession globalSession = SessionHolder.findGlobalSession(xid); + if (Objects.isNull(globalSession)) { + throw new IllegalArgumentException("global session is not exist, may be finished"); + } + List branchSessions = globalSession.getBranchSessions(); + Long paramBranchId = Long.valueOf(branchId); + BranchSession branchSession = branchSessions.stream() + .filter(session -> paramBranchId.equals(session.getBranchId())) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("branch session is not exist, may be finished")); + return new CheckResult(globalSession, branchSession); + } + + protected boolean doDeleteBranch(GlobalSession globalSession, BranchSession branchSession) throws TimeoutException, TransactionException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Branch delete start, xid:{} branchId:{} branchType:{}", + branchSession.getXid(), branchSession.getBranchId(), branchSession.getBranchType()); + } + // local transaction failed, not need to do branch del for phase two + if (branchSession.getStatus() == BranchStatus.PhaseOne_Failed) { + globalSession.removeBranch(branchSession); + return true; + } + boolean result = DefaultCoordinator.getInstance().getCore().doBranchDelete(globalSession, branchSession); + if (result) { + if (branchSession.isAT()) { + result = lockManager.releaseLock(branchSession); + } + if (result) { + globalSession.removeBranch(branchSession); + return true; + } + } + return false; + } + + protected static class CheckResult { + private GlobalSession globalSession; + private BranchSession branchSession; + + public CheckResult(GlobalSession globalSession, BranchSession branchSession) { + this.globalSession = globalSession; + this.branchSession = branchSession; + } + + public GlobalSession getGlobalSession() { + return globalSession; + } + + public void setGlobalSession(GlobalSession globalSession) { + this.globalSession = globalSession; + } + + public BranchSession getBranchSession() { + return branchSession; + } + + public void setBranchSession(BranchSession branchSession) { + this.branchSession = branchSession; + } + } +} diff --git a/server/src/main/java/org/apache/seata/server/console/impl/db/BranchSessionDBServiceImpl.java b/server/src/main/java/org/apache/seata/server/console/impl/db/BranchSessionDBServiceImpl.java index 4746e109b26..375503445dd 100644 --- a/server/src/main/java/org/apache/seata/server/console/impl/db/BranchSessionDBServiceImpl.java +++ b/server/src/main/java/org/apache/seata/server/console/impl/db/BranchSessionDBServiceImpl.java @@ -35,6 +35,7 @@ import org.apache.seata.common.result.PageResult; import org.apache.seata.core.store.db.DataSourceProvider; import org.apache.seata.core.store.db.sql.log.LogStoreSqlsFactory; +import org.apache.seata.server.console.impl.AbstractBranchService; import org.apache.seata.server.console.service.BranchSessionService; import org.apache.seata.server.console.vo.BranchSessionVO; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; @@ -49,7 +50,7 @@ @Component @org.springframework.context.annotation.Configuration @ConditionalOnExpression("#{'db'.equals('${sessionMode}')}") -public class BranchSessionDBServiceImpl implements BranchSessionService { +public class BranchSessionDBServiceImpl extends AbstractBranchService implements BranchSessionService { private String branchTable; diff --git a/server/src/main/java/org/apache/seata/server/console/impl/db/GlobalLockDBServiceImpl.java b/server/src/main/java/org/apache/seata/server/console/impl/db/GlobalLockDBServiceImpl.java index 4ac3ea95755..b1ec8d40d96 100644 --- a/server/src/main/java/org/apache/seata/server/console/impl/db/GlobalLockDBServiceImpl.java +++ b/server/src/main/java/org/apache/seata/server/console/impl/db/GlobalLockDBServiceImpl.java @@ -28,6 +28,7 @@ import org.apache.seata.common.ConfigurationKeys; import org.apache.seata.common.exception.StoreException; import org.apache.seata.common.loader.EnhancedServiceLoader; +import org.apache.seata.common.result.SingleResult; import org.apache.seata.common.util.IOUtil; import org.apache.seata.common.util.PageUtil; import org.apache.seata.common.util.StringUtils; @@ -36,13 +37,18 @@ import org.apache.seata.common.result.PageResult; import org.apache.seata.core.store.db.DataSourceProvider; import org.apache.seata.core.store.db.sql.lock.LockStoreSqlFactory; +import org.apache.seata.server.console.exception.ConsoleException; +import org.apache.seata.server.console.impl.AbstractLockService; import org.apache.seata.server.console.param.GlobalLockParam; import org.apache.seata.server.console.service.GlobalLockService; import org.apache.seata.server.console.vo.GlobalLockVO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; import static org.apache.seata.common.DefaultValues.DEFAULT_LOCK_DB_TABLE; +import static org.apache.seata.core.constants.RedisKeyConstants.SPLIT; /** @@ -52,8 +58,8 @@ @Component @org.springframework.context.annotation.Configuration @ConditionalOnExpression("#{'db'.equals('${lockMode}')}") -public class GlobalLockDBServiceImpl implements GlobalLockService { - +public class GlobalLockDBServiceImpl extends AbstractLockService implements GlobalLockService { + private static final Logger LOGGER = LoggerFactory.getLogger(GlobalLockDBServiceImpl.class); private String lockTable; private String dbType; @@ -108,6 +114,7 @@ public PageResult query(GlobalLockParam param) { count = countRs.getInt(1); } } catch (SQLException e) { + LOGGER.error("query global lock exception, param:{}", param, e); throw new StoreException(e); } finally { IOUtil.close(rs, countRs, ps, countPs, conn); @@ -115,6 +122,31 @@ public PageResult query(GlobalLockParam param) { return PageResult.success(list, count, param.getPageNum(), param.getPageSize()); } + @Override + public SingleResult deleteLock(GlobalLockParam param) { + checkDeleteLock(param); + String rowKey = buildRowKey(param.getTableName(), param.getPk(), param.getResourceId()); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("start to delete global lock,xid:{} branchId:{} row key:{} ", + param.getXid(), param.getBranchId(), rowKey); + } + String deleteLockSql = LockStoreSqlFactory.getLogStoreSql(dbType).getDeleteLockSql(lockTable); + try (Connection conn = dataSource.getConnection(); + PreparedStatement ps = conn.prepareStatement(deleteLockSql)) { + ps.setString(1, rowKey); + ps.setString(2, param.getXid()); + ps.executeUpdate(); + } catch (Exception e) { + throw new ConsoleException(e, String.format("delete global lock," + + "xid:%s ,branchId:%s ,row key:%s failed", param.getXid(), param.getBranchId(), rowKey)); + } + return SingleResult.success(); + } + + private String buildRowKey(String tableName, String pk, String resourceId) { + return resourceId + SPLIT + tableName + SPLIT + pk; + } + private String getWhereConditionByParam(GlobalLockParam param, List sqlParamList) { StringBuilder whereConditionBuilder = new StringBuilder(); if (StringUtils.isNotBlank(param.getXid())) { diff --git a/server/src/main/java/org/apache/seata/server/console/impl/db/GlobalSessionDBServiceImpl.java b/server/src/main/java/org/apache/seata/server/console/impl/db/GlobalSessionDBServiceImpl.java index f27b896dc5f..4bc5b16969d 100644 --- a/server/src/main/java/org/apache/seata/server/console/impl/db/GlobalSessionDBServiceImpl.java +++ b/server/src/main/java/org/apache/seata/server/console/impl/db/GlobalSessionDBServiceImpl.java @@ -38,6 +38,7 @@ import org.apache.seata.common.result.PageResult; import org.apache.seata.core.store.db.DataSourceProvider; import org.apache.seata.core.store.db.sql.log.LogStoreSqlsFactory; +import org.apache.seata.server.console.impl.AbstractGlobalService; import org.apache.seata.server.console.param.GlobalSessionParam; import org.apache.seata.server.console.service.BranchSessionService; import org.apache.seata.server.console.service.GlobalSessionService; @@ -55,7 +56,7 @@ @Component @org.springframework.context.annotation.Configuration @ConditionalOnExpression("#{'db'.equals('${sessionMode}')}") -public class GlobalSessionDBServiceImpl implements GlobalSessionService { +public class GlobalSessionDBServiceImpl extends AbstractGlobalService implements GlobalSessionService { private String globalTable; diff --git a/server/src/main/java/org/apache/seata/server/console/impl/file/BranchSessionFileServiceImpl.java b/server/src/main/java/org/apache/seata/server/console/impl/file/BranchSessionFileServiceImpl.java index 54741fdab6c..efd2337d158 100644 --- a/server/src/main/java/org/apache/seata/server/console/impl/file/BranchSessionFileServiceImpl.java +++ b/server/src/main/java/org/apache/seata/server/console/impl/file/BranchSessionFileServiceImpl.java @@ -16,13 +16,22 @@ */ package org.apache.seata.server.console.impl.file; -import org.apache.seata.common.exception.NotSupportYetException; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.server.console.impl.AbstractBranchService; import org.apache.seata.server.console.vo.BranchSessionVO; import org.apache.seata.common.result.PageResult; import org.apache.seata.server.console.service.BranchSessionService; +import org.apache.seata.server.session.GlobalSession; +import org.apache.seata.server.session.SessionHolder; +import org.apache.seata.server.storage.SessionConverter; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; + /** * Branch Session File ServiceImpl * @@ -30,10 +39,22 @@ @Component @org.springframework.context.annotation.Configuration @ConditionalOnExpression("#{'file'.equals('${sessionMode}')}") -public class BranchSessionFileServiceImpl implements BranchSessionService { +public class BranchSessionFileServiceImpl extends AbstractBranchService implements BranchSessionService { @Override public PageResult queryByXid(String xid) { - throw new NotSupportYetException(); + if (StringUtils.isBlank(xid)) { + throw new IllegalArgumentException("xid should not be blank"); + } + List branchSessionVOList = new ArrayList<>(0); + final Collection allSessions = SessionHolder.getRootSessionManager().allSessions(); + for (GlobalSession globalSession : allSessions) { + if (globalSession.getXid().equals(xid)) { + Set branchSessionVOS = SessionConverter.convertBranchSession(globalSession.getBranchSessions()); + branchSessionVOList = new ArrayList<>(branchSessionVOS); + break; + } + } + return PageResult.success(branchSessionVOList, branchSessionVOList.size(), 0, 0, 0); } } diff --git a/server/src/main/java/org/apache/seata/server/console/impl/file/GlobalLockFileServiceImpl.java b/server/src/main/java/org/apache/seata/server/console/impl/file/GlobalLockFileServiceImpl.java index 0aa574331b6..3480b4c185b 100644 --- a/server/src/main/java/org/apache/seata/server/console/impl/file/GlobalLockFileServiceImpl.java +++ b/server/src/main/java/org/apache/seata/server/console/impl/file/GlobalLockFileServiceImpl.java @@ -23,8 +23,10 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.seata.common.result.SingleResult; import org.apache.seata.common.util.CollectionUtils; import org.apache.seata.common.util.StringUtils; +import org.apache.seata.server.console.impl.AbstractLockService; import org.apache.seata.server.console.param.GlobalLockParam; import org.apache.seata.common.result.PageResult; import org.apache.seata.server.console.vo.GlobalLockVO; @@ -49,7 +51,7 @@ @Component @org.springframework.context.annotation.Configuration @ConditionalOnExpression("#{'file'.equals('${lockMode}')}") -public class GlobalLockFileServiceImpl implements GlobalLockService { +public class GlobalLockFileServiceImpl extends AbstractLockService implements GlobalLockService { @Override public PageResult query(GlobalLockParam param) { @@ -71,6 +73,11 @@ public PageResult query(GlobalLockParam param) { } + @Override + public SingleResult deleteLock(GlobalLockParam param) { + throw new IllegalStateException("Not Support to delete lock in file mode"); + } + /** * filter with tableName and generate RowLock * diff --git a/server/src/main/java/org/apache/seata/server/console/impl/file/GlobalSessionFileServiceImpl.java b/server/src/main/java/org/apache/seata/server/console/impl/file/GlobalSessionFileServiceImpl.java index f2821f4ad83..5d62e352e59 100644 --- a/server/src/main/java/org/apache/seata/server/console/impl/file/GlobalSessionFileServiceImpl.java +++ b/server/src/main/java/org/apache/seata/server/console/impl/file/GlobalSessionFileServiceImpl.java @@ -22,6 +22,7 @@ import java.util.function.Predicate; import java.util.stream.Collectors; +import org.apache.seata.server.console.impl.AbstractGlobalService; import org.apache.seata.server.console.param.GlobalSessionParam; import org.apache.seata.common.result.PageResult; import org.apache.seata.server.console.vo.GlobalSessionVO; @@ -42,7 +43,7 @@ @Component @org.springframework.context.annotation.Configuration @ConditionalOnExpression("#{'file'.equals('${sessionMode}')}") -public class GlobalSessionFileServiceImpl implements GlobalSessionService { +public class GlobalSessionFileServiceImpl extends AbstractGlobalService implements GlobalSessionService { @Override public PageResult query(GlobalSessionParam param) { diff --git a/server/src/main/java/org/apache/seata/server/console/impl/redis/BranchSessionRedisServiceImpl.java b/server/src/main/java/org/apache/seata/server/console/impl/redis/BranchSessionRedisServiceImpl.java index 77b4accb63f..ca52abc0c7d 100644 --- a/server/src/main/java/org/apache/seata/server/console/impl/redis/BranchSessionRedisServiceImpl.java +++ b/server/src/main/java/org/apache/seata/server/console/impl/redis/BranchSessionRedisServiceImpl.java @@ -21,6 +21,7 @@ import org.apache.seata.common.util.CollectionUtils; import org.apache.seata.common.util.StringUtils; import org.apache.seata.common.result.PageResult; +import org.apache.seata.server.console.impl.AbstractBranchService; import org.apache.seata.server.console.vo.BranchSessionVO; import org.apache.seata.core.store.BranchTransactionDO; import org.apache.seata.server.console.service.BranchSessionService; @@ -37,7 +38,7 @@ @Component @org.springframework.context.annotation.Configuration @ConditionalOnExpression("#{'redis'.equals('${sessionMode}')}") -public class BranchSessionRedisServiceImpl implements BranchSessionService { +public class BranchSessionRedisServiceImpl extends AbstractBranchService implements BranchSessionService { @Override public PageResult queryByXid(String xid) { diff --git a/server/src/main/java/org/apache/seata/server/console/impl/redis/GlobalLockRedisServiceImpl.java b/server/src/main/java/org/apache/seata/server/console/impl/redis/GlobalLockRedisServiceImpl.java index 0ee7f6a4720..5be160d03f6 100644 --- a/server/src/main/java/org/apache/seata/server/console/impl/redis/GlobalLockRedisServiceImpl.java +++ b/server/src/main/java/org/apache/seata/server/console/impl/redis/GlobalLockRedisServiceImpl.java @@ -20,7 +20,14 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.StringJoiner; + +import org.apache.seata.common.result.SingleResult; import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.server.console.impl.AbstractLockService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; import org.apache.seata.common.util.BeanUtils; @@ -44,7 +51,8 @@ @Component @org.springframework.context.annotation.Configuration @ConditionalOnExpression("#{'redis'.equals('${lockMode}')}") -public class GlobalLockRedisServiceImpl implements GlobalLockService { +public class GlobalLockRedisServiceImpl extends AbstractLockService implements GlobalLockService { + private static final Logger LOGGER = LoggerFactory.getLogger(GlobalLockRedisServiceImpl.class); @Override public PageResult query(GlobalLockParam param) { @@ -69,6 +77,47 @@ public PageResult query(GlobalLockParam param) { } } + @Override + public SingleResult deleteLock(GlobalLockParam param) { + checkDeleteLock(param); + String rowKey = buildRowKey(param.getTableName(), param.getPk(), param.getResourceId()); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("start to delete global lock,xid:{} branchId:{} row key:{} ", + param.getXid(), param.getBranchId(), rowKey); + } + try (Jedis jedis = JedisPooledFactory.getJedisInstance()) { + // del the row key + jedis.del(rowKey); + String xidLockKey = buildXidLockKey(param.getXid()); + String rowKeys = jedis.hget(xidLockKey, param.getBranchId()); + if (StringUtils.isNotBlank(rowKeys)) { + // Check whether other locks exist. If so, update it. If not, delete it + if (rowKeys.contains(ROW_LOCK_KEY_SPLIT_CHAR)) { + String[] rowKeyArray = rowKeys.split(ROW_LOCK_KEY_SPLIT_CHAR); + StringJoiner lockKeysString = new StringJoiner(ROW_LOCK_KEY_SPLIT_CHAR); + for (String rk : rowKeyArray) { + if (rk.equalsIgnoreCase(rowKey)) { + continue; + } + lockKeysString.add(rk); + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("update global lock key from {} to :{} ",rowKeys, lockKeysString); + } + // update the new lock key + jedis.hset(xidLockKey, param.getBranchId(), lockKeysString.toString()); + } else { + // no other branch session + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("start to delete global lock key:{} ", xidLockKey); + } + jedis.del(xidLockKey); + } + } + } + return SingleResult.success(); + } + private List queryGlobalLockByRowKey(String buildRowKey) { return readGlobalLockByRowKey(buildRowKey); } @@ -115,4 +164,7 @@ private List readGlobalLockByRowKey(String key) { return vos; } + private String buildXidLockKey(String xid) { + return DEFAULT_REDIS_SEATA_GLOBAL_LOCK_PREFIX + xid; + } } diff --git a/server/src/main/java/org/apache/seata/server/console/impl/redis/GlobalSessionRedisServiceImpl.java b/server/src/main/java/org/apache/seata/server/console/impl/redis/GlobalSessionRedisServiceImpl.java index 0d6ae6321bb..81563f28971 100644 --- a/server/src/main/java/org/apache/seata/server/console/impl/redis/GlobalSessionRedisServiceImpl.java +++ b/server/src/main/java/org/apache/seata/server/console/impl/redis/GlobalSessionRedisServiceImpl.java @@ -21,6 +21,7 @@ import java.util.stream.Collectors; import org.apache.seata.common.util.CollectionUtils; import org.apache.seata.common.result.PageResult; +import org.apache.seata.server.console.impl.AbstractGlobalService; import org.apache.seata.server.console.param.GlobalSessionParam; import org.apache.seata.server.console.vo.GlobalSessionVO; import org.apache.seata.core.model.GlobalStatus; @@ -45,7 +46,7 @@ @Component @org.springframework.context.annotation.Configuration @ConditionalOnExpression("#{'redis'.equals('${sessionMode}')}") -public class GlobalSessionRedisServiceImpl implements GlobalSessionService { +public class GlobalSessionRedisServiceImpl extends AbstractGlobalService implements GlobalSessionService { private static final Logger LOGGER = LoggerFactory.getLogger(GlobalSessionRedisServiceImpl.class); diff --git a/server/src/main/java/org/apache/seata/server/console/service/BranchSessionService.java b/server/src/main/java/org/apache/seata/server/console/service/BranchSessionService.java index 7a12c2f3e27..19dc98e4818 100644 --- a/server/src/main/java/org/apache/seata/server/console/service/BranchSessionService.java +++ b/server/src/main/java/org/apache/seata/server/console/service/BranchSessionService.java @@ -16,6 +16,7 @@ */ package org.apache.seata.server.console.service; +import org.apache.seata.common.result.SingleResult; import org.apache.seata.server.console.vo.BranchSessionVO; import org.apache.seata.common.result.PageResult; @@ -31,4 +32,30 @@ public interface BranchSessionService { */ PageResult queryByXid(String xid); + /** + * Stop branch transaction retry + * + * @param xid the global transaction + * @param branchId the branch transaction + * @return SingleResult + */ + SingleResult deleteBranchSession(String xid, String branchId); + + /** + * Start branch transaction retry + * + * @param xid the global transaction + * @param branchId the branch transaction + * @return SingleResult + */ + SingleResult stopBranchRetry(String xid, String branchId); + + /** + * Delete branch transaction + * + * @param xid the global transaction + * @param branchId the branch transaction + * @return SingleResult + */ + SingleResult startBranchRetry(String xid, String branchId); } diff --git a/server/src/main/java/org/apache/seata/server/console/service/GlobalLockService.java b/server/src/main/java/org/apache/seata/server/console/service/GlobalLockService.java index 90f0e08a6be..328f41006ad 100644 --- a/server/src/main/java/org/apache/seata/server/console/service/GlobalLockService.java +++ b/server/src/main/java/org/apache/seata/server/console/service/GlobalLockService.java @@ -17,6 +17,7 @@ package org.apache.seata.server.console.service; import org.apache.seata.common.result.PageResult; +import org.apache.seata.common.result.SingleResult; import org.apache.seata.server.console.param.GlobalLockParam; import org.apache.seata.server.console.vo.GlobalLockVO; @@ -33,5 +34,20 @@ public interface GlobalLockService { */ PageResult query(GlobalLockParam param); + /** + * Delete lock by xid and branchId + * + * @param param param + * @return SingleResult + */ + SingleResult deleteLock(GlobalLockParam param); + /** + * Check if the lock exist the branch session + * + * @param xid xid + * @param branchId branchId + * @return True-exist False-not exist the branch session + */ + SingleResult check(String xid, String branchId); } diff --git a/server/src/main/java/org/apache/seata/server/console/service/GlobalSessionService.java b/server/src/main/java/org/apache/seata/server/console/service/GlobalSessionService.java index 84eb62c9068..2ef56c197ae 100644 --- a/server/src/main/java/org/apache/seata/server/console/service/GlobalSessionService.java +++ b/server/src/main/java/org/apache/seata/server/console/service/GlobalSessionService.java @@ -16,6 +16,7 @@ */ package org.apache.seata.server.console.service; +import org.apache.seata.common.result.SingleResult; import org.apache.seata.server.console.param.GlobalSessionParam; import org.apache.seata.server.console.vo.GlobalSessionVO; import org.apache.seata.common.result.PageResult; @@ -32,4 +33,43 @@ public interface GlobalSessionService { */ PageResult query(GlobalSessionParam param); + /** + * Delete the global session + * + * @param xid The xid + * @return SingleResult + */ + SingleResult deleteGlobalSession(String xid); + + /** + * Stop the global session retry + * + * @param xid The xid + * @return SingleResult + */ + SingleResult stopGlobalRetry(String xid); + + /** + * Start the global session retry + * + * @param xid The xid + * @return SingleResult + */ + SingleResult startGlobalRetry(String xid); + + /** + * Send global session to commit or rollback to rm + * + * @param xid The xid + * @return SingleResult + */ + SingleResult sendCommitOrRollback(String xid); + + /** + * Change the global session status + * + * @param xid The xid + * @return SingleResult + */ + SingleResult changeGlobalStatus(String xid); } diff --git a/server/src/main/java/org/apache/seata/server/coordinator/AbstractCore.java b/server/src/main/java/org/apache/seata/server/coordinator/AbstractCore.java index e7607f427b1..944082a1cac 100644 --- a/server/src/main/java/org/apache/seata/server/coordinator/AbstractCore.java +++ b/server/src/main/java/org/apache/seata/server/coordinator/AbstractCore.java @@ -34,6 +34,8 @@ import org.apache.seata.core.model.GlobalStatus; import org.apache.seata.core.protocol.transaction.BranchCommitRequest; import org.apache.seata.core.protocol.transaction.BranchCommitResponse; +import org.apache.seata.core.protocol.transaction.BranchDeleteRequest; +import org.apache.seata.core.protocol.transaction.BranchDeleteResponse; import org.apache.seata.core.protocol.transaction.BranchRollbackRequest; import org.apache.seata.core.protocol.transaction.BranchRollbackResponse; import org.apache.seata.core.rpc.RemotingServer; @@ -49,6 +51,7 @@ import static org.apache.seata.core.exception.TransactionExceptionCode.BranchTransactionNotExist; import static org.apache.seata.core.exception.TransactionExceptionCode.FailedToAddBranch; +import static org.apache.seata.core.exception.TransactionExceptionCode.FailedToSendBranchDeleteRequest; import static org.apache.seata.core.exception.TransactionExceptionCode.GlobalTransactionNotActive; import static org.apache.seata.core.exception.TransactionExceptionCode.GlobalTransactionStatusInvalid; import static org.apache.seata.core.exception.TransactionExceptionCode.FailedToSendBranchCommitRequest; @@ -261,4 +264,25 @@ public GlobalStatus getStatus(String xid) throws TransactionException { public void doGlobalReport(GlobalSession globalSession, String xid, GlobalStatus globalStatus) throws TransactionException { } + + @Override + public Boolean doBranchDelete(GlobalSession globalSession, BranchSession branchSession) throws TransactionException { + return true; + } + + @Override + public BranchDeleteResponse branchDelete(GlobalSession globalSession, BranchSession branchSession) throws TransactionException { + BranchDeleteRequest request = new BranchDeleteRequest(); + try { + request.setBranchType(branchSession.getBranchType()); + request.setResourceId(branchSession.getResourceId()); + request.setXid(globalSession.getXid()); + request.setBranchId(branchSession.getBranchId()); + return (BranchDeleteResponse) remotingServer.sendSyncRequest( + branchSession.getResourceId(), branchSession.getClientId(), request, branchSession.isAT()); + } catch (TimeoutException e) { + throw new BranchTransactionException(FailedToSendBranchDeleteRequest, String.format("Send branch delete failed," + + " xid = %s branchId = %s", branchSession.getXid(), branchSession.getBranchId()), e); + } + } } diff --git a/server/src/main/java/org/apache/seata/server/coordinator/Core.java b/server/src/main/java/org/apache/seata/server/coordinator/Core.java index 04b335b3a04..c7b59a20c0a 100644 --- a/server/src/main/java/org/apache/seata/server/coordinator/Core.java +++ b/server/src/main/java/org/apache/seata/server/coordinator/Core.java @@ -18,6 +18,7 @@ import org.apache.seata.core.exception.TransactionException; import org.apache.seata.core.model.GlobalStatus; +import org.apache.seata.server.session.BranchSession; import org.apache.seata.server.session.GlobalSession; /** @@ -56,4 +57,12 @@ public interface Core extends TransactionCoordinatorInbound, TransactionCoordina */ void doGlobalReport(GlobalSession globalSession, String xid, GlobalStatus param) throws TransactionException; + /** + * Do branch delete. + * + * @param globalSession the global session + * @param branchSession the branch session + * @throws TransactionException the transaction exception + */ + Boolean doBranchDelete(GlobalSession globalSession, BranchSession branchSession) throws TransactionException; } diff --git a/server/src/main/java/org/apache/seata/server/coordinator/DefaultCoordinator.java b/server/src/main/java/org/apache/seata/server/coordinator/DefaultCoordinator.java index 468e9ccc5ff..b54c7f3ef4d 100644 --- a/server/src/main/java/org/apache/seata/server/coordinator/DefaultCoordinator.java +++ b/server/src/main/java/org/apache/seata/server/coordinator/DefaultCoordinator.java @@ -37,6 +37,7 @@ import org.apache.seata.core.constants.ConfigurationKeys; import org.apache.seata.core.context.RootContext; import org.apache.seata.core.exception.TransactionException; +import org.apache.seata.core.model.BranchStatus; import org.apache.seata.core.model.GlobalStatus; import org.apache.seata.core.protocol.AbstractMessage; import org.apache.seata.core.protocol.AbstractResultMessage; @@ -78,6 +79,7 @@ import org.slf4j.MDC; import static org.apache.seata.common.Constants.ASYNC_COMMITTING; +import static org.apache.seata.common.Constants.AUTO_RESTART_SESSION; import static org.apache.seata.common.Constants.COMMITTING; import static org.apache.seata.common.Constants.RETRY_COMMITTING; import static org.apache.seata.common.Constants.RETRY_ROLLBACKING; @@ -136,6 +138,17 @@ public class DefaultCoordinator extends AbstractTCInboundHandler implements Tran protected static final long UNDO_LOG_DELETE_PERIOD = CONFIG.getLong( ConfigurationKeys.TRANSACTION_UNDO_LOG_DELETE_PERIOD, DEFAULT_UNDO_LOG_DELETE_PERIOD); + /** + * The constant AUTO_RESTART_TIME + */ + protected static final long AUTO_RETRY_TIME = CONFIG.getLong(ConfigurationKeys.AUTO_RESTART_TIME, + DefaultValues.DEFAULT_AUTO_RESTART_TIME); + + /** + * The constant AUTO_RESTART_PERIOD + */ + protected static final long AUTO_RETRY_PERIOD = CONFIG.getLong(ConfigurationKeys.AUTO_RESTART_PERIOD, + DefaultValues.DEFAULT_AUTO_RESTART_PERIOD); /** * The Transaction undo log delay delete period */ @@ -186,6 +199,9 @@ public class DefaultCoordinator extends AbstractTCInboundHandler implements Tran private final ScheduledThreadPoolExecutor syncProcessing = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory(SYNC_PROCESSING, 1)); + private final ScheduledThreadPoolExecutor autoRestartSession = + new ScheduledThreadPoolExecutor(1, new NamedThreadFactory(AUTO_RESTART_SESSION, 1)); + private final GlobalStatus[] retryRollbackingStatuses = new GlobalStatus[] { GlobalStatus.TimeoutRollbacking, GlobalStatus.TimeoutRollbackRetrying, GlobalStatus.RollbackRetrying}; @@ -466,6 +482,49 @@ protected void handleAsyncCommitting() { }); } + /** + * find session stop retry to retry again + */ + protected void handleAutoRestart() { + Collection allSessions = SessionHolder.getRootSessionManager().allSessions(); + if (CollectionUtils.isEmpty(allSessions)) { + return; + } + SessionHelper.forEach(allSessions, globalSession -> { + String xid = globalSession.getXid(); + List branchSessions = globalSession.getBranchSessions(); + branchSessions.forEach(branchSession -> { + if (BranchStatus.STOP_RETRY.equals(branchSession.getStatus()) + && System.currentTimeMillis() - branchSession.getGmtModified() >= AUTO_RETRY_TIME) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Auto restart the branch session to retry,xid: {}, branchId: {}", xid, branchSession.getBranchId()); + } + BranchStatus newStatus = BranchStatus.Registered; + branchSession.setStatus(newStatus); + try { + globalSession.changeBranchStatus(branchSession, newStatus); + } catch (Exception e) { + LOGGER.error("Change branch session status fail, xid: {}, branchId:{}", + globalSession.getXid(), branchSession.getBranchId(), e); + } + } + }); + GlobalStatus globalStatus = globalSession.getStatus(); + GlobalStatus newStatus = GlobalStatus.StopCommitOrCommitRetry.equals(globalStatus) ? GlobalStatus.CommitRetrying : + GlobalStatus.StopRollbackOrRollbackRetry.equals(globalStatus) ? GlobalStatus.RollbackRetrying : null; + try { + if (Objects.nonNull(newStatus) && System.currentTimeMillis() - globalSession.getGmtModified() >= AUTO_RETRY_TIME) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Auto restart the global session to retry,xid: {}", globalSession.getXid()); + } + globalSession.changeGlobalStatus(newStatus); + } + } catch (Exception e) { + LOGGER.error("Auto restart global session retry fail, xid:{}", globalSession.getXid(), e); + } + }); + } + /** * Undo log delete. */ @@ -630,6 +689,8 @@ public void init() { () -> SessionHolder.distributedLockAndExecute(UNDOLOG_DELETE, this::undoLogDelete), UNDO_LOG_DELAY_DELETE_PERIOD, UNDO_LOG_DELETE_PERIOD, TimeUnit.MILLISECONDS); + autoRestartSession.scheduleAtFixedRate(this::handleAutoRestart, 0, AUTO_RETRY_PERIOD, TimeUnit.MILLISECONDS); + rollbackingSchedule(0); committingSchedule(0); @@ -662,6 +723,7 @@ public void destroy() { asyncCommitting.shutdown(); timeoutCheck.shutdown(); undoLogDelete.shutdown(); + autoRestartSession.shutdown(); if (branchRemoveExecutor != null) { branchRemoveExecutor.shutdown(); } @@ -671,6 +733,7 @@ public void destroy() { asyncCommitting.awaitTermination(TIMED_TASK_SHUTDOWN_MAX_WAIT_MILLS, TimeUnit.MILLISECONDS); timeoutCheck.awaitTermination(TIMED_TASK_SHUTDOWN_MAX_WAIT_MILLS, TimeUnit.MILLISECONDS); undoLogDelete.awaitTermination(TIMED_TASK_SHUTDOWN_MAX_WAIT_MILLS, TimeUnit.MILLISECONDS); + autoRestartSession.awaitTermination(TIMED_TASK_SHUTDOWN_MAX_WAIT_MILLS, TimeUnit.MILLISECONDS); if (branchRemoveExecutor != null) { branchRemoveExecutor.awaitTermination(TIMED_TASK_SHUTDOWN_MAX_WAIT_MILLS, TimeUnit.MILLISECONDS); } @@ -694,6 +757,14 @@ public void setRemotingServer(RemotingServer remotingServer) { this.remotingServer = remotingServer; } + /** + * get default core + * @return + */ + public DefaultCore getCore() { + return core; + } + /** * the task to remove branchSession */ 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 5acbc8988de..f2947cd3354 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 @@ -31,6 +31,8 @@ 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.protocol.ResultCode; +import org.apache.seata.core.protocol.transaction.BranchDeleteResponse; import org.apache.seata.core.rpc.RemotingServer; import org.apache.seata.server.metrics.MetricsPublisher; import org.apache.seata.server.session.BranchSession; @@ -130,6 +132,30 @@ public BranchStatus branchRollback(GlobalSession globalSession, BranchSession br return getCore(branchSession.getBranchType()).branchRollback(globalSession, branchSession); } + @Override + public BranchDeleteResponse branchDelete(GlobalSession globalSession, BranchSession branchSession) throws TransactionException { + return getCore(branchSession.getBranchType()).branchDelete(globalSession, branchSession); + } + + @Override + public Boolean doBranchDelete(GlobalSession globalSession, BranchSession branchSession) throws TransactionException { + if (globalSession.isSaga()) { + return true; + } + BranchDeleteResponse response = getCore(branchSession.getBranchType()).branchDelete(globalSession, branchSession); + if (isXaerNotaTimeout(globalSession, response.getBranchStatus())) { + LOGGER.info("Delete branch XAER_NOTA retry timeout, xid = {} branchId = {}", globalSession.getXid(), branchSession.getBranchId()); + return true; + } + // branch transaction can not roll back, stop retry and delete + if (response.getBranchStatus() == BranchStatus.PhaseTwo_RollbackFailed_Unretryable) { + LOGGER.error("Delete branch transaction fail and stop retry, xid = {} branchId = {}", globalSession.getXid(), branchSession.getBranchId()); + return true; + } + return ResultCode.Success.equals(response.getResultCode()); + } + + @Override public String begin(String applicationId, String transactionServiceGroup, String name, int timeout) throws TransactionException { @@ -220,6 +246,10 @@ public boolean doGlobalCommit(GlobalSession globalSession, boolean retrying) thr SessionHelper.removeBranch(globalSession, branchSession, !retrying); return CONTINUE; } + // skip the branch session if not retry + if (retrying && BranchStatus.STOP_RETRY.equals(currentStatus)) { + return CONTINUE; + } try { BranchStatus branchStatus = getCore(branchSession.getBranchType()).branchCommit(globalSession, branchSession); if (isXaerNotaTimeout(globalSession,branchStatus)) { @@ -322,6 +352,10 @@ public boolean doGlobalRollback(GlobalSession globalSession, boolean retrying) t SessionHelper.removeBranch(globalSession, branchSession, !retrying); return CONTINUE; } + // skip the branch session if not retry + if (retrying && BranchStatus.STOP_RETRY.equals(currentBranchStatus)) { + return CONTINUE; + } try { BranchStatus branchStatus = branchRollback(globalSession, branchSession); if (isXaerNotaTimeout(globalSession, branchStatus)) { @@ -362,7 +396,7 @@ public boolean doGlobalRollback(GlobalSession globalSession, boolean retrying) t // In db mode, lock and branch data residual problems may occur. // Therefore, execution needs to be delayed here and cannot be executed synchronously. - if (success) { + if (success && globalSession.getBranchSessions().isEmpty()) { SessionHelper.endRollbacked(globalSession, retrying); LOGGER.info("Rollback global transaction successfully, xid = {}.", globalSession.getXid()); } diff --git a/server/src/main/java/org/apache/seata/server/coordinator/TransactionCoordinatorOutbound.java b/server/src/main/java/org/apache/seata/server/coordinator/TransactionCoordinatorOutbound.java index 04ef312d9a9..b973522a02d 100644 --- a/server/src/main/java/org/apache/seata/server/coordinator/TransactionCoordinatorOutbound.java +++ b/server/src/main/java/org/apache/seata/server/coordinator/TransactionCoordinatorOutbound.java @@ -18,6 +18,7 @@ import org.apache.seata.core.exception.TransactionException; import org.apache.seata.core.model.BranchStatus; +import org.apache.seata.core.protocol.transaction.BranchDeleteResponse; import org.apache.seata.server.session.BranchSession; import org.apache.seata.server.session.GlobalSession; @@ -50,5 +51,14 @@ public interface TransactionCoordinatorOutbound { */ BranchStatus branchRollback(GlobalSession globalSession, BranchSession branchSession) throws TransactionException; - + /** + * Delete a branch transaction. + * + * @param globalSession the global session + * @param branchSession the branch session + * @return delete success or failed + * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown + * out. + */ + BranchDeleteResponse branchDelete(GlobalSession globalSession, BranchSession branchSession) throws TransactionException; } diff --git a/server/src/main/java/org/apache/seata/server/session/BranchSession.java b/server/src/main/java/org/apache/seata/server/session/BranchSession.java index 8688d65339a..9bd19d481ee 100644 --- a/server/src/main/java/org/apache/seata/server/session/BranchSession.java +++ b/server/src/main/java/org/apache/seata/server/session/BranchSession.java @@ -74,6 +74,8 @@ public class BranchSession implements Lockable, Comparable, Sessi private LockStatus lockStatus = Locked; + private long gmtModified; + private final Map> lockHolder; private final LockManager lockManager = LockerManagerFactory.getLockManager(); @@ -87,6 +89,14 @@ public BranchSession(BranchType branchType) { this.lockHolder = branchType == BranchType.AT ? new ConcurrentHashMap<>(8) : Collections.emptyMap(); } + public long getGmtModified() { + return gmtModified; + } + + public void setGmtModified(long gmtModified) { + this.gmtModified = gmtModified; + } + /** * Gets application data. * @@ -407,6 +417,8 @@ public byte[] encode() { byteBuffer.put((byte)status.getCode()); byteBuffer.put((byte)lockStatus.getCode()); + gmtModified = System.currentTimeMillis(); + byteBuffer.putLong(gmtModified); BufferUtils.flip(byteBuffer); byte[] result = new byte[byteBuffer.limit()]; byteBuffer.get(result); @@ -423,6 +435,7 @@ private int calBranchSessionSize(byte[] resourceIdBytes, byte[] lockKeyBytes, by + 4 // applicationDataBytes.length + 4 // xidBytes.size + 1 // statusCode + + 8 // gmtModified + (resourceIdBytes == null ? 0 : resourceIdBytes.length) + (lockKeyBytes == null ? 0 : lockKeyBytes.length) + (clientIdBytes == null ? 0 : clientIdBytes.length) @@ -481,6 +494,7 @@ public void decode(byte[] a) { this.branchType = BranchType.values()[branchTypeId]; } this.status = BranchStatus.get(byteBuffer.get()); + this.gmtModified = byteBuffer.getLong(); } } diff --git a/server/src/main/java/org/apache/seata/server/session/GlobalSession.java b/server/src/main/java/org/apache/seata/server/session/GlobalSession.java index 251b9876e5b..baf2c23bebb 100644 --- a/server/src/main/java/org/apache/seata/server/session/GlobalSession.java +++ b/server/src/main/java/org/apache/seata/server/session/GlobalSession.java @@ -97,6 +97,8 @@ public class GlobalSession implements SessionLifecycle, SessionStorable { private String applicationData; + private long gmtModified; + private final boolean lazyLoadBranch; private volatile boolean active = true; @@ -203,7 +205,7 @@ public boolean isTimeout() { * @return if true retry commit or roll back */ public boolean isDeadSession() { - return (System.currentTimeMillis() - beginTime) > RETRY_DEAD_THRESHOLD; + return (System.currentTimeMillis() - gmtModified) > RETRY_DEAD_THRESHOLD; } /** @@ -604,6 +606,14 @@ public void setActive(boolean active) { this.active = active; } + public long getGmtModified() { + return gmtModified; + } + + public void setGmtModified(long gmtModified) { + this.gmtModified = gmtModified; + } + @Override public byte[] encode() { byte[] byApplicationIdBytes = applicationId != null ? applicationId.getBytes() : null; @@ -661,6 +671,8 @@ public byte[] encode() { } byteBuffer.putLong(beginTime); byteBuffer.put((byte)status.getCode()); + gmtModified = System.currentTimeMillis(); + byteBuffer.putLong(gmtModified); BufferUtils.flip(byteBuffer); byte[] result = new byte[byteBuffer.limit()]; byteBuffer.get(result); @@ -678,6 +690,7 @@ private int calGlobalSessionSize(byte[] byApplicationIdBytes, byte[] byServiceGr + 4 // applicationDataBytes.length + 8 // beginTime + 1 // statusCode + + 8 // gmtModified + (byApplicationIdBytes == null ? 0 : byApplicationIdBytes.length) + (byServiceGroupBytes == null ? 0 : byServiceGroupBytes.length) + (byTxNameBytes == null ? 0 : byTxNameBytes.length) @@ -724,6 +737,7 @@ public void decode(byte[] a) { this.beginTime = byteBuffer.getLong(); this.status = GlobalStatus.get(byteBuffer.get()); + this.gmtModified = byteBuffer.getLong(); } /** @@ -787,11 +801,17 @@ public void asyncCommit() throws TransactionException { } public void queueToRetryCommit() throws TransactionException { + if (this.status == GlobalStatus.StopCommitOrCommitRetry || this.status == GlobalStatus.StopRollbackOrRollbackRetry) { + return; + } changeGlobalStatus(GlobalStatus.CommitRetrying); } public void queueToRetryRollback() throws TransactionException { GlobalStatus currentStatus = this.getStatus(); + if (currentStatus == GlobalStatus.StopCommitOrCommitRetry || currentStatus == GlobalStatus.StopRollbackOrRollbackRetry) { + return; + } GlobalStatus newStatus; if (GlobalStatus.TimeoutRollbacking == currentStatus) { newStatus = GlobalStatus.TimeoutRollbackRetrying; diff --git a/server/src/main/java/org/apache/seata/server/session/SessionHolder.java b/server/src/main/java/org/apache/seata/server/session/SessionHolder.java index e0e7a41b795..227edd53e29 100644 --- a/server/src/main/java/org/apache/seata/server/session/SessionHolder.java +++ b/server/src/main/java/org/apache/seata/server/session/SessionHolder.java @@ -210,6 +210,9 @@ public static void reload(Collection allSessions, SessionMode sto throw new RuntimeException(e); } } + case StopCommitOrCommitRetry: + case StopRollbackOrRollbackRetry: + case Deleting: break; default: { if (acquireLock) { diff --git a/server/src/main/java/org/apache/seata/server/storage/SessionConverter.java b/server/src/main/java/org/apache/seata/server/storage/SessionConverter.java index 4b90905a851..70ebf933b60 100644 --- a/server/src/main/java/org/apache/seata/server/storage/SessionConverter.java +++ b/server/src/main/java/org/apache/seata/server/storage/SessionConverter.java @@ -55,6 +55,9 @@ public static GlobalSession convertGlobalSession(GlobalTransactionDO globalTrans session.setStatus(GlobalStatus.get(globalTransactionDO.getStatus())); session.setApplicationData(globalTransactionDO.getApplicationData()); session.setBeginTime(globalTransactionDO.getBeginTime()); + if (globalTransactionDO.getGmtModified() != null) { + session.setGmtModified(globalTransactionDO.getGmtModified().getTime()); + } return session; } @@ -76,6 +79,9 @@ public static BranchSession convertBranchSession(BranchTransactionDO branchTrans branchSession.setClientId(branchTransactionDO.getClientId()); branchSession.setResourceGroupId(branchTransactionDO.getResourceGroupId()); branchSession.setStatus(BranchStatus.get(branchTransactionDO.getStatus())); + if (branchTransactionDO.getGmtModified() != null) { + branchSession.setGmtModified(branchTransactionDO.getGmtModified().getTime()); + } if (branchTransactionDO instanceof BranchTransactionDTO) { branchSession.setLockKey(((BranchTransactionDTO)branchTransactionDO).getLockKey()); } diff --git a/server/src/main/java/org/apache/seata/server/storage/db/session/DataBaseSessionManager.java b/server/src/main/java/org/apache/seata/server/storage/db/session/DataBaseSessionManager.java index 49b4f49be1f..4042a561506 100644 --- a/server/src/main/java/org/apache/seata/server/storage/db/session/DataBaseSessionManager.java +++ b/server/src/main/java/org/apache/seata/server/storage/db/session/DataBaseSessionManager.java @@ -138,7 +138,8 @@ public Collection allSessions() { return findGlobalSessions( new SessionCondition(GlobalStatus.UnKnown, GlobalStatus.Begin, GlobalStatus.Committing, GlobalStatus.CommitRetrying, GlobalStatus.Rollbacking, GlobalStatus.RollbackRetrying, - GlobalStatus.TimeoutRollbacking, GlobalStatus.TimeoutRollbackRetrying, GlobalStatus.AsyncCommitting)); + GlobalStatus.TimeoutRollbacking, GlobalStatus.TimeoutRollbackRetrying, GlobalStatus.AsyncCommitting, + GlobalStatus.StopRollbackOrRollbackRetry, GlobalStatus.StopCommitOrCommitRetry, GlobalStatus.Deleting)); } @Override diff --git a/server/src/main/java/org/apache/seata/server/storage/file/lock/FileLocker.java b/server/src/main/java/org/apache/seata/server/storage/file/lock/FileLocker.java index 898c10935a0..dd06a293a76 100644 --- a/server/src/main/java/org/apache/seata/server/storage/file/lock/FileLocker.java +++ b/server/src/main/java/org/apache/seata/server/storage/file/lock/FileLocker.java @@ -187,6 +187,14 @@ public void cleanAllLocks() { LOCK_MAP.clear(); } + public static ConcurrentMap>> getLockMap() { + return LOCK_MAP; + } + + public static int getBucketPerTable() { + return BUCKET_PER_TABLE; + } + /** * Because bucket lock map will be key of HashMap(lockHolder), however {@link ConcurrentHashMap} overwrites * {@link Object#hashCode()} and {@link Object#equals(Object)}, that leads to hash key conflict in lockHolder. @@ -197,7 +205,7 @@ public static class BucketLockMap { private final ConcurrentHashMap bucketLockMap = new ConcurrentHashMap<>(); - ConcurrentHashMap get() { + public ConcurrentHashMap get() { return bucketLockMap; } diff --git a/server/src/main/java/org/apache/seata/server/storage/file/session/FileSessionManager.java b/server/src/main/java/org/apache/seata/server/storage/file/session/FileSessionManager.java index c58f046434e..3186d18ce20 100644 --- a/server/src/main/java/org/apache/seata/server/storage/file/session/FileSessionManager.java +++ b/server/src/main/java/org/apache/seata/server/storage/file/session/FileSessionManager.java @@ -285,6 +285,7 @@ private void restore(List stores, Set removedGlob } else { if (this.checkSessionStatus(globalSession)) { foundGlobalSession.setStatus(globalSession.getStatus()); + foundGlobalSession.setGmtModified(globalSession.getGmtModified()); } else { sessionMap.remove(globalSession.getXid()); removedGlobalBuffer.add(globalSession.getXid()); diff --git a/server/src/main/java/org/apache/seata/server/storage/redis/session/RedisSessionManager.java b/server/src/main/java/org/apache/seata/server/storage/redis/session/RedisSessionManager.java index 4941d95824a..4b38752aa0b 100644 --- a/server/src/main/java/org/apache/seata/server/storage/redis/session/RedisSessionManager.java +++ b/server/src/main/java/org/apache/seata/server/storage/redis/session/RedisSessionManager.java @@ -131,7 +131,8 @@ public Collection allSessions() { return findGlobalSessions( new SessionCondition(GlobalStatus.UnKnown, GlobalStatus.Begin, GlobalStatus.Committing, GlobalStatus.CommitRetrying, GlobalStatus.Rollbacking, GlobalStatus.RollbackRetrying, - GlobalStatus.TimeoutRollbacking, GlobalStatus.TimeoutRollbackRetrying, GlobalStatus.AsyncCommitting)); + GlobalStatus.TimeoutRollbacking, GlobalStatus.TimeoutRollbackRetrying, GlobalStatus.AsyncCommitting, + GlobalStatus.StopRollbackOrRollbackRetry, GlobalStatus.StopCommitOrCommitRetry, GlobalStatus.Deleting)); } @Override diff --git a/server/src/test/java/org/apache/seata/server/coordinator/DefaultCoordinatorTest.java b/server/src/test/java/org/apache/seata/server/coordinator/DefaultCoordinatorTest.java index 02a31496a5f..7f77a65f5e0 100644 --- a/server/src/test/java/org/apache/seata/server/coordinator/DefaultCoordinatorTest.java +++ b/server/src/test/java/org/apache/seata/server/coordinator/DefaultCoordinatorTest.java @@ -36,9 +36,13 @@ 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.protocol.ResultCode; import org.apache.seata.core.protocol.RpcMessage; import org.apache.seata.core.protocol.transaction.BranchCommitRequest; import org.apache.seata.core.protocol.transaction.BranchCommitResponse; +import org.apache.seata.core.protocol.transaction.BranchDeleteRequest; +import org.apache.seata.core.protocol.transaction.BranchDeleteResponse; import org.apache.seata.core.protocol.transaction.BranchRollbackRequest; import org.apache.seata.core.protocol.transaction.BranchRollbackResponse; import org.apache.seata.core.rpc.RemotingServer; @@ -122,6 +126,7 @@ public void branchCommit() throws TransactionException { } Assertions.assertEquals(result, BranchStatus.PhaseTwo_Committed); globalSession = SessionHolder.findGlobalSession(xid); + globalSession.setStatus(GlobalStatus.Committed); Assertions.assertNotNull(globalSession); globalSession.end(); } @@ -140,6 +145,22 @@ public void branchRollback(String xid, Long branchId) { Assertions.assertEquals(result, BranchStatus.PhaseTwo_Rollbacked); } + @Test + public void branchDelete() { + try { + String xid = core.begin(applicationId, txServiceGroup, txName, timeout); + Long branchIdAt = core.branchRegister(BranchType.AT, resourceId, clientId, xid, applicationData, lockKeys_1); + Long branchIdXA = core.branchRegister(BranchType.XA, resourceId, clientId, xid, applicationData, null); + GlobalSession globalSession = SessionHolder.findGlobalSession(xid); + Assertions.assertTrue(core.doBranchDelete(globalSession, globalSession.getBranch(branchIdAt))); + Assertions.assertTrue(core.doBranchDelete(globalSession, globalSession.getBranch(branchIdXA))); + globalSession.setStatus(GlobalStatus.Deleting); + globalSession.end(); + } catch (Exception e) { + e.printStackTrace(); + Assertions.fail(e.getMessage()); + } + } @Test public void test_handleRetryRollbacking() throws TransactionException, InterruptedException { @@ -148,12 +169,13 @@ public void test_handleRetryRollbacking() throws TransactionException, Interrupt Long branchId = core.branchRegister(BranchType.AT, "abcd", clientId, xid, applicationData, lockKeys_2); Assertions.assertNotNull(branchId); - + GlobalSession globalSession = SessionHolder.findGlobalSession(xid); Thread.sleep(100); defaultCoordinator.timeoutCheck(); + TimeUnit.MILLISECONDS.sleep(globalSession.getGmtModified() - globalSession.getBeginTime()); defaultCoordinator.handleRetryRollbacking(); - GlobalSession globalSession = SessionHolder.findGlobalSession(xid); + globalSession = SessionHolder.findGlobalSession(xid); Assertions.assertNull(globalSession); } @@ -172,6 +194,7 @@ public void test_handleRetryRollbackingTimeOut() throws TransactionException, In ReflectionUtil.modifyStaticFinalField(defaultCoordinator.getClass(), "MAX_ROLLBACK_RETRY_TIMEOUT", 10L); ReflectionUtil.modifyStaticFinalField(defaultCoordinator.getClass(), "ROLLBACK_RETRY_TIMEOUT_UNLOCK_ENABLE", false); TimeUnit.MILLISECONDS.sleep(100); + TimeUnit.MILLISECONDS.sleep(globalSession.getGmtModified() - globalSession.getBeginTime()); globalSession.queueToRetryRollback(); defaultCoordinator.handleRetryRollbacking(); int lockSize = globalSession.getBranchSessions().get(0).getLockHolder().size(); @@ -201,6 +224,7 @@ public void test_handleRetryRollbackingTimeOut_unlock() throws TransactionExcept TimeUnit.MILLISECONDS.sleep(100); globalSession.queueToRetryRollback(); + TimeUnit.MILLISECONDS.sleep(globalSession.getGmtModified() - globalSession.getBeginTime()); defaultCoordinator.handleRetryRollbacking(); int lockSize = globalSession.getBranchSessions().get(0).getLockHolder().size(); @@ -213,6 +237,21 @@ public void test_handleRetryRollbackingTimeOut_unlock() throws TransactionExcept } } + @Test + public void test_handleRetryRollbackingButSkip() throws TransactionException, InterruptedException, NoSuchFieldException, IllegalAccessException { + String xid = core.begin(applicationId, txServiceGroup, txName, 10); + Long branchId = core.branchRegister(BranchType.TCC, "abcd", clientId, xid, applicationData, ""); + + Assertions.assertNotNull(branchId); + GlobalSession globalSession = SessionHolder.findGlobalSession(xid); + globalSession.getBranch(branchId).setStatus(BranchStatus.STOP_RETRY); + defaultCoordinator.handleRetryRollbacking(); + + globalSession = SessionHolder.findGlobalSession(xid); + Assertions.assertNotNull(globalSession); + globalSession.end(); + } + @AfterAll public static void afterClass() throws Exception { @@ -259,6 +298,11 @@ public Object sendSyncRequest(String resourceId, String clientId, Object message final BranchRollbackResponse branchRollbackResponse = new BranchRollbackResponse(); branchRollbackResponse.setBranchStatus(BranchStatus.PhaseTwo_Rollbacked); return branchRollbackResponse; + } else if (message instanceof BranchDeleteRequest) { + final BranchDeleteResponse branchDeleteResponse = new BranchDeleteResponse(); + branchDeleteResponse.setResultCode(ResultCode.Success); + branchDeleteResponse.setBranchStatus(BranchStatus.PhaseTwo_RollbackFailed_Unretryable); + return branchDeleteResponse; } else { return null; } diff --git a/server/src/test/java/org/apache/seata/server/lock/LockManagerTest.java b/server/src/test/java/org/apache/seata/server/lock/LockManagerTest.java index c96c002ee29..6dc3f040169 100644 --- a/server/src/test/java/org/apache/seata/server/lock/LockManagerTest.java +++ b/server/src/test/java/org/apache/seata/server/lock/LockManagerTest.java @@ -435,6 +435,27 @@ static Stream globalSessionForLockTestProvider() throws ParseExceptio return Stream.of(Arguments.of(globalSession1, globalSession2)); } + static Stream globalSessionProvider() throws ParseException { + final BranchSession[] branchSessions2 = baseBranchSession("employee", "de:1,2;df:3,4;dg:5,6", "eg:7,8;ef:9,10"); + final BranchSession branchSession3 = branchSessions2[0]; + branchSession3.setTransactionId(123456L); + + final BranchSession branchSession4 = branchSessions2[1]; + branchSession4.setTransactionId(123456L); + + + final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + GlobalSession globalSession = new GlobalSession("demo-app", DEFAULT_TX_GROUP, "test2", 6000); + globalSession.setXid("xid2:123456"); + globalSession.add(branchSession3); + globalSession.add(branchSession4); + globalSession.setBeginTime(dateFormat.parse("2022-1-1 08:00:00").getTime()); + + return Stream.of(Arguments.of(globalSession)); + } + + /** * Base branch sessions provider object [ ] [ ]. Could assign resource and lock keys. * diff --git a/server/src/test/java/org/apache/seata/server/raft/execute/BranchSessionExecuteTest.java b/server/src/test/java/org/apache/seata/server/raft/execute/BranchSessionExecuteTest.java index df21b9216ff..3c4b9c84b8e 100644 --- a/server/src/test/java/org/apache/seata/server/raft/execute/BranchSessionExecuteTest.java +++ b/server/src/test/java/org/apache/seata/server/raft/execute/BranchSessionExecuteTest.java @@ -132,6 +132,7 @@ private static GlobalSession mockGlobalSession() { session.setApplicationData("hello, world"); session.setTransactionId(txId); session.setBeginTime(System.currentTimeMillis()); + session.setGmtModified(System.currentTimeMillis()); return session; } @@ -146,6 +147,7 @@ private static BranchSession mockBranchSession(String xid,long transactionId) { session.setLockKey("test"); session.setBranchType(BranchType.AT); session.setApplicationData("hello, world"); + session.setGmtModified(System.currentTimeMillis()); return session; } diff --git a/server/src/test/java/org/apache/seata/server/raft/execute/GlobalSessionExecuteTest.java b/server/src/test/java/org/apache/seata/server/raft/execute/GlobalSessionExecuteTest.java index 45d191790ee..fc4a3940da4 100644 --- a/server/src/test/java/org/apache/seata/server/raft/execute/GlobalSessionExecuteTest.java +++ b/server/src/test/java/org/apache/seata/server/raft/execute/GlobalSessionExecuteTest.java @@ -128,6 +128,7 @@ private static GlobalSession mockGlobalSession() { session.setApplicationData("hello, world"); session.setTransactionId(123); session.setBeginTime(System.currentTimeMillis()); + session.setGmtModified(System.currentTimeMillis()); return session; } diff --git a/server/src/test/java/org/apache/seata/server/session/FileSessionManagerTest.java b/server/src/test/java/org/apache/seata/server/session/FileSessionManagerTest.java index 478529866af..dc0d742a3e3 100644 --- a/server/src/test/java/org/apache/seata/server/session/FileSessionManagerTest.java +++ b/server/src/test/java/org/apache/seata/server/session/FileSessionManagerTest.java @@ -27,18 +27,23 @@ import javax.annotation.Resource; +import org.apache.seata.common.ConfigurationKeys; import org.apache.seata.common.XID; import org.apache.seata.common.loader.EnhancedServiceLoader; import org.apache.seata.common.result.PageResult; import org.apache.seata.common.store.SessionMode; +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.common.util.UUIDGenerator; 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.model.LockStatus; import org.apache.seata.server.console.param.GlobalSessionParam; +import org.apache.seata.server.console.service.BranchSessionService; import org.apache.seata.server.console.service.GlobalSessionService; import org.apache.seata.server.console.vo.GlobalSessionVO; import org.apache.seata.server.storage.file.session.FileSessionManager; +import org.apache.seata.server.store.StoreConfig; import org.apache.seata.server.util.StoreUtil; import org.apache.commons.lang.time.DateUtils; import org.junit.jupiter.api.Assertions; @@ -49,7 +54,9 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationContext; +import static org.apache.seata.common.DefaultValues.DEFAULT_SESSION_STORE_FILE_DIR; import static org.apache.seata.common.DefaultValues.DEFAULT_TX_GROUP; +import static org.apache.seata.server.session.SessionHolder.CONFIG; /** * The type File based session manager test. @@ -65,6 +72,12 @@ public class FileSessionManagerTest { @Resource(type = GlobalSessionService.class) private GlobalSessionService globalSessionService; + @Resource(type = BranchSessionService.class) + private BranchSessionService branchSessionService; + + private static String sessionStorePath = CONFIG.getConfig(ConfigurationKeys.STORE_FILE_DIR, + DEFAULT_SESSION_STORE_FILE_DIR); + @BeforeAll public static void setUp(ApplicationContext context) { StoreUtil.deleteDataFile(); @@ -283,6 +296,16 @@ public void findGlobalSessionsWithPageResultTest(List globalSessi SessionHolder.init(SessionMode.FILE); try { + SessionHolder.init(SessionMode.FILE); + final SessionManager sessionManager = SessionHolder.getRootSessionManager(); + // make sure sessionMaanager is empty + Collection sessions = sessionManager.allSessions(); + if (CollectionUtils.isNotEmpty(sessions)) { + // FileSessionManager use ConcurrentHashMap is thread safe + for (GlobalSession session : sessions) { + sessionManager.removeGlobalSession(session); + } + } for (GlobalSession globalSession : globalSessions) { globalSession.begin(); } @@ -406,6 +429,96 @@ public void onStatusChangeTest(GlobalSession globalSession) throws Exception { } } + @ParameterizedTest + @MethodSource("globalSessionForLockTestProvider") + public void stopGlobalSessionTest(List globalSessions) throws Exception { + try { + SessionHolder.init(SessionMode.FILE); + for (GlobalSession globalSession : globalSessions) { + globalSession.begin(); + } + Assertions.assertThrows(IllegalArgumentException.class, () -> + globalSessionService.stopGlobalRetry(globalSessions.get(0).getXid())); + + GlobalSession globalSession = globalSessions.get(1); + globalSession.changeGlobalStatus(GlobalStatus.CommitRetrying); + String xid = globalSession.getXid(); + globalSessionService.stopGlobalRetry(xid); + Assertions.assertEquals(SessionHolder.findGlobalSession(xid).getStatus(), + GlobalStatus.StopCommitOrCommitRetry); + + globalSession.changeGlobalStatus(GlobalStatus.RollbackRetrying); + globalSessionService.stopGlobalRetry(xid); + Assertions.assertEquals(SessionHolder.findGlobalSession(xid).getStatus(), + GlobalStatus.StopRollbackOrRollbackRetry); + } finally { + for (GlobalSession globalSession : globalSessions) { + globalSession.setStatus(GlobalStatus.Committed); + globalSession.end(); + } + } + } + + @ParameterizedTest + @MethodSource("globalSessionForLockTestProvider") + public void changeGlobalSessionTest(List globalSessions) throws Exception { + try { + SessionHolder.init(SessionMode.FILE); + for (GlobalSession globalSession : globalSessions) { + globalSession.begin(); + } + Assertions.assertThrows(IllegalArgumentException.class, () -> + globalSessionService.changeGlobalStatus(globalSessions.get(0).getXid())); + + GlobalSession globalSession = globalSessions.get(1); + globalSession.changeGlobalStatus(GlobalStatus.CommitFailed); + String xid = globalSession.getXid(); + globalSessionService.changeGlobalStatus(xid); + Assertions.assertEquals(SessionHolder.findGlobalSession(xid).getStatus(), + GlobalStatus.CommitRetrying); + + globalSession.changeGlobalStatus(GlobalStatus.RollbackFailed); + globalSessionService.changeGlobalStatus(xid); + Assertions.assertEquals(SessionHolder.findGlobalSession(xid).getStatus(), + GlobalStatus.RollbackRetrying); + } finally { + for (GlobalSession globalSession : globalSessions) { + globalSession.setStatus(GlobalStatus.Committed); + globalSession.end(); + } + } + } + + @ParameterizedTest + @MethodSource("globalSessionForLockTestProvider") + public void startGlobalSessionTest(List globalSessions) throws Exception { + try { + SessionHolder.init(SessionMode.FILE); + for (GlobalSession globalSession : globalSessions) { + globalSession.begin(); + } + Assertions.assertThrows(IllegalArgumentException.class, () -> + globalSessionService.startGlobalRetry(globalSessions.get(0).getXid())); + + GlobalSession globalSession = globalSessions.get(1); + globalSession.setStatus(GlobalStatus.StopCommitOrCommitRetry); + String xid = globalSession.getXid(); + globalSessionService.startGlobalRetry(xid); + Assertions.assertEquals(SessionHolder.findGlobalSession(xid).getStatus(), + GlobalStatus.CommitRetrying); + + globalSession.setStatus(GlobalStatus.StopRollbackOrRollbackRetry); + globalSessionService.startGlobalRetry(xid); + Assertions.assertEquals(SessionHolder.findGlobalSession(xid).getStatus(), + GlobalStatus.RollbackRetrying); + } finally { + for (GlobalSession globalSession : globalSessions) { + globalSession.setStatus(GlobalStatus.Committed); + globalSession.end(); + } + } + } + /** * On branch status change test. * @@ -475,6 +588,63 @@ public void onCloseTest(GlobalSession globalSession) throws Exception { } } + @ParameterizedTest + @MethodSource("branchSessionsProvider") + public void stopBranchRetryTest(GlobalSession globalSession) throws Exception { + try { + SessionHolder.init(SessionMode.FILE); + globalSession.begin(); + // wrong param for xid and branchId + Assertions.assertThrows(IllegalArgumentException.class, + () -> branchSessionService.stopBranchRetry("xid", null)); + Assertions.assertThrows(IllegalArgumentException.class, + () -> branchSessionService.stopBranchRetry(globalSession.getXid(), "test")); + + // wrong status for branch transaction + List branchSessions = globalSession.getBranchSessions(); + Assertions.assertThrows(IllegalArgumentException.class, + () -> branchSessionService.stopBranchRetry(globalSession.getXid(), + String.valueOf(branchSessions.get(0).getBranchId()))); + + // wrong status for global transaction + globalSession.setStatus(GlobalStatus.Begin); + Assertions.assertThrows(IllegalArgumentException.class, + () -> branchSessionService.stopBranchRetry(globalSession.getXid(), + String.valueOf(branchSessions.get(1).getBranchId()))); + + // success stop + globalSession.setStatus(GlobalStatus.CommitRetrying); + branchSessionService.stopBranchRetry(globalSession.getXid(), String.valueOf(branchSessions.get(1).getBranchId())); + GlobalSession newGlobalSession = SessionHolder.findGlobalSession(globalSession.getXid()); + Assertions.assertEquals(BranchStatus.STOP_RETRY, newGlobalSession.getBranchSessions().get(1).getStatus()); + } finally { + globalSession.setStatus(GlobalStatus.Committed); + globalSession.end(); + } + } + + @ParameterizedTest + @MethodSource("branchSessionsProvider") + public void restartBranchFailRetryTest(GlobalSession globalSession) throws Exception { + try { + SessionHolder.init(SessionMode.FILE); + globalSession.begin(); + List branchSessions = globalSession.getBranchSessions(); + // wrong status for branch transaction + Assertions.assertThrows(IllegalArgumentException.class, + () -> branchSessionService.startBranchRetry(globalSession.getXid(), + String.valueOf(branchSessions.get(0).getBranchId()))); + // success + branchSessionService.startBranchRetry(globalSession.getXid(), + String.valueOf(branchSessions.get(2).getBranchId())); + GlobalSession newGlobalSession = SessionHolder.findGlobalSession(globalSession.getXid()); + Assertions.assertEquals(BranchStatus.Registered, newGlobalSession.getBranchSessions().get(2).getStatus()); + } finally { + globalSession.setStatus(GlobalStatus.Committed); + globalSession.end(); + } + } + /** * On end test. * @@ -558,6 +728,46 @@ static Stream globalSessionsWithPageResultProvider() throws ParseExce ); } + static Stream globalSessionForLockTestProvider() throws ParseException { + BranchSession branchSession1 = new BranchSession(); + branchSession1.setTransactionId(UUIDGenerator.generateUUID()); + branchSession1.setBranchId(1L); + branchSession1.setClientId("c1"); + branchSession1.setResourceGroupId(DEFAULT_TX_GROUP); + branchSession1.setResourceId("department"); + branchSession1.setLockKey("a:1,2"); + branchSession1.setBranchType(BranchType.AT); + branchSession1.setApplicationData("{\"data\":\"test\"}"); + branchSession1.setBranchType(BranchType.AT); + + BranchSession branchSession2 = new BranchSession(); + branchSession2.setTransactionId(UUIDGenerator.generateUUID()); + branchSession2.setBranchId(2L); + branchSession2.setClientId("c1"); + branchSession2.setResourceGroupId(DEFAULT_TX_GROUP); + branchSession2.setResourceId("department"); + branchSession2.setLockKey("e:3,4"); + branchSession2.setBranchType(BranchType.AT); + branchSession2.setApplicationData("{\"data\":\"test\"}"); + branchSession2.setBranchType(BranchType.AT); + + branchSession1.setTransactionId(397215L); + branchSession2.setTransactionId(92482L); + + final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + GlobalSession globalSession1 = new GlobalSession("demo-app", DEFAULT_TX_GROUP, "test1", 6000); + globalSession1.setXid("xid1"); + globalSession1.add(branchSession1); + globalSession1.setBeginTime(dateFormat.parse("2022-1-1 03:00:00").getTime()); + + GlobalSession globalSession2 = new GlobalSession("demo-app", DEFAULT_TX_GROUP, "test2", 6000); + globalSession2.setXid("ddd1"); + globalSession2.add(branchSession2); + globalSession2.setBeginTime(dateFormat.parse("2022-1-1 08:00:00").getTime()); + + return Stream.of(Arguments.of(Arrays.asList(globalSession1, globalSession2))); + } + /** * Branch session provider object [ ] [ ]. * @@ -579,4 +789,36 @@ static Stream branchSessionProvider() { ); } + /** + * Branch sessions provider object [ ] [ ]. + * + * @return the object [ ] [ ] + */ + static Stream branchSessionsProvider() { + GlobalSession globalSession = new GlobalSession("demo-app", DEFAULT_TX_GROUP, "test", 6000); + globalSession.setXid(XID.generateXID(globalSession.getTransactionId())); + globalSession.setStatus(GlobalStatus.CommitRetrying); + BranchSession branchSession = new BranchSession(); + branchSession.setBranchId(1L); + branchSession.setXid(globalSession.getXid()); + branchSession.setResourceGroupId(DEFAULT_TX_GROUP); + branchSession.setStatus(BranchStatus.PhaseOne_Failed); + branchSession.setBranchType(BranchType.AT); + BranchSession branchSession1 = new BranchSession(); + branchSession1.setBranchId(2L); + branchSession1.setXid(globalSession.getXid()); + branchSession1.setResourceGroupId(DEFAULT_TX_GROUP); + branchSession1.setStatus(BranchStatus.Registered); + branchSession1.setBranchType(BranchType.AT); + BranchSession branchSession2 = new BranchSession(); + branchSession2.setBranchId(3L); + branchSession2.setXid(globalSession.getXid()); + branchSession2.setResourceGroupId(DEFAULT_TX_GROUP); + branchSession2.setStatus(BranchStatus.STOP_RETRY); + branchSession2.setBranchType(BranchType.AT); + globalSession.add(branchSession); + globalSession.add(branchSession1); + globalSession.add(branchSession2); + return Stream.of(Arguments.of(globalSession)); + } } diff --git a/server/src/test/java/org/apache/seata/server/store/file/FileTransactionStoreManagerTest.java b/server/src/test/java/org/apache/seata/server/store/file/FileTransactionStoreManagerTest.java index dca761e1238..b1478299f17 100644 --- a/server/src/test/java/org/apache/seata/server/store/file/FileTransactionStoreManagerTest.java +++ b/server/src/test/java/org/apache/seata/server/store/file/FileTransactionStoreManagerTest.java @@ -67,15 +67,18 @@ public void testBigDataWrite() throws Exception { fileTransactionStoreManager = new FileTransactionStoreManager(seataFile.getAbsolutePath(), null); BranchSession branchSessionA = Mockito.mock(BranchSession.class); GlobalSession global = new GlobalSession(); + global.setGmtModified(System.currentTimeMillis()); Mockito.when(branchSessionA.encode()) .thenReturn(createBigBranchSessionData(global, (byte) 'A')); Mockito.when(branchSessionA.getApplicationData()) .thenReturn(new String(createBigApplicationData((byte) 'A'))); + Mockito.when(branchSessionA.getGmtModified()).thenReturn(global.getGmtModified()); BranchSession branchSessionB = Mockito.mock(BranchSession.class); Mockito.when(branchSessionB.encode()) .thenReturn(createBigBranchSessionData(global, (byte) 'B')); Mockito.when(branchSessionB.getApplicationData()) .thenReturn(new String(createBigApplicationData((byte) 'B'))); + Mockito.when(branchSessionB.getGmtModified()).thenReturn(global.getGmtModified()); Assertions.assertTrue(fileTransactionStoreManager.writeSession(TransactionStoreManager.LogOperation.BRANCH_ADD, branchSessionA)); Assertions.assertTrue(fileTransactionStoreManager.writeSession(TransactionStoreManager.LogOperation.BRANCH_ADD, branchSessionB)); List list = fileTransactionStoreManager.readWriteStore(2000, false); @@ -83,8 +86,10 @@ public void testBigDataWrite() throws Exception { Assertions.assertEquals(2, list.size()); BranchSession loadedBranchSessionA = (BranchSession) list.get(0).getSessionRequest(); Assertions.assertEquals(branchSessionA.getApplicationData(), loadedBranchSessionA.getApplicationData()); + Assertions.assertEquals(branchSessionA.getGmtModified(), loadedBranchSessionA.getGmtModified()); BranchSession loadedBranchSessionB = (BranchSession) list.get(1).getSessionRequest(); Assertions.assertEquals(branchSessionB.getApplicationData(), loadedBranchSessionB.getApplicationData()); + Assertions.assertEquals(branchSessionB.getGmtModified(), loadedBranchSessionB.getGmtModified()); } finally { if (fileTransactionStoreManager != null) { fileTransactionStoreManager.shutdown(); @@ -104,6 +109,7 @@ public void testFindTimeoutAndSave() throws Exception { List timeoutSessions = new ArrayList<>(); for (int i = 0; i < 100; i++) { GlobalSession globalSession = new GlobalSession("", "", "", 60000); + globalSession.setGmtModified(System.currentTimeMillis()); BranchSession branchSessionA = Mockito.mock(BranchSession.class); Mockito.when(branchSessionA.encode()) .thenReturn(createBigBranchSessionData(globalSession, (byte) 'A')); @@ -159,6 +165,7 @@ private byte[] createBigBranchSessionData(GlobalSession global, byte c) { + 4 // xidBytes.size + 1 // statusCode + 1 // lockstatus + + 8 // gmtModified + 1; //branchType String xid = global.getXid(); byte[] xidBytes = null; @@ -183,6 +190,7 @@ private byte[] createBigBranchSessionData(GlobalSession global, byte c) { } byteBuffer.put((byte) 0); byteBuffer.put((byte) 0); + byteBuffer.putLong(global.getGmtModified()); byteBuffer.put((byte) 0); BufferUtils.flip(byteBuffer); byte[] bytes = new byte[byteBuffer.limit()]; 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 88003f6f4fb..3dba7efd078 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 @@ -321,6 +321,11 @@ public static boolean deleteFence(String xid, Long branchId) { }); } + @Override + public boolean deleteFenceByXidAndBranchId(String xid, Long branchId) { + return deleteFence(xid, branchId); + } + /** * Delete Common Fence By Datetime * diff --git a/tcc/src/main/java/org/apache/seata/rm/tcc/RMHandlerTCC.java b/tcc/src/main/java/org/apache/seata/rm/tcc/RMHandlerTCC.java index c7040fa21d0..02ea7254c05 100644 --- a/tcc/src/main/java/org/apache/seata/rm/tcc/RMHandlerTCC.java +++ b/tcc/src/main/java/org/apache/seata/rm/tcc/RMHandlerTCC.java @@ -16,23 +16,54 @@ */ package org.apache.seata.rm.tcc; +import org.apache.seata.core.model.BranchStatus; import org.apache.seata.core.model.BranchType; import org.apache.seata.core.model.ResourceManager; +import org.apache.seata.core.protocol.ResultCode; +import org.apache.seata.core.protocol.transaction.BranchDeleteRequest; +import org.apache.seata.core.protocol.transaction.BranchDeleteResponse; import org.apache.seata.core.protocol.transaction.UndoLogDeleteRequest; +import org.apache.seata.integration.tx.api.fence.DefaultCommonFenceHandler; import org.apache.seata.rm.AbstractRMHandler; import org.apache.seata.rm.DefaultResourceManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The type Rm handler tcc. * */ public class RMHandlerTCC extends AbstractRMHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(RMHandlerTCC.class); @Override public void handle(UndoLogDeleteRequest request) { //DO nothing } + @Override + public BranchDeleteResponse handle(BranchDeleteRequest request) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Start tcc delete branch fence, xid:{}, branchId:{}", + request.getXid(), request.getBranchId()); + } + BranchDeleteResponse branchDeleteResponse = new BranchDeleteResponse(); + try { + boolean result = DefaultCommonFenceHandler.get(). + deleteFenceByXidAndBranchId(request.getXid(), request.getBranchId()); + ResultCode code = result ? ResultCode.Success : ResultCode.Failed; + branchDeleteResponse.setResultCode(code); + } catch (Exception e) { + LOGGER.error("Delete tcc fence fail, xid:{}, branchId:{}", request.getXid(), request.getBranchId(), e); + branchDeleteResponse.setResultCode(ResultCode.Failed); + } + branchDeleteResponse.setXid(request.getXid()); + branchDeleteResponse.setBranchId(request.getBranchId()); + // this branch status is no importance + branchDeleteResponse.setBranchStatus(BranchStatus.Unknown); + return branchDeleteResponse; + } + @Override protected ResourceManager getResourceManager() { return DefaultResourceManager.get().getResourceManager(BranchType.TCC);