获取了beego(v1.8.2)版本的代码,发现grace(beego)在新进程运行成功之前,会保持旧进程的正常工作,这段期间用户依旧在请求旧进程;当新进程运行成功之后,会关闭旧进程,使用新进程来处理新的请求。这两种情况下不存在问题,但是如果用户请求旧进程没有处理完,但是新进程已经启动成功,还是会直接切换到新进程,这时就会出现旧进程未处理完请求直接退出的情况。
+1 grace模块的实现是有问题的。。
使用的process.Kill()关闭子进程。。。
看到一篇博客将err = process.Kill()修改为err = process.Signal(os.Interrupt),旧进程不会在新进程启动成功后关闭链接,但这会导致旧进程一直接受请求而无法退出,正在寻找原因和替代办法。
旧进程无法退出是因为超时函数那里,那个计数不准,一直被阻塞。
改动如下:
// serverTimeout forces the server to shutdown in a given timeout - whether it
// finished outstanding requests or not. if Read/WriteTimeout are not set or the
// max header size is very big a connection could hang
func (srv *Server) serverTimeout(d time.Duration) {
defer func() {
if r := recover(); r != nil {
log.Println("WaitGroup at 0", r)
}
}()
if srv.state != StateShuttingDown {
return
}
time.Sleep(d)
log.Println("[STOP - Hammer Time] Forcefully shutting down parent")
//直接杀死自己这个进程by adam modify
process, err := os.FindProcess(os.Getpid())
if err != nil {
log.Println("get self pid error: ", err)
} else {
err = process.Kill()
if err != nil {
log.Println("kill self process error: ", err)
}
}
/*
for {
if srv.state == StateTerminate {
break
}
srv.wg.Done()
}
*/
}
另外,listener.go文件里面:
func (gl graceListener) Accept() (c net.Conn, err error) {
tc, err := gl.Listener.(net.TCPListener).AcceptTCP()
if err != nil {
return
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(3 * time.Minute)
c = graceConn{
Conn: tc,
server: gl.server,
}
//防止wg.Add(1)在计数器意外被done为负数情况下panic,by adam modify
defer func() {
if r := recover(); r != nil {
log.Println("WaitGroup <= -2 ", r)
}
}()
gl.server.wg.Add(1)
return
}
楼上这样修改后,可以解决grace(beego)超时依然无法正常结束旧进程的问题,但我觉得还是没有达到grace本来期望。我需要的是平滑的升级HTTP服务,新进程运行成功后,旧进程继续处理未完成的请求,同时新进程处理新到来的请求,当旧进程上的请求处理完成以后,关闭旧进程。
修改后的grace(beego)的现状:旧进程处理完全部请求后,依旧没有退出,而是等待超时后才退出。期望:旧进程处理完请求后,即可退出。
通过在grace(beego)中添加golang 1.8的新特性server.Shutdown(gracefullly exit)。server.Shutdown首先关闭所有active Listeners和所有处于idle状态的Connections,然后无限等待那些处于active状态的connection变为idle状态后,关闭它们并server退出。这能有效的帮助grace(beego)关闭链接,解决旧进程处理完成后不退出的问题。
@hihcys 但这样就失去了旧版本支持了。。。可能需要做兼容处理?
来一发PR我们围观一下。。。(我本来做了一下,但是发现好啰嗦。。。
现在只做了简单的处理,因为对Golang1.8版本的兼容的问题,就不PR了。
1). 进程直接退出的修改
grace/server.go
原来:
err = process.Kill()
改为:
err = process.Signal(os.Interrupt)
2). 4楼(AdamGuan)的超时函数的修改
省略......(见4楼)
3). 旧进程依旧接受连接的修改
原来:
grace/server.go
case syscall.SIGTERM:
log.Println(pid, "Received SIGTERM.")
srv.shutdown()
````
改为:
``` Bash
case syscall.SIGTERM:
log.Println(pid, "Received SIGTERM.")
ctx, _ := context.WithTimeout(context.Background(), 30*time.Second)
srv.Shutdown(ctx)
srv.shutdown()
这样改动后还是存在Bug,如果同时启动HTTP和HTTPS。发送SIGTERM信号平滑退出Server时,进程会收到两个SIGTERM信号,导致进程直接退出,目前还没时间去解决。
@hihcys 我遇到的问题是 如果同时启动HTTP和HTTPS。平滑启动会导致旧连接不继续处理。关闭https,旧连接就会处理
Most helpful comment
现在只做了简单的处理,因为对Golang1.8版本的兼容的问题,就不PR了。
1). 进程直接退出的修改
grace/server.go
原来:
改为:
2). 4楼(AdamGuan)的超时函数的修改
3). 旧进程依旧接受连接的修改
原来:
grace/server.go
这样改动后还是存在Bug,如果同时启动HTTP和HTTPS。发送SIGTERM信号平滑退出Server时,进程会收到两个SIGTERM信号,导致进程直接退出,目前还没时间去解决。