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 }