如下是踩坑記錄。先擺出結論:避免使用上下文通用的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)