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 4.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. // +build windows
  2. // Copyright 2019 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. // This code is heavily inspired by the archived gofacebook/gracenet/net.go handler
  6. package graceful
  7. import (
  8. "context"
  9. "os"
  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. shutdown chan struct{}
  32. hammer chan struct{}
  33. terminate chan struct{}
  34. done chan struct{}
  35. runningServerWaitGroup sync.WaitGroup
  36. createServerWaitGroup sync.WaitGroup
  37. terminateWaitGroup sync.WaitGroup
  38. shutdownRequested chan struct{}
  39. }
  40. func newGracefulManager(ctx context.Context) *Manager {
  41. manager := &Manager{
  42. isChild: false,
  43. lock: &sync.RWMutex{},
  44. ctx: ctx,
  45. }
  46. manager.createServerWaitGroup.Add(numberOfServersToCreate)
  47. manager.start()
  48. return manager
  49. }
  50. func (g *Manager) start() {
  51. // Make channels
  52. g.terminate = make(chan struct{})
  53. g.shutdown = make(chan struct{})
  54. g.hammer = make(chan struct{})
  55. g.done = make(chan struct{})
  56. g.shutdownRequested = make(chan struct{})
  57. // Set the running state
  58. g.setState(stateRunning)
  59. if skip, _ := strconv.ParseBool(os.Getenv("SKIP_MINWINSVC")); skip {
  60. return
  61. }
  62. // Make SVC process
  63. run := svc.Run
  64. isInteractive, err := svc.IsWindowsService()
  65. if err != nil {
  66. log.Error("Unable to ascertain if running as an Interactive Session: %v", err)
  67. return
  68. }
  69. if isInteractive {
  70. run = debug.Run
  71. }
  72. go func() {
  73. _ = run(WindowsServiceName, g)
  74. }()
  75. }
  76. // Execute makes Manager implement svc.Handler
  77. func (g *Manager) Execute(args []string, changes <-chan svc.ChangeRequest, status chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) {
  78. if setting.StartupTimeout > 0 {
  79. status <- svc.Status{State: svc.StartPending}
  80. } else {
  81. status <- svc.Status{State: svc.StartPending, WaitHint: uint32(setting.StartupTimeout / time.Millisecond)}
  82. }
  83. // Now need to wait for everything to start...
  84. if !g.awaitServer(setting.StartupTimeout) {
  85. return false, 1
  86. }
  87. // We need to implement some way of svc.AcceptParamChange/svc.ParamChange
  88. status <- svc.Status{
  89. State: svc.Running,
  90. Accepts: svc.AcceptStop | svc.AcceptShutdown | acceptHammerCode,
  91. }
  92. waitTime := 30 * time.Second
  93. loop:
  94. for {
  95. select {
  96. case <-g.ctx.Done():
  97. g.DoGracefulShutdown()
  98. waitTime += setting.GracefulHammerTime
  99. break loop
  100. case <-g.shutdownRequested:
  101. waitTime += setting.GracefulHammerTime
  102. break loop
  103. case change := <-changes:
  104. switch change.Cmd {
  105. case svc.Interrogate:
  106. status <- change.CurrentStatus
  107. case svc.Stop, svc.Shutdown:
  108. g.DoGracefulShutdown()
  109. waitTime += setting.GracefulHammerTime
  110. break loop
  111. case hammerCode:
  112. g.DoGracefulShutdown()
  113. g.DoImmediateHammer()
  114. break loop
  115. default:
  116. log.Debug("Unexpected control request: %v", change.Cmd)
  117. }
  118. }
  119. }
  120. status <- svc.Status{
  121. State: svc.StopPending,
  122. WaitHint: uint32(waitTime / time.Millisecond),
  123. }
  124. hammerLoop:
  125. for {
  126. select {
  127. case change := <-changes:
  128. switch change.Cmd {
  129. case svc.Interrogate:
  130. status <- change.CurrentStatus
  131. case svc.Stop, svc.Shutdown, hammerCmd:
  132. g.DoImmediateHammer()
  133. break hammerLoop
  134. default:
  135. log.Debug("Unexpected control request: %v", change.Cmd)
  136. }
  137. case <-g.hammer:
  138. break hammerLoop
  139. }
  140. }
  141. return false, 0
  142. }
  143. // DoImmediateHammer causes an immediate hammer
  144. func (g *Manager) DoImmediateHammer() {
  145. g.doHammerTime(0 * time.Second)
  146. }
  147. // DoGracefulShutdown causes a graceful shutdown
  148. func (g *Manager) DoGracefulShutdown() {
  149. g.lock.Lock()
  150. select {
  151. case <-g.shutdownRequested:
  152. g.lock.Unlock()
  153. default:
  154. close(g.shutdownRequested)
  155. g.lock.Unlock()
  156. g.doShutdown()
  157. }
  158. }
  159. // RegisterServer registers the running of a listening server.
  160. // Any call to RegisterServer must be matched by a call to ServerDone
  161. func (g *Manager) RegisterServer() {
  162. g.runningServerWaitGroup.Add(1)
  163. }
  164. func (g *Manager) awaitServer(limit time.Duration) bool {
  165. c := make(chan struct{})
  166. go func() {
  167. defer close(c)
  168. g.createServerWaitGroup.Wait()
  169. }()
  170. if limit > 0 {
  171. select {
  172. case <-c:
  173. return true // completed normally
  174. case <-time.After(limit):
  175. return false // timed out
  176. case <-g.IsShutdown():
  177. return false
  178. }
  179. } else {
  180. select {
  181. case <-c:
  182. return true // completed normally
  183. case <-g.IsShutdown():
  184. return false
  185. }
  186. }
  187. }