为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
红糖糍粑 2022-07-11 14:51:57
由于种种原因,我觉得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 mainimport (    "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完成的正确、干净、惯用的方法是什么?
查看完整描述

2 回答

?
沧海一幻觉

TA贡献1824条经验 获得超5个赞

要在主 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() {

    fmt.Println("Starting server...")

    err := server.Serve(listener)

    if err != nil && err != http.ErrServerClosed {

        log.Fatal(err)

    }

}()


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()为使用截止日期创建的上下文。


查看完整回答
反对 回复 2022-07-11
?
撒科打诨

TA贡献1934条经验 获得超2个赞

理论上,我可以通过调用 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

// properly

func startHTTPServer(listener net.Listener, handler http.Handler) (stop chan bool, problems chan error) {

    stop, problems = make(chan bool), make(chan error)

    server := &http.Server{Handler: handler} // TODO: set timeouts


    go func() {

        fmt.Println("Starting server...")

        err := server.Serve(listener)

        if err != http.ErrServerClosed {

            problems <- err // TODO: tag/classify as startup error

        }

    }()

    go func() {

        select {

        case <-stop:

            fmt.Println("Stop channel closed; stopping server...")

            err := server.Shutdown(context.TODO())

            fmt.Println("Stopped.")

            problems <- err // TODO: tag/classify as shutdown error

        case e := <-problems:

            problems <- e  // resend, this error  is not for us

            return // stop waiting for stop as server did not start anyway.

        }

    }()

    return stop, problems

}


其他解决方案也是可能的,例如返回单独的启动和关闭错误通道等。


查看完整回答
反对 回复 2022-07-11
  • 2 回答
  • 0 关注
  • 117 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信