code.gitea.io/gitea@v1.19.3/modules/graceful/manager_unix.go (about)

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  //go:build !windows
     5  
     6  package graceful
     7  
     8  import (
     9  	"context"
    10  	"errors"
    11  	"os"
    12  	"os/signal"
    13  	"runtime/pprof"
    14  	"sync"
    15  	"syscall"
    16  	"time"
    17  
    18  	"code.gitea.io/gitea/modules/log"
    19  	"code.gitea.io/gitea/modules/process"
    20  	"code.gitea.io/gitea/modules/setting"
    21  )
    22  
    23  // Manager manages the graceful shutdown process
    24  type Manager struct {
    25  	isChild                bool
    26  	forked                 bool
    27  	lock                   *sync.RWMutex
    28  	state                  state
    29  	shutdownCtx            context.Context
    30  	hammerCtx              context.Context
    31  	terminateCtx           context.Context
    32  	managerCtx             context.Context
    33  	shutdownCtxCancel      context.CancelFunc
    34  	hammerCtxCancel        context.CancelFunc
    35  	terminateCtxCancel     context.CancelFunc
    36  	managerCtxCancel       context.CancelFunc
    37  	runningServerWaitGroup sync.WaitGroup
    38  	createServerWaitGroup  sync.WaitGroup
    39  	terminateWaitGroup     sync.WaitGroup
    40  
    41  	toRunAtShutdown  []func()
    42  	toRunAtHammer    []func()
    43  	toRunAtTerminate []func()
    44  }
    45  
    46  func newGracefulManager(ctx context.Context) *Manager {
    47  	manager := &Manager{
    48  		isChild: len(os.Getenv(listenFDs)) > 0 && os.Getppid() > 1,
    49  		lock:    &sync.RWMutex{},
    50  	}
    51  	manager.createServerWaitGroup.Add(numberOfServersToCreate)
    52  	manager.start(ctx)
    53  	return manager
    54  }
    55  
    56  func (g *Manager) start(ctx context.Context) {
    57  	// Make contexts
    58  	g.terminateCtx, g.terminateCtxCancel = context.WithCancel(ctx)
    59  	g.shutdownCtx, g.shutdownCtxCancel = context.WithCancel(ctx)
    60  	g.hammerCtx, g.hammerCtxCancel = context.WithCancel(ctx)
    61  	g.managerCtx, g.managerCtxCancel = context.WithCancel(ctx)
    62  
    63  	// Next add pprof labels to these contexts
    64  	g.terminateCtx = pprof.WithLabels(g.terminateCtx, pprof.Labels("graceful-lifecycle", "with-terminate"))
    65  	g.shutdownCtx = pprof.WithLabels(g.shutdownCtx, pprof.Labels("graceful-lifecycle", "with-shutdown"))
    66  	g.hammerCtx = pprof.WithLabels(g.hammerCtx, pprof.Labels("graceful-lifecycle", "with-hammer"))
    67  	g.managerCtx = pprof.WithLabels(g.managerCtx, pprof.Labels("graceful-lifecycle", "with-manager"))
    68  
    69  	// Now label this and all goroutines created by this goroutine with the graceful-lifecycle manager
    70  	pprof.SetGoroutineLabels(g.managerCtx)
    71  	defer pprof.SetGoroutineLabels(ctx)
    72  
    73  	// Set the running state & handle signals
    74  	g.setState(stateRunning)
    75  	go g.handleSignals(g.managerCtx)
    76  
    77  	// Handle clean up of unused provided listeners	and delayed start-up
    78  	startupDone := make(chan struct{})
    79  	go func() {
    80  		defer close(startupDone)
    81  		// Wait till we're done getting all of the listeners and then close
    82  		// the unused ones
    83  		g.createServerWaitGroup.Wait()
    84  		// Ignore the error here there's not much we can do with it
    85  		// They're logged in the CloseProvidedListeners function
    86  		_ = CloseProvidedListeners()
    87  	}()
    88  	if setting.StartupTimeout > 0 {
    89  		go func() {
    90  			select {
    91  			case <-startupDone:
    92  				return
    93  			case <-g.IsShutdown():
    94  				func() {
    95  					// When waitgroup counter goes negative it will panic - we don't care about this so we can just ignore it.
    96  					defer func() {
    97  						_ = recover()
    98  					}()
    99  					// Ensure that the createServerWaitGroup stops waiting
   100  					for {
   101  						g.createServerWaitGroup.Done()
   102  					}
   103  				}()
   104  				return
   105  			case <-time.After(setting.StartupTimeout):
   106  				log.Error("Startup took too long! Shutting down")
   107  				g.doShutdown()
   108  			}
   109  		}()
   110  	}
   111  }
   112  
   113  func (g *Manager) handleSignals(ctx context.Context) {
   114  	ctx, _, finished := process.GetManager().AddTypedContext(ctx, "Graceful: HandleSignals", process.SystemProcessType, true)
   115  	defer finished()
   116  
   117  	signalChannel := make(chan os.Signal, 1)
   118  
   119  	signal.Notify(
   120  		signalChannel,
   121  		syscall.SIGHUP,
   122  		syscall.SIGUSR1,
   123  		syscall.SIGUSR2,
   124  		syscall.SIGINT,
   125  		syscall.SIGTERM,
   126  		syscall.SIGTSTP,
   127  	)
   128  
   129  	pid := syscall.Getpid()
   130  	for {
   131  		select {
   132  		case sig := <-signalChannel:
   133  			switch sig {
   134  			case syscall.SIGHUP:
   135  				log.Info("PID: %d. Received SIGHUP. Attempting GracefulRestart...", pid)
   136  				g.DoGracefulRestart()
   137  			case syscall.SIGUSR1:
   138  				log.Warn("PID %d. Received SIGUSR1. Releasing and reopening logs", pid)
   139  				if err := log.ReleaseReopen(); err != nil {
   140  					log.Error("Error whilst releasing and reopening logs: %v", err)
   141  				}
   142  			case syscall.SIGUSR2:
   143  				log.Warn("PID %d. Received SIGUSR2. Hammering...", pid)
   144  				g.DoImmediateHammer()
   145  			case syscall.SIGINT:
   146  				log.Warn("PID %d. Received SIGINT. Shutting down...", pid)
   147  				g.DoGracefulShutdown()
   148  			case syscall.SIGTERM:
   149  				log.Warn("PID %d. Received SIGTERM. Shutting down...", pid)
   150  				g.DoGracefulShutdown()
   151  			case syscall.SIGTSTP:
   152  				log.Info("PID %d. Received SIGTSTP.", pid)
   153  			default:
   154  				log.Info("PID %d. Received %v.", pid, sig)
   155  			}
   156  		case <-ctx.Done():
   157  			log.Warn("PID: %d. Background context for manager closed - %v - Shutting down...", pid, ctx.Err())
   158  			g.DoGracefulShutdown()
   159  			return
   160  		}
   161  	}
   162  }
   163  
   164  func (g *Manager) doFork() error {
   165  	g.lock.Lock()
   166  	if g.forked {
   167  		g.lock.Unlock()
   168  		return errors.New("another process already forked. Ignoring this one")
   169  	}
   170  	g.forked = true
   171  	g.lock.Unlock()
   172  	// We need to move the file logs to append pids
   173  	setting.RestartLogsWithPIDSuffix()
   174  
   175  	_, err := RestartProcess()
   176  
   177  	return err
   178  }
   179  
   180  // DoGracefulRestart causes a graceful restart
   181  func (g *Manager) DoGracefulRestart() {
   182  	if setting.GracefulRestartable {
   183  		log.Info("PID: %d. Forking...", os.Getpid())
   184  		err := g.doFork()
   185  		if err != nil {
   186  			if err.Error() == "another process already forked. Ignoring this one" {
   187  				g.DoImmediateHammer()
   188  			} else {
   189  				log.Error("Error whilst forking from PID: %d : %v", os.Getpid(), err)
   190  			}
   191  		}
   192  	} else {
   193  		log.Info("PID: %d. Not set restartable. Shutting down...", os.Getpid())
   194  
   195  		g.doShutdown()
   196  	}
   197  }
   198  
   199  // DoImmediateHammer causes an immediate hammer
   200  func (g *Manager) DoImmediateHammer() {
   201  	g.doHammerTime(0 * time.Second)
   202  }
   203  
   204  // DoGracefulShutdown causes a graceful shutdown
   205  func (g *Manager) DoGracefulShutdown() {
   206  	g.doShutdown()
   207  }
   208  
   209  // RegisterServer registers the running of a listening server, in the case of unix this means that the parent process can now die.
   210  // Any call to RegisterServer must be matched by a call to ServerDone
   211  func (g *Manager) RegisterServer() {
   212  	KillParent()
   213  	g.runningServerWaitGroup.Add(1)
   214  }