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
}
问题在于,在我的测试代码中,我调用了我的struct Start()
方法(后者又在http.Server上运行Serve()
方法),查看几个vars,然后调用Stop()
,等待Shutdown()
服务器,然后等待http.Server退出(从err
方法返回Serve()
。)
现在,出于某种原因,当我尝试在启动后立即关闭服务器时,Serve()
方法似乎只挂在WaitGroup.Wait()
上。当我添加一个短暂的暂停(尝试100毫秒),或者使用竞赛检测器运行测试时,它运行正常。
不确定是否重要,但在致电Serve()
和Shutdown()
之间没有传入请求。
编辑: link to a playground minimal example。如果您注释掉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
}
停止方法:
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"
... hangs indefinitely
使用竞争检测器运行测试时记录输出:
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
我很想在测试中短暂延迟并将其称为一天,但我想知道为什么它会像这样。
答案 0 :(得分:2)
这是一个已知问题,请参阅此主题:
https://github.com/golang/go/issues/20239
希望他们能尽快解决这个问题但是现在听起来像是在测试中添加一个短暂的延迟是最简单的解决方案 - 它可能不会在现实世界中使用很多,因为你不会在很快就会触发关机开始。