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 }