code.gitea.io/gitea@v1.22.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 "os" 11 "runtime/pprof" 12 "strconv" 13 "time" 14 15 "code.gitea.io/gitea/modules/log" 16 "code.gitea.io/gitea/modules/setting" 17 18 "golang.org/x/sys/windows/svc" 19 "golang.org/x/sys/windows/svc/debug" 20 ) 21 22 // WindowsServiceName is the name of the Windows service 23 var WindowsServiceName = "gitea" 24 25 const ( 26 hammerCode = 128 27 hammerCmd = svc.Cmd(hammerCode) 28 acceptHammerCode = svc.Accepted(hammerCode) 29 ) 30 31 func (g *Manager) start() { 32 // Now label this and all goroutines created by this goroutine with the graceful-lifecycle manager 33 pprof.SetGoroutineLabels(g.managerCtx) 34 defer pprof.SetGoroutineLabels(g.ctx) 35 36 if skip, _ := strconv.ParseBool(os.Getenv("SKIP_MINWINSVC")); skip { 37 log.Trace("Skipping SVC check as SKIP_MINWINSVC is set") 38 return 39 } 40 41 // Make SVC process 42 run := svc.Run 43 44 //lint:ignore SA1019 We use IsAnInteractiveSession because IsWindowsService has a different permissions profile 45 isAnInteractiveSession, err := svc.IsAnInteractiveSession() //nolint:staticcheck 46 if err != nil { 47 log.Error("Unable to ascertain if running as an Windows Service: %v", err) 48 return 49 } 50 if isAnInteractiveSession { 51 log.Trace("Not running a service ... using the debug SVC manager") 52 run = debug.Run 53 } 54 go func() { 55 _ = run(WindowsServiceName, g) 56 }() 57 } 58 59 // Execute makes Manager implement svc.Handler 60 func (g *Manager) Execute(args []string, changes <-chan svc.ChangeRequest, status chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) { 61 if setting.StartupTimeout > 0 { 62 status <- svc.Status{State: svc.StartPending, WaitHint: uint32(setting.StartupTimeout / time.Millisecond)} 63 } else { 64 status <- svc.Status{State: svc.StartPending} 65 } 66 67 log.Trace("Awaiting server start-up") 68 // Now need to wait for everything to start... 69 if !g.awaitServer(setting.StartupTimeout) { 70 log.Trace("... start-up failed ... Stopped") 71 return false, 1 72 } 73 74 log.Trace("Sending Running state to SVC") 75 76 // We need to implement some way of svc.AcceptParamChange/svc.ParamChange 77 status <- svc.Status{ 78 State: svc.Running, 79 Accepts: svc.AcceptStop | svc.AcceptShutdown | acceptHammerCode, 80 } 81 82 log.Trace("Started") 83 84 waitTime := 30 * time.Second 85 86 loop: 87 for { 88 select { 89 case <-g.ctx.Done(): 90 log.Trace("Shutting down") 91 g.DoGracefulShutdown() 92 waitTime += setting.GracefulHammerTime 93 break loop 94 case <-g.shutdownRequested: 95 log.Trace("Shutting down") 96 waitTime += setting.GracefulHammerTime 97 break loop 98 case change := <-changes: 99 switch change.Cmd { 100 case svc.Interrogate: 101 log.Trace("SVC sent interrogate") 102 status <- change.CurrentStatus 103 case svc.Stop, svc.Shutdown: 104 log.Trace("SVC requested shutdown - shutting down") 105 g.DoGracefulShutdown() 106 waitTime += setting.GracefulHammerTime 107 break loop 108 case hammerCode: 109 log.Trace("SVC requested hammer - shutting down and hammering immediately") 110 g.DoGracefulShutdown() 111 g.DoImmediateHammer() 112 break loop 113 default: 114 log.Debug("Unexpected control request: %v", change.Cmd) 115 } 116 } 117 } 118 119 log.Trace("Sending StopPending state to SVC") 120 status <- svc.Status{ 121 State: svc.StopPending, 122 WaitHint: uint32(waitTime / time.Millisecond), 123 } 124 125 hammerLoop: 126 for { 127 select { 128 case change := <-changes: 129 switch change.Cmd { 130 case svc.Interrogate: 131 log.Trace("SVC sent interrogate") 132 status <- change.CurrentStatus 133 case svc.Stop, svc.Shutdown, hammerCmd: 134 log.Trace("SVC requested hammer - hammering immediately") 135 g.DoImmediateHammer() 136 break hammerLoop 137 default: 138 log.Debug("Unexpected control request: %v", change.Cmd) 139 } 140 case <-g.hammerCtx.Done(): 141 break hammerLoop 142 } 143 } 144 145 log.Trace("Stopped") 146 return false, 0 147 } 148 149 func (g *Manager) awaitServer(limit time.Duration) bool { 150 c := make(chan struct{}) 151 go func() { 152 g.createServerCond.L.Lock() 153 for { 154 if g.createdServer >= numberOfServersToCreate { 155 g.createServerCond.L.Unlock() 156 close(c) 157 return 158 } 159 select { 160 case <-g.IsShutdown(): 161 g.createServerCond.L.Unlock() 162 return 163 default: 164 } 165 g.createServerCond.Wait() 166 } 167 }() 168 169 var tc <-chan time.Time 170 if limit > 0 { 171 tc = time.After(limit) 172 } 173 select { 174 case <-c: 175 return true // completed normally 176 case <-tc: 177 return false // timed out 178 case <-g.IsShutdown(): 179 g.createServerCond.Signal() 180 return false 181 } 182 } 183 184 func (g *Manager) notify(msg systemdNotifyMsg) { 185 // Windows doesn't use systemd to notify 186 } 187 188 func KillParent() { 189 // Windows doesn't need to "kill parent" because there is no graceful restart 190 }