mirror of https://github.com/dtm-labs/dtm.git
9 changed files with 191 additions and 241 deletions
@ -1,29 +0,0 @@ |
|||
package dtmcli |
|||
|
|||
import "fmt" |
|||
|
|||
// ConcurrentSaga struct of concurrent saga
|
|||
type ConcurrentSaga struct { |
|||
Saga |
|||
orders map[int][]int |
|||
} |
|||
|
|||
// NewConcurrentSaga create a concurrent saga
|
|||
func NewConcurrentSaga(server string, gid string) *ConcurrentSaga { |
|||
return &ConcurrentSaga{Saga: Saga{TransBase: *NewTransBase(gid, "csaga", server, "")}, orders: map[int][]int{}} |
|||
} |
|||
|
|||
// AddStepOrder specify that step should be after preSteps. Step is larger than all the element in preSteps
|
|||
func (s *ConcurrentSaga) AddStepOrder(step int, preSteps []int) *ConcurrentSaga { |
|||
PanicIf(step > len(s.Steps), fmt.Errorf("step value: %d is invalid. which cannot be larger than total steps: %d", step, len(s.Steps))) |
|||
s.orders[step] = preSteps |
|||
return s |
|||
} |
|||
|
|||
// Submit submit the saga trans
|
|||
func (s *ConcurrentSaga) Submit() error { |
|||
if len(s.orders) > 0 { |
|||
s.CustomData = MustMarshalString(M{"orders": s.orders}) |
|||
} |
|||
return s.callDtm(s, "submit") |
|||
} |
|||
@ -1,175 +0,0 @@ |
|||
package dtmsvr |
|||
|
|||
import ( |
|||
"time" |
|||
|
|||
"github.com/yedf/dtm/common" |
|||
"github.com/yedf/dtm/dtmcli" |
|||
"gorm.io/gorm/clause" |
|||
) |
|||
|
|||
type transCSagaProcessor struct { |
|||
*TransGlobal |
|||
} |
|||
|
|||
func init() { |
|||
registorProcessorCreator("csaga", func(trans *TransGlobal) transProcessor { return &transCSagaProcessor{TransGlobal: trans} }) |
|||
} |
|||
|
|||
func (t *transCSagaProcessor) GenBranches() []TransBranch { |
|||
return genSagaBranches(t.TransGlobal) |
|||
} |
|||
|
|||
type cSagaCustom struct { |
|||
Orders map[int][]int `json:"orders"` |
|||
} |
|||
|
|||
func isPreconditionsSucceed(branches []TransBranch, pres []int) bool { |
|||
for _, pre := range pres { |
|||
if branches[pre].Status != dtmcli.StatusSucceed { |
|||
return false |
|||
} |
|||
} |
|||
return true |
|||
} |
|||
|
|||
type branchResult struct { |
|||
index int |
|||
status string |
|||
started bool |
|||
branchType string |
|||
} |
|||
|
|||
func (t *transCSagaProcessor) ProcessOnce(db *common.DB, branches []TransBranch) error { |
|||
if t.Status == dtmcli.StatusFailed || t.Status == dtmcli.StatusSucceed { |
|||
return nil |
|||
} |
|||
n := len(branches) |
|||
|
|||
orders := map[int][]int{} |
|||
if t.CustomData != "" { |
|||
csc := cSagaCustom{Orders: map[int][]int{}} |
|||
dtmcli.MustUnmarshalString(t.CustomData, &csc) |
|||
for k, v := range csc.Orders { // new branches is doubled, so change the order value
|
|||
orders[2*k+1] = []int{} |
|||
for j := 0; j < len(v); j++ { |
|||
orders[2*k+1] = append(orders[2*k+1], csc.Orders[k][j]*2+1) |
|||
} |
|||
} |
|||
} |
|||
// resultStats
|
|||
var rsActionToStart, rsActionDone, rsActionFailed, rsActionSucceed, rsCompensateToStart, rsCompensateDone, rsCompensateSucceed int |
|||
branchResults := make([]branchResult, n) // save the branch result
|
|||
for i := 0; i < n; i++ { |
|||
b := branches[i] |
|||
if b.BranchType == dtmcli.BranchAction { |
|||
if b.Status == dtmcli.StatusPrepared || b.Status == dtmcli.StatusDoing { |
|||
rsActionToStart++ |
|||
} else if b.Status == dtmcli.StatusFailed { |
|||
rsActionFailed++ |
|||
} |
|||
} |
|||
branchResults[i] = branchResult{status: branches[i].Status, branchType: branches[i].BranchType} |
|||
} |
|||
stopChan := make(chan branchResult, n) |
|||
asyncExecBranch := func(i int) { |
|||
var err error |
|||
defer func() { |
|||
if x := recover(); x != nil { |
|||
err = dtmcli.AsError(x) |
|||
} |
|||
stopChan <- branchResult{index: i, status: branches[i].Status, branchType: branches[i].BranchType} |
|||
if err != nil { |
|||
dtmcli.LogRedf("exec branch error: %v", err) |
|||
} |
|||
}() |
|||
err = t.execBranch(db, &branches[i]) |
|||
} |
|||
needRollback := func(i int) bool { |
|||
br := &branchResults[i] |
|||
return !br.started && br.branchType == dtmcli.BranchCompensate && br.status != dtmcli.StatusSucceed && branchResults[i+1].branchType == dtmcli.BranchAction && branchResults[i+1].status != dtmcli.StatusPrepared |
|||
} |
|||
pickAndRun := func(branchType string) { |
|||
toRun := []int{} |
|||
for current := 0; current < n; current++ { |
|||
br := &branchResults[current] |
|||
if br.branchType == branchType && branchType == dtmcli.BranchAction { |
|||
if (br.status == dtmcli.StatusPrepared || br.status == dtmcli.StatusDoing) && |
|||
!br.started && isPreconditionsSucceed(branches, orders[current]) { |
|||
br.status = dtmcli.StatusDoing |
|||
toRun = append(toRun, current) |
|||
} |
|||
} else if br.branchType == branchType && branchType == dtmcli.BranchCompensate { |
|||
if needRollback(current) { |
|||
toRun = append(toRun, current) |
|||
} |
|||
} |
|||
} |
|||
if branchType == dtmcli.BranchAction && len(toRun) > 0 { |
|||
updates := make([]TransBranch, len(toRun)) |
|||
for i, b := range toRun { |
|||
updates[i].ID = branches[b].ID |
|||
branches[b].Status = dtmcli.StatusDoing |
|||
updates[i].Status = dtmcli.StatusDoing |
|||
} |
|||
dbGet().Must().Clauses(clause.OnConflict{ |
|||
OnConstraint: "trans_branch_pkey", |
|||
DoUpdates: clause.AssignmentColumns([]string{"status"}), |
|||
}).Create(updates) |
|||
} else if branchType == dtmcli.BranchCompensate { |
|||
rsCompensateToStart = len(toRun) |
|||
} |
|||
for _, b := range toRun { |
|||
branchResults[b].started = true |
|||
go asyncExecBranch(b) |
|||
} |
|||
} |
|||
processorTimeout := func() bool { |
|||
return time.Since(t.processStarted)+NowForwardDuration > time.Duration(t.getRetryInterval()-3)*time.Second |
|||
} |
|||
waitOnceForDone := func() { |
|||
select { |
|||
case r := <-stopChan: |
|||
br := &branchResults[r.index] |
|||
br.status = r.status |
|||
if r.branchType == dtmcli.BranchAction { |
|||
rsActionDone++ |
|||
if r.status == dtmcli.StatusFailed { |
|||
rsActionFailed++ |
|||
} else if r.status == dtmcli.StatusSucceed { |
|||
rsActionSucceed++ |
|||
} |
|||
} else { |
|||
rsCompensateDone++ |
|||
if r.status == dtmcli.StatusSucceed { |
|||
rsCompensateSucceed++ |
|||
} |
|||
} |
|||
dtmcli.Logf("branch done: %v", r) |
|||
case <-time.After(time.Duration(time.Second * 3)): |
|||
dtmcli.Logf("wait once for done") |
|||
} |
|||
} |
|||
|
|||
for t.Status == dtmcli.StatusSubmitted && !t.isTimeout() && rsActionFailed == 0 && rsActionDone != rsActionToStart && !processorTimeout() { |
|||
pickAndRun(dtmcli.BranchAction) |
|||
waitOnceForDone() |
|||
} |
|||
if t.Status == dtmcli.StatusSubmitted && rsActionFailed == 0 && rsActionToStart == rsActionSucceed { |
|||
t.changeStatus(db, dtmcli.StatusSucceed) |
|||
return nil |
|||
} |
|||
if t.Status == dtmcli.StatusSubmitted && (rsActionFailed > 0 || t.isTimeout()) { |
|||
t.changeStatus(db, dtmcli.StatusAborting) |
|||
} |
|||
if t.Status == dtmcli.StatusAborting { |
|||
pickAndRun(dtmcli.BranchCompensate) |
|||
for rsCompensateDone != rsCompensateToStart && !processorTimeout() { |
|||
waitOnceForDone() |
|||
} |
|||
} |
|||
if (t.Status == dtmcli.StatusSubmitted || t.Status == dtmcli.StatusAborting) && rsActionFailed > 0 && rsCompensateToStart == rsCompensateSucceed { |
|||
t.changeStatus(db, dtmcli.StatusFailed) |
|||
} |
|||
return nil |
|||
} |
|||
Loading…
Reference in new issue