123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- // Copyright 2019 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- //go:build !windows
-
- package graceful
-
- import (
- "context"
- "errors"
- "os"
- "os/signal"
- "runtime/pprof"
- "strconv"
- "syscall"
- "time"
-
- "code.gitea.io/gitea/modules/graceful/releasereopen"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/process"
- "code.gitea.io/gitea/modules/setting"
- )
-
- func pidMsg() systemdNotifyMsg {
- return systemdNotifyMsg("MAINPID=" + strconv.Itoa(os.Getpid()))
- }
-
- // Notify systemd of status via the notify protocol
- func (g *Manager) notify(msg systemdNotifyMsg) {
- conn, err := getNotifySocket()
- if err != nil {
- // the err is logged in getNotifySocket
- return
- }
- if conn == nil {
- return
- }
- defer conn.Close()
-
- if _, err = conn.Write([]byte(msg)); err != nil {
- log.Warn("Failed to notify NOTIFY_SOCKET: %v", err)
- return
- }
- }
-
- func (g *Manager) start() {
- // Now label this and all goroutines created by this goroutine with the graceful-lifecycle manager
- pprof.SetGoroutineLabels(g.managerCtx)
- defer pprof.SetGoroutineLabels(g.ctx)
-
- g.isChild = len(os.Getenv(listenFDsEnv)) > 0 && os.Getppid() > 1
-
- g.notify(statusMsg("Starting Gitea"))
- g.notify(pidMsg())
- go g.handleSignals(g.managerCtx)
-
- // Handle clean up of unused provided listeners and delayed start-up
- startupDone := make(chan struct{})
- go func() {
- defer func() {
- close(startupDone)
- // Close the unused listeners
- closeProvidedListeners()
- }()
- // Wait for all servers to be created
- g.createServerCond.L.Lock()
- for {
- if g.createdServer >= numberOfServersToCreate {
- g.createServerCond.L.Unlock()
- g.notify(readyMsg)
- return
- }
- select {
- case <-g.IsShutdown():
- g.createServerCond.L.Unlock()
- return
- default:
- }
- g.createServerCond.Wait()
- }
- }()
- if setting.StartupTimeout > 0 {
- go func() {
- select {
- case <-startupDone:
- return
- case <-g.IsShutdown():
- g.createServerCond.Signal()
- return
- case <-time.After(setting.StartupTimeout):
- log.Error("Startup took too long! Shutting down")
- g.notify(statusMsg("Startup took too long! Shutting down"))
- g.notify(stoppingMsg)
- g.doShutdown()
- }
- }()
- }
- }
-
- func (g *Manager) handleSignals(ctx context.Context) {
- ctx, _, finished := process.GetManager().AddTypedContext(ctx, "Graceful: HandleSignals", process.SystemProcessType, true)
- defer finished()
-
- signalChannel := make(chan os.Signal, 1)
-
- signal.Notify(
- signalChannel,
- syscall.SIGHUP,
- syscall.SIGUSR1,
- syscall.SIGUSR2,
- syscall.SIGINT,
- syscall.SIGTERM,
- syscall.SIGTSTP,
- )
-
- watchdogTimeout := getWatchdogTimeout()
- t := &time.Ticker{}
- if watchdogTimeout != 0 {
- g.notify(watchdogMsg)
- t = time.NewTicker(watchdogTimeout / 2)
- }
-
- pid := syscall.Getpid()
- for {
- select {
- case sig := <-signalChannel:
- switch sig {
- case syscall.SIGHUP:
- log.Info("PID: %d. Received SIGHUP. Attempting GracefulRestart...", pid)
- g.DoGracefulRestart()
- case syscall.SIGUSR1:
- log.Warn("PID %d. Received SIGUSR1. Releasing and reopening logs", pid)
- g.notify(statusMsg("Releasing and reopening logs"))
- if err := releasereopen.GetManager().ReleaseReopen(); err != nil {
- log.Error("Error whilst releasing and reopening logs: %v", err)
- }
- case syscall.SIGUSR2:
- log.Warn("PID %d. Received SIGUSR2. Hammering...", pid)
- g.DoImmediateHammer()
- case syscall.SIGINT:
- log.Warn("PID %d. Received SIGINT. Shutting down...", pid)
- g.DoGracefulShutdown()
- case syscall.SIGTERM:
- log.Warn("PID %d. Received SIGTERM. Shutting down...", pid)
- g.DoGracefulShutdown()
- case syscall.SIGTSTP:
- log.Info("PID %d. Received SIGTSTP.", pid)
- default:
- log.Info("PID %d. Received %v.", pid, sig)
- }
- case <-t.C:
- g.notify(watchdogMsg)
- case <-ctx.Done():
- log.Warn("PID: %d. Background context for manager closed - %v - Shutting down...", pid, ctx.Err())
- g.DoGracefulShutdown()
- return
- }
- }
- }
-
- func (g *Manager) 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()
-
- g.notify(reloadingMsg)
-
- // We need to move the file logs to append pids
- setting.RestartLogsWithPIDSuffix()
-
- _, err := RestartProcess()
-
- return err
- }
-
- // DoGracefulRestart causes a graceful restart
- func (g *Manager) DoGracefulRestart() {
- if setting.GracefulRestartable {
- log.Info("PID: %d. Forking...", os.Getpid())
- err := g.doFork()
- if err != nil {
- if err.Error() == "another process already forked. Ignoring this one" {
- g.DoImmediateHammer()
- } else {
- log.Error("Error whilst forking from PID: %d : %v", os.Getpid(), err)
- }
- }
- // doFork calls RestartProcess which starts a new Gitea process, so this parent process needs to exit
- // Otherwise some resources (eg: leveldb lock) will be held by this parent process and the new process will fail to start
- log.Info("PID: %d. Shutting down after forking ...", os.Getpid())
- g.doShutdown()
- } else {
- log.Info("PID: %d. Not set restartable. Shutting down...", os.Getpid())
- g.notify(stoppingMsg)
- g.doShutdown()
- }
- }
|