Skip to content

Commit 51f1cc3

Browse files
committed
Graceful Shutdown for windows and others
Restructures modules/graceful, adding shutdown for windows, removing and replacing the old minwinsvc code. Creates a new waitGroup - terminate which allows for goroutines to finish up after the shutdown of the servers. Shutdown and terminate hooks are added for goroutines.
1 parent f4bdcd6 commit 51f1cc3

28 files changed

+694
-496
lines changed

cmd/web.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,8 @@ func runWeb(ctx *cli.Context) error {
227227
log.Critical("Failed to start server: %v", err)
228228
}
229229
log.Info("HTTP Listener: %s Closed", listenAddr)
230-
graceful.WaitForServers()
230+
graceful.Manager.WaitForServers()
231+
graceful.Manager.WaitForTerminate()
231232
log.Close()
232233
return nil
233234
}

cmd/web_graceful.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// +build !windows
2-
31
// Copyright 2016 The Gitea Authors. All rights reserved.
42
// Use of this source code is governed by a MIT-style
53
// license that can be found in the LICENSE file.
@@ -27,11 +25,11 @@ func runHTTPSWithTLSConfig(listenAddr string, tlsConfig *tls.Config, m http.Hand
2725

2826
// NoHTTPRedirector tells our cleanup routine that we will not be using a fallback http redirector
2927
func NoHTTPRedirector() {
30-
graceful.InformCleanup()
28+
graceful.Manager.InformCleanup()
3129
}
3230

3331
// NoMainListener tells our cleanup routine that we will not be using a possibly provided listener
3432
// for our main HTTP/HTTPS service
3533
func NoMainListener() {
36-
graceful.InformCleanup()
34+
graceful.Manager.InformCleanup()
3735
}

cmd/web_windows.go

-37
This file was deleted.

models/repo_indexer.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func InitRepoIndexer() {
8484
if setting.Indexer.StartupTimeout > 0 {
8585
go func() {
8686
timeout := setting.Indexer.StartupTimeout
87-
if graceful.IsChild && setting.GracefulHammerTime > 0 {
87+
if graceful.Manager.IsChild() && setting.GracefulHammerTime > 0 {
8888
timeout += setting.GracefulHammerTime
8989
}
9090
select {

modules/graceful/cleanup.go

-40
This file was deleted.

modules/graceful/graceful_windows.go

-16
This file was deleted.

modules/graceful/manager.go

+252
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
// Copyright 2019 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package graceful
6+
7+
import (
8+
"time"
9+
10+
"code.gitea.io/gitea/modules/log"
11+
"code.gitea.io/gitea/modules/setting"
12+
)
13+
14+
type state uint8
15+
16+
const (
17+
stateInit state = iota
18+
stateRunning
19+
stateShuttingDown
20+
stateTerminate
21+
)
22+
23+
// There are three places that could inherit sockets:
24+
//
25+
// * HTTP or HTTPS main listener
26+
// * HTTP redirection fallback
27+
// * SSH
28+
//
29+
// If you add an additional place you must increment this number
30+
// and add a function to call manager.InformCleanup if it's not going to be used
31+
const numberOfServersToCreate = 3
32+
33+
// Manager represents the graceful server manager interface
34+
var Manager *gracefulManager
35+
36+
func init() {
37+
Manager = newGracefulManager()
38+
}
39+
40+
// RunnableWithShutdownFns is a runnable with functions to run at shutdown and terminate
41+
// After the callback to atShutdown is called and is complete, the main function must return.
42+
// Similarly the callback function provided to atTerminate must return once termination is complete.
43+
// Please note that use of the atShutdown and atTerminate callbacks will create go-routines that will wait till till their respective signals
44+
// - users must therefore be careful to only call these as necessary.
45+
// If run is not expected to run indefinitely RunWithShutdownChan is likely to be more appropriate.
46+
type RunnableWithShutdownFns func(atShutdown, atTerminate func(func()))
47+
48+
// RunWithShutdownFns takes a function that has both atShutdown and atTerminate callbacks
49+
// After the callback to atShutdown is called and is complete, the main function must return.
50+
// Similarly the callback function provided to atTerminate must return once termination is complete.
51+
// Please note that use of the atShutdown and atTerminate callbacks will create go-routines that will wait till till their respective signals
52+
// - users must therefore be careful to only call these as necessary.
53+
// If run is not expected to run indefinitely RunWithShutdownChan is likely to be more appropriate.
54+
func (g *gracefulManager) RunWithShutdownFns(run RunnableWithShutdownFns) {
55+
g.runningServerWaitGroup.Add(1)
56+
defer g.runningServerWaitGroup.Done()
57+
run(func(atShutdown func()) {
58+
go func() {
59+
<-g.IsShutdown()
60+
atShutdown()
61+
}()
62+
}, func(atTerminate func()) {
63+
g.RunAtTerminate(atTerminate)
64+
})
65+
}
66+
67+
// RunnableWithShutdownChan is a runnable with functions to run at shutdown and terminate.
68+
// After the atShutdown channel is closed, the main function must return once shutdown is complete.
69+
// (Optionally IsHammer may be waited for instead however, this should be avoided if possible.)
70+
// The callback function provided to atTerminate must return once termination is complete.
71+
// Please note that use of the atTerminate function will create a go-routine that will wait till terminate - users must therefore be careful to only call this as necessary.
72+
type RunnableWithShutdownChan func(atShutdown <-chan struct{}, atTerminate func(func()))
73+
74+
// RunWithShutdownChan takes a function that has channel to watch for shutdown and atTerminate callbacks
75+
// After the atShutdown channel is closed, the main function must return once shutdown is complete.
76+
// (Optionally IsHammer may be waited for instead however, this should be avoided if possible.)
77+
// The callback function provided to atTerminate must return once termination is complete.
78+
// Please note that use of the atTerminate function will create a go-routine that will wait till terminate - users must therefore be careful to only call this as necessary.
79+
func (g *gracefulManager) RunWithShutdownChan(run RunnableWithShutdownChan) {
80+
g.runningServerWaitGroup.Add(1)
81+
defer g.runningServerWaitGroup.Done()
82+
run(g.IsShutdown(), func(atTerminate func()) {
83+
g.RunAtTerminate(atTerminate)
84+
})
85+
}
86+
87+
// RunAtTerminate adds to the terminate wait group and creates a go-routine to run the provided function at termination
88+
func (g *gracefulManager) RunAtTerminate(terminate func()) {
89+
g.terminateWaitGroup.Add(1)
90+
go func() {
91+
<-g.IsTerminate()
92+
terminate()
93+
g.terminateWaitGroup.Done()
94+
}()
95+
}
96+
97+
// RunAtShutdown creates a go-routine to run the provided function at shutdown
98+
func (g *gracefulManager) RunAtShutdown(shutdown func()) {
99+
go func() {
100+
<-g.IsShutdown()
101+
shutdown()
102+
}()
103+
}
104+
105+
func (g *gracefulManager) doShutdown() {
106+
if !g.setStateTransition(stateRunning, stateShuttingDown) {
107+
return
108+
}
109+
g.lock.Lock()
110+
close(g.shutdown)
111+
g.lock.Unlock()
112+
113+
if setting.GracefulHammerTime >= 0 {
114+
go g.doHammerTime(setting.GracefulHammerTime)
115+
}
116+
go func() {
117+
g.WaitForServers()
118+
<-time.After(1 * time.Second)
119+
g.doTerminate()
120+
}()
121+
}
122+
123+
func (g *gracefulManager) doHammerTime(d time.Duration) {
124+
time.Sleep(d)
125+
select {
126+
case <-g.hammer:
127+
default:
128+
log.Warn("Setting Hammer condition")
129+
close(g.hammer)
130+
}
131+
132+
}
133+
134+
func (g *gracefulManager) doTerminate() {
135+
if !g.setStateTransition(stateShuttingDown, stateTerminate) {
136+
return
137+
}
138+
g.lock.Lock()
139+
close(g.terminate)
140+
g.lock.Unlock()
141+
}
142+
143+
// IsChild returns if the current process is a child of previous Gitea process
144+
func (g *gracefulManager) IsChild() bool {
145+
return g.isChild
146+
}
147+
148+
// IsShutdown returns a channel which will be closed at shutdown.
149+
// The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
150+
func (g *gracefulManager) IsShutdown() <-chan struct{} {
151+
g.lock.RLock()
152+
if g.shutdown == nil {
153+
g.lock.RUnlock()
154+
g.lock.Lock()
155+
if g.shutdown == nil {
156+
g.shutdown = make(chan struct{})
157+
}
158+
defer g.lock.Unlock()
159+
return g.shutdown
160+
}
161+
defer g.lock.RUnlock()
162+
return g.shutdown
163+
}
164+
165+
// IsHammer returns a channel which will be closed at hammer
166+
// The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
167+
// Servers running within the running server wait group should respond to IsHammer
168+
// if not shutdown already
169+
func (g *gracefulManager) IsHammer() <-chan struct{} {
170+
g.lock.RLock()
171+
if g.hammer == nil {
172+
g.lock.RUnlock()
173+
g.lock.Lock()
174+
if g.hammer == nil {
175+
g.hammer = make(chan struct{})
176+
}
177+
defer g.lock.Unlock()
178+
return g.hammer
179+
}
180+
defer g.lock.RUnlock()
181+
return g.hammer
182+
}
183+
184+
// IsTerminate returns a channel which will be closed at terminate
185+
// The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
186+
// IsTerminate will only close once all running servers have stopped
187+
func (g *gracefulManager) IsTerminate() <-chan struct{} {
188+
g.lock.RLock()
189+
if g.terminate == nil {
190+
g.lock.RUnlock()
191+
g.lock.Lock()
192+
if g.terminate == nil {
193+
g.terminate = make(chan struct{})
194+
}
195+
defer g.lock.Unlock()
196+
return g.terminate
197+
}
198+
defer g.lock.RUnlock()
199+
return g.terminate
200+
}
201+
202+
// ServerDone declares a running server done and subtracts one from the
203+
// running server wait group. Users probably do not want to call this
204+
// and should use one of the RunWithShutdown* functions
205+
func (g *gracefulManager) ServerDone() {
206+
g.runningServerWaitGroup.Done()
207+
}
208+
209+
// WaitForServers waits for all running servers to finish. Users should probably
210+
// instead use AtTerminate or IsTerminate
211+
func (g *gracefulManager) WaitForServers() {
212+
g.runningServerWaitGroup.Wait()
213+
}
214+
215+
// WaitForTerminate waits for all terminating actions to finish.
216+
// Only the main go-routine should use this
217+
func (g *gracefulManager) WaitForTerminate() {
218+
g.terminateWaitGroup.Wait()
219+
}
220+
221+
func (g *gracefulManager) getState() state {
222+
g.lock.RLock()
223+
defer g.lock.RUnlock()
224+
return g.state
225+
}
226+
227+
func (g *gracefulManager) setStateTransition(old, new state) bool {
228+
if old != g.getState() {
229+
return false
230+
}
231+
g.lock.Lock()
232+
if g.state != old {
233+
g.lock.Unlock()
234+
return false
235+
}
236+
g.state = new
237+
g.lock.Unlock()
238+
return true
239+
}
240+
241+
func (g *gracefulManager) setState(st state) {
242+
g.lock.Lock()
243+
defer g.lock.Unlock()
244+
245+
g.state = st
246+
}
247+
248+
// InformCleanup tells the cleanup wait group that we have either taken a listener
249+
// or will not be taking a listener
250+
func (g *gracefulManager) InformCleanup() {
251+
g.createServerWaitGroup.Done()
252+
}

0 commit comments

Comments
 (0)