如何将context.WithCancel和context.WithTimeout API结合使用,是否有必要?

时间:2018-06-13 11:22:55

标签: go

现在我做了类似的事情:

func contextHandler(h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ctx, cancel := context.WithCancel(r.Context())
        ctx, cancel = context.WithTimeout(ctx, config.EnvConfig.RequestTimeout)

        defer cancel()

        if cn, ok := w.(http.CloseNotifier); ok {
            go func(done <-chan struct{}, closed <-chan bool) {
                select {
                case <-done:
                case <-closed:
                    logger.Debug("message", "client connection has gone away, request will be cancelled")
                    cancel()
                }
            }(ctx.Done(), cn.CloseNotify())
        }

        h.ServeHTTP(w, r.WithContext(ctx))
    })
}

请注意这两行:

ctx, cancel := context.WithCancel(r.Context())
ctx, cancel = context.WithTimeout(ctx, config.EnvConfig.RequestTimeout)

根据我的测试:deliberately kill the client requestdeliberately make the request exceed the deadline,两者都工作正常(我的意思是可以按预期接收到取消信号和超时信号),我关心的是:后者{{1} } function将覆盖cancel返回的前一个,所以:

  1. 像这样一起使用这两个API是一种正确的方法吗?
  2. 甚至有必要一起使用这两个API吗?
  3. 请帮忙解释一下。

2 个答案:

答案 0 :(得分:2)

由于您的CancelFunc调用返回的WithCancel被立即覆盖,这会导致程序中的资源(即内存)泄漏。来自context documentation

  

WithCancel,WithDeadline和WithTimeout函数接受Context(父)并返回派生的Context(子)和CancelFunc。调用CancelFunc会取消子项及其子项,删除父项对子项的引用,并停止任何关联的计时器。 未能调用CancelFunc会泄漏子项及其子项,直到取消父项或计时器触发。

从代码中删除WithCancel上下文将解决此问题。

此外,HTTP请求的取消由HTTP服务器管理,如http.Request.Context方法文档中所述:

  

对于传入服务器请求,当客户端连接关闭,请求被取消(使用HTTP / 2)或ServeHTTP方法返回时,将取消上下文。

当服务器取消请求上下文时,所有子上下文都将被取消。

答案 1 :(得分:0)

您可以只使用WithTimeout(),而不要同时使用两个API,因为WithTimeout()会像context.CancelFunc一样返回WithCancel(),您可以随时调用它来取消目标进程/例程。当然,取消应该在WithTimeout()设置的截止日期之前完成。

所以

  

像这样同时使用这两个API是正确的方法吗?
   甚至有必要一起使用这两个API吗?

否,不需要同时使用两者,而是由包上下文中的任何API使用返回的context.CancelFunc