code.gitea.io/gitea@v1.19.3/modules/graceful/manager.go (about) 1 // Copyright 2019 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package graceful 5 6 import ( 7 "context" 8 "runtime/pprof" 9 "sync" 10 "time" 11 12 "code.gitea.io/gitea/modules/log" 13 "code.gitea.io/gitea/modules/process" 14 "code.gitea.io/gitea/modules/setting" 15 ) 16 17 type state uint8 18 19 const ( 20 stateInit state = iota 21 stateRunning 22 stateShuttingDown 23 stateTerminate 24 ) 25 26 // There are some places that could inherit sockets: 27 // 28 // * HTTP or HTTPS main listener 29 // * HTTP or HTTPS install listener 30 // * HTTP redirection fallback 31 // * Builtin SSH listener 32 // 33 // If you add an additional place you must increment this number 34 // and add a function to call manager.InformCleanup if it's not going to be used 35 const numberOfServersToCreate = 4 36 37 // Manager represents the graceful server manager interface 38 var manager *Manager 39 40 var initOnce = sync.Once{} 41 42 // GetManager returns the Manager 43 func GetManager() *Manager { 44 InitManager(context.Background()) 45 return manager 46 } 47 48 // InitManager creates the graceful manager in the provided context 49 func InitManager(ctx context.Context) { 50 initOnce.Do(func() { 51 manager = newGracefulManager(ctx) 52 53 // Set the process default context to the HammerContext 54 process.DefaultContext = manager.HammerContext() 55 }) 56 } 57 58 // WithCallback is a runnable to call when the caller has finished 59 type WithCallback func(callback func()) 60 61 // RunnableWithShutdownFns is a runnable with functions to run at shutdown and terminate 62 // After the callback to atShutdown is called and is complete, the main function must return. 63 // Similarly the callback function provided to atTerminate must return once termination is complete. 64 // Please note that use of the atShutdown and atTerminate callbacks will create go-routines that will wait till their respective signals 65 // - users must therefore be careful to only call these as necessary. 66 type RunnableWithShutdownFns func(atShutdown, atTerminate func(func())) 67 68 // RunWithShutdownFns takes a function that has both atShutdown and atTerminate callbacks 69 // After the callback to atShutdown is called and is complete, the main function must return. 70 // Similarly the callback function provided to atTerminate must return once termination is complete. 71 // Please note that use of the atShutdown and atTerminate callbacks will create go-routines that will wait till their respective signals 72 // - users must therefore be careful to only call these as necessary. 73 func (g *Manager) RunWithShutdownFns(run RunnableWithShutdownFns) { 74 g.runningServerWaitGroup.Add(1) 75 defer g.runningServerWaitGroup.Done() 76 defer func() { 77 if err := recover(); err != nil { 78 log.Critical("PANIC during RunWithShutdownFns: %v\nStacktrace: %s", err, log.Stack(2)) 79 g.doShutdown() 80 } 81 }() 82 run(func(atShutdown func()) { 83 g.lock.Lock() 84 defer g.lock.Unlock() 85 g.toRunAtShutdown = append(g.toRunAtShutdown, 86 func() { 87 defer func() { 88 if err := recover(); err != nil { 89 log.Critical("PANIC during RunWithShutdownFns: %v\nStacktrace: %s", err, log.Stack(2)) 90 g.doShutdown() 91 } 92 }() 93 atShutdown() 94 }) 95 }, func(atTerminate func()) { 96 g.RunAtTerminate(atTerminate) 97 }) 98 } 99 100 // RunWithShutdownContext takes a function that has a context to watch for shutdown. 101 // After the provided context is Done(), the main function must return once shutdown is complete. 102 // (Optionally the HammerContext may be obtained and waited for however, this should be avoided if possible.) 103 func (g *Manager) RunWithShutdownContext(run func(context.Context)) { 104 g.runningServerWaitGroup.Add(1) 105 defer g.runningServerWaitGroup.Done() 106 defer func() { 107 if err := recover(); err != nil { 108 log.Critical("PANIC during RunWithShutdownContext: %v\nStacktrace: %s", err, log.Stack(2)) 109 g.doShutdown() 110 } 111 }() 112 ctx := g.ShutdownContext() 113 pprof.SetGoroutineLabels(ctx) // We don't have a label to restore back to but I think this is fine 114 run(ctx) 115 } 116 117 // RunAtTerminate adds to the terminate wait group and creates a go-routine to run the provided function at termination 118 func (g *Manager) RunAtTerminate(terminate func()) { 119 g.terminateWaitGroup.Add(1) 120 g.lock.Lock() 121 defer g.lock.Unlock() 122 g.toRunAtTerminate = append(g.toRunAtTerminate, 123 func() { 124 defer g.terminateWaitGroup.Done() 125 defer func() { 126 if err := recover(); err != nil { 127 log.Critical("PANIC during RunAtTerminate: %v\nStacktrace: %s", err, log.Stack(2)) 128 } 129 }() 130 terminate() 131 }) 132 } 133 134 // RunAtShutdown creates a go-routine to run the provided function at shutdown 135 func (g *Manager) RunAtShutdown(ctx context.Context, shutdown func()) { 136 g.lock.Lock() 137 defer g.lock.Unlock() 138 g.toRunAtShutdown = append(g.toRunAtShutdown, 139 func() { 140 defer func() { 141 if err := recover(); err != nil { 142 log.Critical("PANIC during RunAtShutdown: %v\nStacktrace: %s", err, log.Stack(2)) 143 } 144 }() 145 select { 146 case <-ctx.Done(): 147 return 148 default: 149 shutdown() 150 } 151 }) 152 } 153 154 // RunAtHammer creates a go-routine to run the provided function at shutdown 155 func (g *Manager) RunAtHammer(hammer func()) { 156 g.lock.Lock() 157 defer g.lock.Unlock() 158 g.toRunAtHammer = append(g.toRunAtHammer, 159 func() { 160 defer func() { 161 if err := recover(); err != nil { 162 log.Critical("PANIC during RunAtHammer: %v\nStacktrace: %s", err, log.Stack(2)) 163 } 164 }() 165 hammer() 166 }) 167 } 168 169 func (g *Manager) doShutdown() { 170 if !g.setStateTransition(stateRunning, stateShuttingDown) { 171 g.DoImmediateHammer() 172 return 173 } 174 g.lock.Lock() 175 g.shutdownCtxCancel() 176 atShutdownCtx := pprof.WithLabels(g.hammerCtx, pprof.Labels("graceful-lifecycle", "post-shutdown")) 177 pprof.SetGoroutineLabels(atShutdownCtx) 178 for _, fn := range g.toRunAtShutdown { 179 go fn() 180 } 181 g.lock.Unlock() 182 183 if setting.GracefulHammerTime >= 0 { 184 go g.doHammerTime(setting.GracefulHammerTime) 185 } 186 go func() { 187 g.WaitForServers() 188 // Mop up any remaining unclosed events. 189 g.doHammerTime(0) 190 <-time.After(1 * time.Second) 191 g.doTerminate() 192 g.WaitForTerminate() 193 g.lock.Lock() 194 g.managerCtxCancel() 195 g.lock.Unlock() 196 }() 197 } 198 199 func (g *Manager) doHammerTime(d time.Duration) { 200 time.Sleep(d) 201 g.lock.Lock() 202 select { 203 case <-g.hammerCtx.Done(): 204 default: 205 log.Warn("Setting Hammer condition") 206 g.hammerCtxCancel() 207 atHammerCtx := pprof.WithLabels(g.terminateCtx, pprof.Labels("graceful-lifecycle", "post-hammer")) 208 pprof.SetGoroutineLabels(atHammerCtx) 209 for _, fn := range g.toRunAtHammer { 210 go fn() 211 } 212 } 213 g.lock.Unlock() 214 } 215 216 func (g *Manager) doTerminate() { 217 if !g.setStateTransition(stateShuttingDown, stateTerminate) { 218 return 219 } 220 g.lock.Lock() 221 select { 222 case <-g.terminateCtx.Done(): 223 default: 224 log.Warn("Terminating") 225 g.terminateCtxCancel() 226 atTerminateCtx := pprof.WithLabels(g.managerCtx, pprof.Labels("graceful-lifecycle", "post-terminate")) 227 pprof.SetGoroutineLabels(atTerminateCtx) 228 229 for _, fn := range g.toRunAtTerminate { 230 go fn() 231 } 232 } 233 g.lock.Unlock() 234 } 235 236 // IsChild returns if the current process is a child of previous Gitea process 237 func (g *Manager) IsChild() bool { 238 return g.isChild 239 } 240 241 // IsShutdown returns a channel which will be closed at shutdown. 242 // The order of closure is IsShutdown, IsHammer (potentially), IsTerminate 243 func (g *Manager) IsShutdown() <-chan struct{} { 244 return g.shutdownCtx.Done() 245 } 246 247 // IsHammer returns a channel which will be closed at hammer 248 // The order of closure is IsShutdown, IsHammer (potentially), IsTerminate 249 // Servers running within the running server wait group should respond to IsHammer 250 // if not shutdown already 251 func (g *Manager) IsHammer() <-chan struct{} { 252 return g.hammerCtx.Done() 253 } 254 255 // IsTerminate returns a channel which will be closed at terminate 256 // The order of closure is IsShutdown, IsHammer (potentially), IsTerminate 257 // IsTerminate will only close once all running servers have stopped 258 func (g *Manager) IsTerminate() <-chan struct{} { 259 return g.terminateCtx.Done() 260 } 261 262 // ServerDone declares a running server done and subtracts one from the 263 // running server wait group. Users probably do not want to call this 264 // and should use one of the RunWithShutdown* functions 265 func (g *Manager) ServerDone() { 266 g.runningServerWaitGroup.Done() 267 } 268 269 // WaitForServers waits for all running servers to finish. Users should probably 270 // instead use AtTerminate or IsTerminate 271 func (g *Manager) WaitForServers() { 272 g.runningServerWaitGroup.Wait() 273 } 274 275 // WaitForTerminate waits for all terminating actions to finish. 276 // Only the main go-routine should use this 277 func (g *Manager) WaitForTerminate() { 278 g.terminateWaitGroup.Wait() 279 } 280 281 func (g *Manager) getState() state { 282 g.lock.RLock() 283 defer g.lock.RUnlock() 284 return g.state 285 } 286 287 func (g *Manager) setStateTransition(old, new state) bool { 288 if old != g.getState() { 289 return false 290 } 291 g.lock.Lock() 292 if g.state != old { 293 g.lock.Unlock() 294 return false 295 } 296 g.state = new 297 g.lock.Unlock() 298 return true 299 } 300 301 func (g *Manager) setState(st state) { 302 g.lock.Lock() 303 defer g.lock.Unlock() 304 305 g.state = st 306 } 307 308 // InformCleanup tells the cleanup wait group that we have either taken a listener or will not be taking a listener. 309 // At the moment the total number of servers (numberOfServersToCreate) are pre-defined as a const before global init, 310 // so this function MUST be called if a server is not used. 311 func (g *Manager) InformCleanup() { 312 g.createServerWaitGroup.Done() 313 } 314 315 // Done allows the manager to be viewed as a context.Context, it returns a channel that is closed when the server is finished terminating 316 func (g *Manager) Done() <-chan struct{} { 317 return g.managerCtx.Done() 318 } 319 320 // Err allows the manager to be viewed as a context.Context done at Terminate 321 func (g *Manager) Err() error { 322 return g.managerCtx.Err() 323 } 324 325 // Value allows the manager to be viewed as a context.Context done at Terminate 326 func (g *Manager) Value(key interface{}) interface{} { 327 return g.managerCtx.Value(key) 328 } 329 330 // Deadline returns nil as there is no fixed Deadline for the manager, it allows the manager to be viewed as a context.Context 331 func (g *Manager) Deadline() (deadline time.Time, ok bool) { 332 return g.managerCtx.Deadline() 333 }