Error Overwrite When Calling Multiple Functions in mr.Finish
go-zero版本:1.8.5 1.8.3 1.8.0都试过 代码:
err = mr.Finish(
func() error {
// 检查是否禁止页面投注
forbidBetConfig, err := l.svcCtx.CacheRpc.GetForbidBetConfig(l.ctx, &cache.GetForbidBetConfigReq{
Key: vars.ForbidPageRewardConfigKey,
})
if err != nil {
return err
}
if forbidBetConfig.IsForbid {
return errors.New("禁止页面投注")
}
return nil
},
func() error {
// 检查用户是否禁止页面投注
user, err = User.WithContext(l.ctx).Where(User.ID.Eq(uid)).First()
if err != nil {
return errors.New("用户不存在")
}
if user.ForbidPageBet {
return errors.New("用户禁止页面投注")
}
return nil
},
func() error {
// 检查游戏是否存在
game, err = Game.WithContext(l.ctx).Where(Game.ID.Eq(req.GameID)).First()
if err != nil {
return errors.New("游戏不存在")
}
if game.Status != vars.StatusNormal {
return errors.New("游戏状态异常")
}
return nil
},
func() error {
// 检查轮次是否存在
round, err = GameRound.WithContext(l.ctx).Where(GameRound.GameID.Eq(req.GameID), GameRound.Round.Eq(req.Round)).First()
if err != nil {
return errors.New("轮次不存在")
}
if round.Status == vars.GameStatusSettling || round.Status == vars.GameStatusSettled {
return errors.New("轮次不在可投注范围")
}
return nil
},
func() error {
// 检查轮次中下注是否超限
betLimitResp, err := l.svcCtx.CacheRpc.GetBetLimit(l.ctx, &cache.GetBetLimitReq{
GameId: req.GameID,
})
if err != nil {
return err
}
betLimit = &vars.BetLimitCacheConfigValue{}
for k, v := range betLimitResp.BetLimits {
betLimit.Set(k, vars.BetLimit{
Min: v.Min,
Max: v.Max,
})
}
return nil
},
func() error {
// 检查当前轮次投注是否已达上限
err = GameRoundJoin.WithContext(l.ctx).Where(GameRoundJoin.GameID.Eq(req.GameID), GameRoundJoin.Round.Eq(req.Round), GameRoundJoin.Symbol.Eq(req.Symbol), GameRoundJoin.JoinType.Eq(vars.GameJoinTypePage), GameRoundJoin.UserID.Eq(uid)).Select(field.NewUnsafeFieldRaw("COALESCE(SUM(amount_int),0)")).Scan(&sum)
if err != nil {
return errors.New("获取当前轮次投注金额失败")
}
return nil
},
func() error {
// 检查用户钱包是否存在
userWallet, err = UserWallet.WithContext(l.ctx).Where(UserWallet.UserID.Eq(uid), UserWallet.Symbol.Eq(req.Symbol)).First()
if err != nil {
return errors.New("用户钱包不存在")
}
return nil
},
)
if err != nil {
logx.Errorf("CreateGameRoundJoinLogic mr 查询失败 Error: %v", err)
logx.Errorf("CreateGameRoundJoinLogic2 mr 查询失败 Error: %v", err)
return nil, err
}
同一个请求出现:leted_atIS NULL ORDER BYuser_wallet.id` LIMIT 1
2025-07-29T19:20:09.311+08:00 error CreateGameRoundJoinLogic mr 查询失败 Error: 轮次不在可投注范围 caller=game_round_join/create_game_round_join_logic.go:156
2025-07-29T19:20:09.311+08:00 error CreateGameRoundJoinLogic2 mr 查询失败 Error:
解决方案: github.com/sourcegraph/conc/pool
Did you mean if one of the methods returned error, the overall error is still nil?
I tried with the following code, but didn't reproduce.
func TestFinishWithPartialErrors(t *testing.T) {
defer goleak.VerifyNone(t)
errDummy := errors.New("dummy")
t.Run("one error", func(t *testing.T) {
err := Finish(func() error {
return errDummy
}, func() error {
return nil
}, func() error {
return nil
})
assert.Equal(t, errDummy, err)
})
t.Run("two errors", func(t *testing.T) {
err := Finish(func() error {
return errDummy
}, func() error {
return errDummy
}, func() error {
return nil
})
assert.Equal(t, errDummy, err)
})
}
This is a race condition problem. Inside the mr.Finish functions, you assign to the outer err variable. Because the functions run at the same time, they overwrite each other. This can make the final err become nil.
The fix is to use a local variable inside each function and just return the error. Let mr collect the result. Do not change the outer err inside the functions.