// 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() }