Skip to content

Commit

Permalink
fix(net/ghttp): server shutdown not graceful using admin api `/debug/…
Browse files Browse the repository at this point in the history
…admin/shutdown` (#3777)
  • Loading branch information
gqcn authored Sep 19, 2024
1 parent fd33dcb commit c4327f6
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 29 deletions.
8 changes: 7 additions & 1 deletion net/ghttp/ghttp_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
12 changes: 9 additions & 3 deletions net/ghttp/ghttp_server_admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 changes: 14 additions & 7 deletions net/ghttp/ghttp_server_admin_process.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
22 changes: 13 additions & 9 deletions net/ghttp/ghttp_server_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
// ======================================================================================================
Expand All @@ -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.
Expand Down
22 changes: 22 additions & 0 deletions net/ghttp/ghttp_server_config_api.go
Original file line number Diff line number Diff line change
@@ -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
}
35 changes: 26 additions & 9 deletions net/ghttp/ghttp_server_config_mess.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
1 change: 1 addition & 0 deletions net/ghttp/ghttp_server_graceful.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
39 changes: 39 additions & 0 deletions net/ghttp/ghttp_z_unit_feature_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
}

0 comments on commit c4327f6

Please sign in to comment.