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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  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. // +build windows
  7. package graceful
  8. import (
  9. "context"
  10. "os"
  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. doneCtx context.Context
  36. shutdownCtxCancel context.CancelFunc
  37. hammerCtxCancel context.CancelFunc
  38. terminateCtxCancel context.CancelFunc
  39. doneCtxCancel 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.doneCtx, g.doneCtxCancel = context.WithCancel(g.ctx)
  64. // Make channels
  65. g.shutdownRequested = make(chan struct{})
  66. // Set the running state
  67. g.setState(stateRunning)
  68. if skip, _ := strconv.ParseBool(os.Getenv("SKIP_MINWINSVC")); skip {
  69. log.Trace("Skipping SVC check as SKIP_MINWINSVC is set")
  70. return
  71. }
  72. // Make SVC process
  73. run := svc.Run
  74. //lint:ignore SA1019 We use IsAnInteractiveSession because IsWindowsService has a different permissions profile
  75. isAnInteractiveSession, err := svc.IsAnInteractiveSession()
  76. if err != nil {
  77. log.Error("Unable to ascertain if running as an Windows Service: %v", err)
  78. return
  79. }
  80. if isAnInteractiveSession {
  81. log.Trace("Not running a service ... using the debug SVC manager")
  82. run = debug.Run
  83. }
  84. go func() {
  85. _ = run(WindowsServiceName, g)
  86. }()
  87. }
  88. // Execute makes Manager implement svc.Handler
  89. func (g *Manager) Execute(args []string, changes <-chan svc.ChangeRequest, status chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) {
  90. if setting.StartupTimeout > 0 {
  91. status <- svc.Status{State: svc.StartPending}
  92. } else {
  93. status <- svc.Status{State: svc.StartPending, WaitHint: uint32(setting.StartupTimeout / time.Millisecond)}
  94. }
  95. log.Trace("Awaiting server start-up")
  96. // Now need to wait for everything to start...
  97. if !g.awaitServer(setting.StartupTimeout) {
  98. log.Trace("... start-up failed ... Stopped")
  99. return false, 1
  100. }
  101. log.Trace("Sending Running state to SVC")
  102. // We need to implement some way of svc.AcceptParamChange/svc.ParamChange
  103. status <- svc.Status{
  104. State: svc.Running,
  105. Accepts: svc.AcceptStop | svc.AcceptShutdown | acceptHammerCode,
  106. }
  107. log.Trace("Started")
  108. waitTime := 30 * time.Second
  109. loop:
  110. for {
  111. select {
  112. case <-g.ctx.Done():
  113. log.Trace("Shutting down")
  114. g.DoGracefulShutdown()
  115. waitTime += setting.GracefulHammerTime
  116. break loop
  117. case <-g.shutdownRequested:
  118. log.Trace("Shutting down")
  119. waitTime += setting.GracefulHammerTime
  120. break loop
  121. case change := <-changes:
  122. switch change.Cmd {
  123. case svc.Interrogate:
  124. log.Trace("SVC sent interrogate")
  125. status <- change.CurrentStatus
  126. case svc.Stop, svc.Shutdown:
  127. log.Trace("SVC requested shutdown - shutting down")
  128. g.DoGracefulShutdown()
  129. waitTime += setting.GracefulHammerTime
  130. break loop
  131. case hammerCode:
  132. log.Trace("SVC requested hammer - shutting down and hammering immediately")
  133. g.DoGracefulShutdown()
  134. g.DoImmediateHammer()
  135. break loop
  136. default:
  137. log.Debug("Unexpected control request: %v", change.Cmd)
  138. }
  139. }
  140. }
  141. log.Trace("Sending StopPending state to SVC")
  142. status <- svc.Status{
  143. State: svc.StopPending,
  144. WaitHint: uint32(waitTime / time.Millisecond),
  145. }
  146. hammerLoop:
  147. for {
  148. select {
  149. case change := <-changes:
  150. switch change.Cmd {
  151. case svc.Interrogate:
  152. log.Trace("SVC sent interrogate")
  153. status <- change.CurrentStatus
  154. case svc.Stop, svc.Shutdown, hammerCmd:
  155. log.Trace("SVC requested hammer - hammering immediately")
  156. g.DoImmediateHammer()
  157. break hammerLoop
  158. default:
  159. log.Debug("Unexpected control request: %v", change.Cmd)
  160. }
  161. case <-g.hammerCtx.Done():
  162. break hammerLoop
  163. }
  164. }
  165. log.Trace("Stopped")
  166. return false, 0
  167. }
  168. // DoImmediateHammer causes an immediate hammer
  169. func (g *Manager) DoImmediateHammer() {
  170. g.doHammerTime(0 * time.Second)
  171. }
  172. // DoGracefulShutdown causes a graceful shutdown
  173. func (g *Manager) DoGracefulShutdown() {
  174. g.lock.Lock()
  175. select {
  176. case <-g.shutdownRequested:
  177. g.lock.Unlock()
  178. default:
  179. close(g.shutdownRequested)
  180. g.lock.Unlock()
  181. g.doShutdown()
  182. }
  183. }
  184. // RegisterServer registers the running of a listening server.
  185. // Any call to RegisterServer must be matched by a call to ServerDone
  186. func (g *Manager) RegisterServer() {
  187. g.runningServerWaitGroup.Add(1)
  188. }
  189. func (g *Manager) awaitServer(limit time.Duration) bool {
  190. c := make(chan struct{})
  191. go func() {
  192. defer close(c)
  193. g.createServerWaitGroup.Wait()
  194. }()
  195. if limit > 0 {
  196. select {
  197. case <-c:
  198. return true // completed normally
  199. case <-time.After(limit):
  200. return false // timed out
  201. case <-g.IsShutdown():
  202. return false
  203. }
  204. } else {
  205. select {
  206. case <-c:
  207. return true // completed normally
  208. case <-g.IsShutdown():
  209. return false
  210. }
  211. }
  212. }