code.gitea.io/gitea@v1.19.3/modules/graceful/manager_windows.go (about) 1 // Copyright 2019 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 // This code is heavily inspired by the archived gofacebook/gracenet/net.go handler 4 5 //go:build windows 6 7 package graceful 8 9 import ( 10 "context" 11 "os" 12 "runtime/pprof" 13 "strconv" 14 "sync" 15 "time" 16 17 "code.gitea.io/gitea/modules/log" 18 "code.gitea.io/gitea/modules/setting" 19 20 "golang.org/x/sys/windows/svc" 21 "golang.org/x/sys/windows/svc/debug" 22 ) 23 24 // WindowsServiceName is the name of the Windows service 25 var WindowsServiceName = "gitea" 26 27 const ( 28 hammerCode = 128 29 hammerCmd = svc.Cmd(hammerCode) 30 acceptHammerCode = svc.Accepted(hammerCode) 31 ) 32 33 // Manager manages the graceful shutdown process 34 type Manager struct { 35 ctx context.Context 36 isChild bool 37 lock *sync.RWMutex 38 state state 39 shutdownCtx context.Context 40 hammerCtx context.Context 41 terminateCtx context.Context 42 managerCtx context.Context 43 shutdownCtxCancel context.CancelFunc 44 hammerCtxCancel context.CancelFunc 45 terminateCtxCancel context.CancelFunc 46 managerCtxCancel context.CancelFunc 47 runningServerWaitGroup sync.WaitGroup 48 createServerWaitGroup sync.WaitGroup 49 terminateWaitGroup sync.WaitGroup 50 shutdownRequested chan struct{} 51 52 toRunAtShutdown []func() 53 toRunAtHammer []func() 54 toRunAtTerminate []func() 55 } 56 57 func newGracefulManager(ctx context.Context) *Manager { 58 manager := &Manager{ 59 isChild: false, 60 lock: &sync.RWMutex{}, 61 ctx: ctx, 62 } 63 manager.createServerWaitGroup.Add(numberOfServersToCreate) 64 manager.start() 65 return manager 66 } 67 68 func (g *Manager) start() { 69 // Make contexts 70 g.terminateCtx, g.terminateCtxCancel = context.WithCancel(g.ctx) 71 g.shutdownCtx, g.shutdownCtxCancel = context.WithCancel(g.ctx) 72 g.hammerCtx, g.hammerCtxCancel = context.WithCancel(g.ctx) 73 g.managerCtx, g.managerCtxCancel = context.WithCancel(g.ctx) 74 75 // Next add pprof labels to these contexts 76 g.terminateCtx = pprof.WithLabels(g.terminateCtx, pprof.Labels("graceful-lifecycle", "with-terminate")) 77 g.shutdownCtx = pprof.WithLabels(g.shutdownCtx, pprof.Labels("graceful-lifecycle", "with-shutdown")) 78 g.hammerCtx = pprof.WithLabels(g.hammerCtx, pprof.Labels("graceful-lifecycle", "with-hammer")) 79 g.managerCtx = pprof.WithLabels(g.managerCtx, pprof.Labels("graceful-lifecycle", "with-manager")) 80 81 // Now label this and all goroutines created by this goroutine with the graceful-lifecycle manager 82 pprof.SetGoroutineLabels(g.managerCtx) 83 defer pprof.SetGoroutineLabels(g.ctx) 84 85 // Make channels 86 g.shutdownRequested = make(chan struct{}) 87 88 // Set the running state 89 g.setState(stateRunning) 90 if skip, _ := strconv.ParseBool(os.Getenv("SKIP_MINWINSVC")); skip { 91 log.Trace("Skipping SVC check as SKIP_MINWINSVC is set") 92 return 93 } 94 95 // Make SVC process 96 run := svc.Run 97 98 //lint:ignore SA1019 We use IsAnInteractiveSession because IsWindowsService has a different permissions profile 99 isAnInteractiveSession, err := svc.IsAnInteractiveSession() 100 if err != nil { 101 log.Error("Unable to ascertain if running as an Windows Service: %v", err) 102 return 103 } 104 if isAnInteractiveSession { 105 log.Trace("Not running a service ... using the debug SVC manager") 106 run = debug.Run 107 } 108 go func() { 109 _ = run(WindowsServiceName, g) 110 }() 111 } 112 113 // Execute makes Manager implement svc.Handler 114 func (g *Manager) Execute(args []string, changes <-chan svc.ChangeRequest, status chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) { 115 if setting.StartupTimeout > 0 { 116 status <- svc.Status{State: svc.StartPending, WaitHint: uint32(setting.StartupTimeout / time.Millisecond)} 117 } else { 118 status <- svc.Status{State: svc.StartPending} 119 } 120 121 log.Trace("Awaiting server start-up") 122 // Now need to wait for everything to start... 123 if !g.awaitServer(setting.StartupTimeout) { 124 log.Trace("... start-up failed ... Stopped") 125 return false, 1 126 } 127 128 log.Trace("Sending Running state to SVC") 129 130 // We need to implement some way of svc.AcceptParamChange/svc.ParamChange 131 status <- svc.Status{ 132 State: svc.Running, 133 Accepts: svc.AcceptStop | svc.AcceptShutdown | acceptHammerCode, 134 } 135 136 log.Trace("Started") 137 138 waitTime := 30 * time.Second 139 140 loop: 141 for { 142 select { 143 case <-g.ctx.Done(): 144 log.Trace("Shutting down") 145 g.DoGracefulShutdown() 146 waitTime += setting.GracefulHammerTime 147 break loop 148 case <-g.shutdownRequested: 149 log.Trace("Shutting down") 150 waitTime += setting.GracefulHammerTime 151 break loop 152 case change := <-changes: 153 switch change.Cmd { 154 case svc.Interrogate: 155 log.Trace("SVC sent interrogate") 156 status <- change.CurrentStatus 157 case svc.Stop, svc.Shutdown: 158 log.Trace("SVC requested shutdown - shutting down") 159 g.DoGracefulShutdown() 160 waitTime += setting.GracefulHammerTime 161 break loop 162 case hammerCode: 163 log.Trace("SVC requested hammer - shutting down and hammering immediately") 164 g.DoGracefulShutdown() 165 g.DoImmediateHammer() 166 break loop 167 default: 168 log.Debug("Unexpected control request: %v", change.Cmd) 169 } 170 } 171 } 172 173 log.Trace("Sending StopPending state to SVC") 174 status <- svc.Status{ 175 State: svc.StopPending, 176 WaitHint: uint32(waitTime / time.Millisecond), 177 } 178 179 hammerLoop: 180 for { 181 select { 182 case change := <-changes: 183 switch change.Cmd { 184 case svc.Interrogate: 185 log.Trace("SVC sent interrogate") 186 status <- change.CurrentStatus 187 case svc.Stop, svc.Shutdown, hammerCmd: 188 log.Trace("SVC requested hammer - hammering immediately") 189 g.DoImmediateHammer() 190 break hammerLoop 191 default: 192 log.Debug("Unexpected control request: %v", change.Cmd) 193 } 194 case <-g.hammerCtx.Done(): 195 break hammerLoop 196 } 197 } 198 199 log.Trace("Stopped") 200 return false, 0 201 } 202 203 // DoImmediateHammer causes an immediate hammer 204 func (g *Manager) DoImmediateHammer() { 205 g.doHammerTime(0 * time.Second) 206 } 207 208 // DoGracefulShutdown causes a graceful shutdown 209 func (g *Manager) DoGracefulShutdown() { 210 g.lock.Lock() 211 select { 212 case <-g.shutdownRequested: 213 g.lock.Unlock() 214 default: 215 close(g.shutdownRequested) 216 g.lock.Unlock() 217 g.doShutdown() 218 } 219 } 220 221 // RegisterServer registers the running of a listening server. 222 // Any call to RegisterServer must be matched by a call to ServerDone 223 func (g *Manager) RegisterServer() { 224 g.runningServerWaitGroup.Add(1) 225 } 226 227 func (g *Manager) awaitServer(limit time.Duration) bool { 228 c := make(chan struct{}) 229 go func() { 230 defer close(c) 231 g.createServerWaitGroup.Wait() 232 }() 233 if limit > 0 { 234 select { 235 case <-c: 236 return true // completed normally 237 case <-time.After(limit): 238 return false // timed out 239 case <-g.IsShutdown(): 240 return false 241 } 242 } else { 243 select { 244 case <-c: 245 return true // completed normally 246 case <-g.IsShutdown(): 247 return false 248 } 249 } 250 }