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 }