英文:
(goroutine leaks) http.TimeoutHandler does not kill respective ServeHTTP goroutine
问题
超时处理程序将ServeHTTP执行移动到一个新的goroutine,但无法在计时器结束后终止该goroutine。在每个请求上,它创建两个goroutine,但ServeHTTP goroutine永远不会使用上下文终止。
无法找到终止goroutine的方法。
编辑 使用time.Sleep函数的for循环表示超出我们计时器范围的大量计算。可以用任何其他函数替换它。
package main
import (
"fmt"
"io"
"net/http"
"runtime"
"time"
)
type api struct{}
func (a api) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// For-loop block represents huge computation and usually takes more time
// Can replace with any code
i := 0
for {
if i == 500 {
break
}
fmt.Printf("#goroutines: %d\n", runtime.NumGoroutine())
time.Sleep(1 * time.Second)
i++
}
_, _ = io.WriteString(w, "Hello World!")
}
func main() {
var a api
s := http.NewServeMux()
s.Handle("/", a)
h := http.TimeoutHandler(s, 1*time.Second, `Timeout`)
fmt.Printf("#goroutines: %d\n", runtime.NumGoroutine())
_ = http.ListenAndServe(":8080", h)
}
ServeHTTP goroutine应该随请求上下文一起终止,但通常情况下并不会发生。
英文:
Timeout handler moves ServeHTTP execution on a new goroutine, but not able to kill that goroutine after the timer ends. On every request, it creates two goroutines, but ServeHTTP goroutines never kill with context.
Not able to find a way to kill goroutines.
Edit For-loop with time.Sleep function, represents huge computation which goes beyond our timer. Can replace it with any other function.
package main
import (
"fmt"
"io"
"net/http"
"runtime"
"time"
)
type api struct{}
func (a api) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// For-loop block represents huge computation and usually takes more time
// Can replace with any code
i := 0
for {
if i == 500 {
break
}
fmt.Printf("#goroutines: %d\n", runtime.NumGoroutine())
time.Sleep(1 * time.Second)
i++
}
_, _ = io.WriteString(w, "Hello World!")
}
func main() {
var a api
s := http.NewServeMux()
s.Handle("/", a)
h := http.TimeoutHandler(s, 1*time.Second, `Timeout`)
fmt.Printf("#goroutines: %d\n", runtime.NumGoroutine())
_ = http.ListenAndServe(":8080", h)
}
ServeHTTP goroutine should kill along with request context, normally which does not happen.
答案1
得分: 1
使用context.Context来指示go协程中止其函数。当然,go协程必须监听这种取消事件。
所以对于你的代码,可以这样做:
ctx := req.Context() // 这将在1秒后被你的TimeoutHandler隐式取消
i := 0
for {
if i == 500 {
break
}
// 对于任何长时间等待(1秒等),始终检查上下文的状态
select {
case <-time.After(1 * time.Second): // 没有取消,所以继续执行
case <-ctx.Done():
fmt.Println("请求上下文已被取消:", ctx.Err())
return // 终止go协程
}
i++
}
Playground: https://play.golang.org/p/VEnW0vsItXm
注意: Context
被设计为可链接的 - 允许以级联方式取消多个子任务。
在典型的REST调用中,我们会发起一个数据库请求。因此,为了确保这样的阻塞和/或慢速调用能够及时完成,我们应该使用QueryContext而不是Query - 将HTTP请求的上下文作为第一个参数传入。
英文:
Use context.Context to instruct go-routines to abort their function. The go-routines, of course, have to listen for such cancelation events.
So for your code, do something like:
ctx := req.Context() // this will be implicitly canceled by your TimeoutHandler after 1s
i := 0
for {
if i == 500 {
break
}
// for any long wait (1s etc.) always check the state of your context
select {
case <-time.After(1 * time.Second): // no cancelation, so keep going
case <-ctx.Done():
fmt.Println("request context has been canceled:", ctx.Err())
return // terminates go-routine
}
i++
}
Playground: https://play.golang.org/p/VEnW0vsItXm
Note: Context
are designed to be chained - allowing for multiple levels of sub-tasks to be canceled in a cascading manner.
In a typical REST call one would initiate a database request. So, to ensure such a blocking and/or slow call completes in a timely manner, instead of using Query one should use QueryContext - passing in the http request's context as the first argument.
答案2
得分: 0
我发现,如果你没有任何方法来访问你的通道,那么在 goroutine 运行时就没有办法杀死或停止它。
在大型计算任务中,你必须在特定的时间间隔或特定任务完成后监视通道。
英文:
I found, if you do not have any way to reach to your channel then there is no way to kill or stop goroutine when it is running.
In the large computational task, you have to watch the channel on a specific interval or after specific task completion.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论