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 }