go ctx超時致使資源釋放失敗

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

思考: ctx的cancel/timeout機制

閱讀context代碼能夠發現,ctx的cancel/timeout機制,對當前ctx以及其子ctx有效,不影響父ctx。code

ps: context的父子關係以下資源

father := context.Background()
    son := context.WithValue(father, key, value)
相關文章
相關標籤/搜索