@ -6,9 +6,9 @@ package graceful
import (
import (
"context"
"context"
"sync"
"time"
"time"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/setting"
@ -34,14 +34,24 @@ const (
const numberOfServersToCreate = 3
const numberOfServersToCreate = 3
// Manager represents the graceful server manager interface
// Manager represents the graceful server manager interface
var Manager * gracefulManager
var manager * Manager
func init ( ) {
var initOnce = sync . Once { }
Manager = newGracefulManager ( context . Background ( ) )
// Set the git default context to the HammerContext
// GetManager returns the Manager
git . DefaultContext = Manager . HammerContext ( )
func GetManager ( ) * Manager {
// Set the process default context to the HammerContext
InitManager ( context . Background ( ) )
process . DefaultContext = Manager . HammerContext ( )
return manager
}
// InitManager creates the graceful manager in the provided context
func InitManager ( ctx context . Context ) {
initOnce . Do ( func ( ) {
manager = newGracefulManager ( ctx )
// 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
// CallbackWithContext is combined runnable and context to watch to see if the caller has finished
@ -61,7 +71,7 @@ type RunnableWithShutdownFns func(atShutdown, atTerminate func(context.Context,
// Please note that use of the atShutdown and atTerminate callbacks will create go-routines that will wait till their respective signals
// 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.
// - 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.
// If run is not expected to run indefinitely RunWithShutdownChan is likely to be more appropriate.
func ( g * graceful Manager) RunWithShutdownFns ( run RunnableWithShutdownFns ) {
func ( g * Manager ) RunWithShutdownFns ( run RunnableWithShutdownFns ) {
g . runningServerWaitGroup . Add ( 1 )
g . runningServerWaitGroup . Add ( 1 )
defer g . runningServerWaitGroup . Done ( )
defer g . runningServerWaitGroup . Done ( )
run ( func ( ctx context . Context , atShutdown func ( ) ) {
run ( func ( ctx context . Context , atShutdown func ( ) ) {
@ -90,7 +100,7 @@ type RunnableWithShutdownChan func(atShutdown <-chan struct{}, atTerminate Callb
// (Optionally IsHammer may be waited for instead however, this should be avoided if possible.)
// (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.
// 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.
// 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 * graceful Manager) RunWithShutdownChan ( run RunnableWithShutdownChan ) {
func ( g * Manager ) RunWithShutdownChan ( run RunnableWithShutdownChan ) {
g . runningServerWaitGroup . Add ( 1 )
g . runningServerWaitGroup . Add ( 1 )
defer g . runningServerWaitGroup . Done ( )
defer g . runningServerWaitGroup . Done ( )
run ( g . IsShutdown ( ) , func ( ctx context . Context , atTerminate func ( ) ) {
run ( g . IsShutdown ( ) , func ( ctx context . Context , atTerminate func ( ) ) {
@ -101,14 +111,14 @@ func (g *gracefulManager) RunWithShutdownChan(run RunnableWithShutdownChan) {
// RunWithShutdownContext takes a function that has a context to watch for shutdown.
// 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.
// 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.)
// (Optionally the HammerContext may be obtained and waited for however, this should be avoided if possible.)
func ( g * graceful Manager) RunWithShutdownContext ( run func ( context . Context ) ) {
func ( g * Manager ) RunWithShutdownContext ( run func ( context . Context ) ) {
g . runningServerWaitGroup . Add ( 1 )
g . runningServerWaitGroup . Add ( 1 )
defer g . runningServerWaitGroup . Done ( )
defer g . runningServerWaitGroup . Done ( )
run ( g . ShutdownContext ( ) )
run ( g . ShutdownContext ( ) )
}
}
// RunAtTerminate adds to the terminate wait group and creates a go-routine to run the provided function at termination
// RunAtTerminate adds to the terminate wait group and creates a go-routine to run the provided function at termination
func ( g * graceful Manager) RunAtTerminate ( ctx context . Context , terminate func ( ) ) {
func ( g * Manager ) RunAtTerminate ( ctx context . Context , terminate func ( ) ) {
g . terminateWaitGroup . Add ( 1 )
g . terminateWaitGroup . Add ( 1 )
go func ( ) {
go func ( ) {
select {
select {
@ -121,7 +131,7 @@ func (g *gracefulManager) RunAtTerminate(ctx context.Context, terminate func())
}
}
// RunAtShutdown creates a go-routine to run the provided function at shutdown
// RunAtShutdown creates a go-routine to run the provided function at shutdown
func ( g * graceful Manager) RunAtShutdown ( ctx context . Context , shutdown func ( ) ) {
func ( g * Manager ) RunAtShutdown ( ctx context . Context , shutdown func ( ) ) {
go func ( ) {
go func ( ) {
select {
select {
case <- g . IsShutdown ( ) :
case <- g . IsShutdown ( ) :
@ -132,7 +142,7 @@ func (g *gracefulManager) RunAtShutdown(ctx context.Context, shutdown func()) {
}
}
// RunAtHammer creates a go-routine to run the provided function at shutdown
// RunAtHammer creates a go-routine to run the provided function at shutdown
func ( g * graceful Manager) RunAtHammer ( ctx context . Context , hammer func ( ) ) {
func ( g * Manager ) RunAtHammer ( ctx context . Context , hammer func ( ) ) {
go func ( ) {
go func ( ) {
select {
select {
case <- g . IsHammer ( ) :
case <- g . IsHammer ( ) :
@ -141,7 +151,7 @@ func (g *gracefulManager) RunAtHammer(ctx context.Context, hammer func()) {
}
}
} ( )
} ( )
}
}
func ( g * graceful Manager) doShutdown ( ) {
func ( g * Manager ) doShutdown ( ) {
if ! g . setStateTransition ( stateRunning , stateShuttingDown ) {
if ! g . setStateTransition ( stateRunning , stateShuttingDown ) {
return
return
}
}
@ -158,48 +168,47 @@ func (g *gracefulManager) doShutdown() {
g . doHammerTime ( 0 )
g . doHammerTime ( 0 )
<- time . After ( 1 * time . Second )
<- time . After ( 1 * time . Second )
g . doTerminate ( )
g . doTerminate ( )
g . WaitForTerminate ( )
g . lock . Lock ( )
close ( g . done )
g . lock . Unlock ( )
} ( )
} ( )
}
}
func ( g * gracefulManager ) doHammerTime ( d time . Duration ) {
func ( g * Manager ) doHammerTime ( d time . Duration ) {
time . Sleep ( d )
time . Sleep ( d )
g . lock . Lock ( )
select {
select {
case <- g . hammer :
case <- g . hammer :
default :
default :
log . Warn ( "Setting Hammer condition" )
log . Warn ( "Setting Hammer condition" )
close ( g . hammer )
close ( g . hammer )
}
}
g . lock . Unlock ( )
}
}
func ( g * graceful Manager) doTerminate ( ) {
func ( g * Manager ) doTerminate ( ) {
if ! g . setStateTransition ( stateShuttingDown , stateTerminate ) {
if ! g . setStateTransition ( stateShuttingDown , stateTerminate ) {
return
return
}
}
g . lock . Lock ( )
g . lock . Lock ( )
close ( g . terminate )
select {
case <- g . terminate :
default :
log . Warn ( "Terminating" )
close ( g . terminate )
}
g . lock . Unlock ( )
g . lock . Unlock ( )
}
}
// IsChild returns if the current process is a child of previous Gitea process
// IsChild returns if the current process is a child of previous Gitea process
func ( g * gracefulManager ) IsChild ( ) bool {
func ( g * Manager ) IsChild ( ) bool {
return g . isChild
return g . isChild
}
}
// IsShutdown returns a channel which will be closed at shutdown.
// IsShutdown returns a channel which will be closed at shutdown.
// The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
// The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
func ( g * gracefulManager ) IsShutdown ( ) <- chan struct { } {
func ( g * Manager ) 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
return g . shutdown
}
}
@ -207,65 +216,43 @@ func (g *gracefulManager) IsShutdown() <-chan struct{} {
// The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
// The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
// Servers running within the running server wait group should respond to IsHammer
// Servers running within the running server wait group should respond to IsHammer
// if not shutdown already
// if not shutdown already
func ( g * gracefulManager ) IsHammer ( ) <- chan struct { } {
func ( g * Manager ) 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
return g . hammer
}
}
// IsTerminate returns a channel which will be closed at terminate
// IsTerminate returns a channel which will be closed at terminate
// The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
// The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
// IsTerminate will only close once all running servers have stopped
// IsTerminate will only close once all running servers have stopped
func ( g * gracefulManager ) IsTerminate ( ) <- chan struct { } {
func ( g * Manager ) 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
return g . terminate
}
}
// ServerDone declares a running server done and subtracts one from the
// ServerDone declares a running server done and subtracts one from the
// running server wait group. Users probably do not want to call this
// running server wait group. Users probably do not want to call this
// and should use one of the RunWithShutdown* functions
// and should use one of the RunWithShutdown* functions
func ( g * graceful Manager) ServerDone ( ) {
func ( g * Manager ) ServerDone ( ) {
g . runningServerWaitGroup . Done ( )
g . runningServerWaitGroup . Done ( )
}
}
// WaitForServers waits for all running servers to finish. Users should probably
// WaitForServers waits for all running servers to finish. Users should probably
// instead use AtTerminate or IsTerminate
// instead use AtTerminate or IsTerminate
func ( g * graceful Manager) WaitForServers ( ) {
func ( g * Manager ) WaitForServers ( ) {
g . runningServerWaitGroup . Wait ( )
g . runningServerWaitGroup . Wait ( )
}
}
// WaitForTerminate waits for all terminating actions to finish.
// WaitForTerminate waits for all terminating actions to finish.
// Only the main go-routine should use this
// Only the main go-routine should use this
func ( g * graceful Manager) WaitForTerminate ( ) {
func ( g * Manager ) WaitForTerminate ( ) {
g . terminateWaitGroup . Wait ( )
g . terminateWaitGroup . Wait ( )
}
}
func ( g * graceful Manager) getState ( ) state {
func ( g * Manager ) getState ( ) state {
g . lock . RLock ( )
g . lock . RLock ( )
defer g . lock . RUnlock ( )
defer g . lock . RUnlock ( )
return g . state
return g . state
}
}
func ( g * graceful Manager) setStateTransition ( old , new state ) bool {
func ( g * Manager ) setStateTransition ( old , new state ) bool {
if old != g . getState ( ) {
if old != g . getState ( ) {
return false
return false
}
}
@ -279,7 +266,7 @@ func (g *gracefulManager) setStateTransition(old, new state) bool {
return true
return true
}
}
func ( g * graceful Manager) setState ( st state ) {
func ( g * Manager ) setState ( st state ) {
g . lock . Lock ( )
g . lock . Lock ( )
defer g . lock . Unlock ( )
defer g . lock . Unlock ( )
@ -288,6 +275,31 @@ func (g *gracefulManager) setState(st state) {
// InformCleanup tells the cleanup wait group that we have either taken a listener
// InformCleanup tells the cleanup wait group that we have either taken a listener
// or will not be taking a listener
// or will not be taking a listener
func ( g * graceful Manager) InformCleanup ( ) {
func ( g * Manager ) InformCleanup ( ) {
g . createServerWaitGroup . Done ( )
g . createServerWaitGroup . Done ( )
}
}
// Done allows the manager to be viewed as a context.Context, it returns a channel that is closed when the server is finished terminating
func ( g * Manager ) Done ( ) <- chan struct { } {
return g . done
}
// Err allows the manager to be viewed as a context.Context done at Terminate, it returns ErrTerminate
func ( g * Manager ) Err ( ) error {
select {
case <- g . Done ( ) :
return ErrTerminate
default :
return nil
}
}
// Value allows the manager to be viewed as a context.Context done at Terminate, it has no values
func ( g * Manager ) Value ( key interface { } ) interface { } {
return nil
}
// Deadline returns nil as there is no fixed Deadline for the manager, it allows the manager to be viewed as a context.Context
func ( g * Manager ) Deadline ( ) ( deadline time . Time , ok bool ) {
return
}