code.gitea.io/gitea@v1.22.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 type RunCanceler interface { 27 Run() 28 Cancel() 29 } 30 31 // There are some places that could inherit sockets: 32 // 33 // * HTTP or HTTPS main listener 34 // * HTTP or HTTPS install listener 35 // * HTTP redirection fallback 36 // * Builtin SSH listener 37 // 38 // If you add a new place you must increment this number 39 // and add a function to call manager.InformCleanup if it's not going to be used 40 const numberOfServersToCreate = 4 41 42 var ( 43 manager *Manager 44 initOnce sync.Once 45 ) 46 47 // GetManager returns the Manager 48 func GetManager() *Manager { 49 InitManager(context.Background()) 50 return manager 51 } 52 53 // InitManager creates the graceful manager in the provided context 54 func InitManager(ctx context.Context) { 55 initOnce.Do(func() { 56 manager = newGracefulManager(ctx) 57 58 // Set the process default context to the HammerContext 59 process.DefaultContext = manager.HammerContext() 60 }) 61 } 62 63 // RunWithCancel helps to run a function with a custom context, the Cancel function will be called at shutdown 64 // The Cancel function should stop the Run function in predictable time. 65 func (g *Manager) RunWithCancel(rc RunCanceler) { 66 g.RunAtShutdown(context.Background(), rc.Cancel) 67 g.runningServerWaitGroup.Add(1) 68 defer g.runningServerWaitGroup.Done() 69 defer func() { 70 if err := recover(); err != nil { 71 log.Critical("PANIC during RunWithCancel: %v\nStacktrace: %s", err, log.Stack(2)) 72 g.doShutdown() 73 } 74 }() 75 rc.Run() 76 } 77 78 // RunWithShutdownContext takes a function that has a context to watch for shutdown. 79 // After the provided context is Done(), the main function must return once shutdown is complete. 80 // (Optionally the HammerContext may be obtained and waited for however, this should be avoided if possible.) 81 func (g *Manager) RunWithShutdownContext(run func(context.Context)) { 82 g.runningServerWaitGroup.Add(1) 83 defer g.runningServerWaitGroup.Done() 84 defer func() { 85 if err := recover(); err != nil { 86 log.Critical("PANIC during RunWithShutdownContext: %v\nStacktrace: %s", err, log.Stack(2)) 87 g.doShutdown() 88 } 89 }() 90 ctx := g.ShutdownContext() 91 pprof.SetGoroutineLabels(ctx) // We don't have a label to restore back to but I think this is fine 92 run(ctx) 93 } 94 95 // RunAtTerminate adds to the terminate wait group and creates a go-routine to run the provided function at termination 96 func (g *Manager) RunAtTerminate(terminate func()) { 97 g.terminateWaitGroup.Add(1) 98 g.lock.Lock() 99 defer g.lock.Unlock() 100 g.toRunAtTerminate = append(g.toRunAtTerminate, 101 func() { 102 defer g.terminateWaitGroup.Done() 103 defer func() { 104 if err := recover(); err != nil { 105 log.Critical("PANIC during RunAtTerminate: %v\nStacktrace: %s", err, log.Stack(2)) 106 } 107 }() 108 terminate() 109 }) 110 } 111 112 // RunAtShutdown creates a go-routine to run the provided function at shutdown 113 func (g *Manager) RunAtShutdown(ctx context.Context, shutdown func()) { 114 g.lock.Lock() 115 defer g.lock.Unlock() 116 g.toRunAtShutdown = append(g.toRunAtShutdown, 117 func() { 118 defer func() { 119 if err := recover(); err != nil { 120 log.Critical("PANIC during RunAtShutdown: %v\nStacktrace: %s", err, log.Stack(2)) 121 } 122 }() 123 select { 124 case <-ctx.Done(): 125 return 126 default: 127 shutdown() 128 } 129 }) 130 } 131 132 func (g *Manager) doShutdown() { 133 if !g.setStateTransition(stateRunning, stateShuttingDown) { 134 g.DoImmediateHammer() 135 return 136 } 137 g.lock.Lock() 138 g.shutdownCtxCancel() 139 atShutdownCtx := pprof.WithLabels(g.hammerCtx, pprof.Labels("graceful-lifecycle", "post-shutdown")) 140 pprof.SetGoroutineLabels(atShutdownCtx) 141 for _, fn := range g.toRunAtShutdown { 142 go fn() 143 } 144 g.lock.Unlock() 145 146 if setting.GracefulHammerTime >= 0 { 147 go g.doHammerTime(setting.GracefulHammerTime) 148 } 149 go func() { 150 g.runningServerWaitGroup.Wait() 151 // Mop up any remaining unclosed events. 152 g.doHammerTime(0) 153 <-time.After(1 * time.Second) 154 g.doTerminate() 155 g.terminateWaitGroup.Wait() 156 g.lock.Lock() 157 g.managerCtxCancel() 158 g.lock.Unlock() 159 }() 160 } 161 162 func (g *Manager) doHammerTime(d time.Duration) { 163 time.Sleep(d) 164 g.lock.Lock() 165 select { 166 case <-g.hammerCtx.Done(): 167 default: 168 log.Warn("Setting Hammer condition") 169 g.hammerCtxCancel() 170 atHammerCtx := pprof.WithLabels(g.terminateCtx, pprof.Labels("graceful-lifecycle", "post-hammer")) 171 pprof.SetGoroutineLabels(atHammerCtx) 172 } 173 g.lock.Unlock() 174 } 175 176 func (g *Manager) doTerminate() { 177 if !g.setStateTransition(stateShuttingDown, stateTerminate) { 178 return 179 } 180 g.lock.Lock() 181 select { 182 case <-g.terminateCtx.Done(): 183 default: 184 log.Warn("Terminating") 185 g.terminateCtxCancel() 186 atTerminateCtx := pprof.WithLabels(g.managerCtx, pprof.Labels("graceful-lifecycle", "post-terminate")) 187 pprof.SetGoroutineLabels(atTerminateCtx) 188 189 for _, fn := range g.toRunAtTerminate { 190 go fn() 191 } 192 } 193 g.lock.Unlock() 194 } 195 196 // IsChild returns if the current process is a child of previous Gitea process 197 func (g *Manager) IsChild() bool { 198 return g.isChild 199 } 200 201 // IsShutdown returns a channel which will be closed at shutdown. 202 // The order of closure is shutdown, hammer (potentially), terminate 203 func (g *Manager) IsShutdown() <-chan struct{} { 204 return g.shutdownCtx.Done() 205 } 206 207 // IsHammer returns a channel which will be closed at hammer. 208 // Servers running within the running server wait group should respond to IsHammer 209 // if not shutdown already 210 func (g *Manager) IsHammer() <-chan struct{} { 211 return g.hammerCtx.Done() 212 } 213 214 // ServerDone declares a running server done and subtracts one from the 215 // running server wait group. Users probably do not want to call this 216 // and should use one of the RunWithShutdown* functions 217 func (g *Manager) ServerDone() { 218 g.runningServerWaitGroup.Done() 219 } 220 221 func (g *Manager) setStateTransition(old, new state) bool { 222 g.lock.Lock() 223 if g.state != old { 224 g.lock.Unlock() 225 return false 226 } 227 g.state = new 228 g.lock.Unlock() 229 return true 230 } 231 232 // InformCleanup tells the cleanup wait group that we have either taken a listener or will not be taking a listener. 233 // At the moment the total number of servers (numberOfServersToCreate) are pre-defined as a const before global init, 234 // so this function MUST be called if a server is not used. 235 func (g *Manager) InformCleanup() { 236 g.createServerCond.L.Lock() 237 defer g.createServerCond.L.Unlock() 238 g.createdServer++ 239 g.createServerCond.Signal() 240 } 241 242 // Done allows the manager to be viewed as a context.Context, it returns a channel that is closed when the server is finished terminating 243 func (g *Manager) Done() <-chan struct{} { 244 return g.managerCtx.Done() 245 } 246 247 // Err allows the manager to be viewed as a context.Context done at Terminate 248 func (g *Manager) Err() error { 249 return g.managerCtx.Err() 250 } 251 252 // Value allows the manager to be viewed as a context.Context done at Terminate 253 func (g *Manager) Value(key any) any { 254 return g.managerCtx.Value(key) 255 } 256 257 // Deadline returns nil as there is no fixed Deadline for the manager, it allows the manager to be viewed as a context.Context 258 func (g *Manager) Deadline() (deadline time.Time, ok bool) { 259 return g.managerCtx.Deadline() 260 }