code.gitea.io/gitea@v1.22.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  	"strconv"
    15  	"syscall"
    16  	"time"
    17  
    18  	"code.gitea.io/gitea/modules/graceful/releasereopen"
    19  	"code.gitea.io/gitea/modules/log"
    20  	"code.gitea.io/gitea/modules/process"
    21  	"code.gitea.io/gitea/modules/setting"
    22  )
    23  
    24  func pidMsg() systemdNotifyMsg {
    25  	return systemdNotifyMsg("MAINPID=" + strconv.Itoa(os.Getpid()))
    26  }
    27  
    28  // Notify systemd of status via the notify protocol
    29  func (g *Manager) notify(msg systemdNotifyMsg) {
    30  	conn, err := getNotifySocket()
    31  	if err != nil {
    32  		// the err is logged in getNotifySocket
    33  		return
    34  	}
    35  	if conn == nil {
    36  		return
    37  	}
    38  	defer conn.Close()
    39  
    40  	if _, err = conn.Write([]byte(msg)); err != nil {
    41  		log.Warn("Failed to notify NOTIFY_SOCKET: %v", err)
    42  		return
    43  	}
    44  }
    45  
    46  func (g *Manager) start() {
    47  	// Now label this and all goroutines created by this goroutine with the graceful-lifecycle manager
    48  	pprof.SetGoroutineLabels(g.managerCtx)
    49  	defer pprof.SetGoroutineLabels(g.ctx)
    50  
    51  	g.isChild = len(os.Getenv(listenFDsEnv)) > 0 && os.Getppid() > 1
    52  
    53  	g.notify(statusMsg("Starting Gitea"))
    54  	g.notify(pidMsg())
    55  	go g.handleSignals(g.managerCtx)
    56  
    57  	// Handle clean up of unused provided listeners	and delayed start-up
    58  	startupDone := make(chan struct{})
    59  	go func() {
    60  		defer func() {
    61  			close(startupDone)
    62  			// Close the unused listeners
    63  			closeProvidedListeners()
    64  		}()
    65  		// Wait for all servers to be created
    66  		g.createServerCond.L.Lock()
    67  		for {
    68  			if g.createdServer >= numberOfServersToCreate {
    69  				g.createServerCond.L.Unlock()
    70  				g.notify(readyMsg)
    71  				return
    72  			}
    73  			select {
    74  			case <-g.IsShutdown():
    75  				g.createServerCond.L.Unlock()
    76  				return
    77  			default:
    78  			}
    79  			g.createServerCond.Wait()
    80  		}
    81  	}()
    82  	if setting.StartupTimeout > 0 {
    83  		go func() {
    84  			select {
    85  			case <-startupDone:
    86  				return
    87  			case <-g.IsShutdown():
    88  				g.createServerCond.Signal()
    89  				return
    90  			case <-time.After(setting.StartupTimeout):
    91  				log.Error("Startup took too long! Shutting down")
    92  				g.notify(statusMsg("Startup took too long! Shutting down"))
    93  				g.notify(stoppingMsg)
    94  				g.doShutdown()
    95  			}
    96  		}()
    97  	}
    98  }
    99  
   100  func (g *Manager) handleSignals(ctx context.Context) {
   101  	ctx, _, finished := process.GetManager().AddTypedContext(ctx, "Graceful: HandleSignals", process.SystemProcessType, true)
   102  	defer finished()
   103  
   104  	signalChannel := make(chan os.Signal, 1)
   105  
   106  	signal.Notify(
   107  		signalChannel,
   108  		syscall.SIGHUP,
   109  		syscall.SIGUSR1,
   110  		syscall.SIGUSR2,
   111  		syscall.SIGINT,
   112  		syscall.SIGTERM,
   113  		syscall.SIGTSTP,
   114  	)
   115  
   116  	watchdogTimeout := getWatchdogTimeout()
   117  	t := &time.Ticker{}
   118  	if watchdogTimeout != 0 {
   119  		g.notify(watchdogMsg)
   120  		t = time.NewTicker(watchdogTimeout / 2)
   121  	}
   122  
   123  	pid := syscall.Getpid()
   124  	for {
   125  		select {
   126  		case sig := <-signalChannel:
   127  			switch sig {
   128  			case syscall.SIGHUP:
   129  				log.Info("PID: %d. Received SIGHUP. Attempting GracefulRestart...", pid)
   130  				g.DoGracefulRestart()
   131  			case syscall.SIGUSR1:
   132  				log.Warn("PID %d. Received SIGUSR1. Releasing and reopening logs", pid)
   133  				g.notify(statusMsg("Releasing and reopening logs"))
   134  				if err := releasereopen.GetManager().ReleaseReopen(); err != nil {
   135  					log.Error("Error whilst releasing and reopening logs: %v", err)
   136  				}
   137  			case syscall.SIGUSR2:
   138  				log.Warn("PID %d. Received SIGUSR2. Hammering...", pid)
   139  				g.DoImmediateHammer()
   140  			case syscall.SIGINT:
   141  				log.Warn("PID %d. Received SIGINT. Shutting down...", pid)
   142  				g.DoGracefulShutdown()
   143  			case syscall.SIGTERM:
   144  				log.Warn("PID %d. Received SIGTERM. Shutting down...", pid)
   145  				g.DoGracefulShutdown()
   146  			case syscall.SIGTSTP:
   147  				log.Info("PID %d. Received SIGTSTP.", pid)
   148  			default:
   149  				log.Info("PID %d. Received %v.", pid, sig)
   150  			}
   151  		case <-t.C:
   152  			g.notify(watchdogMsg)
   153  		case <-ctx.Done():
   154  			log.Warn("PID: %d. Background context for manager closed - %v - Shutting down...", pid, ctx.Err())
   155  			g.DoGracefulShutdown()
   156  			return
   157  		}
   158  	}
   159  }
   160  
   161  func (g *Manager) doFork() error {
   162  	g.lock.Lock()
   163  	if g.forked {
   164  		g.lock.Unlock()
   165  		return errors.New("another process already forked. Ignoring this one")
   166  	}
   167  	g.forked = true
   168  	g.lock.Unlock()
   169  
   170  	g.notify(reloadingMsg)
   171  
   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  		// doFork calls RestartProcess which starts a new Gitea process, so this parent process needs to exit
   193  		// Otherwise some resources (eg: leveldb lock) will be held by this parent process and the new process will fail to start
   194  		log.Info("PID: %d. Shutting down after forking ...", os.Getpid())
   195  		g.doShutdown()
   196  	} else {
   197  		log.Info("PID: %d. Not set restartable. Shutting down...", os.Getpid())
   198  		g.notify(stoppingMsg)
   199  		g.doShutdown()
   200  	}
   201  }