熟悉 Go 语言的同学都知道,context 包只对外提供了两种取消原因 context.DeadlineExceeded 和 context.Canceled,不支持自定义原因,就像下面这样:
func main() {
// Pass a context with a timeout to tell a blocking function that it
// should abandon its work after the timeout elapses.
timeoutDuration := 3 * time.Second
ctx, cancel := context.WithTimeout(context.Background(), timeoutDuration)
defer cancel() // block until context is timed out
<-ctx.Done()
switch ctx.Err() {
case context.DeadlineExceeded:
fmt.Println("context timeout exceeded")
case context.Canceled:
fmt.Println("context cancelled by force")
}
// output:
// context timeout exceeded
}
上面的两种错误已经能够满足大部分场景,但是如果有时候我想知道更多关于 context 取消的原因就只能额外自定义 error,比如遇到某类取消错误时是否需要重试等。
另外,如果是显示地调用 context.CancelFunc() 函数(上面代码的 cancel 变量)取消,现在是没有办法表明这是否是由于错误造成的。
介于此,之前社区就有人提案:如果是显示取消 context,允许自定义取消原因。
这不它就来了,Go1.20 目前已经支持这一特性。
Go1.20 的 context 包提供了 context.WithCancelCause() 支持自定义取消原因,并且提供了提取取消原因的 api:
package mainimport (
"context"
"errors"
"fmt"
)
var ErrTemporarilyUnavailable = fmt.Errorf("service temporarily unavailable")
func main() {
ctx, cancel := context.WithCancelCause(context.Background())
// operation failed, let's notify the caller by cancelling the context
cancel(ErrTemporarilyUnavailable)
switch ctx.Err() {
case context.Canceled:
fmt.Println("context cancelled by force")
}
// get the cause of cancellation, in this case the ErrTemporarilyUnavailable error
err := context.Cause(ctx)
if errors.Is(err, ErrTemporarilyUnavailable) {
fmt.Printf("cancallation reason: %s", err)
}
// cancallation reason: service temporarily unavailable
}
上面的代码,在取消的时候传入了自定义错误 “ErrTemporarilyUnavailable”,并且使用 context.Cause() 提取错误原因。
有了这一特性,以后就可以基于取消原因,做更近一步的逻辑操作了,比如是否需要重试等。
跟着 context.WithCancelCause() 之后,目前有最新的提案,支持 WithDeadlineCause 和 WithTimeoutCause,目前该提案已经被官方接受,正在开发。
我们先来尝鲜,看下这两个分别怎么用?
context.WithTimeoutCause()
var ErrFailure = fmt.Errorf("request took too long")func main() {
timeout := time.Duration(2 * time.Second)
ctx, _ := context.WithTimeoutCause(context.Background(), timeout, ErrFailure)
// wait for the context to timeout
<-ctx.Done()
switch ctx.Err() {
case context.DeadlineExceeded:
fmt.Printf("operation could not complete: %s", context.Cause(ctx))
}
// operation could not complete: request took too long
}
context.WithDeadlineCause()
var ErrFailure = fmt.Errorf("request took too long")func main() {
timeout := time.Now().Add(time.Duration(2 * time.Second))
ctx, _ := context.WithDeadlineCause(context.Background(), timeout, ErrFailure)
// wait for the context to timeout
<-ctx.Done()
switch ctx.Err() {
case context.DeadlineExceeded:
fmt.Printf("operation could not complete: %s", context.Cause(ctx))
}
// operation could not complete: request took too long
}
这些新特性有没有给你编程带来便利呢?欢迎留言“开喷”!
推荐阅读