我试图实现go服务器的正常关闭,如本博文http://grisha.org/blog/2014/06/03/graceful-restart-in-golang/中所述。主要部分如下。
自定义侦听器:
var httpWg sync.WaitGroup // initialised in the other part
type gracefulListener struct {
net.Listener
stop chan error
stopped bool
}
func newGracefulListener(l net.Listener) (gl *gracefulListener) {
gl = &gracefulListener{Listener: l, stop: make(chan error)}
go func() {
_ = <-gl.stop
gl.stopped = true
gl.stop <- gl.Listener.Close()
}()
return
}
func (gl *gracefulListener) Accept() (c net.Conn, err error) {
c, err = gl.Listener.Accept()
if err != nil {
return
}
c = gracefulConn{Conn: c} // wrap using our custom connection
httpWg.Add(1) // increase the counter
return
}
func (gl *gracefulListener) Close() error {
if gl.stopped {
return syscall.EINVAL
}
gl.stop <- nil
return <-gl.stop
}
func (gl *gracefulListener) File() *os.File {
tl := gl.Listener.(*net.TCPListener)
fl, _ := tl.File()
return fl
}
Custom Conn:
type gracefulConn struct {
net.Conn
}
func (w gracefulConn) Close() error {
httpWg.Done() // <- panics sometimes
return w.Conn.Close()
}
这个想法是当程序收到SIGTERM时,它会停止提供新连接,只是等待现有连接的httpWg.Wait()
完成。
这种方法在本地工作,但是当我部署它时,有时我会在gracefulConn.Close()
httpWg.Done()
行感到恐慌:
panic: sync: negative WaitGroup counter
恐慌不是在我停止服务器而是在例行服务期间发生的。
怎么可能有Close()
次呼叫再Accept()
次呼叫?或者我错过了什么?
P.S。我尝试将stopped
属性和互斥锁添加到gracefullConn
,因此在Close
中它会锁定互斥锁并检查stopped
以确保我们只停止它一次。但是,我仍然受到同样的恐慌。
答案 0 :(得分:2)
Close()
可以多次调用,因此您肯定需要在func (w gracefulConn) Close() error
中检查该内容。
P.S。我试图将已停止的属性和互斥锁添加到gracefullConn,因此在关闭时它会锁定互斥锁并检查已停止以确保我们只停止它一次。但是,我仍然受到同样的恐慌。
请记住gracefulConn
如果作为值而不是引用传递,那么任何互斥锁/标志都不会按预期工作。因此,请务必将c = gracefulConn{Conn: c}
变为c = &gracefulConn{Conn: c}
。
答案 1 :(得分:1)
我认为这是错误的方法。
你真的不关心连接是否开放 - 持久连接怎么样?
您关心的是您是否正在积极使用这些连接。您最好将WaitGroup
放入我认为的ServeMux
方法的自定义ServeHTTP
中。然后,您可以在函数调用周围使用WaitGroup
。