(goroutine 泄漏)http.TimeoutHandler 不会杀死相应的 ServeHTTP goroutine

(goroutine leaks) http.TimeoutHandler does not kill respective ServeHTTP goroutine

超时处理程序在新的 goroutine 上移动 ServeHTTP 执行,但无法在计时器结束后终止该 goroutine。对于每个请求,它都会创建两个 goroutine,但 ServeHTTP goroutines 永远不会用上下文杀死。

无法找到杀死 goroutines 的方法。

Edit For-loop with time.Sleep 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 应该与请求上下文一起终止,通常情况下不会发生这种情况。

使用context.Context指示go-routines中止他们的功能。 go-routines当然要监听这样的取消事件。

因此,对于您的代码,请执行以下操作:

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++
}

游乐场:https://play.golang.org/p/VEnW0vsItXm


注意: Context 被设计为链接 - 允许以级联方式取消多个级别的 sub-tasks。

在典型的 REST 调用中,一个人会发起一个数据库请求。因此,为了确保这种阻塞 and/or 慢速调用及时完成,而不是使用 Query one should use QueryContext - 将 http 请求的上下文作为第一个参数传递。

我发现,如果您无法访问您的频道,那么当 goroutine 处于 运行.

时,就无法终止或停止 goroutine

在大型计算任务中,您必须在特定时间间隔或特定任务完成后观看频道。