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

时间:2019-06-26 17:13:10

标签: go httphandler goroutine timeoutexception

超时处理程序将ServeHTTP执行移到新的goroutine上,但是在计时器结束后无法终止该goroutine。在每个请求上,它都会创建两个goroutine,但是ServeHTTP goroutine永远不会因上下文而终止。

无法找到杀死goroutine的方法。

编辑带时间的循环。睡眠功能代表了超出我们计时器的巨大计算量。可以用其他任何功能替换它。

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应该与请求上下文一起杀死,通常不会发生。

2 个答案:

答案 0 :(得分:1)

使用context.Context指示go-routine终止其功能。当然,例程必须侦听此类取消事件。

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

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设计为可链接的-允许以级联方式取消多个级别的子任务。

在典型的REST调用中,将启动数据库请求。因此,为了确保及时完成这种阻塞和/或缓慢的调用,应该使用Query而不是使用QueryContext-将http请求的上下文作为第一个参数传递。

答案 1 :(得分:0)

我发现,如果您无法访问频道,则无法在运行goroutine时终止它。

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