2019-11-21 18:32:02 +00:00
// +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 (
2019-11-30 08:40:22 -06:00
"context"
2019-11-21 18:32:02 +00:00
"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
}
2019-11-30 08:40:22 -06:00
func newGracefulManager ( ctx context . Context ) * gracefulManager {
2019-11-21 18:32:02 +00:00
manager := & gracefulManager {
isChild : len ( os . Getenv ( listenFDs ) ) > 0 && os . Getppid ( ) > 1 ,
lock : & sync . RWMutex { } ,
}
manager . createServerWaitGroup . Add ( numberOfServersToCreate )
2019-11-30 08:40:22 -06:00
manager . Run ( ctx )
2019-11-21 18:32:02 +00:00
return manager
}
2019-11-30 08:40:22 -06:00
func ( g * gracefulManager ) Run ( ctx context . Context ) {
2019-11-21 18:32:02 +00:00
g . setState ( stateRunning )
2019-11-30 08:40:22 -06:00
go g . handleSignals ( ctx )
2019-11-21 18:32:02 +00:00
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 ( )
}
} ( )
}
}
2019-11-30 08:40:22 -06:00
func ( g * gracefulManager ) handleSignals ( ctx context . Context ) {
2019-11-21 18:32:02 +00:00
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 {
2019-11-30 08:40:22 -06:00
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 ( )
2019-11-21 18:32:02 +00:00
}
2019-11-30 08:40:22 -06:00
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 )
2019-11-21 18:32:02 +00:00
g . doShutdown ( )
2019-11-30 08:40:22 -06:00
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 )
2019-11-21 18:32:02 +00:00
}
2019-11-30 08:40:22 -06:00
case <- ctx . Done ( ) :
log . Warn ( "PID: %d. Background context for manager closed - %v - Shutting down..." , pid , ctx . Err ( ) )
2019-11-21 18:32:02 +00:00
g . doShutdown ( )
}
}
}
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 )
}