1
// Copyright 2019 The Gitea Authors. All rights reserved.
2
// SPDX-License-Identifier: MIT
12
"code.gitea.io/gitea/modules/log"
13
"code.gitea.io/gitea/modules/process"
14
"code.gitea.io/gitea/modules/setting"
20
stateInit state = iota
26
type RunCanceler interface {
31
// There are some places that could inherit sockets:
33
// * HTTP or HTTPS main listener
34
// * HTTP or HTTPS install listener
35
// * HTTP redirection fallback
36
// * Builtin SSH listener
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
47
// GetManager returns the Manager
48
func GetManager() *Manager {
49
InitManager(context.Background())
53
// InitManager creates the graceful manager in the provided context
54
func InitManager(ctx context.Context) {
56
manager = newGracefulManager(ctx)
58
// Set the process default context to the HammerContext
59
process.DefaultContext = manager.HammerContext()
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()
70
if err := recover(); err != nil {
71
log.Critical("PANIC during RunWithCancel: %v\nStacktrace: %s", err, log.Stack(2))
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()
85
if err := recover(); err != nil {
86
log.Critical("PANIC during RunWithShutdownContext: %v\nStacktrace: %s", err, log.Stack(2))
90
ctx := g.ShutdownContext()
91
pprof.SetGoroutineLabels(ctx) // We don't have a label to restore back to but I think this is fine
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)
100
g.toRunAtTerminate = append(g.toRunAtTerminate,
102
defer g.terminateWaitGroup.Done()
104
if err := recover(); err != nil {
105
log.Critical("PANIC during RunAtTerminate: %v\nStacktrace: %s", err, log.Stack(2))
112
// RunAtShutdown creates a go-routine to run the provided function at shutdown
113
func (g *Manager) RunAtShutdown(ctx context.Context, shutdown func()) {
115
defer g.lock.Unlock()
116
g.toRunAtShutdown = append(g.toRunAtShutdown,
119
if err := recover(); err != nil {
120
log.Critical("PANIC during RunAtShutdown: %v\nStacktrace: %s", err, log.Stack(2))
132
func (g *Manager) doShutdown() {
133
if !g.setStateTransition(stateRunning, stateShuttingDown) {
134
g.DoImmediateHammer()
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 {
146
if setting.GracefulHammerTime >= 0 {
147
go g.doHammerTime(setting.GracefulHammerTime)
150
g.runningServerWaitGroup.Wait()
151
// Mop up any remaining unclosed events.
153
<-time.After(1 * time.Second)
155
g.terminateWaitGroup.Wait()
162
func (g *Manager) doHammerTime(d time.Duration) {
166
case <-g.hammerCtx.Done():
168
log.Warn("Setting Hammer condition")
170
atHammerCtx := pprof.WithLabels(g.terminateCtx, pprof.Labels("graceful-lifecycle", "post-hammer"))
171
pprof.SetGoroutineLabels(atHammerCtx)
176
func (g *Manager) doTerminate() {
177
if !g.setStateTransition(stateShuttingDown, stateTerminate) {
182
case <-g.terminateCtx.Done():
184
log.Warn("Terminating")
185
g.terminateCtxCancel()
186
atTerminateCtx := pprof.WithLabels(g.managerCtx, pprof.Labels("graceful-lifecycle", "post-terminate"))
187
pprof.SetGoroutineLabels(atTerminateCtx)
189
for _, fn := range g.toRunAtTerminate {
196
// IsChild returns if the current process is a child of previous Gitea process
197
func (g *Manager) IsChild() bool {
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()
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()
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()
221
func (g *Manager) setStateTransition(old, new state) bool {
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()
239
g.createServerCond.Signal()
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()
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()
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)
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()