Skip to content

Commit e93af21

Browse files
committed
Add timeout for start of indexers and make hammer time configurable
1 parent a92957a commit e93af21

File tree

9 files changed

+81
-26
lines changed

9 files changed

+81
-26
lines changed

custom/conf/app.ini.sample

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,12 @@ LFS_CONTENT_PATH = data/lfs
241241
LFS_JWT_SECRET =
242242
; LFS authentication validity period (in time.Duration), pushes taking longer than this may fail.
243243
LFS_HTTP_AUTH_EXPIRY = 20m
244+
; Allow graceful restarts using SIGHUP to fork
245+
ALLOW_GRACEFUL_RESTARTS = true
246+
; After a restart the parent will finish ongoing requests before
247+
; shutting down. Force shutdown if this process takes longer than this delay.
248+
; set to a negative value to disable
249+
GRACEFUL_HAMMER_TIME = 60s
244250

245251
; Define allowed algorithms and their minimum key length (use -1 to disable a type)
246252
[ssh.minimum_key_sizes]
@@ -290,6 +296,9 @@ ISSUE_INDEXER_QUEUE_DIR = indexers/issues.queue
290296
ISSUE_INDEXER_QUEUE_CONN_STR = "addrs=127.0.0.1:6379 db=0"
291297
; Batch queue number, default is 20
292298
ISSUE_INDEXER_QUEUE_BATCH_NUMBER = 20
299+
; Timeout the indexer if it takes longer than this to start.
300+
; Set to a negative value to disable timeout.
301+
STARTUP_TIMEOUT=30s
293302

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

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
151151
- `LETSENCRYPT_ACCEPTTOS`: **false**: This is an explicit check that you accept the terms of service for Let's Encrypt.
152152
- `LETSENCRYPT_DIRECTORY`: **https**: Directory that Letsencrypt will use to cache information such as certs and private keys.
153153
- `LETSENCRYPT_EMAIL`: **[email protected]**: Email used by Letsencrypt to notify about problems with issued certificates. (No default)
154+
- `ALLOW_GRACEFUL_RESTARTS`: **true**: Perform a graceful restart on SIGHUP
155+
- `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.
154156

155157
## Database (`database`)
156158

@@ -179,6 +181,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
179181
- `REPO_INDEXER_PATH`: **indexers/repos.bleve**: Index file used for code search.
180182
- `UPDATE_BUFFER_LEN`: **20**: Buffer length of index request.
181183
- `MAX_FILE_SIZE`: **1048576**: Maximum size in bytes of files to be indexed.
184+
- `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.)
182185

183186
## Security (`security`)
184187

models/repo_indexer.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import (
88
"fmt"
99
"strconv"
1010
"strings"
11+
"time"
1112

1213
"code.gitea.io/gitea/modules/base"
1314
"code.gitea.io/gitea/modules/git"
15+
"code.gitea.io/gitea/modules/graceful"
1416
"code.gitea.io/gitea/modules/indexer"
1517
"code.gitea.io/gitea/modules/log"
1618
"code.gitea.io/gitea/modules/setting"
@@ -69,11 +71,27 @@ func InitRepoIndexer() {
6971
if !setting.Indexer.RepoIndexerEnabled {
7072
return
7173
}
74+
waitChannel := make(chan struct{})
7275
repoIndexerOperationQueue = make(chan repoIndexerOperation, setting.Indexer.UpdateQueueLength)
7376
go func() {
7477
indexer.InitRepoIndexer(populateRepoIndexerAsynchronously)
7578
go processRepoIndexerOperationQueue()
79+
close(waitChannel)
7680
}()
81+
if setting.Indexer.StartupTimeout > 0 {
82+
go func() {
83+
timeout := setting.Indexer.StartupTimeout
84+
if graceful.IsChild && setting.GracefulHammerTime > 0 {
85+
timeout += setting.GracefulHammerTime
86+
}
87+
select {
88+
case <-waitChannel:
89+
case <-time.After(timeout):
90+
log.Fatal("Timedout starting Repo Indexer")
91+
}
92+
}()
93+
94+
}
7795
}
7896

7997
// populateRepoIndexerAsynchronously asynchronously populates the repo indexer

modules/graceful/server.go

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,15 @@ var (
3636
DefaultWriteTimeOut time.Duration
3737
// DefaultMaxHeaderBytes default max header bytes
3838
DefaultMaxHeaderBytes int
39-
// DefaultHammerTime default hammer time
40-
DefaultHammerTime time.Duration
4139

42-
// We have been forked iff LISTEN_FDS is set and our parents PID is not 1
43-
isChild = len(os.Getenv(listenFDs)) > 0 && os.Getppid() > 1
40+
// IsChild reports if we are a fork iff LISTEN_FDS is set and our parent PID is not 1
41+
IsChild = len(os.Getenv(listenFDs)) > 0 && os.Getppid() > 1
4442
)
4543

4644
func init() {
4745
runningServerReg = sync.RWMutex{}
4846

4947
DefaultMaxHeaderBytes = 0 // use http.DefaultMaxHeaderBytes - which currently is 1 << 20 (1MB)
50-
51-
// after a restart the parent will finish ongoing requests before
52-
// shutting down. set to a negative value to disable
53-
DefaultHammerTime = 60 * time.Second
5448
}
5549

5650
// ServeFunction represents a listen.Accept loop
@@ -76,7 +70,11 @@ func NewServer(network, address string) *Server {
7670
runningServerReg.Lock()
7771
defer runningServerReg.Unlock()
7872

79-
log.Info("Beginning new server: %s:%s on PID: %d (%t)", network, address, os.Getpid(), isChild)
73+
if IsChild {
74+
log.Info("Restarting new server: %s:%s on PID: %d", network, address, os.Getpid())
75+
} else {
76+
log.Info("Starting new server: %s:%s on PID: %d", network, address, os.Getpid())
77+
}
8078
srv := &Server{
8179
wg: sync.WaitGroup{},
8280
sigChan: make(chan os.Signal),
@@ -108,7 +106,7 @@ func (srv *Server) ListenAndServe(serve ServeFunction) error {
108106

109107
srv.listener = newWrappedListener(l, srv)
110108

111-
if isChild {
109+
if IsChild {
112110
_ = syscall.Kill(syscall.Getppid(), syscall.SIGTERM)
113111
}
114112

@@ -154,7 +152,7 @@ func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFun
154152
wl := newWrappedListener(l, srv)
155153
srv.listener = tls.NewListener(wl, tlsConfig)
156154

157-
if isChild {
155+
if IsChild {
158156
_ = syscall.Kill(syscall.Getppid(), syscall.SIGTERM)
159157
}
160158
srv.BeforeBegin(srv.network, srv.address)

modules/graceful/server_hooks.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,20 @@ import (
1212
"time"
1313

1414
"code.gitea.io/gitea/modules/log"
15+
"code.gitea.io/gitea/modules/setting"
1516
)
1617

17-
// shutdown closes the listener so that no new connections are accepted. it also
18-
// starts a goroutine that will hammer (stop all running requests) the server
19-
// after DefaultHammerTime.
18+
// shutdown closes the listener so that no new connections are accepted
19+
// and starts a goroutine that will hammer (stop all running requests) the server
20+
// after setting.GracefulHammerTime.
2021
func (srv *Server) shutdown() {
2122
if srv.getState() != stateRunning {
2223
return
2324
}
2425

2526
srv.setState(stateShuttingDown)
26-
if DefaultHammerTime >= 0 {
27-
go srv.hammerTime(DefaultHammerTime)
27+
if setting.GracefulHammerTime >= 0 {
28+
go srv.hammerTime(setting.GracefulHammerTime)
2829
}
2930

3031
if srv.OnShutdown != nil {

modules/graceful/server_signals.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"time"
1212

1313
"code.gitea.io/gitea/modules/log"
14+
"code.gitea.io/gitea/modules/setting"
1415
)
1516

1617
var hookableSignals []os.Signal
@@ -42,10 +43,16 @@ func (srv *Server) handleSignals() {
4243
srv.preSignalHooks(sig)
4344
switch sig {
4445
case syscall.SIGHUP:
45-
log.Info("PID: %d. Received SIGHUP. Forking...", pid)
46-
err := srv.fork()
47-
if err != nil {
48-
log.Error("Error whilst forking from PID: %d : %v", pid, err)
46+
if setting.GracefulRestartable {
47+
log.Info("PID: %d. Received SIGHUP. Forking...", pid)
48+
err := srv.fork()
49+
if err != nil {
50+
log.Error("Error whilst forking from PID: %d : %v", pid, err)
51+
}
52+
} else {
53+
log.Info("PID: %d. Received SIGHUP. Not set restartable. Shutting down...", pid)
54+
55+
srv.shutdown()
4956
}
5057
case syscall.SIGUSR1:
5158
log.Info("PID %d. Received SIGUSR1.", pid)

modules/indexer/issues/indexer.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
package issues
66

77
import (
8-
"sync"
8+
"time"
99

1010
"code.gitea.io/gitea/models"
11+
"code.gitea.io/gitea/modules/graceful"
1112
"code.gitea.io/gitea/modules/log"
1213
"code.gitea.io/gitea/modules/setting"
1314
"code.gitea.io/gitea/modules/util"
@@ -50,13 +51,12 @@ var (
5051
// issueIndexerQueue queue of issue ids to be updated
5152
issueIndexerQueue Queue
5253
issueIndexer Indexer
53-
wg sync.WaitGroup
54+
waitChannel = make(chan struct{})
5455
)
5556

5657
// InitIssueIndexer initialize issue indexer, syncReindex is true then reindex until
5758
// all issue index done.
5859
func InitIssueIndexer(syncReindex bool) {
59-
wg.Add(1)
6060
go func() {
6161
var populate bool
6262
var dummyQueue bool
@@ -133,10 +133,22 @@ func InitIssueIndexer(syncReindex bool) {
133133
go populateIssueIndexer()
134134
}
135135
}
136-
wg.Done()
136+
close(waitChannel)
137137
}()
138138
if syncReindex {
139-
wg.Wait()
139+
<-waitChannel
140+
} else if setting.Indexer.StartupTimeout > 0 {
141+
go func() {
142+
timeout := setting.Indexer.StartupTimeout
143+
if graceful.IsChild && setting.GracefulHammerTime > 0 {
144+
timeout += setting.GracefulHammerTime
145+
}
146+
select {
147+
case <-waitChannel:
148+
case <-time.After(timeout):
149+
log.Fatal("Timedout starting Issue Indexer")
150+
}
151+
}()
140152
}
141153
}
142154

@@ -217,7 +229,7 @@ func DeleteRepoIssueIndexer(repo *models.Repository) {
217229

218230
// SearchIssuesByKeyword search issue ids by keywords and repo id
219231
func SearchIssuesByKeyword(repoID int64, keyword string) ([]int64, error) {
220-
wg.Wait()
232+
<-waitChannel
221233
var issueIDs []int64
222234
res, err := issueIndexer.Search(keyword, repoID, 1000, 0)
223235
if err != nil {

modules/setting/indexer.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package setting
77
import (
88
"path"
99
"path/filepath"
10+
"time"
1011
)
1112

1213
// enumerates all the indexer queue types
@@ -29,6 +30,7 @@ var (
2930
IssueQueueDir string
3031
IssueQueueConnStr string
3132
IssueQueueBatchNumber int
33+
StartupTimeout time.Duration
3234
}{
3335
IssueType: "bleve",
3436
IssuePath: "indexers/issues.bleve",
@@ -57,4 +59,5 @@ func newIndexerService() {
5759
Indexer.IssueQueueDir = sec.Key("ISSUE_INDEXER_QUEUE_DIR").MustString(path.Join(AppDataPath, "indexers/issues.queue"))
5860
Indexer.IssueQueueConnStr = sec.Key("ISSUE_INDEXER_QUEUE_CONN_STR").MustString(path.Join(AppDataPath, ""))
5961
Indexer.IssueQueueBatchNumber = sec.Key("ISSUE_INDEXER_QUEUE_BATCH_NUMBER").MustInt(20)
62+
Indexer.StartupTimeout = sec.Key("STARTUP_TIMEOUT").MustDuration(30 * time.Second)
6063
}

modules/setting/setting.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ var (
9696
LetsEncryptTOS bool
9797
LetsEncryptDirectory string
9898
LetsEncryptEmail string
99+
GracefulRestartable bool
100+
GracefulHammerTime time.Duration
99101

100102
SSH = struct {
101103
Disabled bool `ini:"DISABLE_SSH"`
@@ -568,6 +570,8 @@ func NewContext() {
568570
Domain = sec.Key("DOMAIN").MustString("localhost")
569571
HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0")
570572
HTTPPort = sec.Key("HTTP_PORT").MustString("3000")
573+
GracefulRestartable = sec.Key("ALLOW_GRACEFUL_RESTARTS").MustBool(true)
574+
GracefulHammerTime = sec.Key("GRACEFUL_HAMMER_TIME").MustDuration(60 * time.Second)
571575

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

0 commit comments

Comments
 (0)