* 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_TIMEOUTtags/v1.11.0-rc1
@@ -227,7 +227,8 @@ func runWeb(ctx *cli.Context) error { | |||
log.Critical("Failed to start server: %v", err) | |||
} | |||
log.Info("HTTP Listener: %s Closed", listenAddr) | |||
graceful.WaitForServers() | |||
graceful.Manager.WaitForServers() | |||
graceful.Manager.WaitForTerminate() | |||
log.Close() | |||
return nil | |||
} |
@@ -1,5 +1,3 @@ | |||
// +build !windows | |||
// Copyright 2016 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. | |||
@@ -27,11 +25,11 @@ func runHTTPSWithTLSConfig(listenAddr string, tlsConfig *tls.Config, m http.Hand | |||
// NoHTTPRedirector tells our cleanup routine that we will not be using a fallback http redirector | |||
func NoHTTPRedirector() { | |||
graceful.InformCleanup() | |||
graceful.Manager.InformCleanup() | |||
} | |||
// NoMainListener tells our cleanup routine that we will not be using a possibly provided listener | |||
// for our main HTTP/HTTPS service | |||
func NoMainListener() { | |||
graceful.InformCleanup() | |||
graceful.Manager.InformCleanup() | |||
} |
@@ -1,37 +0,0 @@ | |||
// +build windows | |||
// Copyright 2016 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 cmd | |||
import ( | |||
"crypto/tls" | |||
"net/http" | |||
) | |||
func runHTTP(listenAddr string, m http.Handler) error { | |||
return http.ListenAndServe(listenAddr, m) | |||
} | |||
func runHTTPS(listenAddr, certFile, keyFile string, m http.Handler) error { | |||
return http.ListenAndServeTLS(listenAddr, certFile, keyFile, m) | |||
} | |||
func runHTTPSWithTLSConfig(listenAddr string, tlsConfig *tls.Config, m http.Handler) error { | |||
server := &http.Server{ | |||
Addr: listenAddr, | |||
Handler: m, | |||
TLSConfig: tlsConfig, | |||
} | |||
return server.ListenAndServeTLS("", "") | |||
} | |||
// NoHTTPRedirector is a no-op on Windows | |||
func NoHTTPRedirector() { | |||
} | |||
// NoMainListener is a no-op on Windows | |||
func NoMainListener() { | |||
} |
@@ -189,6 +189,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. | |||
- `LETSENCRYPT_EMAIL`: **email@example.com**: Email used by Letsencrypt to notify about problems with issued certificates. (No default) | |||
- `ALLOW_GRACEFUL_RESTARTS`: **true**: Perform a graceful restart on SIGHUP | |||
- `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. | |||
- `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. | |||
## Database (`database`) | |||
@@ -84,7 +84,7 @@ func InitRepoIndexer() { | |||
if setting.Indexer.StartupTimeout > 0 { | |||
go func() { | |||
timeout := setting.Indexer.StartupTimeout | |||
if graceful.IsChild && setting.GracefulHammerTime > 0 { | |||
if graceful.Manager.IsChild() && setting.GracefulHammerTime > 0 { | |||
timeout += setting.GracefulHammerTime | |||
} | |||
select { | |||
@@ -1,40 +0,0 @@ | |||
// +build !windows | |||
// 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 "sync" | |||
var cleanupWaitGroup sync.WaitGroup | |||
func init() { | |||
cleanupWaitGroup = sync.WaitGroup{} | |||
// 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 InformCleanup if it's not going to be used | |||
cleanupWaitGroup.Add(3) | |||
// Wait till we're done getting all of the listeners and then close | |||
// the unused ones | |||
go func() { | |||
cleanupWaitGroup.Wait() | |||
// Ignore the error here there's not much we can do with it | |||
// They're logged in the CloseProvidedListeners function | |||
_ = CloseProvidedListeners() | |||
}() | |||
} | |||
// InformCleanup tells the cleanup wait group that we have either taken a listener | |||
// or will not be taking a listener | |||
func InformCleanup() { | |||
cleanupWaitGroup.Done() | |||
} |
@@ -1,16 +0,0 @@ | |||
// +build windows | |||
// 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. | |||
// This code is heavily inspired by the archived gofacebook/gracenet/net.go handler | |||
package graceful | |||
// This file contains shims for windows builds | |||
const IsChild = false | |||
// WaitForServers waits for all running servers to finish | |||
func WaitForServers() { | |||
} |
@@ -0,0 +1,187 @@ | |||
// 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() | |||
} |
@@ -0,0 +1,141 @@ | |||
// +build !windows | |||
// 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 ( | |||
"errors" | |||
"os" | |||
"os/signal" | |||
"sync" | |||
"syscall" | |||
"time" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
) | |||
type gracefulManager struct { | |||
isChild bool | |||
forked bool | |||
lock *sync.RWMutex | |||
state state | |||
shutdown chan struct{} | |||
hammer chan struct{} | |||
terminate chan struct{} | |||
runningServerWaitGroup sync.WaitGroup | |||
createServerWaitGroup sync.WaitGroup | |||
terminateWaitGroup sync.WaitGroup | |||
} | |||
func newGracefulManager() *gracefulManager { | |||
manager := &gracefulManager{ | |||
isChild: len(os.Getenv(listenFDs)) > 0 && os.Getppid() > 1, | |||
lock: &sync.RWMutex{}, | |||
} | |||
manager.createServerWaitGroup.Add(numberOfServersToCreate) | |||
manager.Run() | |||
return manager | |||
} | |||
func (g *gracefulManager) Run() { | |||
g.setState(stateRunning) | |||
go g.handleSignals() | |||
c := make(chan struct{}) | |||
go func() { | |||
defer close(c) | |||
// Wait till we're done getting all of the listeners and then close | |||
// the unused ones | |||
g.createServerWaitGroup.Wait() | |||
// Ignore the error here there's not much we can do with it | |||
// They're logged in the CloseProvidedListeners function | |||
_ = CloseProvidedListeners() | |||
}() | |||
if setting.StartupTimeout > 0 { | |||
go func() { | |||
select { | |||
case <-c: | |||
return | |||
case <-g.IsShutdown(): | |||
return | |||
case <-time.After(setting.StartupTimeout): | |||
log.Error("Startup took too long! Shutting down") | |||
g.doShutdown() | |||
} | |||
}() | |||
} | |||
} | |||
func (g *gracefulManager) handleSignals() { | |||
var sig os.Signal | |||
signalChannel := make(chan os.Signal, 1) | |||
signal.Notify( | |||
signalChannel, | |||
syscall.SIGHUP, | |||
syscall.SIGUSR1, | |||
syscall.SIGUSR2, | |||
syscall.SIGINT, | |||
syscall.SIGTERM, | |||
syscall.SIGTSTP, | |||
) | |||
pid := syscall.Getpid() | |||
for { | |||
sig = <-signalChannel | |||
switch sig { | |||
case syscall.SIGHUP: | |||
if setting.GracefulRestartable { | |||
log.Info("PID: %d. Received SIGHUP. Forking...", pid) | |||
err := g.doFork() | |||
if err != nil && err.Error() != "another process already forked. Ignoring this one" { | |||
log.Error("Error whilst forking from PID: %d : %v", pid, err) | |||
} | |||
} else { | |||
log.Info("PID: %d. Received SIGHUP. Not set restartable. Shutting down...", pid) | |||
g.doShutdown() | |||
} | |||
case syscall.SIGUSR1: | |||
log.Info("PID %d. Received SIGUSR1.", pid) | |||
case syscall.SIGUSR2: | |||
log.Warn("PID %d. Received SIGUSR2. Hammering...", pid) | |||
g.doHammerTime(0 * time.Second) | |||
case syscall.SIGINT: | |||
log.Warn("PID %d. Received SIGINT. Shutting down...", pid) | |||
g.doShutdown() | |||
case syscall.SIGTERM: | |||
log.Warn("PID %d. Received SIGTERM. Shutting down...", pid) | |||
g.doShutdown() | |||
case syscall.SIGTSTP: | |||
log.Info("PID %d. Received SIGTSTP.", pid) | |||
default: | |||
log.Info("PID %d. Received %v.", pid, sig) | |||
} | |||
} | |||
} | |||
func (g *gracefulManager) doFork() error { | |||
g.lock.Lock() | |||
if g.forked { | |||
g.lock.Unlock() | |||
return errors.New("another process already forked. Ignoring this one") | |||
} | |||
g.forked = true | |||
g.lock.Unlock() | |||
// We need to move the file logs to append pids | |||
setting.RestartLogsWithPIDSuffix() | |||
_, err := RestartProcess() | |||
return err | |||
} | |||
func (g *gracefulManager) RegisterServer() { | |||
KillParent() | |||
g.runningServerWaitGroup.Add(1) | |||
} |
@@ -0,0 +1,162 @@ | |||
// +build windows | |||
// 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. | |||
// This code is heavily inspired by the archived gofacebook/gracenet/net.go handler | |||
package graceful | |||
import ( | |||
"os" | |||
"strconv" | |||
"sync" | |||
"time" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
"golang.org/x/sys/windows/svc" | |||
"golang.org/x/sys/windows/svc/debug" | |||
) | |||
var WindowsServiceName = "gitea" | |||
const ( | |||
hammerCode = 128 | |||
hammerCmd = svc.Cmd(hammerCode) | |||
acceptHammerCode = svc.Accepted(hammerCode) | |||
) | |||
type gracefulManager struct { | |||
isChild bool | |||
lock *sync.RWMutex | |||
state state | |||
shutdown chan struct{} | |||
hammer chan struct{} | |||
terminate chan struct{} | |||
runningServerWaitGroup sync.WaitGroup | |||
createServerWaitGroup sync.WaitGroup | |||
terminateWaitGroup sync.WaitGroup | |||
} | |||
func newGracefulManager() *gracefulManager { | |||
manager := &gracefulManager{ | |||
isChild: false, | |||
lock: &sync.RWMutex{}, | |||
} | |||
manager.createServerWaitGroup.Add(numberOfServersToCreate) | |||
manager.Run() | |||
return manager | |||
} | |||
func (g *gracefulManager) Run() { | |||
g.setState(stateRunning) | |||
if skip, _ := strconv.ParseBool(os.Getenv("SKIP_MINWINSVC")); skip { | |||
return | |||
} | |||
run := svc.Run | |||
isInteractive, err := svc.IsAnInteractiveSession() | |||
if err != nil { | |||
log.Error("Unable to ascertain if running as an Interactive Session: %v", err) | |||
return | |||
} | |||
if isInteractive { | |||
run = debug.Run | |||
} | |||
go run(WindowsServiceName, g) | |||
} | |||
// Execute makes gracefulManager implement svc.Handler | |||
func (g *gracefulManager) Execute(args []string, changes <-chan svc.ChangeRequest, status chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) { | |||
if setting.StartupTimeout > 0 { | |||
status <- svc.Status{State: svc.StartPending} | |||
} else { | |||
status <- svc.Status{State: svc.StartPending, WaitHint: uint32(setting.StartupTimeout/time.Millisecond)} | |||
} | |||
// Now need to wait for everything to start... | |||
if !g.awaitServer(setting.StartupTimeout) { | |||
return false, 1 | |||
} | |||
// We need to implement some way of svc.AcceptParamChange/svc.ParamChange | |||
status <- svc.Status{ | |||
State: svc.Running, | |||
Accepts: svc.AcceptStop | svc.AcceptShutdown | acceptHammerCode, | |||
} | |||
waitTime := 30 * time.Second | |||
loop: | |||
for change := range changes { | |||
switch change.Cmd { | |||
case svc.Interrogate: | |||
status <- change.CurrentStatus | |||
case svc.Stop, svc.Shutdown: | |||
g.doShutdown() | |||
waitTime += setting.GracefulHammerTime | |||
break loop | |||
case hammerCode: | |||
g.doShutdown() | |||
g.doHammerTime(0 *time.Second) | |||
break loop | |||
default: | |||
log.Debug("Unexpected control request: %v", change.Cmd) | |||
} | |||
} | |||
status <- svc.Status{ | |||
State: svc.StopPending, | |||
WaitHint: uint32(waitTime/time.Millisecond), | |||
} | |||
hammerLoop: | |||
for { | |||
select { | |||
case change := <-changes: | |||
switch change.Cmd { | |||
case svc.Interrogate: | |||
status <- change.CurrentStatus | |||
case svc.Stop, svc.Shutdown, hammerCmd: | |||
g.doHammerTime(0 * time.Second) | |||
break hammerLoop | |||
default: | |||
log.Debug("Unexpected control request: %v", change.Cmd) | |||
} | |||
case <-g.hammer: | |||
break hammerLoop | |||
} | |||
} | |||
return false, 0 | |||
} | |||
func (g *gracefulManager) RegisterServer() { | |||
g.runningServerWaitGroup.Add(1) | |||
} | |||
func (g *gracefulManager) awaitServer(limit time.Duration) bool { | |||
c := make(chan struct{}) | |||
go func() { | |||
defer close(c) | |||
g.createServerWaitGroup.Wait() | |||
}() | |||
if limit > 0 { | |||
select { | |||
case <-c: | |||
return true // completed normally | |||
case <-time.After(limit): | |||
return false // timed out | |||
case <-g.IsShutdown(): | |||
return false | |||
} | |||
} else { | |||
select { | |||
case <-c: | |||
return true // completed normally | |||
case <-g.IsShutdown(): | |||
return false | |||
} | |||
} | |||
} |
@@ -100,7 +100,7 @@ func CloseProvidedListeners() error { | |||
// creates a new one using net.Listen. | |||
func GetListener(network, address string) (net.Listener, error) { | |||
// Add a deferral to say that we've tried to grab a listener | |||
defer InformCleanup() | |||
defer Manager.InformCleanup() | |||
switch network { | |||
case "tcp", "tcp4", "tcp6": | |||
tcpAddr, err := net.ResolveTCPAddr(network, address) |
@@ -0,0 +1,19 @@ | |||
// +build windows | |||
// 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. | |||
// This code is heavily inspired by the archived gofacebook/gracenet/net.go handler | |||
package graceful | |||
import "net" | |||
// GetListener obtains a listener for the local network address. | |||
// On windows this is basically just a shim around net.Listen. | |||
func GetListener(network, address string) (net.Listener, error) { | |||
// Add a deferral to say that we've tried to grab a listener | |||
defer Manager.InformCleanup() | |||
return net.Listen(network, address) | |||
} |
@@ -21,7 +21,7 @@ var killParent sync.Once | |||
// KillParent sends the kill signal to the parent process if we are a child | |||
func KillParent() { | |||
killParent.Do(func() { | |||
if IsChild { | |||
if Manager.IsChild() { | |||
ppid := syscall.Getppid() | |||
if ppid > 1 { | |||
_ = syscall.Kill(ppid, syscall.SIGTERM) | |||
@@ -79,7 +79,3 @@ func RestartProcess() (int, error) { | |||
} | |||
return process.Pid, nil | |||
} | |||
type filer interface { | |||
File() (*os.File, error) | |||
} |
@@ -1,5 +1,3 @@ | |||
// +build !windows | |||
// 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. | |||
@@ -19,37 +17,16 @@ import ( | |||
"code.gitea.io/gitea/modules/log" | |||
) | |||
type state uint8 | |||
const ( | |||
stateInit state = iota | |||
stateRunning | |||
stateShuttingDown | |||
stateTerminate | |||
) | |||
var ( | |||
// RWMutex for when adding servers or shutting down | |||
runningServerReg sync.RWMutex | |||
runningServerWG sync.WaitGroup | |||
// ensure we only fork once | |||
runningServersForked bool | |||
// DefaultReadTimeOut default read timeout | |||
DefaultReadTimeOut time.Duration | |||
// DefaultWriteTimeOut default write timeout | |||
DefaultWriteTimeOut time.Duration | |||
// DefaultMaxHeaderBytes default max header bytes | |||
DefaultMaxHeaderBytes int | |||
// IsChild reports if we are a fork iff LISTEN_FDS is set and our parent PID is not 1 | |||
IsChild = len(os.Getenv(listenFDs)) > 0 && os.Getppid() > 1 | |||
) | |||
func init() { | |||
runningServerReg = sync.RWMutex{} | |||
runningServerWG = sync.WaitGroup{} | |||
DefaultMaxHeaderBytes = 0 // use http.DefaultMaxHeaderBytes - which currently is 1 << 20 (1MB) | |||
} | |||
@@ -58,43 +35,29 @@ type ServeFunction = func(net.Listener) error | |||
// Server represents our graceful server | |||
type Server struct { | |||
network string | |||
address string | |||
listener net.Listener | |||
PreSignalHooks map[os.Signal][]func() | |||
PostSignalHooks map[os.Signal][]func() | |||
wg sync.WaitGroup | |||
sigChan chan os.Signal | |||
state state | |||
lock *sync.RWMutex | |||
BeforeBegin func(network, address string) | |||
OnShutdown func() | |||
} | |||
// WaitForServers waits for all running servers to finish | |||
func WaitForServers() { | |||
runningServerWG.Wait() | |||
network string | |||
address string | |||
listener net.Listener | |||
wg sync.WaitGroup | |||
state state | |||
lock *sync.RWMutex | |||
BeforeBegin func(network, address string) | |||
OnShutdown func() | |||
} | |||
// NewServer creates a server on network at provided address | |||
func NewServer(network, address string) *Server { | |||
runningServerReg.Lock() | |||
defer runningServerReg.Unlock() | |||
if IsChild { | |||
if Manager.IsChild() { | |||
log.Info("Restarting new server: %s:%s on PID: %d", network, address, os.Getpid()) | |||
} else { | |||
log.Info("Starting new server: %s:%s on PID: %d", network, address, os.Getpid()) | |||
} | |||
srv := &Server{ | |||
wg: sync.WaitGroup{}, | |||
sigChan: make(chan os.Signal), | |||
PreSignalHooks: map[os.Signal][]func(){}, | |||
PostSignalHooks: map[os.Signal][]func(){}, | |||
state: stateInit, | |||
lock: &sync.RWMutex{}, | |||
network: network, | |||
address: address, | |||
wg: sync.WaitGroup{}, | |||
state: stateInit, | |||
lock: &sync.RWMutex{}, | |||
network: network, | |||
address: address, | |||
} | |||
srv.BeforeBegin = func(network, addr string) { | |||
@@ -107,7 +70,7 @@ func NewServer(network, address string) *Server { | |||
// ListenAndServe listens on the provided network address and then calls Serve | |||
// to handle requests on incoming connections. | |||
func (srv *Server) ListenAndServe(serve ServeFunction) error { | |||
go srv.handleSignals() | |||
go srv.awaitShutdown() | |||
l, err := GetListener(srv.network, srv.address) | |||
if err != nil { | |||
@@ -117,8 +80,6 @@ func (srv *Server) ListenAndServe(serve ServeFunction) error { | |||
srv.listener = newWrappedListener(l, srv) | |||
KillParent() | |||
srv.BeforeBegin(srv.network, srv.address) | |||
return srv.Serve(serve) | |||
@@ -150,7 +111,7 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string, serve ServeFuncti | |||
// ListenAndServeTLSConfig listens on the provided network address and then calls | |||
// Serve to handle requests on incoming TLS connections. | |||
func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFunction) error { | |||
go srv.handleSignals() | |||
go srv.awaitShutdown() | |||
l, err := GetListener(srv.network, srv.address) | |||
if err != nil { | |||
@@ -161,7 +122,6 @@ func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFun | |||
wl := newWrappedListener(l, srv) | |||
srv.listener = tls.NewListener(wl, tlsConfig) | |||
KillParent() | |||
srv.BeforeBegin(srv.network, srv.address) | |||
return srv.Serve(serve) | |||
@@ -178,12 +138,12 @@ func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFun | |||
func (srv *Server) Serve(serve ServeFunction) error { | |||
defer log.Debug("Serve() returning... (PID: %d)", syscall.Getpid()) | |||
srv.setState(stateRunning) | |||
runningServerWG.Add(1) | |||
Manager.RegisterServer() | |||
err := serve(srv.listener) | |||
log.Debug("Waiting for connections to finish... (PID: %d)", syscall.Getpid()) | |||
srv.wg.Wait() | |||
srv.setState(stateTerminate) | |||
runningServerWG.Done() | |||
Manager.ServerDone() | |||
// use of closed means that the listeners are closed - i.e. we should be shutting down - return nil | |||
if err != nil && strings.Contains(err.Error(), "use of closed") { | |||
return nil | |||
@@ -205,6 +165,10 @@ func (srv *Server) setState(st state) { | |||
srv.state = st | |||
} | |||
type filer interface { | |||
File() (*os.File, error) | |||
} | |||
type wrappedListener struct { | |||
net.Listener | |||
stopped bool | |||
@@ -1,5 +1,3 @@ | |||
// +build !windows | |||
// 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. | |||
@@ -7,29 +5,37 @@ | |||
package graceful | |||
import ( | |||
"errors" | |||
"fmt" | |||
"os" | |||
"runtime" | |||
"time" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
) | |||
// awaitShutdown waits for the shutdown signal from the Manager | |||
func (srv *Server) awaitShutdown() { | |||
select { | |||
case <-Manager.IsShutdown(): | |||
// Shutdown | |||
srv.doShutdown() | |||
case <-Manager.IsHammer(): | |||
// Hammer | |||
srv.doShutdown() | |||
srv.doHammer() | |||
} | |||
<-Manager.IsHammer() | |||
srv.doHammer() | |||
} | |||
// shutdown closes the listener so that no new connections are accepted | |||
// and starts a goroutine that will hammer (stop all running requests) the server | |||
// after setting.GracefulHammerTime. | |||
func (srv *Server) shutdown() { | |||
func (srv *Server) doShutdown() { | |||
// only shutdown if we're running. | |||
if srv.getState() != stateRunning { | |||
return | |||
} | |||
srv.setState(stateShuttingDown) | |||
if setting.GracefulHammerTime >= 0 { | |||
go srv.hammerTime(setting.GracefulHammerTime) | |||
} | |||
if srv.OnShutdown != nil { | |||
srv.OnShutdown() | |||
@@ -42,14 +48,7 @@ func (srv *Server) shutdown() { | |||
} | |||
} | |||
// hammerTime forces the server to shutdown in a given timeout - whether it | |||
// finished outstanding requests or not. if Read/WriteTimeout are not set or the | |||
// max header size is very big a connection could hang... | |||
// | |||
// srv.Serve() will not return until all connections are served. this will | |||
// unblock the srv.wg.Wait() in Serve() thus causing ListenAndServe* functions to | |||
// return. | |||
func (srv *Server) hammerTime(d time.Duration) { | |||
func (srv *Server) doHammer() { | |||
defer func() { | |||
// We call srv.wg.Done() until it panics. | |||
// 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) { | |||
if srv.getState() != stateShuttingDown { | |||
return | |||
} | |||
time.Sleep(d) | |||
log.Warn("Forcefully shutting down parent") | |||
for { | |||
if srv.getState() == stateTerminate { | |||
@@ -74,48 +72,3 @@ func (srv *Server) hammerTime(d time.Duration) { | |||
runtime.Gosched() | |||
} | |||
} | |||
func (srv *Server) fork() error { | |||
runningServerReg.Lock() | |||
defer runningServerReg.Unlock() | |||
// only one server instance should fork! | |||
if runningServersForked { | |||
return errors.New("another process already forked. Ignoring this one") | |||
} | |||
runningServersForked = true | |||
// We need to move the file logs to append pids | |||
setting.RestartLogsWithPIDSuffix() | |||
_, err := RestartProcess() | |||
return err | |||
} | |||
// RegisterPreSignalHook registers a function to be run before the signal handler for | |||
// a given signal. These are not mutex locked and should therefore be only called before Serve. | |||
func (srv *Server) RegisterPreSignalHook(sig os.Signal, f func()) (err error) { | |||
for _, s := range hookableSignals { | |||
if s == sig { | |||
srv.PreSignalHooks[sig] = append(srv.PreSignalHooks[sig], f) | |||
return | |||
} | |||
} | |||
err = fmt.Errorf("Signal %v is not supported", sig) | |||
return | |||
} | |||
// RegisterPostSignalHook registers a function to be run after the signal handler for | |||
// a given signal. These are not mutex locked and should therefore be only called before Serve. | |||
func (srv *Server) RegisterPostSignalHook(sig os.Signal, f func()) (err error) { | |||
for _, s := range hookableSignals { | |||
if s == sig { | |||
srv.PostSignalHooks[sig] = append(srv.PostSignalHooks[sig], f) | |||
return | |||
} | |||
} | |||
err = fmt.Errorf("Signal %v is not supported", sig) | |||
return | |||
} |
@@ -1,5 +1,3 @@ | |||
// +build !windows | |||
// 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. | |||
@@ -1,95 +0,0 @@ | |||
// +build !windows | |||
// 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 ( | |||
"os" | |||
"os/signal" | |||
"syscall" | |||
"time" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
) | |||
var hookableSignals []os.Signal | |||
func init() { | |||
hookableSignals = []os.Signal{ | |||
syscall.SIGHUP, | |||
syscall.SIGUSR1, | |||
syscall.SIGUSR2, | |||
syscall.SIGINT, | |||
syscall.SIGTERM, | |||
syscall.SIGTSTP, | |||
} | |||
} | |||
// handleSignals listens for os Signals and calls any hooked in function that the | |||
// user had registered with the signal. | |||
func (srv *Server) handleSignals() { | |||
var sig os.Signal | |||
signal.Notify( | |||
srv.sigChan, | |||
hookableSignals..., | |||
) | |||
pid := syscall.Getpid() | |||
for { | |||
sig = <-srv.sigChan | |||
srv.preSignalHooks(sig) | |||
switch sig { | |||
case syscall.SIGHUP: | |||
if setting.GracefulRestartable { | |||
log.Info("PID: %d. Received SIGHUP. Forking...", pid) | |||
err := srv.fork() | |||
if err != nil && err.Error() != "another process already forked. Ignoring this one" { | |||
log.Error("Error whilst forking from PID: %d : %v", pid, err) | |||
} | |||
} else { | |||
log.Info("PID: %d. Received SIGHUP. Not set restartable. Shutting down...", pid) | |||
srv.shutdown() | |||
} | |||
case syscall.SIGUSR1: | |||
log.Info("PID %d. Received SIGUSR1.", pid) | |||
case syscall.SIGUSR2: | |||
log.Warn("PID %d. Received SIGUSR2. Hammering...", pid) | |||
srv.hammerTime(0 * time.Second) | |||
case syscall.SIGINT: | |||
log.Warn("PID %d. Received SIGINT. Shutting down...", pid) | |||
srv.shutdown() | |||
case syscall.SIGTERM: | |||
log.Warn("PID %d. Received SIGTERM. Shutting down...", pid) | |||
srv.shutdown() | |||
case syscall.SIGTSTP: | |||
log.Info("PID %d. Received SIGTSTP.") | |||
default: | |||
log.Info("PID %d. Received %v.", sig) | |||
} | |||
srv.postSignalHooks(sig) | |||
} | |||
} | |||
func (srv *Server) preSignalHooks(sig os.Signal) { | |||
if _, notSet := srv.PreSignalHooks[sig]; !notSet { | |||
return | |||
} | |||
for _, f := range srv.PreSignalHooks[sig] { | |||
f() | |||
} | |||
} | |||
func (srv *Server) postSignalHooks(sig os.Signal) { | |||
if _, notSet := srv.PostSignalHooks[sig]; !notSet { | |||
return | |||
} | |||
for _, f := range srv.PostSignalHooks[sig] { | |||
f() | |||
} | |||
} |
@@ -172,7 +172,7 @@ func InitIssueIndexer(syncReindex bool) { | |||
} else if setting.Indexer.StartupTimeout > 0 { | |||
go func() { | |||
timeout := setting.Indexer.StartupTimeout | |||
if graceful.IsChild && setting.GracefulHammerTime > 0 { | |||
if graceful.Manager.IsChild() && setting.GracefulHammerTime > 0 { | |||
timeout += setting.GracefulHammerTime | |||
} | |||
select { | |||
@@ -1,20 +0,0 @@ | |||
Copyright (c) 2015 Daniel Theophanes | |||
This software is provided 'as-is', without any express or implied | |||
warranty. In no event will the authors be held liable for any damages | |||
arising from the use of this software. | |||
Permission is granted to anyone to use this software for any purpose, | |||
including commercial applications, and to alter it and redistribute it | |||
freely, subject to the following restrictions: | |||
1. The origin of this software must not be misrepresented; you must not | |||
claim that you wrote the original software. If you use this software | |||
in a product, an acknowledgment in the product documentation would be | |||
appreciated but is not required. | |||
2. Altered source versions must be plainly marked as such, and must not be | |||
misrepresented as being the original software. | |||
3. This notice may not be removed or altered from any source | |||
distribution. |
@@ -1,18 +0,0 @@ | |||
### Minimal windows service stub | |||
Programs designed to run from most *nix style operating systems | |||
can import this package to enable running programs as services without modifying | |||
them. | |||
``` | |||
import _ "github.com/kardianos/minwinsvc" | |||
``` | |||
If you need more control over the exit behavior, set | |||
``` | |||
minwinsvc.SetOnExit(func() { | |||
// Do something. | |||
// Within 10 seconds call: | |||
os.Exit(0) | |||
}) | |||
``` |
@@ -1,18 +0,0 @@ | |||
// Copyright 2015 Daniel Theophanes. | |||
// Use of this source code is governed by a zlib-style | |||
// license that can be found in the LICENSE file.package service | |||
// Package minwinsvc is a minimal non-invasive windows only service stub. | |||
// | |||
// Import to allow running as a windows service. | |||
// import _ "github.com/kardianos/minwinsvc" | |||
// This will detect if running as a windows service | |||
// and install required callbacks for windows. | |||
package minwinsvc | |||
// SetOnExit sets the function to be called when the windows service | |||
// requests an exit. If this is not called, or if it is called where | |||
// f == nil, then it defaults to calling "os.Exit(0)". | |||
func SetOnExit(f func()) { | |||
setOnExit(f) | |||
} |
@@ -1,11 +0,0 @@ | |||
// Copyright 2015 Daniel Theophanes. | |||
// Use of this source code is governed by a zlib-style | |||
// license that can be found in the LICENSE file.package service | |||
//+build !windows | |||
package minwinsvc | |||
func setOnExit(f func()) { | |||
// Nothing. | |||
} |
@@ -1,76 +0,0 @@ | |||
// Copyright 2015 Daniel Theophanes. | |||
// Use of this source code is governed by a zlib-style | |||
// license that can be found in the LICENSE file.package service | |||
//+build windows | |||
package minwinsvc | |||
import ( | |||
"os" | |||
"strconv" | |||
"sync" | |||
"golang.org/x/sys/windows/svc" | |||
) | |||
var ( | |||
onExit func() | |||
guard sync.Mutex | |||
skip, _ = strconv.ParseBool(os.Getenv("SKIP_MINWINSVC")) | |||
isSSH = os.Getenv("SSH_ORIGINAL_COMMAND") != "" | |||
) | |||
func init() { | |||
if skip || isSSH { | |||
return | |||
} | |||
interactive, err := svc.IsAnInteractiveSession() | |||
if err != nil { | |||
panic(err) | |||
} | |||
if interactive { | |||
return | |||
} | |||
go func() { | |||
_ = svc.Run("", runner{}) | |||
guard.Lock() | |||
f := onExit | |||
guard.Unlock() | |||
// Don't hold this lock in user code. | |||
if f != nil { | |||
f() | |||
} | |||
// Make sure we exit. | |||
os.Exit(0) | |||
}() | |||
} | |||
func setOnExit(f func()) { | |||
guard.Lock() | |||
onExit = f | |||
guard.Unlock() | |||
} | |||
type runner struct{} | |||
func (runner) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) { | |||
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | |||
changes <- svc.Status{State: svc.StartPending} | |||
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} | |||
for { | |||
c := <-r | |||
switch c.Cmd { | |||
case svc.Interrogate: | |||
changes <- c.CurrentStatus | |||
case svc.Stop, svc.Shutdown: | |||
changes <- svc.Status{State: svc.StopPending} | |||
return false, 0 | |||
} | |||
} | |||
return false, 0 | |||
} |
@@ -24,7 +24,6 @@ import ( | |||
"code.gitea.io/gitea/modules/generate" | |||
"code.gitea.io/gitea/modules/git" | |||
"code.gitea.io/gitea/modules/log" | |||
_ "code.gitea.io/gitea/modules/minwinsvc" // import minwinsvc for windows services | |||
"code.gitea.io/gitea/modules/user" | |||
shellquote "github.com/kballard/go-shellquote" | |||
@@ -99,6 +98,7 @@ var ( | |||
LetsEncryptEmail string | |||
GracefulRestartable bool | |||
GracefulHammerTime time.Duration | |||
StartupTimeout time.Duration | |||
StaticURLPrefix string | |||
SSH = struct { | |||
@@ -569,6 +569,7 @@ func NewContext() { | |||
HTTPPort = sec.Key("HTTP_PORT").MustString("3000") | |||
GracefulRestartable = sec.Key("ALLOW_GRACEFUL_RESTARTS").MustBool(true) | |||
GracefulHammerTime = sec.Key("GRACEFUL_HAMMER_TIME").MustDuration(60 * time.Second) | |||
StartupTimeout = sec.Key("STARTUP_TIMEOUT").MustDuration(0 * time.Second) | |||
defaultAppURL := string(Protocol) + "://" + Domain | |||
if (Protocol == HTTP && HTTPPort != "80") || (Protocol == HTTPS && HTTPPort != "443") { | |||
@@ -1,5 +1,3 @@ | |||
// +build !windows | |||
// 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. | |||
@@ -26,5 +24,5 @@ func listen(server *ssh.Server) { | |||
// Unused informs our cleanup routine that we will not be using a ssh port | |||
func Unused() { | |||
graceful.InformCleanup() | |||
graceful.Manager.InformCleanup() | |||
} |
@@ -1,24 +0,0 @@ | |||
// +build windows | |||
// 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 ssh | |||
import ( | |||
"code.gitea.io/gitea/modules/log" | |||
"github.com/gliderlabs/ssh" | |||
) | |||
func listen(server *ssh.Server) { | |||
err := server.ListenAndServe() | |||
if err != nil { | |||
log.Critical("Failed to serve with builtin SSH server. %s", err) | |||
} | |||
} | |||
// Unused does nothing on windows | |||
func Unused() { | |||
// Do nothing | |||
} |
@@ -0,0 +1,56 @@ | |||
// Copyright 2012 The Go Authors. All rights reserved. | |||
// Use of this source code is governed by a BSD-style | |||
// license that can be found in the LICENSE file. | |||
// +build windows | |||
package debug | |||
import ( | |||
"os" | |||
"strconv" | |||
) | |||
// Log interface allows different log implementations to be used. | |||
type Log interface { | |||
Close() error | |||
Info(eid uint32, msg string) error | |||
Warning(eid uint32, msg string) error | |||
Error(eid uint32, msg string) error | |||
} | |||
// ConsoleLog provides access to the console. | |||
type ConsoleLog struct { | |||
Name string | |||
} | |||
// New creates new ConsoleLog. | |||
func New(source string) *ConsoleLog { | |||
return &ConsoleLog{Name: source} | |||
} | |||
// Close closes console log l. | |||
func (l *ConsoleLog) Close() error { | |||
return nil | |||
} | |||
func (l *ConsoleLog) report(kind string, eid uint32, msg string) error { | |||
s := l.Name + "." + kind + "(" + strconv.Itoa(int(eid)) + "): " + msg + "\n" | |||
_, err := os.Stdout.Write([]byte(s)) | |||
return err | |||
} | |||
// Info writes an information event msg with event id eid to the console l. | |||
func (l *ConsoleLog) Info(eid uint32, msg string) error { | |||
return l.report("info", eid, msg) | |||
} | |||
// Warning writes an warning event msg with event id eid to the console l. | |||
func (l *ConsoleLog) Warning(eid uint32, msg string) error { | |||
return l.report("warn", eid, msg) | |||
} | |||
// Error writes an error event msg with event id eid to the console l. | |||
func (l *ConsoleLog) Error(eid uint32, msg string) error { | |||
return l.report("error", eid, msg) | |||
} |
@@ -0,0 +1,45 @@ | |||
// Copyright 2012 The Go Authors. All rights reserved. | |||
// Use of this source code is governed by a BSD-style | |||
// license that can be found in the LICENSE file. | |||
// +build windows | |||
// Package debug provides facilities to execute svc.Handler on console. | |||
// | |||
package debug | |||
import ( | |||
"os" | |||
"os/signal" | |||
"syscall" | |||
"golang.org/x/sys/windows/svc" | |||
) | |||
// Run executes service name by calling appropriate handler function. | |||
// The process is running on console, unlike real service. Use Ctrl+C to | |||
// send "Stop" command to your service. | |||
func Run(name string, handler svc.Handler) error { | |||
cmds := make(chan svc.ChangeRequest) | |||
changes := make(chan svc.Status) | |||
sig := make(chan os.Signal) | |||
signal.Notify(sig) | |||
go func() { | |||
status := svc.Status{State: svc.Stopped} | |||
for { | |||
select { | |||
case <-sig: | |||
cmds <- svc.ChangeRequest{Cmd: svc.Stop, CurrentStatus: status} | |||
case status = <-changes: | |||
} | |||
} | |||
}() | |||
_, errno := handler.Execute([]string{name}, cmds, changes) | |||
if errno != 0 { | |||
return syscall.Errno(errno) | |||
} | |||
return nil | |||
} |
@@ -482,6 +482,7 @@ golang.org/x/sys/cpu | |||
golang.org/x/sys/unix | |||
golang.org/x/sys/windows | |||
golang.org/x/sys/windows/svc | |||
golang.org/x/sys/windows/svc/debug | |||
# golang.org/x/text v0.3.2 | |||
golang.org/x/text/encoding | |||
golang.org/x/text/encoding/charmap | |||