mirror of
https://gitee.com/johng/gf.git
synced 2024-12-02 12:17:53 +08:00
fix(net/ghttp): server shutdown not graceful using admin api /debug/admin/shutdown
(#3777)
This commit is contained in:
parent
fd33dcb97b
commit
c4327f62e7
@ -247,7 +247,13 @@ func (s *Server) Start() error {
|
||||
|
||||
// If this is a child process, it then notifies its parent exit.
|
||||
if gproc.IsChild() {
|
||||
gtimer.SetTimeout(ctx, time.Duration(s.config.GracefulTimeout)*time.Second, func(ctx context.Context) {
|
||||
var gracefulTimeout = time.Duration(s.config.GracefulTimeout) * time.Second
|
||||
gtimer.SetTimeout(ctx, gracefulTimeout, func(ctx context.Context) {
|
||||
intlog.Printf(
|
||||
ctx,
|
||||
`pid[%d]: notice parent server graceful shuttingdown, ppid: %d`,
|
||||
gproc.Pid(), gproc.PPid(),
|
||||
)
|
||||
if err := gproc.Send(gproc.PPid(), []byte("exit"), adminGProcCommGroup); err != nil {
|
||||
intlog.Errorf(ctx, `server error in process communication: %+v`, err)
|
||||
}
|
||||
|
@ -36,8 +36,14 @@ func (p *utilAdmin) Index(r *Request) {
|
||||
<body>
|
||||
<p>Pid: {{.pid}}</p>
|
||||
<p>File Path: {{.path}}</p>
|
||||
<p><a href="{{$.uri}}/restart">Restart</a></p>
|
||||
<p><a href="{{$.uri}}/shutdown">Shutdown</a></p>
|
||||
<p>
|
||||
<a href="{{$.uri}}/restart">Restart</a>
|
||||
please make sure it is running using standalone binary not from IDE or "go run"
|
||||
</p>
|
||||
<p>
|
||||
<a href="{{$.uri}}/shutdown">Shutdown</a>
|
||||
graceful shutdown the server
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
`, data)
|
||||
@ -89,7 +95,7 @@ func (s *Server) Shutdown() error {
|
||||
// Only shut down current servers.
|
||||
// It may have multiple underlying http servers.
|
||||
for _, v := range s.servers {
|
||||
v.close(ctx)
|
||||
v.shutdown(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"github.com/gogf/gf/v2/os/gproc"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
@ -55,7 +56,10 @@ var (
|
||||
// The optional parameter `newExeFilePath` specifies the new binary file for creating process.
|
||||
func RestartAllServer(ctx context.Context, newExeFilePath string) error {
|
||||
if !gracefulEnabled {
|
||||
return gerror.NewCode(gcode.CodeInvalidOperation, "graceful reload feature is disabled")
|
||||
return gerror.NewCode(
|
||||
gcode.CodeInvalidOperation,
|
||||
"graceful reload feature is disabled",
|
||||
)
|
||||
}
|
||||
serverActionLocker.Lock()
|
||||
defer serverActionLocker.Unlock()
|
||||
@ -115,13 +119,16 @@ func checkActionFrequency() error {
|
||||
// forkReloadProcess creates a new child process and copies the fd to child process.
|
||||
func forkReloadProcess(ctx context.Context, newExeFilePath ...string) error {
|
||||
var (
|
||||
path = os.Args[0]
|
||||
binaryPath = os.Args[0]
|
||||
)
|
||||
if len(newExeFilePath) > 0 && newExeFilePath[0] != "" {
|
||||
path = newExeFilePath[0]
|
||||
binaryPath = newExeFilePath[0]
|
||||
}
|
||||
if !gfile.Exists(binaryPath) {
|
||||
return gerror.Newf(`binary file path "%s" does not exist`, binaryPath)
|
||||
}
|
||||
var (
|
||||
p = gproc.NewProcess(path, os.Args[1:], os.Environ())
|
||||
p = gproc.NewProcess(binaryPath, os.Args[1:], os.Environ())
|
||||
sfm = getServerFdMap()
|
||||
)
|
||||
for name, m := range sfm {
|
||||
@ -145,9 +152,9 @@ func forkReloadProcess(ctx context.Context, newExeFilePath ...string) error {
|
||||
buffer, _ := gjson.Encode(sfm)
|
||||
p.Env = append(p.Env, adminActionReloadEnvKey+"="+string(buffer))
|
||||
if _, err := p.Start(ctx); err != nil {
|
||||
glog.Errorf(
|
||||
intlog.Errorf(
|
||||
ctx,
|
||||
"%d: fork process failed, error:%s, %s",
|
||||
"%d: fork process failed, error: %s, %s",
|
||||
gproc.Pid(), err.Error(), string(buffer),
|
||||
)
|
||||
return err
|
||||
@ -254,7 +261,7 @@ func shutdownWebServersGracefully(ctx context.Context, signal os.Signal) {
|
||||
gproc.Pid(), signal.String(),
|
||||
)
|
||||
} else {
|
||||
glog.Printf(ctx, "%d: server gracefully shutting down by api", gproc.Pid())
|
||||
glog.Printf(ctx, "pid[%d]: server gracefully shutting down by api", gproc.Pid())
|
||||
}
|
||||
serverMapping.RLockFunc(func(m map[string]interface{}) {
|
||||
for _, v := range m {
|
||||
|
@ -230,6 +230,19 @@ type ServerConfig struct {
|
||||
SwaggerPath string `json:"swaggerPath"` // SwaggerPath specifies the swagger UI path for route registering.
|
||||
SwaggerUITemplate string `json:"swaggerUITemplate"` // SwaggerUITemplate specifies the swagger UI custom template
|
||||
|
||||
// ======================================================================================================
|
||||
// Graceful reload & shutdown.
|
||||
// ======================================================================================================
|
||||
|
||||
// Graceful enables graceful reload feature for all servers of the process.
|
||||
Graceful bool `json:"graceful"`
|
||||
|
||||
// GracefulTimeout set the maximum survival time (seconds) of the parent process.
|
||||
GracefulTimeout int `json:"gracefulTimeout"`
|
||||
|
||||
// GracefulShutdownTimeout set the maximum survival time (seconds) before stopping the server.
|
||||
GracefulShutdownTimeout int `json:"gracefulShutdownTimeout"`
|
||||
|
||||
// ======================================================================================================
|
||||
// Other.
|
||||
// ======================================================================================================
|
||||
@ -254,15 +267,6 @@ type ServerConfig struct {
|
||||
|
||||
// DumpRouterMap specifies whether automatically dumps router map when server starts.
|
||||
DumpRouterMap bool `json:"dumpRouterMap"`
|
||||
|
||||
// Graceful enables graceful reload feature for all servers of the process.
|
||||
Graceful bool `json:"graceful"`
|
||||
|
||||
// GracefulTimeout set the maximum survival time (seconds) of the parent process.
|
||||
GracefulTimeout uint8 `json:"gracefulTimeout"`
|
||||
|
||||
// GracefulShutdownTimeout set the maximum survival time (seconds) before stopping the server.
|
||||
GracefulShutdownTimeout uint8 `json:"gracefulShutdownTimeout"`
|
||||
}
|
||||
|
||||
// NewConfig creates and returns a ServerConfig object with default configurations.
|
||||
|
22
net/ghttp/ghttp_server_config_api.go
Normal file
22
net/ghttp/ghttp_server_config_api.go
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package ghttp
|
||||
|
||||
// SetSwaggerPath sets the SwaggerPath for server.
|
||||
func (s *Server) SetSwaggerPath(path string) {
|
||||
s.config.SwaggerPath = path
|
||||
}
|
||||
|
||||
// SetSwaggerUITemplate sets the Swagger template for server.
|
||||
func (s *Server) SetSwaggerUITemplate(swaggerUITemplate string) {
|
||||
s.config.SwaggerUITemplate = swaggerUITemplate
|
||||
}
|
||||
|
||||
// SetOpenApiPath sets the OpenApiPath for server.
|
||||
func (s *Server) SetOpenApiPath(path string) {
|
||||
s.config.OpenApiPath = path
|
||||
}
|
@ -27,17 +27,34 @@ func (s *Server) SetFormParsingMemory(maxMemory int64) {
|
||||
s.config.FormParsingMemory = maxMemory
|
||||
}
|
||||
|
||||
// SetSwaggerPath sets the SwaggerPath for server.
|
||||
func (s *Server) SetSwaggerPath(path string) {
|
||||
s.config.SwaggerPath = path
|
||||
// SetGraceful sets the Graceful for server.
|
||||
func (s *Server) SetGraceful(graceful bool) {
|
||||
s.config.Graceful = graceful
|
||||
// note: global setting.
|
||||
gracefulEnabled = graceful
|
||||
}
|
||||
|
||||
// SetSwaggerUITemplate sets the Swagger template for server.
|
||||
func (s *Server) SetSwaggerUITemplate(swaggerUITemplate string) {
|
||||
s.config.SwaggerUITemplate = swaggerUITemplate
|
||||
// GetGraceful returns the Graceful for server.
|
||||
func (s *Server) GetGraceful() bool {
|
||||
return s.config.Graceful
|
||||
}
|
||||
|
||||
// SetOpenApiPath sets the OpenApiPath for server.
|
||||
func (s *Server) SetOpenApiPath(path string) {
|
||||
s.config.OpenApiPath = path
|
||||
// SetGracefulTimeout sets the GracefulTimeout for server.
|
||||
func (s *Server) SetGracefulTimeout(gracefulTimeout int) {
|
||||
s.config.GracefulTimeout = gracefulTimeout
|
||||
}
|
||||
|
||||
// GetGracefulTimeout returns the GracefulTimeout for server.
|
||||
func (s *Server) GetGracefulTimeout() int {
|
||||
return s.config.GracefulTimeout
|
||||
}
|
||||
|
||||
// SetGracefulShutdownTimeout sets the GracefulShutdownTimeout for server.
|
||||
func (s *Server) SetGracefulShutdownTimeout(gracefulShutdownTimeout int) {
|
||||
s.config.GracefulShutdownTimeout = gracefulShutdownTimeout
|
||||
}
|
||||
|
||||
// GetGracefulShutdownTimeout returns the GracefulShutdownTimeout for server.
|
||||
func (s *Server) GetGracefulShutdownTimeout() int {
|
||||
return s.config.GracefulShutdownTimeout
|
||||
}
|
||||
|
@ -259,6 +259,7 @@ func (s *gracefulServer) getRawListener() net.Listener {
|
||||
}
|
||||
|
||||
// close shuts down the server forcibly.
|
||||
// for graceful shutdown, please use gracefulServer.shutdown.
|
||||
func (s *gracefulServer) close(ctx context.Context) {
|
||||
if s.status.Val() == ServerStatusStopped {
|
||||
return
|
||||
|
@ -163,3 +163,42 @@ func Test_ClientMaxBodySize_File(t *testing.T) {
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Config_Graceful(t *testing.T) {
|
||||
var (
|
||||
defaultConfig = ghttp.NewConfig()
|
||||
expect = true
|
||||
)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := g.Server(guid.S())
|
||||
t.Assert(s.GetGraceful(), defaultConfig.Graceful)
|
||||
s.SetGraceful(expect)
|
||||
t.Assert(s.GetGraceful(), expect)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Config_GracefulTimeout(t *testing.T) {
|
||||
var (
|
||||
defaultConfig = ghttp.NewConfig()
|
||||
expect = 3
|
||||
)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := g.Server(guid.S())
|
||||
t.Assert(s.GetGracefulTimeout(), defaultConfig.GracefulTimeout)
|
||||
s.SetGracefulTimeout(expect)
|
||||
t.Assert(s.GetGracefulTimeout(), expect)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Config_GracefulShutdownTimeout(t *testing.T) {
|
||||
var (
|
||||
defaultConfig = ghttp.NewConfig()
|
||||
expect = 10
|
||||
)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := g.Server(guid.S())
|
||||
t.Assert(s.GetGracefulShutdownTimeout(), defaultConfig.GracefulShutdownTimeout)
|
||||
s.SetGracefulShutdownTimeout(expect)
|
||||
t.Assert(s.GetGracefulShutdownTimeout(), expect)
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user