英文:
http.Server Serve method hangs when calling Shutdown immediately
问题
我在Go的http.Server中遇到了一个问题,我将其嵌入在一个结构体中,该结构体应该控制服务器的启动和关闭。该结构体的定义如下:
type HTTPListen struct {
Consumers []pipeline.Consumer
Cfg HTTPListenConfig
Srv *http.Server
Logger log.Logger
wg *sync.WaitGroup
mu sync.Mutex
state State
}
问题是,在我的测试代码中,我调用了结构体的Start()
方法(该方法在http.Server上运行Serve()
方法),检查了一些变量,然后调用Stop()
方法,该方法关闭服务器,然后等待http.Server退出(从Serve()
方法返回err
)。
现在,由于某种原因,当我尝试在启动后立即关闭服务器时,Serve()
方法似乎在WaitGroup.Wait()
上挂起。当我添加一个短暂的延迟(尝试了100毫秒),或者在运行带有竞争检测器的测试时,它就正常工作。
不确定是否重要,但在调用Serve()
和Shutdown()
之间没有传入的请求。
编辑:[最小示例的Playground链接][1]。如果注释掉time.Sleep
调用,程序将挂起。
下面是这两个方法的相关代码:
func (h *HTTPListen) Start() error {
h.Logger.Log("msg", "starting HTTPListen input")
addr := h.Cfg.ListenAddr
ln, err := net.Listen("tcp", addr)
if err != nil {
h.Logger.Log("msg", "failed to create listener on tcp/"+addr+": "+err.Error())
h.setState(StateFailed)
return err
}
h.wg.Add(1)
go func() {
defer h.wg.Done()
err := h.Srv.Serve(ln)
h.Logger.Log("msg", "HTTP server stopped: "+err.Error())
}()
h.setState(StateStarted)
h.Logger.Log("msg", "HTTPListen input started")
return nil
}
Stop方法:
```go
func (h *HTTPListen) Stop() error {
h.Logger.Log("msg", "stopping HTTPListen input")
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
if err := h.Srv.Shutdown(ctx); err != nil {
h.Logger.Log("msg", "HTTP server shutdown deadline expired")
}
h.wg.Wait()
h.setState(StateStopped)
h.Logger.Log("msg", "HTTPListen input stopped")
return nil
}
运行日志输出:
kwz@cyclone ~/s/stblogd> go test -v ./pkg/pipeline/input/ -run TestHTTPListen_StartStop
=== RUN TestHTTPListen_StartStop
msg="starting HTTPListen input"
msg="HTTPListen input started"
msg="stopping HTTPListen input"
... 无限期挂起
使用竞争检测器运行测试时的日志输出:
```shell
kwz@cyclone ~/s/stblogd> go test -race -v ./pkg/pipeline/input/ -run TestHTTPListen_StartStop
=== RUN TestHTTPListen_StartStop
msg="starting HTTPListen input"
msg="HTTPListen input started"
msg="stopping HTTPListen input"
msg="HTTP server stopped: http: Server closed"
msg="HTTPListen input stopped"
--- PASS: TestHTTPListen_StartStop (0.00s)
PASS
ok stblogd/pkg/pipeline/input 1.007s
我倾向于在测试中加入一个短暂的延迟,然后就可以了结了,但我想知道为什么会出现这种情况。
[1]: https://play.golang.org/p/s1jCfE44c3
<details>
<summary>英文:</summary>
I have an issue with Go's http.Server, which I'm embedding in a struct that is supposed to control the server startup and shutdown. The struct looks like this:
type HTTPListen struct {
Consumers []pipeline.Consumer
Cfg HTTPListenConfig
Srv *http.Server
Logger log.Logger
wg *sync.WaitGroup
mu sync.Mutex
state State
}
The issue is that in my test code, I call my struct's `Start()` method (which in turn runs the `Serve()` method on the http.Server), check a few vars, and then call `Stop()`, whitch `Shutdown()`s the server and then waits for the http.Server to exit (return `err` from the `Serve()` method).
Now, for some reason, the `Serve()` method seems to just hang on the `WaitGroup.Wait()`, when I try to shutdown the server immediately after starting. When I add a short pause (tried 100ms), or when running the tests with the race detector, It works just fine.
Not sure if it matters, but there are no incoming requests between calling `Serve()` and `Shutdown()`.
**EDIT:** [link to a playground minimal example][1]. If you comment out the `time.Sleep` call, the program hangs.
Here is the relevant code for the two methods:
func (h *HTTPListen) Start() error {
h.Logger.Log("msg", "starting HTTPListen input")
addr := h.Cfg.ListenAddr
ln, err := net.Listen("tcp", addr)
if err != nil {
h.Logger.Log("msg", "failed to create listener on tcp/"+addr+": "+err.Error())
h.setState(StateFailed)
return err
}
h.wg.Add(1)
go func() {
defer h.wg.Done()
err := h.Srv.Serve(ln)
h.Logger.Log("msg", "HTTP server stopped: "+err.Error())
}()
h.setState(StateStarted)
h.Logger.Log("msg", "HTTPListen input started")
return nil
}
Stop method:
func (h *HTTPListen) Stop() error {
h.Logger.Log("msg", "stopping HTTPListen input")
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
if err := h.Srv.Shutdown(ctx); err != nil {
h.Logger.Log("msg", "HTTP server shutdown deadline expired")
}
h.wg.Wait()
h.setState(StateStopped)
h.Logger.Log("msg", "HTTPListen input stopped")
return nil
}
Log output:
kwz@cyclone ~/s/stblogd> go test -v ./pkg/pipeline/input/ -run TestHTTPListen_StartStop
=== RUN TestHTTPListen_StartStop
msg="starting HTTPListen input"
msg="HTTPListen input started"
msg="stopping HTTPListen input"
... hangs indefinitely
Log output when running tests with the race detector:
kwz@cyclone ~/s/stblogd> go test -race -v ./pkg/pipeline/input/ -run TestHTTPListen_StartStop
=== RUN TestHTTPListen_StartStop
msg="starting HTTPListen input"
msg="HTTPListen input started"
msg="stopping HTTPListen input"
msg="HTTP server stopped: http: Server closed"
msg="HTTPListen input stopped"
--- PASS: TestHTTPListen_StartStop (0.00s)
PASS
ok stblogd/pkg/pipeline/input 1.007s
I'm tempted to just slap a short delay on the test and call it a day, but I would like to know why it behaves like this.
[1]: https://play.golang.org/p/s1jCfE44c3
</details>
# 答案1
**得分**: 2
这是一个已知的问题,可以参考这个帖子:
https://github.com/golang/go/issues/20239
希望他们能尽快修复,但目前似乎在你的测试中添加一个短暂的延迟是最简单的解决方案——在实际使用中可能不会经常出现这个问题,因为你不会在启动后很快触发关闭。
<details>
<summary>英文:</summary>
This is a known issue, see this thread:
https://github.com/golang/go/issues/20239
Hopefully they will fix it soon but for now it sounds like adding a short delay in your test is the simplest solution - it probably doesn't come up in real world use much because you won't trigger a shutdown so soon after starting.
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论