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.6KB

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