WithContext方法在上下文为nil时需要引发panic吗?

huangapple go评论85阅读模式
英文:

Does WithContext method need to panic if context is nil?

问题

我想为一个结构体编写一个WithContext方法,并从net/httpRequest.WithContext方法中获取灵感。

我的问题是:为什么Request.WithContext方法在上下文为nil时会引发panic:

func (r *Request) WithContext(ctx context.Context) *Request {
	if ctx == nil {
		panic("nil context")
	}
	...
}

我应该也这样做吗?

关于我为什么想创建一个WithContext方法的更多背景信息:我正在实现一个接口,该接口在其签名中没有提供上下文参数,但我认为实现需要它。

更具体地说,我正在使用Go的官方Redis客户端为gorilla/session编写一个Redis后端,其中GetSet方法接受context.Context

我的想法是,当需要时,使用新的上下文对象对我的Redis存储进行浅拷贝,然后使用它:

type redisStore struct {
	codecs  []securecookie.Codec
	backend Backend // Redis客户端的自定义接口
	options *sessions.Options
	ctx     context.Context
}

func (s *redisStore) WithContext(ctx context.Context) *redisStore {
	if ctx == nil {
		panic("nil context")
	}
	s2 := new(redisStore)
	*s2 = *s
	s2.ctx = ctx
	return s2
}

// Backend

type Backend interface {
	Set(context.Context, string, interface{}) error
	Get(context.Context, string) (string, error)
	Del(context.Context, string) error
}
英文:

I want to write a WithContext method for a struct and am taking inspiration from net/http's Request.WithContext.

My question is: why does Request.WithContext panic if the context is nil:

func (r *Request) WithContext(ctx context.Context) *Request {
	if ctx == nil {
		panic("nil context")
	}
	...
}

And should mine as well?

For more context on why I want to create a WithContext method: I am implementing an interface that does not provide a context parameter in its signature but believe the implementation requires it.

More specifically, I am writing a Redis backend for gorilla/session using the official Redis client for Go, where the Get and Set methods take context.Context.

The idea is that my redis store will be shallow copied with the new context object, when needed, and then used:

type redisStore struct {
	codecs  []securecookie.Codec
	backend Backend // custom interface for Redis client
	options *sessions.Options
	ctx     context.Context
}

func (s *redisStore) WithContext(ctx context.Context) *redisStore {
	if ctx == nil {
		panic("nil context")
	}
	s2 := new(redisStore)
	*s2 = *s
	s2.ctx = ctx
	return s2
}

// Backend

type Backend interface {
	Set(context.Context, string, interface{}) error
	Get(context.Context, string) (string, error)
	Del(context.Context, string) error
}

答案1

得分: 1

恐慌的目的是为了快速失败并拒绝一个nil上下文,而不改变函数签名。

如果函数不恐慌,那么它必须返回错误以拒绝错误的输入:

func (r *Request) WithContext(ctx context.Context) (*Request, error) {
    if ctx == nil {
        return nil, errors.New("nil ctx")
    }
    ...
}

然后调用这个函数的人必须处理错误,以避免使用无效的请求:

request, err = request.WithContext(nil)
if err != nil {
   
}

通过处理错误,你引入了一个控制流分支,失去了方法链式调用的能力。你也不能立即将WithContext的返回值用作函数参数:

// 不能这样做,因为WithContext也返回一个错误
data, err := fetchDataWithContext(request.WithContext(ctx), otherParam)

此外,这将创建一个最终会被垃圾回收的错误实例。所有这些都很繁琐,可用性差,只是为了说“不要给我一个空的上下文”。

关于使用上下文创建一个Redis存储,上下文文档已经很清楚:

> 包context定义了Context类型,它在API边界和进程之间传递截止时间、取消信号和其他请求范围的值。

重要的细节是请求范围。因此,在Redis客户端本身设置上下文与此建议相违背。你应该在每个get/set调用中传递上下文值。

英文:

The purpose of panicking is to "fail fast" and reject a nil context without changing the function signature.

If the function does not panic then it must return error in order to reject a bad input:

func (r *Request) WithContext(ctx context.Context) (*Request, error) {
    if ctx == nil {
        return nil, errors.New("nil ctx")
    }
    ...
}

And then who calls this function must handle the error to avoid using an invalid request:

request, err = request.WithContext(nil)
if err != nil {
   
}

By handling the error you are introducing a control flow branch, and you lose method chaining. You also cannot immediately use WithContext return value into a function parameter:

// cannot do, because WithContext returns an error too
data, err := fetchDataWithContext(request.WithContext(ctx), otherParam)

Also it would create an error instance that will be eventually garbage collected. This all is cumbersome, poor usability and unnecessary alloc simply for saying "don't give me a nil context".

About creating a redis store with a context, the context documentation is clear:

> Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.

The important detail is request-scoped. So setting a context in the redis client itself is contrary to this recommendation. You should pass context values at each get/set call.

答案2

得分: 0

如果客户端关闭连接,HTTP请求的上下文将被取消。当上下文被取消时,所有子上下文也将被取消,因此如果传递了nil上下文给WithContext函数,会导致panic错误。

你是否应该在redis存储中引发panic错误取决于你如何使用该上下文。通常情况下,将上下文包含在结构体中并不是一个好主意。一个可接受的做法是,如果结构体本身就是一个上下文。上下文应该为每个调用创建,应该在该调用的持续时间内存在,然后被丢弃。

英文:

The context of an HTTP request is canceled if the client closes the connection. When the context is canceled, all its child contexts are also canceled, so a nil context would panic then. Because of this, you cannot pass a nil context to WithContext.

Whether or not your redis store should panic depends on how you are going to use that context. It is usually not a good idea to include a context in a struct. One acceptable way of doing that is if the struct itself is a context. Contexts should be created for each call, should live for the duration of that call, and then thrown away.

huangapple
  • 本文由 发表于 2021年11月14日 13:19:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/69960484.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定