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