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