现在我做了类似的事情:
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 request
和deliberately make the request exceed the deadline
,两者都工作正常(我的意思是可以按预期接收到取消信号和超时信号),我关心的是:后者{{1} } function将覆盖cancel
返回的前一个,所以:
请帮忙解释一下。
答案 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
。