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_TIMEOUTtokarchuk/v1.17
parent
d7ac9727bb
commit
cbaa1de9ec
@ -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() { |
|
||||||
} |
|
@ -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 |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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) |
||||||
|
} |
@ -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() |
|
||||||
} |
|
||||||
} |
|
@ -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 |
|
||||||
} |
|
@ -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 |
||||||
|
} |
Loading…
Reference in new issue