You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

manager_windows.go 6.7KB

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