|
|
@@ -0,0 +1,325 @@ |
|
|
|
/* |
|
|
|
* 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 |
|
|
|
} |