传递什么上下文来等待某事完成?

由于种种原因,我觉得http.ListenAndServe不适合我的需要。


我需要能够确定绑定的地址和端口(即使用时":0"),所以我引入了一个net.Listener,读取listener.Addr()然后传递给http.Serve(listener, nil)。


然后我需要能够使用不同的 URL 处理程序运行两个 HTTP 服务器,所以我引入了一个http.NewServeMux(),添加了必要的mux.HandleFunc("/path", fn)处理程序,并传递为http.Serve(listener, mux).


然后我需要能够干净地停止这些服务器,并关闭任何连接,独立于主程序本身,所以现在我介绍了&http.Server{Handler: mux}哪些我可以go func() { server.Serve(listener) }()。


理论上,我可以通过调用来阻止它server.Shutdown(ctx),但现在import "context"似乎没有一个可用的上下文提供我想要的。我希望能够等到干净关闭完成,然后继续我的代码。


我的理解是我应该能够<- ctx.Done()实现这一点,但我已经尝试了两者context.Background(),context.TODO()而且似乎都没有“触发” ctx.Done(),我最终永远阻塞了。其他context选项似乎是基于时间的。


如果我不等待或通过,似乎完成得太快,我看不到任何东西实际上是关闭的()nilserver.Shutdown(ctx)runtime.Numgoroutine() != 1


我可以time.Sleep(duration)任意持续一段时间,但我不想要任意持续时间。我想知道server.Shutdown已经干净地完成了。


package main


import (

    "fmt"

    "net"

    "net/http"

    "runtime"

    "time"

)


func main() {

    var err error


    listener, err := net.Listen("tcp", "localhost:0")

    fmt.Printf("Listening on http://%v\n", listener.Addr())


    mux := http.NewServeMux()

    mux.HandleFunc("/", handleIndex)


    stop, err := startHTTPServer(listener, mux)


    d, _ := time.ParseDuration("5s")


    time.Sleep(d)   // delay here just for example of "long-running" server

    close(stop)     // closing the channel returned by my helper should trigger shutdown

    time.Sleep(d)   // if this delay is here, I see the "Stopped" message


    if err != nil {

        panic(err)

    }


    fmt.Printf("End of program, active goroutines: %v", runtime.NumGoroutine())

}


我都试过了context.Background()和context.TODO()。我试过new(context.Context)了,但那抛出了一个SIGSEGV. 我试过nil了,根本不用等。


我尝试添加 a sync.WaitGroupand 调用wg.Wait()而不是 second time.Sleep(d),但我仍然需要等到server.Shutdown()完成后再调用wg.Done()(并且defer wg.Done()调用它太早了)。


我觉得,对于 Contexts、WaitGroups 等,我只是在代码中添加了一些杂乱无章的东西,而没有真正理解为什么它们是必要的。


等待server.Shutdown完成的正确、干净、惯用的方法是什么?


红糖糍粑
浏览 193回答 2
2回答

沧海一幻觉

要在主 goroutine 中等待 Shutdown 完成,请从该 goroutine 调用 Shutdown。消除通道和额外的 goroutine。listener, _ := net.Listen("tcp", "localhost:0")fmt.Printf("Listening on http://%v\n", listener.Addr())mux := http.NewServeMux()mux.HandleFunc("/", handleIndex)server := &http.Server{Handler: mux}go func() {&nbsp; &nbsp; fmt.Println("Starting server...")&nbsp; &nbsp; err := server.Serve(listener)&nbsp; &nbsp; if err != nil && err != http.ErrServerClosed {&nbsp; &nbsp; &nbsp; &nbsp; log.Fatal(err)&nbsp; &nbsp; }}()time.Sleep(5 * time.Second) // delay here just for example of "long-running" server// Shutdown and wait for server to complete.server.Shutdown(context.Background())如果要限制 Shutdown 等待服务器关闭的时间,请替换context.Background()为使用截止日期创建的上下文。

撒科打诨

理论上,我可以通过调用 server.Shutdown(ctx) 来阻止它,但现在 import "context" 中的任何可用上下文似乎都不能提供我想要的。我希望能够等到干净关闭完成,然后继续我的代码。这意味着您应该传递一个不会超时的上下文。既不context.Background()也不context.TODO()超时,因此是合适的(真的,见下文)。您是否使用其中一个取决于您是否计划超时关闭(您应该防止粗糙的客户端阻止您关闭您的服务器)。我的理解是我应该能够 <- ctx.Done() 来实现这一点,但我已经尝试了 context.Background() 和 context.TODO() 并且似乎都没有“触发”ctx.Done(),我最终永远阻塞了。其他上下文选项似乎是基于时间的。嗯,这个是错的。也context.Background()不会context.TODO()关闭它们Done,因为它们不会超时。但是不需要 等待 done: 是一个正常的函数,一旦服务器实际正确关闭(这就是你想要的)或上下文超时,它就会返回。无论如何只是返回。server.Shutdownserver.Shutdown一个简单的server.Shutdown(context.TODO())是你想要的。(现在,从长远来看:传递一个在很长但有限的时间后超时的上下文。)但是无论如何,您的代码看起来都很可疑:您的 func startHTTPServer 没有正确处理错误:不是启动错误,也不是停止错误。如果您的服务器没有启动,您将无法停止它,您的代码只会吞下错误。犯错也很活泼。您的问题可能不是来自传递给 server.Shutdown 的上下文,而是来自其他地方。问题是你的代码没有等待 server.Shutdown 返回,因为这个函数是在一个 goroutine 中启动的,没有任何同步回 startHTTPServer 的调用者:不要那样做。真的: server.Shutdown 的上下文不是问题。以下是未经测试的代码,它稍微克服了这些问题。正如您在所有 TOOD 中看到的那样,它还没有准备好生产。// startHTTPServer is a helper function to start a server and returns// a channel to stop the server and a channel reporting errors during// starting/stopping the server or nil if the server was shut down// properlyfunc startHTTPServer(listener net.Listener, handler http.Handler) (stop chan bool, problems chan error) {&nbsp; &nbsp; stop, problems = make(chan bool), make(chan error)&nbsp; &nbsp; server := &http.Server{Handler: handler} // TODO: set timeouts&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println("Starting server...")&nbsp; &nbsp; &nbsp; &nbsp; err := server.Serve(listener)&nbsp; &nbsp; &nbsp; &nbsp; if err != http.ErrServerClosed {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; problems <- err // TODO: tag/classify as startup error&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }()&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; case <-stop:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Println("Stop channel closed; stopping server...")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; err := server.Shutdown(context.TODO())&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Println("Stopped.")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; problems <- err // TODO: tag/classify as shutdown error&nbsp; &nbsp; &nbsp; &nbsp; case e := <-problems:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; problems <- e&nbsp; // resend, this error&nbsp; is not for us&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return // stop waiting for stop as server did not start anyway.&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }()&nbsp; &nbsp; return stop, problems}其他解决方案也是可能的,例如返回单独的启动和关闭错误通道等。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go