如下是踩坑记录。先摆出结论:避免使用上下文通用的ctx来释放资源。redis
// 入参的ctx自己已经带了超时 func DoSomehting(ctx context.Context, args interface{}) error { // 使用redis做为分布式锁 isDuplidated, err := redis.SetDeDupliated(ctx, args) if err != nil { return err } if isDupdated { return nil } // 释放锁. 使用了ctx, 有问题. defer redis.DeleteDeDuplicated(ctx, args) // 业务操做 err = doSomethingFoo(ctx, args) if err != nil { return err } err = doSomethingBar(ctx, args) if err != nil { return err } return nil }
入参的ctx带有cancel机制。
问题在于defer那一行代码,释放资源使用了 DoSomething
的ctx。若是业务操做代码cancel了ctx,或者是执行了耗时操做,而正好 redis.DeleteDeDuplicated
也使用了ctx的cancel机制,那么这个redis锁就没法释放了。分布式
若是ctx中带有通用的上下文信息,须要写个函数生成一个新的ctx,同时把原来ctx的kv复制出来。不然直接使用context.Background()就行了。函数
func CopyCtx(ctx context) context.Context { ret := context.Background() ret = context.WithValue(ret, ctxKeyFoo, ctx.Value(ctxKeyFoo) ret = context.WithValue(ret, ctxKeyBar, ctx.Value(ctxKeyBar) return ret } ... defer redis.DeleteDeDuplicated(CopyCtx(ctx), args) // defer redis.DeleteDeDuplicated(context.Background(), args) ...
阅读context代码能够发现,ctx的cancel/timeout机制,对当前ctx以及其子ctx有效,不影响父ctx。code
ps: context的父子关系以下资源
father := context.Background() son := context.WithValue(father, key, value)