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