go ctx超时导致资源释放失败

shanyin · · 1152 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

以下是踩坑记录。先摆出结论:避免使用上下文通用的ctx来释放资源

翻车代码

// 入参的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。

ps: context的父子关系如下

    father := context.Background()
    son := context.WithValue(father, key, value)

有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:shanyin

查看原文:go ctx超时导致资源释放失败

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1152 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传