mirror of https://github.com/dtm-labs/dtm.git
7 changed files with 221 additions and 200 deletions
@ -0,0 +1,126 @@ |
|||
package dtmsvr |
|||
|
|||
import ( |
|||
"errors" |
|||
"fmt" |
|||
"time" |
|||
|
|||
"github.com/gin-gonic/gin" |
|||
"github.com/yedf/dtm/common" |
|||
"github.com/yedf/dtm/dtmcli/dtmimp" |
|||
"github.com/yedf/dtm/dtmgrpc/dtmgimp" |
|||
"gorm.io/gorm" |
|||
) |
|||
|
|||
var errUniqueConflict = errors.New("unique key conflict error") |
|||
|
|||
// TransGlobal global transaction
|
|||
type TransGlobal struct { |
|||
common.ModelBase |
|||
Gid string `json:"gid"` |
|||
TransType string `json:"trans_type"` |
|||
Steps []map[string]string `json:"steps" gorm:"-"` |
|||
Payloads []string `json:"payloads" gorm:"-"` |
|||
BinPayloads [][]byte `json:"-" gorm:"-"` |
|||
Status string `json:"status"` |
|||
QueryPrepared string `json:"query_prepared"` |
|||
Protocol string `json:"protocol"` |
|||
CommitTime *time.Time |
|||
FinishTime *time.Time |
|||
RollbackTime *time.Time |
|||
Options string |
|||
CustomData string `json:"custom_data"` |
|||
NextCronInterval int64 |
|||
NextCronTime *time.Time |
|||
dtmimp.TransOptions |
|||
lastTouched time.Time // record the start time of process
|
|||
} |
|||
|
|||
// TableName TableName
|
|||
func (*TransGlobal) TableName() string { |
|||
return "dtm.trans_global" |
|||
} |
|||
|
|||
// TransBranch branch transaction
|
|||
type TransBranch struct { |
|||
common.ModelBase |
|||
Gid string |
|||
URL string `json:"url"` |
|||
BinData []byte |
|||
BranchID string `json:"branch_id"` |
|||
Op string |
|||
Status string |
|||
FinishTime *time.Time |
|||
RollbackTime *time.Time |
|||
} |
|||
|
|||
// TableName TableName
|
|||
func (*TransBranch) TableName() string { |
|||
return "dtm.trans_branch_op" |
|||
} |
|||
|
|||
type transProcessor interface { |
|||
GenBranches() []TransBranch |
|||
ProcessOnce(db *common.DB, branches []TransBranch) error |
|||
} |
|||
|
|||
type processorCreator func(*TransGlobal) transProcessor |
|||
|
|||
var processorFac = map[string]processorCreator{} |
|||
|
|||
func registorProcessorCreator(transType string, creator processorCreator) { |
|||
processorFac[transType] = creator |
|||
} |
|||
|
|||
func (t *TransGlobal) getProcessor() transProcessor { |
|||
return processorFac[t.TransType](t) |
|||
} |
|||
|
|||
type cronType int |
|||
|
|||
const ( |
|||
cronBackoff cronType = iota |
|||
cronReset |
|||
cronKeep |
|||
) |
|||
|
|||
// TransFromContext TransFromContext
|
|||
func TransFromContext(c *gin.Context) *TransGlobal { |
|||
b, err := c.GetRawData() |
|||
e2p(err) |
|||
m := TransGlobal{} |
|||
dtmimp.MustUnmarshal(b, &m) |
|||
dtmimp.Logf("creating trans in prepare") |
|||
// Payloads will be store in BinPayloads, Payloads is only used to Unmarshal
|
|||
for _, p := range m.Payloads { |
|||
m.BinPayloads = append(m.BinPayloads, []byte(p)) |
|||
} |
|||
for _, d := range m.Steps { |
|||
if d["data"] != "" { |
|||
m.BinPayloads = append(m.BinPayloads, []byte(d["data"])) |
|||
} |
|||
} |
|||
m.Protocol = "http" |
|||
return &m |
|||
} |
|||
|
|||
// TransFromDtmRequest TransFromContext
|
|||
func TransFromDtmRequest(c *dtmgimp.DtmRequest) *TransGlobal { |
|||
r := TransGlobal{ |
|||
Gid: c.Gid, |
|||
TransType: c.TransType, |
|||
QueryPrepared: c.QueryPrepared, |
|||
Protocol: "grpc", |
|||
BinPayloads: c.BinPayloads, |
|||
} |
|||
if c.Steps != "" { |
|||
dtmimp.MustUnmarshalString(c.Steps, &r.Steps) |
|||
} |
|||
return &r |
|||
} |
|||
|
|||
func checkAffected(db1 *gorm.DB) { |
|||
if db1.RowsAffected == 0 { |
|||
panic(fmt.Errorf("rows affected 0, please check for abnormal trans")) |
|||
} |
|||
} |
|||
@ -0,0 +1,83 @@ |
|||
package dtmsvr |
|||
|
|||
import ( |
|||
"time" |
|||
|
|||
"github.com/yedf/dtm/common" |
|||
"github.com/yedf/dtm/dtmcli" |
|||
"github.com/yedf/dtm/dtmcli/dtmimp" |
|||
"gorm.io/gorm" |
|||
"gorm.io/gorm/clause" |
|||
) |
|||
|
|||
// Process process global transaction once
|
|||
func (t *TransGlobal) Process(db *common.DB) map[string]interface{} { |
|||
r := t.process(db) |
|||
transactionMetrics(t, r["dtm_result"] == dtmcli.ResultSuccess) |
|||
return r |
|||
} |
|||
|
|||
func (t *TransGlobal) process(db *common.DB) map[string]interface{} { |
|||
if t.Options != "" { |
|||
dtmimp.MustUnmarshalString(t.Options, &t.TransOptions) |
|||
} |
|||
|
|||
if !t.WaitResult { |
|||
go t.processInner(db) |
|||
return dtmcli.MapSuccess |
|||
} |
|||
submitting := t.Status == dtmcli.StatusSubmitted |
|||
err := t.processInner(db) |
|||
if err != nil { |
|||
return map[string]interface{}{"dtm_result": dtmcli.ResultFailure, "message": err.Error()} |
|||
} |
|||
if submitting && t.Status != dtmcli.StatusSucceed { |
|||
return map[string]interface{}{"dtm_result": dtmcli.ResultFailure, "message": "trans failed by user"} |
|||
} |
|||
return dtmcli.MapSuccess |
|||
} |
|||
|
|||
func (t *TransGlobal) processInner(db *common.DB) (rerr error) { |
|||
defer handlePanic(&rerr) |
|||
defer func() { |
|||
if rerr != nil { |
|||
dtmimp.LogRedf("processInner got error: %s", rerr.Error()) |
|||
} |
|||
if TransProcessedTestChan != nil { |
|||
dtmimp.Logf("processed: %s", t.Gid) |
|||
TransProcessedTestChan <- t.Gid |
|||
dtmimp.Logf("notified: %s", t.Gid) |
|||
} |
|||
}() |
|||
dtmimp.Logf("processing: %s status: %s", t.Gid, t.Status) |
|||
branches := []TransBranch{} |
|||
db.Must().Where("gid=?", t.Gid).Order("id asc").Find(&branches) |
|||
t.lastTouched = time.Now() |
|||
rerr = t.getProcessor().ProcessOnce(db, branches) |
|||
return |
|||
} |
|||
|
|||
func (t *TransGlobal) saveNew(db *common.DB) error { |
|||
return db.Transaction(func(db1 *gorm.DB) error { |
|||
db := &common.DB{DB: db1} |
|||
t.setNextCron(cronReset) |
|||
t.Options = dtmimp.MustMarshalString(t.TransOptions) |
|||
if t.Options == "{}" { |
|||
t.Options = "" |
|||
} |
|||
dbr := db.Must().Clauses(clause.OnConflict{ |
|||
DoNothing: true, |
|||
}).Create(t) |
|||
if dbr.RowsAffected <= 0 { // 如果这个不是新事务,返回错误
|
|||
return errUniqueConflict |
|||
} |
|||
branches := t.getProcessor().GenBranches() |
|||
if len(branches) > 0 { |
|||
checkLocalhost(branches) |
|||
db.Must().Clauses(clause.OnConflict{ |
|||
DoNothing: true, |
|||
}).Create(&branches) |
|||
} |
|||
return nil |
|||
}) |
|||
} |
|||
Loading…
Reference in new issue