diff options
Diffstat (limited to 'modules/graceful')
-rw-r--r-- | modules/graceful/context.go | 90 | ||||
-rw-r--r-- | modules/graceful/manager.go | 108 | ||||
-rw-r--r-- | modules/graceful/manager_unix.go | 68 | ||||
-rw-r--r-- | modules/graceful/manager_windows.go | 35 |
4 files changed, 255 insertions, 46 deletions
diff --git a/modules/graceful/context.go b/modules/graceful/context.go new file mode 100644 index 0000000000..a4a4df7dea --- /dev/null +++ b/modules/graceful/context.go @@ -0,0 +1,90 @@ +// 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 ( + "context" + "fmt" + "time" +) + +// Errors for context.Err() +var ( + ErrShutdown = fmt.Errorf("Graceful Manager called Shutdown") + ErrHammer = fmt.Errorf("Graceful Manager called Hammer") + ErrTerminate = fmt.Errorf("Graceful Manager called Terminate") +) + +// ChannelContext is a context that wraps a channel and error as a context +type ChannelContext struct { + done <-chan struct{} + err error +} + +// NewChannelContext creates a ChannelContext from a channel and error +func NewChannelContext(done <-chan struct{}, err error) *ChannelContext { + return &ChannelContext{ + done: done, + err: err, + } +} + +// Deadline returns the time when work done on behalf of this context +// should be canceled. There is no Deadline for a ChannelContext +func (ctx *ChannelContext) Deadline() (deadline time.Time, ok bool) { + return +} + +// Done returns the channel provided at the creation of this context. +// When closed, work done on behalf of this context should be canceled. +func (ctx *ChannelContext) Done() <-chan struct{} { + return ctx.done +} + +// Err returns nil, if Done is not closed. If Done is closed, +// Err returns the error provided at the creation of this context +func (ctx *ChannelContext) Err() error { + select { + case <-ctx.done: + return ctx.err + default: + return nil + } +} + +// Value returns nil for all calls as no values are or can be associated with this context +func (ctx *ChannelContext) Value(key interface{}) interface{} { + return nil +} + +// ShutdownContext returns a context.Context that is Done at shutdown +// Callers using this context should ensure that they are registered as a running server +// in order that they are waited for. +func (g *gracefulManager) ShutdownContext() context.Context { + return &ChannelContext{ + done: g.IsShutdown(), + err: ErrShutdown, + } +} + +// HammerContext returns a context.Context that is Done at hammer +// Callers using this context should ensure that they are registered as a running server +// in order that they are waited for. +func (g *gracefulManager) HammerContext() context.Context { + return &ChannelContext{ + done: g.IsHammer(), + err: ErrHammer, + } +} + +// TerminateContext returns a context.Context that is Done at terminate +// Callers using this context should ensure that they are registered as a terminating server +// in order that they are waited for. +func (g *gracefulManager) TerminateContext() context.Context { + return &ChannelContext{ + done: g.IsTerminate(), + err: ErrTerminate, + } +} diff --git a/modules/graceful/manager.go b/modules/graceful/manager.go index 48f76635ff..b9a56ca9c6 100644 --- a/modules/graceful/manager.go +++ b/modules/graceful/manager.go @@ -5,9 +5,12 @@ package graceful import ( + "context" "time" + "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" ) @@ -34,9 +37,110 @@ const numberOfServersToCreate = 3 var Manager *gracefulManager func init() { - Manager = newGracefulManager() + Manager = newGracefulManager(context.Background()) + // Set the git default context to the HammerContext + git.DefaultContext = Manager.HammerContext() + // Set the process default context to the HammerContext + process.DefaultContext = Manager.HammerContext() } +// CallbackWithContext is combined runnable and context to watch to see if the caller has finished +type CallbackWithContext func(ctx context.Context, callback func()) + +// RunnableWithShutdownFns is a runnable with functions to run at shutdown and terminate +// After the callback to atShutdown is called and is complete, the main function must return. +// Similarly the callback function provided to atTerminate must return once termination is complete. +// Please note that use of the atShutdown and atTerminate callbacks will create go-routines that will wait till their respective signals +// - users must therefore be careful to only call these as necessary. +// If run is not expected to run indefinitely RunWithShutdownChan is likely to be more appropriate. +type RunnableWithShutdownFns func(atShutdown, atTerminate func(context.Context, func())) + +// RunWithShutdownFns takes a function that has both atShutdown and atTerminate callbacks +// After the callback to atShutdown is called and is complete, the main function must return. +// Similarly the callback function provided to atTerminate must return once termination is complete. +// Please note that use of the atShutdown and atTerminate callbacks will create go-routines that will wait till their respective signals +// - users must therefore be careful to only call these as necessary. +// If run is not expected to run indefinitely RunWithShutdownChan is likely to be more appropriate. +func (g *gracefulManager) RunWithShutdownFns(run RunnableWithShutdownFns) { + g.runningServerWaitGroup.Add(1) + defer g.runningServerWaitGroup.Done() + run(func(ctx context.Context, atShutdown func()) { + go func() { + select { + case <-g.IsShutdown(): + atShutdown() + case <-ctx.Done(): + return + } + }() + }, func(ctx context.Context, atTerminate func()) { + g.RunAtTerminate(ctx, atTerminate) + }) +} + +// RunnableWithShutdownChan is a runnable with functions to run at shutdown and terminate. +// After the atShutdown channel is closed, the main function must return once shutdown is complete. +// (Optionally IsHammer may be waited for instead however, this should be avoided if possible.) +// The callback function provided to atTerminate must return once termination is complete. +// Please note that use of the atTerminate function will create a go-routine that will wait till terminate - users must therefore be careful to only call this as necessary. +type RunnableWithShutdownChan func(atShutdown <-chan struct{}, atTerminate CallbackWithContext) + +// RunWithShutdownChan takes a function that has channel to watch for shutdown and atTerminate callbacks +// After the atShutdown channel is closed, the main function must return once shutdown is complete. +// (Optionally IsHammer may be waited for instead however, this should be avoided if possible.) +// The callback function provided to atTerminate must return once termination is complete. +// Please note that use of the atTerminate function will create a go-routine that will wait till terminate - users must therefore be careful to only call this as necessary. +func (g *gracefulManager) RunWithShutdownChan(run RunnableWithShutdownChan) { + g.runningServerWaitGroup.Add(1) + defer g.runningServerWaitGroup.Done() + run(g.IsShutdown(), func(ctx context.Context, atTerminate func()) { + g.RunAtTerminate(ctx, atTerminate) + }) +} + +// RunWithShutdownContext takes a function that has a context to watch for shutdown. +// After the provided context is Done(), the main function must return once shutdown is complete. +// (Optionally the HammerContext may be obtained and waited for however, this should be avoided if possible.) +func (g *gracefulManager) RunWithShutdownContext(run func(context.Context)) { + g.runningServerWaitGroup.Add(1) + defer g.runningServerWaitGroup.Done() + run(g.ShutdownContext()) +} + +// RunAtTerminate adds to the terminate wait group and creates a go-routine to run the provided function at termination +func (g *gracefulManager) RunAtTerminate(ctx context.Context, terminate func()) { + g.terminateWaitGroup.Add(1) + go func() { + select { + case <-g.IsTerminate(): + terminate() + case <-ctx.Done(): + } + g.terminateWaitGroup.Done() + }() +} + +// RunAtShutdown creates a go-routine to run the provided function at shutdown +func (g *gracefulManager) RunAtShutdown(ctx context.Context, shutdown func()) { + go func() { + select { + case <-g.IsShutdown(): + shutdown() + case <-ctx.Done(): + } + }() +} + +// RunAtHammer creates a go-routine to run the provided function at shutdown +func (g *gracefulManager) RunAtHammer(ctx context.Context, hammer func()) { + go func() { + select { + case <-g.IsHammer(): + hammer() + case <-ctx.Done(): + } + }() +} func (g *gracefulManager) doShutdown() { if !g.setStateTransition(stateRunning, stateShuttingDown) { return @@ -50,6 +154,8 @@ func (g *gracefulManager) doShutdown() { } go func() { g.WaitForServers() + // Mop up any remaining unclosed events. + g.doHammerTime(0) <-time.After(1 * time.Second) g.doTerminate() }() diff --git a/modules/graceful/manager_unix.go b/modules/graceful/manager_unix.go index 15b0ff4448..1ffc59f0df 100644 --- a/modules/graceful/manager_unix.go +++ b/modules/graceful/manager_unix.go @@ -7,6 +7,7 @@ package graceful import ( + "context" "errors" "os" "os/signal" @@ -31,19 +32,19 @@ type gracefulManager struct { terminateWaitGroup sync.WaitGroup } -func newGracefulManager() *gracefulManager { +func newGracefulManager(ctx context.Context) *gracefulManager { manager := &gracefulManager{ isChild: len(os.Getenv(listenFDs)) > 0 && os.Getppid() > 1, lock: &sync.RWMutex{}, } manager.createServerWaitGroup.Add(numberOfServersToCreate) - manager.Run() + manager.Run(ctx) return manager } -func (g *gracefulManager) Run() { +func (g *gracefulManager) Run(ctx context.Context) { g.setState(stateRunning) - go g.handleSignals() + go g.handleSignals(ctx) c := make(chan struct{}) go func() { defer close(c) @@ -69,9 +70,7 @@ func (g *gracefulManager) Run() { } } -func (g *gracefulManager) handleSignals() { - var sig os.Signal - +func (g *gracefulManager) handleSignals(ctx context.Context) { signalChannel := make(chan os.Signal, 1) signal.Notify( @@ -86,35 +85,40 @@ func (g *gracefulManager) handleSignals() { 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) + select { + case 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() } - } else { - log.Info("PID: %d. Received SIGHUP. Not set restartable. Shutting down...", pid) - + 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) } - 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) + case <-ctx.Done(): + log.Warn("PID: %d. Background context for manager closed - %v - Shutting down...", pid, ctx.Err()) g.doShutdown() - case syscall.SIGTSTP: - log.Info("PID %d. Received SIGTSTP.", pid) - default: - log.Info("PID %d. Received %v.", pid, sig) } } } diff --git a/modules/graceful/manager_windows.go b/modules/graceful/manager_windows.go index 925b1fc560..dd48a8d74c 100644 --- a/modules/graceful/manager_windows.go +++ b/modules/graceful/manager_windows.go @@ -8,6 +8,7 @@ package graceful import ( + "context" "os" "strconv" "sync" @@ -29,6 +30,7 @@ const ( ) type gracefulManager struct { + ctx context.Context isChild bool lock *sync.RWMutex state state @@ -40,10 +42,11 @@ type gracefulManager struct { terminateWaitGroup sync.WaitGroup } -func newGracefulManager() *gracefulManager { +func newGracefulManager(ctx context.Context) *gracefulManager { manager := &gracefulManager{ isChild: false, lock: &sync.RWMutex{}, + ctx: ctx, } manager.createServerWaitGroup.Add(numberOfServersToCreate) manager.Run() @@ -89,23 +92,29 @@ func (g *gracefulManager) Execute(args []string, changes <-chan svc.ChangeReques waitTime := 30 * time.Second loop: - for change := range changes { - switch change.Cmd { - case svc.Interrogate: - status <- change.CurrentStatus - case svc.Stop, svc.Shutdown: + for { + select { + case <-g.ctx.Done(): 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) + case change := <-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), |