Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG:workflow-tcc-Do本地方法模式无法回滚 #521

Open
yangwenbinch opened this issue Sep 10, 2024 · 1 comment
Open

BUG:workflow-tcc-Do本地方法模式无法回滚 #521

yangwenbinch opened this issue Sep 10, 2024 · 1 comment

Comments

@yangwenbinch
Copy link

当我使用以下代码进行tcc事务时,无法正常进行事务回滚处理

我将使用方式和发现的问题一起记录下,如我的使用存在问题欢迎指正

	wfName := "wf_test"
	err := workflow.Register2(wfName, func(wf *workflow.Workflow, data []byte) ([]byte, error) {
		_, err := wf.NewBranch().OnCommit(func(bb *dtmcli.BranchBarrier) error {
			db, err := sql.Open("mysql",
				"dtm_wr:737ab8ef38666796@tcp(rw-18cc7d.mysql.qihudb.net:4362)/dtm")
			tx, err := db.Begin()
			err = bb.Call(tx, func(tx *sql.Tx) error {
				fmt.Println("COMMIT")
				return nil
			})
			return err
		}).OnRollback(func(bb *dtmcli.BranchBarrier) error {
			db, err := sql.Open("mysql",
				"dtm_wr:737ab8ef38666796@tcp(rw-18cc7d.mysql.qihudb.net:4362)/dtm")
			tx, err := db.Begin()
			err = bb.Call(tx, func(tx *sql.Tx) error {
				fmt.Println("ROLLBACK")
				return nil
			})
			return err
		}).Do(func(bb *dtmcli.BranchBarrier) ([]byte, error) {
			db, err := sql.Open("mysql",
				"dtm_wr:737ab8ef38666796@tcp(rw-18cc7d.mysql.qihudb.net:4362)/dtm")
			tx, err := db.Begin()
			err = bb.Call(tx, func(tx *sql.Tx) error {
				err = errors.New("test-err")
				return dtmcli.ErrFailure
			})
			if err != nil {
				return nil, err
			}
			return []byte("123"), nil
		})
		if err != nil {
			return nil, err
		}
		return []byte("123"), nil
	})
	if err != nil {
		fmt.Println(err)
	}

在wf.Do方法中会调用 wf.recordedDo方法,在 wf.recordedDo中有以下逻辑

	if !wf.Options.CompensateErrorBranch && sr.Status == dtmcli.StatusFailed {
		lastFailed := len(wf.failedOps) - 1
		if lastFailed >= 0 && wf.failedOps[lastFailed].branchID == wf.currentBranch {
			wf.failedOps = wf.failedOps[:lastFailed]
		}
	}

此时会将注册时注入的failedOps丢弃,这里感觉逻辑是只取一个failedOps所以数组截取应为
wf.failedOps = wf.failedOps[lastFailed:]

同时在OnRollback时

dtmimp.PanicIf(wf.currentRollbackAdded, fmt.Errorf("one branch can only add one rollback callback"))

此代码已保证回滚方法的唯一

此外,在调用事务Execute2方法后调用defaultFac.execute 在此方法中调用 wf.process,在 wf.process调用handler进行Do方法,根据Do方法返回的错误在 wf.processPhase2方法中进行回滚

func (wf *Workflow) processPhase2(err error) error {
	ops := wf.succeededOps
	if err == nil {
		wf.currentOp = dtmimp.OpCommit
	} else {
		wf.currentOp = dtmimp.OpRollback
		ops = wf.failedOps
	}
	for i := len(ops) - 1; i >= 0; i-- {
		op := ops[i]

		err1 := wf.callPhase2(op.branchID, op.fn)
		if err1 != nil {
			return err1
		}
	}
	return err
}

此方法的传参err后续会直接透传返回,导致回滚后的事务也返回failure状态

若错误不为dtmcli.ErrFailure则会在回调前被拦截并返回,重试prepare

除此之外
在调用回滚方法时,如果使用子事务屏障

	err = bb.Call(tx, func(tx *sql.Tx) error {
				fmt.Println("ROLLBACK")
				return nil
			})

在bb.Call方法中存在代码逻辑

	if (bb.Op == dtmimp.OpCancel || bb.Op == dtmimp.OpCompensate || bb.Op == dtmimp.OpRollback) && originAffected > 0 || // null compensate
		currentAffected == 0 { // repeated request or dangled request
		return
	}

此时originAffected ==1 且 currentAffected ==1
但因为 bb.Op == dtmimp.OpRollback直接过滤rollback方法

@yedf2
Copy link
Contributor

yedf2 commented Sep 16, 2024

一般报bug的的标准流程是,复现问题的步骤,预期结果和当前的错误结果。
能否按照这种结构给出你说的这个无法回滚的问题呢?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants