Skip to content

Commit 167e8f1

Browse files
authored
Restore Graceful Restarting & Socket Activation (#7274)
* Prevent deadlock in indexer initialisation during graceful restart * Move from gracehttp to our own service to add graceful ssh * Add timeout for start of indexers and make hammer time configurable * Fix issue with re-initialization in indexer during tests * move the code to detect use of closed to graceful * Handle logs gracefully - add a pid suffix just before restart * Move to using a cond and a holder for indexers * use time.Since * Add some comments and attribution * update modules.txt * Use zero to disable timeout * Move RestartProcess to its own file * Add cleanup routine
1 parent 4a290bd commit 167e8f1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1199
-2006
lines changed

cmd/web.go

+26-9
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,13 @@ func runLetsEncrypt(listenAddr, domain, directory, email string, m http.Handler)
7575
}
7676
go func() {
7777
log.Info("Running Let's Encrypt handler on %s", setting.HTTPAddr+":"+setting.PortToRedirect)
78-
var err = http.ListenAndServe(setting.HTTPAddr+":"+setting.PortToRedirect, certManager.HTTPHandler(http.HandlerFunc(runLetsEncryptFallbackHandler))) // all traffic coming into HTTP will be redirect to HTTPS automatically (LE HTTP-01 validation happens here)
78+
// all traffic coming into HTTP will be redirect to HTTPS automatically (LE HTTP-01 validation happens here)
79+
var err = runHTTP(setting.HTTPAddr+":"+setting.PortToRedirect, certManager.HTTPHandler(http.HandlerFunc(runLetsEncryptFallbackHandler)))
7980
if err != nil {
8081
log.Fatal("Failed to start the Let's Encrypt handler on port %s: %v", setting.PortToRedirect, err)
8182
}
8283
}()
83-
server := &http.Server{
84-
Addr: listenAddr,
85-
Handler: m,
86-
TLSConfig: certManager.TLSConfig(),
87-
}
88-
return server.ListenAndServeTLS("", "")
84+
return runHTTPSWithTLSConfig(listenAddr, certManager.TLSConfig(), context2.ClearHandler(m))
8985
}
9086

9187
func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) {
@@ -101,12 +97,21 @@ func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) {
10197
}
10298

10399
func runWeb(ctx *cli.Context) error {
100+
if os.Getppid() > 1 && len(os.Getenv("LISTEN_FDS")) > 0 {
101+
log.Info("Restarting Gitea on PID: %d from parent PID: %d", os.Getpid(), os.Getppid())
102+
} else {
103+
log.Info("Starting Gitea on PID: %d", os.Getpid())
104+
}
105+
106+
// Set pid file setting
104107
if ctx.IsSet("pid") {
105108
setting.CustomPID = ctx.String("pid")
106109
}
107110

111+
// Perform global initialization
108112
routers.GlobalInit()
109113

114+
// Set up Macaron
110115
m := routes.NewMacaron()
111116
routes.RegisterRoutes(m)
112117

@@ -164,6 +169,7 @@ func runWeb(ctx *cli.Context) error {
164169
var err error
165170
switch setting.Protocol {
166171
case setting.HTTP:
172+
NoHTTPRedirector()
167173
err = runHTTP(listenAddr, context2.ClearHandler(m))
168174
case setting.HTTPS:
169175
if setting.EnableLetsEncrypt {
@@ -172,9 +178,15 @@ func runWeb(ctx *cli.Context) error {
172178
}
173179
if setting.RedirectOtherPort {
174180
go runHTTPRedirector()
181+
} else {
182+
NoHTTPRedirector()
175183
}
176184
err = runHTTPS(listenAddr, setting.CertFile, setting.KeyFile, context2.ClearHandler(m))
177185
case setting.FCGI:
186+
NoHTTPRedirector()
187+
// FCGI listeners are provided as stdin - this is orthogonal to the LISTEN_FDS approach
188+
// in graceful and systemD
189+
NoMainListener()
178190
var listener net.Listener
179191
listener, err = net.Listen("tcp", listenAddr)
180192
if err != nil {
@@ -187,6 +199,10 @@ func runWeb(ctx *cli.Context) error {
187199
}()
188200
err = fcgi.Serve(listener, context2.ClearHandler(m))
189201
case setting.UnixSocket:
202+
// This could potentially be inherited using LISTEN_FDS but currently
203+
// these cannot be inherited
204+
NoHTTPRedirector()
205+
NoMainListener()
190206
if err := os.Remove(listenAddr); err != nil && !os.IsNotExist(err) {
191207
log.Fatal("Failed to remove unix socket directory %s: %v", listenAddr, err)
192208
}
@@ -207,8 +223,9 @@ func runWeb(ctx *cli.Context) error {
207223
}
208224

209225
if err != nil {
210-
log.Fatal("Failed to start server: %v", err)
226+
log.Critical("Failed to start server: %v", err)
211227
}
212-
228+
log.Info("HTTP Listener: %s Closed", listenAddr)
229+
log.Close()
213230
return nil
214231
}

cmd/web_graceful.go

+18-26
Original file line numberDiff line numberDiff line change
@@ -10,36 +10,28 @@ import (
1010
"crypto/tls"
1111
"net/http"
1212

13-
"code.gitea.io/gitea/modules/log"
14-
15-
"github.com/facebookgo/grace/gracehttp"
13+
"code.gitea.io/gitea/modules/graceful"
1614
)
1715

1816
func runHTTP(listenAddr string, m http.Handler) error {
19-
return gracehttp.Serve(&http.Server{
20-
Addr: listenAddr,
21-
Handler: m,
22-
})
17+
return graceful.HTTPListenAndServe("tcp", listenAddr, m)
2318
}
2419

2520
func runHTTPS(listenAddr, certFile, keyFile string, m http.Handler) error {
26-
config := &tls.Config{
27-
MinVersion: tls.VersionTLS10,
28-
}
29-
if config.NextProtos == nil {
30-
config.NextProtos = []string{"http/1.1"}
31-
}
32-
33-
config.Certificates = make([]tls.Certificate, 1)
34-
var err error
35-
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
36-
if err != nil {
37-
log.Fatal("Failed to load https cert file %s: %v", listenAddr, err)
38-
}
39-
40-
return gracehttp.Serve(&http.Server{
41-
Addr: listenAddr,
42-
Handler: m,
43-
TLSConfig: config,
44-
})
21+
return graceful.HTTPListenAndServeTLS("tcp", listenAddr, certFile, keyFile, m)
22+
}
23+
24+
func runHTTPSWithTLSConfig(listenAddr string, tlsConfig *tls.Config, m http.Handler) error {
25+
return graceful.HTTPListenAndServeTLSConfig("tcp", listenAddr, tlsConfig, m)
26+
}
27+
28+
// NoHTTPRedirector tells our cleanup routine that we will not be using a fallback http redirector
29+
func NoHTTPRedirector() {
30+
graceful.InformCleanup()
31+
}
32+
33+
// NoMainListener tells our cleanup routine that we will not be using a possibly provided listener
34+
// for our main HTTP/HTTPS service
35+
func NoMainListener() {
36+
graceful.InformCleanup()
4537
}

cmd/web_windows.go

+18
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package cmd
88

99
import (
10+
"crypto/tls"
1011
"net/http"
1112
)
1213

@@ -17,3 +18,20 @@ func runHTTP(listenAddr string, m http.Handler) error {
1718
func runHTTPS(listenAddr, certFile, keyFile string, m http.Handler) error {
1819
return http.ListenAndServeTLS(listenAddr, certFile, keyFile, m)
1920
}
21+
22+
func runHTTPSWithTLSConfig(listenAddr string, tlsConfig *tls.Config, m http.Handler) error {
23+
server := &http.Server{
24+
Addr: listenAddr,
25+
Handler: m,
26+
TLSConfig: tlsConfig,
27+
}
28+
return server.ListenAndServeTLS("", "")
29+
}
30+
31+
// NoHTTPRedirector is a no-op on Windows
32+
func NoHTTPRedirector() {
33+
}
34+
35+
// NoMainListener is a no-op on Windows
36+
func NoMainListener() {
37+
}

custom/conf/app.ini.sample

+9
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,12 @@ LFS_CONTENT_PATH = data/lfs
244244
LFS_JWT_SECRET =
245245
; LFS authentication validity period (in time.Duration), pushes taking longer than this may fail.
246246
LFS_HTTP_AUTH_EXPIRY = 20m
247+
; Allow graceful restarts using SIGHUP to fork
248+
ALLOW_GRACEFUL_RESTARTS = true
249+
; After a restart the parent will finish ongoing requests before
250+
; shutting down. Force shutdown if this process takes longer than this delay.
251+
; set to a negative value to disable
252+
GRACEFUL_HAMMER_TIME = 60s
247253
; Static resources, includes resources on custom/, public/ and all uploaded avatars web browser cache time, default is 6h
248254
STATIC_CACHE_TIME = 6h
249255

@@ -299,6 +305,9 @@ ISSUE_INDEXER_QUEUE_DIR = indexers/issues.queue
299305
ISSUE_INDEXER_QUEUE_CONN_STR = "addrs=127.0.0.1:6379 db=0"
300306
; Batch queue number, default is 20
301307
ISSUE_INDEXER_QUEUE_BATCH_NUMBER = 20
308+
; Timeout the indexer if it takes longer than this to start.
309+
; Set to zero to disable timeout.
310+
STARTUP_TIMEOUT=30s
302311

303312
; repo indexer by default disabled, since it uses a lot of disk space
304313
REPO_INDEXER_ENABLED = false

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

+3
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
157157
- `LETSENCRYPT_ACCEPTTOS`: **false**: This is an explicit check that you accept the terms of service for Let's Encrypt.
158158
- `LETSENCRYPT_DIRECTORY`: **https**: Directory that Letsencrypt will use to cache information such as certs and private keys.
159159
- `LETSENCRYPT_EMAIL`: **email@example.com**: Email used by Letsencrypt to notify about problems with issued certificates. (No default)
160+
- `ALLOW_GRACEFUL_RESTARTS`: **true**: Perform a graceful restart on SIGHUP
161+
- `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.
160162

161163
## Database (`database`)
162164

@@ -189,6 +191,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
189191
- `REPO_INDEXER_EXCLUDE`: **empty**: A comma separated list of glob patterns (see https://github.com/gobwas/glob) to **exclude** from the index. Files that match this list will not be indexed, even if they match in `REPO_INDEXER_INCLUDE`.
190192
- `UPDATE_BUFFER_LEN`: **20**: Buffer length of index request.
191193
- `MAX_FILE_SIZE`: **1048576**: Maximum size in bytes of files to be indexed.
194+
- `STARTUP_TIMEOUT`: **30s**: If the indexer takes longer than this timeout to start - fail. (This timeout will be added to the hammer time above for child processes - as bleve will not start until the previous parent is shutdown.) Set to zero to never timeout.
192195

193196
## Admin (`admin`)
194197
- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**: Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled

go.mod

-5
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,8 @@ require (
3232
github.com/emirpasic/gods v1.12.0
3333
github.com/etcd-io/bbolt v1.3.2 // indirect
3434
github.com/ethantkoenig/rupture v0.0.0-20180203182544-0a76f03a811a
35-
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect
3635
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
37-
github.com/facebookgo/freeport v0.0.0-20150612182905-d4adf43b75b9 // indirect
38-
github.com/facebookgo/grace v0.0.0-20160926231715-5729e484473f
39-
github.com/facebookgo/httpdown v0.0.0-20160323221027-a3b1354551a2 // indirect
4036
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
41-
github.com/facebookgo/stats v0.0.0-20151006221625-1b76add642e4 // indirect
4237
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
4338
github.com/gliderlabs/ssh v0.2.2
4439
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd // indirect

go.sum

-10
Original file line numberDiff line numberDiff line change
@@ -142,20 +142,10 @@ github.com/etcd-io/bbolt v1.3.2 h1:RLRQ0TKLX7DlBRXAJHvbmXL17Q3KNnTBtZ9B6Qo+/Y0=
142142
github.com/etcd-io/bbolt v1.3.2/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
143143
github.com/ethantkoenig/rupture v0.0.0-20180203182544-0a76f03a811a h1:M1bRpaZAn4GSsqu3hdK2R8H0AH9O6vqCTCbm2oAFGfE=
144144
github.com/ethantkoenig/rupture v0.0.0-20180203182544-0a76f03a811a/go.mod h1:MkKY/CB98aVE4VxO63X5vTQKUgcn+3XP15LMASe3lYs=
145-
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw=
146-
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA=
147145
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
148146
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
149-
github.com/facebookgo/freeport v0.0.0-20150612182905-d4adf43b75b9 h1:wWke/RUCl7VRjQhwPlR/v0glZXNYzBHdNUzf/Am2Nmg=
150-
github.com/facebookgo/freeport v0.0.0-20150612182905-d4adf43b75b9/go.mod h1:uPmAp6Sws4L7+Q/OokbWDAK1ibXYhB3PXFP1kol5hPg=
151-
github.com/facebookgo/grace v0.0.0-20160926231715-5729e484473f h1:0mlfEUWnUDVZnqWEVHGerL5bKYDKMEmT/Qk/W/3nGuo=
152-
github.com/facebookgo/grace v0.0.0-20160926231715-5729e484473f/go.mod h1:KigFdumBXUPSwzLDbeuzyt0elrL7+CP7TKuhrhT4bcU=
153-
github.com/facebookgo/httpdown v0.0.0-20160323221027-a3b1354551a2 h1:3Zvf9wRhl1cOhckN1oRGWPOkIhOketmEcrQ4TeFAoR4=
154-
github.com/facebookgo/httpdown v0.0.0-20160323221027-a3b1354551a2/go.mod h1:TUV/fX3XrTtBQb5+ttSUJzcFgLNpILONFTKmBuk5RSw=
155147
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
156148
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
157-
github.com/facebookgo/stats v0.0.0-20151006221625-1b76add642e4 h1:0YtRCqIZs2+Tz49QuH6cJVw/IFqzo39gEqZ0iYLxD2M=
158-
github.com/facebookgo/stats v0.0.0-20151006221625-1b76add642e4/go.mod h1:vsJz7uE339KUCpBXx3JAJzSRH7Uk4iGGyJzR529qDIA=
159149
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y=
160150
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
161151
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=

models/repo_indexer.go

+25-2
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ import (
88
"fmt"
99
"strconv"
1010
"strings"
11+
"time"
1112

1213
"code.gitea.io/gitea/modules/base"
1314
"code.gitea.io/gitea/modules/charset"
1415
"code.gitea.io/gitea/modules/git"
16+
"code.gitea.io/gitea/modules/graceful"
1517
"code.gitea.io/gitea/modules/indexer"
1618
"code.gitea.io/gitea/modules/log"
1719
"code.gitea.io/gitea/modules/setting"
@@ -70,9 +72,30 @@ func InitRepoIndexer() {
7072
if !setting.Indexer.RepoIndexerEnabled {
7173
return
7274
}
75+
waitChannel := make(chan time.Duration)
7376
repoIndexerOperationQueue = make(chan repoIndexerOperation, setting.Indexer.UpdateQueueLength)
74-
indexer.InitRepoIndexer(populateRepoIndexerAsynchronously)
75-
go processRepoIndexerOperationQueue()
77+
go func() {
78+
start := time.Now()
79+
log.Info("Initializing Repository Indexer")
80+
indexer.InitRepoIndexer(populateRepoIndexerAsynchronously)
81+
go processRepoIndexerOperationQueue()
82+
waitChannel <- time.Since(start)
83+
}()
84+
if setting.Indexer.StartupTimeout > 0 {
85+
go func() {
86+
timeout := setting.Indexer.StartupTimeout
87+
if graceful.IsChild && setting.GracefulHammerTime > 0 {
88+
timeout += setting.GracefulHammerTime
89+
}
90+
select {
91+
case duration := <-waitChannel:
92+
log.Info("Repository Indexer Initialization took %v", duration)
93+
case <-time.After(timeout):
94+
log.Fatal("Repository Indexer Initialization Timed-Out after: %v", timeout)
95+
}
96+
}()
97+
98+
}
7699
}
77100

78101
// populateRepoIndexerAsynchronously asynchronously populates the repo indexer

modules/graceful/cleanup.go

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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 "sync"
8+
9+
var cleanupWaitGroup sync.WaitGroup
10+
11+
func init() {
12+
cleanupWaitGroup = sync.WaitGroup{}
13+
14+
// There are three places that could inherit sockets:
15+
//
16+
// * HTTP or HTTPS main listener
17+
// * HTTP redirection fallback
18+
// * SSH
19+
//
20+
// If you add an additional place you must increment this number
21+
// and add a function to call InformCleanup if it's not going to be used
22+
cleanupWaitGroup.Add(3)
23+
24+
// Wait till we're done getting all of the listeners and then close
25+
// the unused ones
26+
go func() {
27+
cleanupWaitGroup.Wait()
28+
// Ignore the error here there's not much we can do with it
29+
// They're logged in the CloseProvidedListeners function
30+
_ = CloseProvidedListeners()
31+
}()
32+
}
33+
34+
// InformCleanup tells the cleanup wait group that we have either taken a listener
35+
// or will not be taking a listener
36+
func InformCleanup() {
37+
cleanupWaitGroup.Done()
38+
}

0 commit comments

Comments
 (0)