diff options
Diffstat (limited to 'modules/graceful/manager.go')
-rw-r--r-- | modules/graceful/manager.go | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/modules/graceful/manager.go b/modules/graceful/manager.go new file mode 100644 index 0000000000..48f76635ff --- /dev/null +++ b/modules/graceful/manager.go @@ -0,0 +1,187 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package graceful + +import ( + "time" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" +) + +type state uint8 + +const ( + stateInit state = iota + stateRunning + stateShuttingDown + stateTerminate +) + +// There are three places that could inherit sockets: +// +// * HTTP or HTTPS main listener +// * HTTP redirection fallback +// * SSH +// +// If you add an additional place you must increment this number +// and add a function to call manager.InformCleanup if it's not going to be used +const numberOfServersToCreate = 3 + +// Manager represents the graceful server manager interface +var Manager *gracefulManager + +func init() { + Manager = newGracefulManager() +} + +func (g *gracefulManager) doShutdown() { + if !g.setStateTransition(stateRunning, stateShuttingDown) { + return + } + g.lock.Lock() + close(g.shutdown) + g.lock.Unlock() + + if setting.GracefulHammerTime >= 0 { + go g.doHammerTime(setting.GracefulHammerTime) + } + go func() { + g.WaitForServers() + <-time.After(1 * time.Second) + g.doTerminate() + }() +} + +func (g *gracefulManager) doHammerTime(d time.Duration) { + time.Sleep(d) + select { + case <-g.hammer: + default: + log.Warn("Setting Hammer condition") + close(g.hammer) + } + +} + +func (g *gracefulManager) doTerminate() { + if !g.setStateTransition(stateShuttingDown, stateTerminate) { + return + } + g.lock.Lock() + close(g.terminate) + g.lock.Unlock() +} + +// IsChild returns if the current process is a child of previous Gitea process +func (g *gracefulManager) IsChild() bool { + return g.isChild +} + +// IsShutdown returns a channel which will be closed at shutdown. +// The order of closure is IsShutdown, IsHammer (potentially), IsTerminate +func (g *gracefulManager) IsShutdown() <-chan struct{} { + g.lock.RLock() + if g.shutdown == nil { + g.lock.RUnlock() + g.lock.Lock() + if g.shutdown == nil { + g.shutdown = make(chan struct{}) + } + defer g.lock.Unlock() + return g.shutdown + } + defer g.lock.RUnlock() + return g.shutdown +} + +// IsHammer returns a channel which will be closed at hammer +// The order of closure is IsShutdown, IsHammer (potentially), IsTerminate +// Servers running within the running server wait group should respond to IsHammer +// if not shutdown already +func (g *gracefulManager) IsHammer() <-chan struct{} { + g.lock.RLock() + if g.hammer == nil { + g.lock.RUnlock() + g.lock.Lock() + if g.hammer == nil { + g.hammer = make(chan struct{}) + } + defer g.lock.Unlock() + return g.hammer + } + defer g.lock.RUnlock() + return g.hammer +} + +// IsTerminate returns a channel which will be closed at terminate +// The order of closure is IsShutdown, IsHammer (potentially), IsTerminate +// IsTerminate will only close once all running servers have stopped +func (g *gracefulManager) IsTerminate() <-chan struct{} { + g.lock.RLock() + if g.terminate == nil { + g.lock.RUnlock() + g.lock.Lock() + if g.terminate == nil { + g.terminate = make(chan struct{}) + } + defer g.lock.Unlock() + return g.terminate + } + defer g.lock.RUnlock() + return g.terminate +} + +// ServerDone declares a running server done and subtracts one from the +// running server wait group. Users probably do not want to call this +// and should use one of the RunWithShutdown* functions +func (g *gracefulManager) ServerDone() { + g.runningServerWaitGroup.Done() +} + +// WaitForServers waits for all running servers to finish. Users should probably +// instead use AtTerminate or IsTerminate +func (g *gracefulManager) WaitForServers() { + g.runningServerWaitGroup.Wait() +} + +// WaitForTerminate waits for all terminating actions to finish. +// Only the main go-routine should use this +func (g *gracefulManager) WaitForTerminate() { + g.terminateWaitGroup.Wait() +} + +func (g *gracefulManager) getState() state { + g.lock.RLock() + defer g.lock.RUnlock() + return g.state +} + +func (g *gracefulManager) setStateTransition(old, new state) bool { + if old != g.getState() { + return false + } + g.lock.Lock() + if g.state != old { + g.lock.Unlock() + return false + } + g.state = new + g.lock.Unlock() + return true +} + +func (g *gracefulManager) setState(st state) { + g.lock.Lock() + defer g.lock.Unlock() + + g.state = st +} + +// InformCleanup tells the cleanup wait group that we have either taken a listener +// or will not be taking a listener +func (g *gracefulManager) InformCleanup() { + g.createServerWaitGroup.Done() +} |