Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit cbaa1de

Browse files
zeripathtechknowlogick
authored andcommittedNov 21, 2019
Add Graceful shutdown for Windows and hooks for shutdown of goroutines (#8964)
* 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. * Remove unused functions - these can be added in a different PR * Add startup timeout functionality * Document STARTUP_TIMEOUT
1 parent d7ac972 commit cbaa1de

30 files changed

+666
-497
lines changed
 

‎cmd/web.go

Lines changed: 2 additions & 1 deletion
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

Lines changed: 2 additions & 4 deletions
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

Lines changed: 0 additions & 37 deletions
This file was deleted.

‎custom/conf/app.ini.sample

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,9 @@ ALLOW_GRACEFUL_RESTARTS = true
287287
; shutting down. Force shutdown if this process takes longer than this delay.
288288
; set to a negative value to disable
289289
GRACEFUL_HAMMER_TIME = 60s
290+
; Allows the setting of a startup timeout and waithint for Windows as SVC service
291+
; 0 disables this.
292+
STARTUP_TIMEOUT = 0
290293
; Static resources, includes resources on custom/, public/ and all uploaded avatars web browser cache time, default is 6h
291294
STATIC_CACHE_TIME = 6h
292295

@@ -897,4 +900,4 @@ QUEUE_CONN_STR = "addrs=127.0.0.1:6379 db=0"
897900
; Max attempts per http/https request on migrations.
898901
MAX_ATTEMPTS = 3
899902
; Backoff time per http/https request retry (seconds)
900-
RETRY_BACKOFF = 3
903+
RETRY_BACKOFF = 3

‎docs/content/doc/advanced/config-cheat-sheet.en-us.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
189189
- `LETSENCRYPT_EMAIL`: **email@example.com**: Email used by Letsencrypt to notify about problems with issued certificates. (No default)
190190
- `ALLOW_GRACEFUL_RESTARTS`: **true**: Perform a graceful restart on SIGHUP
191191
- `GRACEFUL_HAMMER_TIME`: **60s**: After a restart the parent process will stop accepting new connections and will allow requests to finish before stopping. Shutdown will be forced if it takes longer than this time.
192+
- `STARTUP_TIMEOUT`: **0**: Shutsdown the server if startup takes longer than the provided time. On Windows setting this sends a waithint to the SVC host to tell the SVC host startup may take some time. Please note startup is determined by the opening of the listeners - HTTP/HTTPS/SSH. Indexers may take longer to startup and can have their own timeouts.
192193

193194
## Database (`database`)
194195

‎models/repo_indexer.go

Lines changed: 1 addition & 1 deletion
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

Lines changed: 0 additions & 40 deletions
This file was deleted.

‎modules/graceful/graceful_windows.go

Lines changed: 0 additions & 16 deletions
This file was deleted.

‎modules/graceful/manager.go

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
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+
func (g *gracefulManager) doShutdown() {
41+
if !g.setStateTransition(stateRunning, stateShuttingDown) {
42+
return
43+
}
44+
g.lock.Lock()
45+
close(g.shutdown)
46+
g.lock.Unlock()
47+
48+
if setting.GracefulHammerTime >= 0 {
49+
go g.doHammerTime(setting.GracefulHammerTime)
50+
}
51+
go func() {
52+
g.WaitForServers()
53+
<-time.After(1 * time.Second)
54+
g.doTerminate()
55+
}()
56+
}
57+
58+
func (g *gracefulManager) doHammerTime(d time.Duration) {
59+
time.Sleep(d)
60+
select {
61+
case <-g.hammer:
62+
default:
63+
log.Warn("Setting Hammer condition")
64+
close(g.hammer)
65+
}
66+
67+
}
68+
69+
func (g *gracefulManager) doTerminate() {
70+
if !g.setStateTransition(stateShuttingDown, stateTerminate) {
71+
return
72+
}
73+
g.lock.Lock()
74+
close(g.terminate)
75+
g.lock.Unlock()
76+
}
77+
78+
// IsChild returns if the current process is a child of previous Gitea process
79+
func (g *gracefulManager) IsChild() bool {
80+
return g.isChild
81+
}
82+
83+
// IsShutdown returns a channel which will be closed at shutdown.
84+
// The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
85+
func (g *gracefulManager) IsShutdown() <-chan struct{} {
86+
g.lock.RLock()
87+
if g.shutdown == nil {
88+
g.lock.RUnlock()
89+
g.lock.Lock()
90+
if g.shutdown == nil {
91+
g.shutdown = make(chan struct{})
92+
}
93+
defer g.lock.Unlock()
94+
return g.shutdown
95+
}
96+
defer g.lock.RUnlock()
97+
return g.shutdown
98+
}
99+
100+
// IsHammer returns a channel which will be closed at hammer
101+
// The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
102+
// Servers running within the running server wait group should respond to IsHammer
103+
// if not shutdown already
104+
func (g *gracefulManager) IsHammer() <-chan struct{} {
105+
g.lock.RLock()
106+
if g.hammer == nil {
107+
g.lock.RUnlock()
108+
g.lock.Lock()
109+
if g.hammer == nil {
110+
g.hammer = make(chan struct{})
111+
}
112+
defer g.lock.Unlock()
113+
return g.hammer
114+
}
115+
defer g.lock.RUnlock()
116+
return g.hammer
117+
}
118+
119+
// IsTerminate returns a channel which will be closed at terminate
120+
// The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
121+
// IsTerminate will only close once all running servers have stopped
122+
func (g *gracefulManager) IsTerminate() <-chan struct{} {
123+
g.lock.RLock()
124+
if g.terminate == nil {
125+
g.lock.RUnlock()
126+
g.lock.Lock()
127+
if g.terminate == nil {
128+
g.terminate = make(chan struct{})
129+
}
130+
defer g.lock.Unlock()
131+
return g.terminate
132+
}
133+
defer g.lock.RUnlock()
134+
return g.terminate
135+
}
136+
137+
// ServerDone declares a running server done and subtracts one from the
138+
// running server wait group. Users probably do not want to call this
139+
// and should use one of the RunWithShutdown* functions
140+
func (g *gracefulManager) ServerDone() {
141+
g.runningServerWaitGroup.Done()
142+
}
143+
144+
// WaitForServers waits for all running servers to finish. Users should probably
145+
// instead use AtTerminate or IsTerminate
146+
func (g *gracefulManager) WaitForServers() {
147+
g.runningServerWaitGroup.Wait()
148+
}
149+
150+
// WaitForTerminate waits for all terminating actions to finish.
151+
// Only the main go-routine should use this
152+
func (g *gracefulManager) WaitForTerminate() {
153+
g.terminateWaitGroup.Wait()
154+
}
155+
156+
func (g *gracefulManager) getState() state {
157+
g.lock.RLock()
158+
defer g.lock.RUnlock()
159+
return g.state
160+
}
161+
162+
func (g *gracefulManager) setStateTransition(old, new state) bool {
163+
if old != g.getState() {
164+
return false
165+
}
166+
g.lock.Lock()
167+
if g.state != old {
168+
g.lock.Unlock()
169+
return false
170+
}
171+
g.state = new
172+
g.lock.Unlock()
173+
return true
174+
}
175+
176+
func (g *gracefulManager) setState(st state) {
177+
g.lock.Lock()
178+
defer g.lock.Unlock()
179+
180+
g.state = st
181+
}
182+
183+
// InformCleanup tells the cleanup wait group that we have either taken a listener
184+
// or will not be taking a listener
185+
func (g *gracefulManager) InformCleanup() {
186+
g.createServerWaitGroup.Done()
187+
}

‎modules/graceful/manager_unix.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// +build !windows
2+
3+
// Copyright 2019 The Gitea Authors. All rights reserved.
4+
// Use of this source code is governed by a MIT-style
5+
// license that can be found in the LICENSE file.
6+
7+
package graceful
8+
9+
import (
10+
"errors"
11+
"os"
12+
"os/signal"
13+
"sync"
14+
"syscall"
15+
"time"
16+
17+
"code.gitea.io/gitea/modules/log"
18+
"code.gitea.io/gitea/modules/setting"
19+
)
20+
21+
type gracefulManager struct {
22+
isChild bool
23+
forked bool
24+
lock *sync.RWMutex
25+
state state
26+
shutdown chan struct{}
27+
hammer chan struct{}
28+
terminate chan struct{}
29+
runningServerWaitGroup sync.WaitGroup
30+
createServerWaitGroup sync.WaitGroup
31+
terminateWaitGroup sync.WaitGroup
32+
}
33+
34+
func newGracefulManager() *gracefulManager {
35+
manager := &gracefulManager{
36+
isChild: len(os.Getenv(listenFDs)) > 0 && os.Getppid() > 1,
37+
lock: &sync.RWMutex{},
38+
}
39+
manager.createServerWaitGroup.Add(numberOfServersToCreate)
40+
manager.Run()
41+
return manager
42+
}
43+
44+
func (g *gracefulManager) Run() {
45+
g.setState(stateRunning)
46+
go g.handleSignals()
47+
c := make(chan struct{})
48+
go func() {
49+
defer close(c)
50+
// Wait till we're done getting all of the listeners and then close
51+
// the unused ones
52+
g.createServerWaitGroup.Wait()
53+
// Ignore the error here there's not much we can do with it
54+
// They're logged in the CloseProvidedListeners function
55+
_ = CloseProvidedListeners()
56+
}()
57+
if setting.StartupTimeout > 0 {
58+
go func() {
59+
select {
60+
case <-c:
61+
return
62+
case <-g.IsShutdown():
63+
return
64+
case <-time.After(setting.StartupTimeout):
65+
log.Error("Startup took too long! Shutting down")
66+
g.doShutdown()
67+
}
68+
}()
69+
}
70+
}
71+
72+
func (g *gracefulManager) handleSignals() {
73+
var sig os.Signal
74+
75+
signalChannel := make(chan os.Signal, 1)
76+
77+
signal.Notify(
78+
signalChannel,
79+
syscall.SIGHUP,
80+
syscall.SIGUSR1,
81+
syscall.SIGUSR2,
82+
syscall.SIGINT,
83+
syscall.SIGTERM,
84+
syscall.SIGTSTP,
85+
)
86+
87+
pid := syscall.Getpid()
88+
for {
89+
sig = <-signalChannel
90+
switch sig {
91+
case syscall.SIGHUP:
92+
if setting.GracefulRestartable {
93+
log.Info("PID: %d. Received SIGHUP. Forking...", pid)
94+
err := g.doFork()
95+
if err != nil && err.Error() != "another process already forked. Ignoring this one" {
96+
log.Error("Error whilst forking from PID: %d : %v", pid, err)
97+
}
98+
} else {
99+
log.Info("PID: %d. Received SIGHUP. Not set restartable. Shutting down...", pid)
100+
101+
g.doShutdown()
102+
}
103+
case syscall.SIGUSR1:
104+
log.Info("PID %d. Received SIGUSR1.", pid)
105+
case syscall.SIGUSR2:
106+
log.Warn("PID %d. Received SIGUSR2. Hammering...", pid)
107+
g.doHammerTime(0 * time.Second)
108+
case syscall.SIGINT:
109+
log.Warn("PID %d. Received SIGINT. Shutting down...", pid)
110+
g.doShutdown()
111+
case syscall.SIGTERM:
112+
log.Warn("PID %d. Received SIGTERM. Shutting down...", pid)
113+
g.doShutdown()
114+
case syscall.SIGTSTP:
115+
log.Info("PID %d. Received SIGTSTP.", pid)
116+
default:
117+
log.Info("PID %d. Received %v.", pid, sig)
118+
}
119+
}
120+
}
121+
122+
func (g *gracefulManager) doFork() error {
123+
g.lock.Lock()
124+
if g.forked {
125+
g.lock.Unlock()
126+
return errors.New("another process already forked. Ignoring this one")
127+
}
128+
g.forked = true
129+
g.lock.Unlock()
130+
// We need to move the file logs to append pids
131+
setting.RestartLogsWithPIDSuffix()
132+
133+
_, err := RestartProcess()
134+
135+
return err
136+
}
137+
138+
func (g *gracefulManager) RegisterServer() {
139+
KillParent()
140+
g.runningServerWaitGroup.Add(1)
141+
}

‎modules/graceful/manager_windows.go

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// +build windows
2+
3+
// Copyright 2019 The Gitea Authors. All rights reserved.
4+
// Use of this source code is governed by a MIT-style
5+
// license that can be found in the LICENSE file.
6+
// This code is heavily inspired by the archived gofacebook/gracenet/net.go handler
7+
8+
package graceful
9+
10+
import (
11+
"os"
12+
"strconv"
13+
"sync"
14+
"time"
15+
16+
"code.gitea.io/gitea/modules/log"
17+
"code.gitea.io/gitea/modules/setting"
18+
19+
"golang.org/x/sys/windows/svc"
20+
"golang.org/x/sys/windows/svc/debug"
21+
)
22+
23+
var WindowsServiceName = "gitea"
24+
25+
const (
26+
hammerCode = 128
27+
hammerCmd = svc.Cmd(hammerCode)
28+
acceptHammerCode = svc.Accepted(hammerCode)
29+
)
30+
31+
32+
type gracefulManager struct {
33+
isChild bool
34+
lock *sync.RWMutex
35+
state state
36+
shutdown chan struct{}
37+
hammer chan struct{}
38+
terminate chan struct{}
39+
runningServerWaitGroup sync.WaitGroup
40+
createServerWaitGroup sync.WaitGroup
41+
terminateWaitGroup sync.WaitGroup
42+
}
43+
44+
func newGracefulManager() *gracefulManager {
45+
manager := &gracefulManager{
46+
isChild: false,
47+
lock: &sync.RWMutex{},
48+
}
49+
manager.createServerWaitGroup.Add(numberOfServersToCreate)
50+
manager.Run()
51+
return manager
52+
}
53+
54+
func (g *gracefulManager) Run() {
55+
g.setState(stateRunning)
56+
if skip, _ := strconv.ParseBool(os.Getenv("SKIP_MINWINSVC")); skip {
57+
return
58+
}
59+
run := svc.Run
60+
isInteractive, err := svc.IsAnInteractiveSession()
61+
if err != nil {
62+
log.Error("Unable to ascertain if running as an Interactive Session: %v", err)
63+
return
64+
}
65+
if isInteractive {
66+
run = debug.Run
67+
}
68+
go run(WindowsServiceName, g)
69+
}
70+
71+
// Execute makes gracefulManager implement svc.Handler
72+
func (g *gracefulManager) Execute(args []string, changes <-chan svc.ChangeRequest, status chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) {
73+
if setting.StartupTimeout > 0 {
74+
status <- svc.Status{State: svc.StartPending}
75+
} else {
76+
status <- svc.Status{State: svc.StartPending, WaitHint: uint32(setting.StartupTimeout/time.Millisecond)}
77+
}
78+
79+
// Now need to wait for everything to start...
80+
if !g.awaitServer(setting.StartupTimeout) {
81+
return false, 1
82+
}
83+
84+
// We need to implement some way of svc.AcceptParamChange/svc.ParamChange
85+
status <- svc.Status{
86+
State: svc.Running,
87+
Accepts: svc.AcceptStop | svc.AcceptShutdown | acceptHammerCode,
88+
}
89+
90+
waitTime := 30 * time.Second
91+
92+
loop:
93+
for change := range changes {
94+
switch change.Cmd {
95+
case svc.Interrogate:
96+
status <- change.CurrentStatus
97+
case svc.Stop, svc.Shutdown:
98+
g.doShutdown()
99+
waitTime += setting.GracefulHammerTime
100+
break loop
101+
case hammerCode:
102+
g.doShutdown()
103+
g.doHammerTime(0 *time.Second)
104+
break loop
105+
default:
106+
log.Debug("Unexpected control request: %v", change.Cmd)
107+
}
108+
}
109+
110+
status <- svc.Status{
111+
State: svc.StopPending,
112+
WaitHint: uint32(waitTime/time.Millisecond),
113+
}
114+
115+
hammerLoop:
116+
for {
117+
select {
118+
case change := <-changes:
119+
switch change.Cmd {
120+
case svc.Interrogate:
121+
status <- change.CurrentStatus
122+
case svc.Stop, svc.Shutdown, hammerCmd:
123+
g.doHammerTime(0 * time.Second)
124+
break hammerLoop
125+
default:
126+
log.Debug("Unexpected control request: %v", change.Cmd)
127+
}
128+
case <-g.hammer:
129+
break hammerLoop
130+
}
131+
}
132+
return false, 0
133+
}
134+
135+
func (g *gracefulManager) RegisterServer() {
136+
g.runningServerWaitGroup.Add(1)
137+
}
138+
139+
func (g *gracefulManager) awaitServer(limit time.Duration) bool {
140+
c := make(chan struct{})
141+
go func() {
142+
defer close(c)
143+
g.createServerWaitGroup.Wait()
144+
}()
145+
if limit > 0 {
146+
select {
147+
case <-c:
148+
return true // completed normally
149+
case <-time.After(limit):
150+
return false // timed out
151+
case <-g.IsShutdown():
152+
return false
153+
}
154+
} else {
155+
select {
156+
case <-c:
157+
return true // completed normally
158+
case <-g.IsShutdown():
159+
return false
160+
}
161+
}
162+
}

‎modules/graceful/net.go renamed to ‎modules/graceful/net_unix.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ func CloseProvidedListeners() error {
100100
// creates a new one using net.Listen.
101101
func GetListener(network, address string) (net.Listener, error) {
102102
// Add a deferral to say that we've tried to grab a listener
103-
defer InformCleanup()
103+
defer Manager.InformCleanup()
104104
switch network {
105105
case "tcp", "tcp4", "tcp6":
106106
tcpAddr, err := net.ResolveTCPAddr(network, address)

‎modules/graceful/net_windows.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// +build windows
2+
3+
// Copyright 2019 The Gitea Authors. All rights reserved.
4+
// Use of this source code is governed by a MIT-style
5+
// license that can be found in the LICENSE file.
6+
// This code is heavily inspired by the archived gofacebook/gracenet/net.go handler
7+
8+
package graceful
9+
10+
import "net"
11+
12+
// GetListener obtains a listener for the local network address.
13+
// On windows this is basically just a shim around net.Listen.
14+
func GetListener(network, address string) (net.Listener, error) {
15+
// Add a deferral to say that we've tried to grab a listener
16+
defer Manager.InformCleanup()
17+
18+
return net.Listen(network, address)
19+
}

‎modules/graceful/restart.go renamed to ‎modules/graceful/restart_unix.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ var killParent sync.Once
2121
// KillParent sends the kill signal to the parent process if we are a child
2222
func KillParent() {
2323
killParent.Do(func() {
24-
if IsChild {
24+
if Manager.IsChild() {
2525
ppid := syscall.Getppid()
2626
if ppid > 1 {
2727
_ = syscall.Kill(ppid, syscall.SIGTERM)
@@ -79,7 +79,3 @@ func RestartProcess() (int, error) {
7979
}
8080
return process.Pid, nil
8181
}
82-
83-
type filer interface {
84-
File() (*os.File, error)
85-
}

‎modules/graceful/server.go

Lines changed: 22 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// +build !windows
2-
31
// Copyright 2019 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.
@@ -19,37 +17,16 @@ import (
1917
"code.gitea.io/gitea/modules/log"
2018
)
2119

22-
type state uint8
23-
24-
const (
25-
stateInit state = iota
26-
stateRunning
27-
stateShuttingDown
28-
stateTerminate
29-
)
30-
3120
var (
32-
// RWMutex for when adding servers or shutting down
33-
runningServerReg sync.RWMutex
34-
runningServerWG sync.WaitGroup
35-
// ensure we only fork once
36-
runningServersForked bool
37-
3821
// DefaultReadTimeOut default read timeout
3922
DefaultReadTimeOut time.Duration
4023
// DefaultWriteTimeOut default write timeout
4124
DefaultWriteTimeOut time.Duration
4225
// DefaultMaxHeaderBytes default max header bytes
4326
DefaultMaxHeaderBytes int
44-
45-
// IsChild reports if we are a fork iff LISTEN_FDS is set and our parent PID is not 1
46-
IsChild = len(os.Getenv(listenFDs)) > 0 && os.Getppid() > 1
4727
)
4828

4929
func init() {
50-
runningServerReg = sync.RWMutex{}
51-
runningServerWG = sync.WaitGroup{}
52-
5330
DefaultMaxHeaderBytes = 0 // use http.DefaultMaxHeaderBytes - which currently is 1 << 20 (1MB)
5431
}
5532

@@ -58,43 +35,29 @@ type ServeFunction = func(net.Listener) error
5835

5936
// Server represents our graceful server
6037
type Server struct {
61-
network string
62-
address string
63-
listener net.Listener
64-
PreSignalHooks map[os.Signal][]func()
65-
PostSignalHooks map[os.Signal][]func()
66-
wg sync.WaitGroup
67-
sigChan chan os.Signal
68-
state state
69-
lock *sync.RWMutex
70-
BeforeBegin func(network, address string)
71-
OnShutdown func()
72-
}
73-
74-
// WaitForServers waits for all running servers to finish
75-
func WaitForServers() {
76-
runningServerWG.Wait()
38+
network string
39+
address string
40+
listener net.Listener
41+
wg sync.WaitGroup
42+
state state
43+
lock *sync.RWMutex
44+
BeforeBegin func(network, address string)
45+
OnShutdown func()
7746
}
7847

7948
// NewServer creates a server on network at provided address
8049
func NewServer(network, address string) *Server {
81-
runningServerReg.Lock()
82-
defer runningServerReg.Unlock()
83-
84-
if IsChild {
50+
if Manager.IsChild() {
8551
log.Info("Restarting new server: %s:%s on PID: %d", network, address, os.Getpid())
8652
} else {
8753
log.Info("Starting new server: %s:%s on PID: %d", network, address, os.Getpid())
8854
}
8955
srv := &Server{
90-
wg: sync.WaitGroup{},
91-
sigChan: make(chan os.Signal),
92-
PreSignalHooks: map[os.Signal][]func(){},
93-
PostSignalHooks: map[os.Signal][]func(){},
94-
state: stateInit,
95-
lock: &sync.RWMutex{},
96-
network: network,
97-
address: address,
56+
wg: sync.WaitGroup{},
57+
state: stateInit,
58+
lock: &sync.RWMutex{},
59+
network: network,
60+
address: address,
9861
}
9962

10063
srv.BeforeBegin = func(network, addr string) {
@@ -107,7 +70,7 @@ func NewServer(network, address string) *Server {
10770
// ListenAndServe listens on the provided network address and then calls Serve
10871
// to handle requests on incoming connections.
10972
func (srv *Server) ListenAndServe(serve ServeFunction) error {
110-
go srv.handleSignals()
73+
go srv.awaitShutdown()
11174

11275
l, err := GetListener(srv.network, srv.address)
11376
if err != nil {
@@ -117,8 +80,6 @@ func (srv *Server) ListenAndServe(serve ServeFunction) error {
11780

11881
srv.listener = newWrappedListener(l, srv)
11982

120-
KillParent()
121-
12283
srv.BeforeBegin(srv.network, srv.address)
12384

12485
return srv.Serve(serve)
@@ -150,7 +111,7 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string, serve ServeFuncti
150111
// ListenAndServeTLSConfig listens on the provided network address and then calls
151112
// Serve to handle requests on incoming TLS connections.
152113
func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFunction) error {
153-
go srv.handleSignals()
114+
go srv.awaitShutdown()
154115

155116
l, err := GetListener(srv.network, srv.address)
156117
if err != nil {
@@ -161,7 +122,6 @@ func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFun
161122
wl := newWrappedListener(l, srv)
162123
srv.listener = tls.NewListener(wl, tlsConfig)
163124

164-
KillParent()
165125
srv.BeforeBegin(srv.network, srv.address)
166126

167127
return srv.Serve(serve)
@@ -178,12 +138,12 @@ func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFun
178138
func (srv *Server) Serve(serve ServeFunction) error {
179139
defer log.Debug("Serve() returning... (PID: %d)", syscall.Getpid())
180140
srv.setState(stateRunning)
181-
runningServerWG.Add(1)
141+
Manager.RegisterServer()
182142
err := serve(srv.listener)
183143
log.Debug("Waiting for connections to finish... (PID: %d)", syscall.Getpid())
184144
srv.wg.Wait()
185145
srv.setState(stateTerminate)
186-
runningServerWG.Done()
146+
Manager.ServerDone()
187147
// use of closed means that the listeners are closed - i.e. we should be shutting down - return nil
188148
if err != nil && strings.Contains(err.Error(), "use of closed") {
189149
return nil
@@ -205,6 +165,10 @@ func (srv *Server) setState(st state) {
205165
srv.state = st
206166
}
207167

168+
type filer interface {
169+
File() (*os.File, error)
170+
}
171+
208172
type wrappedListener struct {
209173
net.Listener
210174
stopped bool

‎modules/graceful/server_hooks.go

Lines changed: 17 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,41 @@
1-
// +build !windows
2-
31
// Copyright 2019 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.
64

75
package graceful
86

97
import (
10-
"errors"
11-
"fmt"
128
"os"
139
"runtime"
14-
"time"
1510

1611
"code.gitea.io/gitea/modules/log"
17-
"code.gitea.io/gitea/modules/setting"
1812
)
1913

14+
// awaitShutdown waits for the shutdown signal from the Manager
15+
func (srv *Server) awaitShutdown() {
16+
select {
17+
case <-Manager.IsShutdown():
18+
// Shutdown
19+
srv.doShutdown()
20+
case <-Manager.IsHammer():
21+
// Hammer
22+
srv.doShutdown()
23+
srv.doHammer()
24+
}
25+
<-Manager.IsHammer()
26+
srv.doHammer()
27+
}
28+
2029
// shutdown closes the listener so that no new connections are accepted
2130
// and starts a goroutine that will hammer (stop all running requests) the server
2231
// after setting.GracefulHammerTime.
23-
func (srv *Server) shutdown() {
32+
func (srv *Server) doShutdown() {
2433
// only shutdown if we're running.
2534
if srv.getState() != stateRunning {
2635
return
2736
}
2837

2938
srv.setState(stateShuttingDown)
30-
if setting.GracefulHammerTime >= 0 {
31-
go srv.hammerTime(setting.GracefulHammerTime)
32-
}
3339

3440
if srv.OnShutdown != nil {
3541
srv.OnShutdown()
@@ -42,14 +48,7 @@ func (srv *Server) shutdown() {
4248
}
4349
}
4450

45-
// hammerTime forces the server to shutdown in a given timeout - whether it
46-
// finished outstanding requests or not. if Read/WriteTimeout are not set or the
47-
// max header size is very big a connection could hang...
48-
//
49-
// srv.Serve() will not return until all connections are served. this will
50-
// unblock the srv.wg.Wait() in Serve() thus causing ListenAndServe* functions to
51-
// return.
52-
func (srv *Server) hammerTime(d time.Duration) {
51+
func (srv *Server) doHammer() {
5352
defer func() {
5453
// We call srv.wg.Done() until it panics.
5554
// This happens if we call Done() when the WaitGroup counter is already at 0
@@ -62,7 +61,6 @@ func (srv *Server) hammerTime(d time.Duration) {
6261
if srv.getState() != stateShuttingDown {
6362
return
6463
}
65-
time.Sleep(d)
6664
log.Warn("Forcefully shutting down parent")
6765
for {
6866
if srv.getState() == stateTerminate {
@@ -74,48 +72,3 @@ func (srv *Server) hammerTime(d time.Duration) {
7472
runtime.Gosched()
7573
}
7674
}
77-
78-
func (srv *Server) fork() error {
79-
runningServerReg.Lock()
80-
defer runningServerReg.Unlock()
81-
82-
// only one server instance should fork!
83-
if runningServersForked {
84-
return errors.New("another process already forked. Ignoring this one")
85-
}
86-
87-
runningServersForked = true
88-
89-
// We need to move the file logs to append pids
90-
setting.RestartLogsWithPIDSuffix()
91-
92-
_, err := RestartProcess()
93-
94-
return err
95-
}
96-
97-
// RegisterPreSignalHook registers a function to be run before the signal handler for
98-
// a given signal. These are not mutex locked and should therefore be only called before Serve.
99-
func (srv *Server) RegisterPreSignalHook(sig os.Signal, f func()) (err error) {
100-
for _, s := range hookableSignals {
101-
if s == sig {
102-
srv.PreSignalHooks[sig] = append(srv.PreSignalHooks[sig], f)
103-
return
104-
}
105-
}
106-
err = fmt.Errorf("Signal %v is not supported", sig)
107-
return
108-
}
109-
110-
// RegisterPostSignalHook registers a function to be run after the signal handler for
111-
// a given signal. These are not mutex locked and should therefore be only called before Serve.
112-
func (srv *Server) RegisterPostSignalHook(sig os.Signal, f func()) (err error) {
113-
for _, s := range hookableSignals {
114-
if s == sig {
115-
srv.PostSignalHooks[sig] = append(srv.PostSignalHooks[sig], f)
116-
return
117-
}
118-
}
119-
err = fmt.Errorf("Signal %v is not supported", sig)
120-
return
121-
}

‎modules/graceful/server_http.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// +build !windows
2-
31
// Copyright 2019 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.

‎modules/graceful/server_signals.go

Lines changed: 0 additions & 95 deletions
This file was deleted.

‎modules/indexer/issues/indexer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ func InitIssueIndexer(syncReindex bool) {
172172
} else if setting.Indexer.StartupTimeout > 0 {
173173
go func() {
174174
timeout := setting.Indexer.StartupTimeout
175-
if graceful.IsChild && setting.GracefulHammerTime > 0 {
175+
if graceful.Manager.IsChild() && setting.GracefulHammerTime > 0 {
176176
timeout += setting.GracefulHammerTime
177177
}
178178
select {

‎modules/minwinsvc/LICENSE

Lines changed: 0 additions & 20 deletions
This file was deleted.

‎modules/minwinsvc/README.md

Lines changed: 0 additions & 18 deletions
This file was deleted.

‎modules/minwinsvc/minwinsvc.go

Lines changed: 0 additions & 18 deletions
This file was deleted.

‎modules/minwinsvc/svc_other.go

Lines changed: 0 additions & 11 deletions
This file was deleted.

‎modules/minwinsvc/svc_windows.go

Lines changed: 0 additions & 76 deletions
This file was deleted.

‎modules/setting/setting.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
"code.gitea.io/gitea/modules/generate"
2525
"code.gitea.io/gitea/modules/git"
2626
"code.gitea.io/gitea/modules/log"
27-
_ "code.gitea.io/gitea/modules/minwinsvc" // import minwinsvc for windows services
2827
"code.gitea.io/gitea/modules/user"
2928

3029
shellquote "github.com/kballard/go-shellquote"
@@ -99,6 +98,7 @@ var (
9998
LetsEncryptEmail string
10099
GracefulRestartable bool
101100
GracefulHammerTime time.Duration
101+
StartupTimeout time.Duration
102102
StaticURLPrefix string
103103

104104
SSH = struct {
@@ -569,6 +569,7 @@ func NewContext() {
569569
HTTPPort = sec.Key("HTTP_PORT").MustString("3000")
570570
GracefulRestartable = sec.Key("ALLOW_GRACEFUL_RESTARTS").MustBool(true)
571571
GracefulHammerTime = sec.Key("GRACEFUL_HAMMER_TIME").MustDuration(60 * time.Second)
572+
StartupTimeout = sec.Key("STARTUP_TIMEOUT").MustDuration(0 * time.Second)
572573

573574
defaultAppURL := string(Protocol) + "://" + Domain
574575
if (Protocol == HTTP && HTTPPort != "80") || (Protocol == HTTPS && HTTPPort != "443") {

‎modules/ssh/ssh_graceful.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// +build !windows
2-
31
// Copyright 2019 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.
@@ -26,5 +24,5 @@ func listen(server *ssh.Server) {
2624

2725
// Unused informs our cleanup routine that we will not be using a ssh port
2826
func Unused() {
29-
graceful.InformCleanup()
27+
graceful.Manager.InformCleanup()
3028
}

‎modules/ssh/ssh_windows.go

Lines changed: 0 additions & 24 deletions
This file was deleted.

‎vendor/golang.org/x/sys/windows/svc/debug/log.go

Lines changed: 56 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/golang.org/x/sys/windows/svc/debug/service.go

Lines changed: 45 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/modules.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,7 @@ golang.org/x/sys/cpu
482482
golang.org/x/sys/unix
483483
golang.org/x/sys/windows
484484
golang.org/x/sys/windows/svc
485+
golang.org/x/sys/windows/svc/debug
485486
# golang.org/x/text v0.3.2
486487
golang.org/x/text/encoding
487488
golang.org/x/text/encoding/charmap

0 commit comments

Comments
 (0)
Please sign in to comment.