22

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

 4 years ago
source link: https://www.tuicool.com/articles/RbuAfuA
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

以下是踩坑记录。先摆出结论: 避免使用上下文通用的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)

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK