|
- /*
- * 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 xa
-
- import (
- "context"
- "fmt"
- "time"
-
- "github.com/seata/seata-go/pkg/datasource/sql"
- "github.com/seata/seata-go/pkg/datasource/sql/datasource"
- "github.com/seata/seata-go/pkg/datasource/sql/exec"
- "github.com/seata/seata-go/pkg/protocol/branch"
- "github.com/seata/seata-go/pkg/protocol/message"
- "github.com/seata/seata-go/pkg/rm"
- )
-
- type ConnectionProxyXA struct {
- xaBranchXid *XABranchXid
- currentAutoCommitStatus bool `default:"true"`
- xaActive bool `default:"false"`
- kept bool `default:"false"`
- rollBacked bool `default:"false"`
- branchRegisterTime int64 `default:"0"`
- prepareTime int64 `default:"0"`
- timeout int `default:"0"`
- proxyShouldBeHeld bool `default:"false"`
- originalConnection sql.Conn
- xaConnection XAConnection
- xaResource exec.XAResource
- resource sql.BaseDataSourceResource
- xid string
- }
-
- const timeout int = 60000
-
- func NewConnectionProxyXA(originalConnection sql.Conn, xaConnection XAConnection, resource sql.BaseDataSourceResource, xid string) (*ConnectionProxyXA, error) {
- connectionProxyXA := &ConnectionProxyXA{}
-
- connectionProxyXA.originalConnection = originalConnection
- connectionProxyXA.xaConnection = xaConnection
- connectionProxyXA.resource = resource
- connectionProxyXA.xid = xid
-
- connectionProxyXA.proxyShouldBeHeld = connectionProxyXA.resource.IsShouldBeHeld()
-
- xaResource, err := xaConnection.getXAResource()
- if err != nil {
- return nil, fmt.Errorf("get xa resource failed")
- } else {
- connectionProxyXA.xaResource = xaResource
- }
- var rootContext sql.RootContext
- transactionTimeout, ok := rootContext.GetTimeout()
- if !ok {
- transactionTimeout = timeout
- }
- if transactionTimeout < timeout {
- transactionTimeout = timeout
- }
- connectionProxyXA.timeout = transactionTimeout
- connectionProxyXA.currentAutoCommitStatus = connectionProxyXA.originalConnection.GetAutoCommit()
- if !connectionProxyXA.currentAutoCommitStatus {
- return nil, fmt.Errorf("connection[autocommit=false] as default is NOT supported")
- }
-
- return connectionProxyXA, nil
- }
-
- func (c *ConnectionProxyXA) keepIfNecessary() {
- if c.ShouldBeHeld() {
- c.resource.Hold(c.xaBranchXid.String(), c)
- }
- }
-
- func (c *ConnectionProxyXA) releaseIfNecessary() {
- if c.ShouldBeHeld() {
- if c.xaBranchXid == nil {
- if c.IsHeld() {
- c.resource.Release(c.xaBranchXid.String(), c)
- }
- }
- }
- }
-
- func (c *ConnectionProxyXA) XaCommit(xid string, branchId int64) error {
- xaXid := Build(xid, branchId)
- err := c.xaResource.Commit(xaXid.String(), false)
- c.releaseIfNecessary()
- return err
- }
-
- func (c *ConnectionProxyXA) XaRollbackByBranchId(xid string, branchId int64) {
- xaXid := Build(xid, branchId)
- c.XaRollback(xaXid)
- }
-
- func (c *ConnectionProxyXA) XaRollback(xaXid XAXid) error {
- err := c.xaResource.Rollback(xaXid.GetGlobalXid())
- c.releaseIfNecessary()
- return err
- }
-
- func (c *ConnectionProxyXA) SetAutoCommit(autoCommit bool) error {
- if c.currentAutoCommitStatus == autoCommit {
- return nil
- }
- if autoCommit {
- if c.xaActive {
- _ = c.Commit()
- }
- } else {
- if c.xaActive {
- return fmt.Errorf("should NEVER happen: setAutoCommit from true to false while xa branch is active")
- }
-
- c.branchRegisterTime = time.Now().UnixMilli()
- var branchRegisterParam rm.BranchRegisterParam
- branchRegisterParam.BranchType = branch.BranchTypeXA
- branchRegisterParam.ResourceId = c.resource.GetResourceId()
- branchRegisterParam.Xid = c.xid
- branchId, err := datasource.GetDataSourceManager(branch.BranchTypeXA).BranchRegister(context.TODO(), branchRegisterParam)
- if err != nil {
- c.cleanXABranchContext()
- return fmt.Errorf("failed to register xa branch [%v]", c.xid)
- }
- c.xaBranchXid = Build(c.xid, branchId)
- c.keepIfNecessary()
- err = c.start()
- if err != nil {
- c.cleanXABranchContext()
- return fmt.Errorf("failed to start xa branch [%v]", c.xid)
- }
- c.xaActive = true
- }
- c.currentAutoCommitStatus = autoCommit
- return nil
- }
-
- func (c *ConnectionProxyXA) GetAutoCommit() bool {
- return c.currentAutoCommitStatus
- }
-
- func (c *ConnectionProxyXA) Commit() error {
- if c.currentAutoCommitStatus {
- return nil
- }
- if !c.xaActive || c.xaBranchXid == nil {
- return fmt.Errorf("should NOT commit on an inactive session")
- }
- now := time.Now().UnixMilli()
- if c.end(exec.TMSUCCESS) != nil {
- return c.commitErrorHandle()
- }
- if c.checkTimeout(now) != nil {
- return c.commitErrorHandle()
- }
- if c.xaResource.XAPrepare(c.xaBranchXid.String()) != nil {
- return c.commitErrorHandle()
- }
- return nil
- }
-
- func (c *ConnectionProxyXA) commitErrorHandle() error {
- req := message.BranchReportRequest{
- BranchType: branch.BranchTypeXA,
- Xid: c.xid,
- BranchId: c.xaBranchXid.GetBranchId(),
- Status: branch.BranchStatusPhaseoneFailed,
- ApplicationData: nil,
- ResourceId: c.resource.GetResourceId(),
- }
- if datasource.NewBasicSourceManager().BranchReport(context.TODO(), req) != nil {
- c.cleanXABranchContext()
- return fmt.Errorf("Failed to report XA branch commit-failure on [%v] - [%v]", c.xid, c.xaBranchXid.GetBranchId())
- }
- c.cleanXABranchContext()
- return fmt.Errorf("Failed to end(TMSUCCESS)/prepare xa branch on [%v] - [%v]", c.xid, c.xaBranchXid.GetBranchId())
- }
-
- func (c *ConnectionProxyXA) Rollback() error {
- if c.currentAutoCommitStatus {
- return nil
- }
- if !c.xaActive || c.xaBranchXid == nil {
- return fmt.Errorf("should NOT rollback on an inactive session")
- }
- if !c.rollBacked {
- if c.xaResource.End(c.xaBranchXid.String(), exec.TMFAIL) != nil {
- return c.rollbackErrorHandle()
- }
- if c.XaRollback(c.xaBranchXid) != nil {
- c.cleanXABranchContext()
- return c.rollbackErrorHandle()
- }
- req := message.BranchReportRequest{
- BranchType: branch.BranchTypeXA,
- Xid: c.xid,
- BranchId: c.xaBranchXid.GetBranchId(),
- Status: branch.BranchStatusPhaseoneFailed,
- ApplicationData: nil,
- ResourceId: c.resource.GetResourceId(),
- }
- if datasource.NewBasicSourceManager().BranchReport(context.TODO(), req) != nil {
- c.cleanXABranchContext()
- return fmt.Errorf("failed to report XA branch commit-failure on [%v] - [%v]", c.xid, c.xaBranchXid.GetBranchId())
- }
- }
- c.cleanXABranchContext()
- return nil
- }
-
- func (c *ConnectionProxyXA) rollbackErrorHandle() error {
- return fmt.Errorf("failed to end(TMFAIL) xa branch on [%v] - [%v]", c.xid, c.xaBranchXid.GetBranchId())
- }
-
- func (c *ConnectionProxyXA) start() error {
- err := c.xaResource.Start(c.xaBranchXid.String(), exec.TMNOFLAGS)
- if err := c.termination(c.xaBranchXid.String()); err != nil {
- c.xaResource.End(c.xaBranchXid.String(), exec.TMFAIL)
- c.XaRollback(c.xaBranchXid)
- return err
- }
- return err
- }
-
- func (c *ConnectionProxyXA) end(flags int) error {
- err := c.termination(c.xaBranchXid.String())
- if err != nil {
- return err
- }
- err = c.xaResource.End(c.xaBranchXid.String(), flags)
- if err != nil {
- return err
- }
- return nil
- }
-
- func (c *ConnectionProxyXA) cleanXABranchContext() {
- c.branchRegisterTime = 0
- c.prepareTime = 0
- c.timeout = 0
- c.xaActive = false
- if !c.IsHeld() {
- c.xaBranchXid = nil
- }
- }
-
- func (c *ConnectionProxyXA) checkTimeout(now int64) error {
- if now-c.branchRegisterTime > int64(c.timeout) {
- c.XaRollback(c.xaBranchXid)
- return fmt.Errorf("XA branch timeout error")
- }
- return nil
- }
-
- func (c *ConnectionProxyXA) Close() error {
- c.rollBacked = false
- if c.IsHeld() && c.ShouldBeHeld() {
- return nil
- }
- c.cleanXABranchContext()
- if err := c.originalConnection.Close(); err != nil {
- return err
- }
- return nil
- }
-
- func (c *ConnectionProxyXA) CloseForce() error {
- physicalConn := c.originalConnection
- if err := physicalConn.Close(); err != nil {
- return err
- }
- c.rollBacked = false
- c.cleanXABranchContext()
- if err := c.originalConnection.Close(); err != nil {
- return err
- }
- c.releaseIfNecessary()
- return nil
- }
-
- func (c *ConnectionProxyXA) SetHeld(kept bool) {
- c.kept = kept
- }
-
- func (c *ConnectionProxyXA) IsHeld() bool {
- return c.kept
- }
-
- func (c *ConnectionProxyXA) ShouldBeHeld() bool {
- return c.proxyShouldBeHeld || c.resource.GetDB() != nil
- }
-
- func (c *ConnectionProxyXA) GetPrepareTime() int64 {
- return c.prepareTime
- }
-
- func (c *ConnectionProxyXA) setPrepareTime(prepareTime int64) {
- c.prepareTime = prepareTime
- }
-
- func (c *ConnectionProxyXA) termination(xaBranchXid string) error {
- branchStatus, err := c.resource.GetBranchStatus(xaBranchXid)
- if err != nil {
- c.releaseIfNecessary()
- return fmt.Errorf("failed xa branch [%v] the global transaction has finish, branch status: [%v]", c.xid, branchStatus)
- }
- return nil
- }
|