github.com/wangyougui/gf/v2@v2.6.5/net/ghttp/ghttp_server_admin_process.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/wangyougui/gf.
     6  
     7  package ghttp
     8  
     9  import (
    10  	"bytes"
    11  	"context"
    12  	"fmt"
    13  	"os"
    14  	"runtime"
    15  	"strings"
    16  	"sync"
    17  	"time"
    18  
    19  	"github.com/wangyougui/gf/v2/container/gtype"
    20  	"github.com/wangyougui/gf/v2/encoding/gjson"
    21  	"github.com/wangyougui/gf/v2/errors/gcode"
    22  	"github.com/wangyougui/gf/v2/errors/gerror"
    23  	"github.com/wangyougui/gf/v2/internal/intlog"
    24  	"github.com/wangyougui/gf/v2/os/glog"
    25  	"github.com/wangyougui/gf/v2/os/gproc"
    26  	"github.com/wangyougui/gf/v2/os/gtime"
    27  	"github.com/wangyougui/gf/v2/os/gtimer"
    28  	"github.com/wangyougui/gf/v2/text/gstr"
    29  	"github.com/wangyougui/gf/v2/util/gconv"
    30  )
    31  
    32  const (
    33  	// Allow executing management command after server starts after this interval in milliseconds.
    34  	adminActionIntervalLimit = 2000
    35  	adminActionNone          = 0
    36  	adminActionRestarting    = 1
    37  	adminActionShuttingDown  = 2
    38  	adminActionReloadEnvKey  = "GF_SERVER_RELOAD"
    39  	adminActionRestartEnvKey = "GF_SERVER_RESTART"
    40  	adminGProcCommGroup      = "GF_GPROC_HTTP_SERVER"
    41  )
    42  
    43  var (
    44  	// serverActionLocker is the locker for server administration operations.
    45  	serverActionLocker sync.Mutex
    46  
    47  	// serverActionLastTime is timestamp in milliseconds of last administration operation.
    48  	serverActionLastTime = gtype.NewInt64(gtime.TimestampMilli())
    49  
    50  	// serverProcessStatus is the server status for operation of current process.
    51  	serverProcessStatus = gtype.NewInt()
    52  )
    53  
    54  // RestartAllServer restarts all the servers of the process gracefully.
    55  // The optional parameter `newExeFilePath` specifies the new binary file for creating process.
    56  func RestartAllServer(ctx context.Context, newExeFilePath string) error {
    57  	if !gracefulEnabled {
    58  		return gerror.NewCode(gcode.CodeInvalidOperation, "graceful reload feature is disabled")
    59  	}
    60  	serverActionLocker.Lock()
    61  	defer serverActionLocker.Unlock()
    62  	if err := checkProcessStatus(); err != nil {
    63  		return err
    64  	}
    65  	if err := checkActionFrequency(); err != nil {
    66  		return err
    67  	}
    68  	return restartWebServers(ctx, nil, newExeFilePath)
    69  }
    70  
    71  // ShutdownAllServer shuts down all servers of current process gracefully.
    72  func ShutdownAllServer(ctx context.Context) error {
    73  	serverActionLocker.Lock()
    74  	defer serverActionLocker.Unlock()
    75  	if err := checkProcessStatus(); err != nil {
    76  		return err
    77  	}
    78  	if err := checkActionFrequency(); err != nil {
    79  		return err
    80  	}
    81  	shutdownWebServersGracefully(ctx, nil)
    82  	return nil
    83  }
    84  
    85  // checkProcessStatus checks the server status of current process.
    86  func checkProcessStatus() error {
    87  	status := serverProcessStatus.Val()
    88  	if status > 0 {
    89  		switch status {
    90  		case adminActionRestarting:
    91  			return gerror.NewCode(gcode.CodeInvalidOperation, "server is restarting")
    92  
    93  		case adminActionShuttingDown:
    94  			return gerror.NewCode(gcode.CodeInvalidOperation, "server is shutting down")
    95  		}
    96  	}
    97  	return nil
    98  }
    99  
   100  // checkActionFrequency checks the operation frequency.
   101  // It returns error if it is too frequency.
   102  func checkActionFrequency() error {
   103  	interval := gtime.TimestampMilli() - serverActionLastTime.Val()
   104  	if interval < adminActionIntervalLimit {
   105  		return gerror.NewCodef(
   106  			gcode.CodeInvalidOperation,
   107  			"too frequent action, please retry in %d ms",
   108  			adminActionIntervalLimit-interval,
   109  		)
   110  	}
   111  	serverActionLastTime.Set(gtime.TimestampMilli())
   112  	return nil
   113  }
   114  
   115  // forkReloadProcess creates a new child process and copies the fd to child process.
   116  func forkReloadProcess(ctx context.Context, newExeFilePath ...string) error {
   117  	var (
   118  		path = os.Args[0]
   119  	)
   120  	if len(newExeFilePath) > 0 && newExeFilePath[0] != "" {
   121  		path = newExeFilePath[0]
   122  	}
   123  	var (
   124  		p   = gproc.NewProcess(path, os.Args, os.Environ())
   125  		sfm = getServerFdMap()
   126  	)
   127  	for name, m := range sfm {
   128  		for fdk, fdv := range m {
   129  			if len(fdv) > 0 {
   130  				s := ""
   131  				for _, item := range gstr.SplitAndTrim(fdv, ",") {
   132  					array := strings.Split(item, "#")
   133  					fd := uintptr(gconv.Uint(array[1]))
   134  					if fd > 0 {
   135  						s += fmt.Sprintf("%s#%d,", array[0], 3+len(p.ExtraFiles))
   136  						p.ExtraFiles = append(p.ExtraFiles, os.NewFile(fd, ""))
   137  					} else {
   138  						s += fmt.Sprintf("%s#%d,", array[0], 0)
   139  					}
   140  				}
   141  				sfm[name][fdk] = strings.TrimRight(s, ",")
   142  			}
   143  		}
   144  	}
   145  	buffer, _ := gjson.Encode(sfm)
   146  	p.Env = append(p.Env, adminActionReloadEnvKey+"="+string(buffer))
   147  	if _, err := p.Start(ctx); err != nil {
   148  		glog.Errorf(
   149  			ctx,
   150  			"%d: fork process failed, error:%s, %s",
   151  			gproc.Pid(), err.Error(), string(buffer),
   152  		)
   153  		return err
   154  	}
   155  	return nil
   156  }
   157  
   158  // forkRestartProcess creates a new server process.
   159  func forkRestartProcess(ctx context.Context, newExeFilePath ...string) error {
   160  	var (
   161  		path = os.Args[0]
   162  	)
   163  	if len(newExeFilePath) > 0 && newExeFilePath[0] != "" {
   164  		path = newExeFilePath[0]
   165  	}
   166  	if err := os.Unsetenv(adminActionReloadEnvKey); err != nil {
   167  		intlog.Errorf(ctx, `%+v`, err)
   168  	}
   169  	env := os.Environ()
   170  	env = append(env, adminActionRestartEnvKey+"=1")
   171  	p := gproc.NewProcess(path, os.Args, env)
   172  	if _, err := p.Start(ctx); err != nil {
   173  		glog.Errorf(
   174  			ctx,
   175  			`%d: fork process failed, error:%s, are you running using "go run"?`,
   176  			gproc.Pid(), err.Error(),
   177  		)
   178  		return err
   179  	}
   180  	return nil
   181  }
   182  
   183  // getServerFdMap returns all the servers name to file descriptor mapping as map.
   184  func getServerFdMap() map[string]listenerFdMap {
   185  	sfm := make(map[string]listenerFdMap)
   186  	serverMapping.RLockFunc(func(m map[string]interface{}) {
   187  		for k, v := range m {
   188  			sfm[k] = v.(*Server).getListenerFdMap()
   189  		}
   190  	})
   191  	return sfm
   192  }
   193  
   194  // bufferToServerFdMap converts binary content to fd map.
   195  func bufferToServerFdMap(buffer []byte) map[string]listenerFdMap {
   196  	sfm := make(map[string]listenerFdMap)
   197  	if len(buffer) > 0 {
   198  		j, _ := gjson.LoadContent(buffer)
   199  		for k := range j.Var().Map() {
   200  			m := make(map[string]string)
   201  			for mapKey, mapValue := range j.Get(k).MapStrStr() {
   202  				m[mapKey] = mapValue
   203  			}
   204  			sfm[k] = m
   205  		}
   206  	}
   207  	return sfm
   208  }
   209  
   210  // restartWebServers restarts all servers.
   211  func restartWebServers(ctx context.Context, signal os.Signal, newExeFilePath string) error {
   212  	serverProcessStatus.Set(adminActionRestarting)
   213  	if runtime.GOOS == "windows" {
   214  		if signal != nil {
   215  			// Controlled by signal.
   216  			forceCloseWebServers(ctx)
   217  			if err := forkRestartProcess(ctx, newExeFilePath); err != nil {
   218  				intlog.Errorf(ctx, `%+v`, err)
   219  			}
   220  			return nil
   221  		}
   222  		// Controlled by web page.
   223  		// It should ensure the response wrote to client and then close all servers gracefully.
   224  		gtimer.SetTimeout(ctx, time.Second, func(ctx context.Context) {
   225  			forceCloseWebServers(ctx)
   226  			if err := forkRestartProcess(ctx, newExeFilePath); err != nil {
   227  				intlog.Errorf(ctx, `%+v`, err)
   228  			}
   229  		})
   230  		return nil
   231  	}
   232  	if err := forkReloadProcess(ctx, newExeFilePath); err != nil {
   233  		glog.Printf(ctx, "%d: server restarts failed", gproc.Pid())
   234  		serverProcessStatus.Set(adminActionNone)
   235  		return err
   236  	} else {
   237  		if signal != nil {
   238  			glog.Printf(ctx, "%d: server restarting by signal: %s", gproc.Pid(), signal)
   239  		} else {
   240  			glog.Printf(ctx, "%d: server restarting by web admin", gproc.Pid())
   241  		}
   242  	}
   243  
   244  	return nil
   245  }
   246  
   247  // shutdownWebServersGracefully gracefully shuts down all servers.
   248  func shutdownWebServersGracefully(ctx context.Context, signal os.Signal) {
   249  	serverProcessStatus.Set(adminActionShuttingDown)
   250  	if signal != nil {
   251  		glog.Printf(
   252  			ctx,
   253  			"%d: server gracefully shutting down by signal: %s",
   254  			gproc.Pid(), signal.String(),
   255  		)
   256  	} else {
   257  		glog.Printf(ctx, "%d: server gracefully shutting down by api", gproc.Pid())
   258  	}
   259  	serverMapping.RLockFunc(func(m map[string]interface{}) {
   260  		for _, v := range m {
   261  			server := v.(*Server)
   262  			server.doServiceDeregister()
   263  			for _, s := range server.servers {
   264  				s.shutdown(ctx)
   265  			}
   266  		}
   267  	})
   268  }
   269  
   270  // forceCloseWebServers forced shuts down all servers.
   271  func forceCloseWebServers(ctx context.Context) {
   272  	serverMapping.RLockFunc(func(m map[string]interface{}) {
   273  		for _, v := range m {
   274  			for _, s := range v.(*Server).servers {
   275  				s.close(ctx)
   276  			}
   277  		}
   278  	})
   279  }
   280  
   281  // handleProcessMessage receives and handles the message from processes,
   282  // which are commonly used for graceful reloading feature.
   283  func handleProcessMessage() {
   284  	var (
   285  		ctx = context.TODO()
   286  	)
   287  	for {
   288  		if msg := gproc.Receive(adminGProcCommGroup); msg != nil {
   289  			if bytes.EqualFold(msg.Data, []byte("exit")) {
   290  				intlog.Printf(ctx, "%d: process message: exit", gproc.Pid())
   291  				shutdownWebServersGracefully(ctx, nil)
   292  				allShutdownChan <- struct{}{}
   293  				intlog.Printf(ctx, "%d: process message: exit done", gproc.Pid())
   294  				return
   295  			}
   296  		}
   297  	}
   298  }