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 }