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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  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.IsAnInteractiveSession()
  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 run(WindowsServiceName, g)
  73. }
  74. // Execute makes Manager implement svc.Handler
  75. func (g *Manager) Execute(args []string, changes <-chan svc.ChangeRequest, status chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) {
  76. if setting.StartupTimeout > 0 {
  77. status <- svc.Status{State: svc.StartPending}
  78. } else {
  79. status <- svc.Status{State: svc.StartPending, WaitHint: uint32(setting.StartupTimeout / time.Millisecond)}
  80. }
  81. // Now need to wait for everything to start...
  82. if !g.awaitServer(setting.StartupTimeout) {
  83. return false, 1
  84. }
  85. // We need to implement some way of svc.AcceptParamChange/svc.ParamChange
  86. status <- svc.Status{
  87. State: svc.Running,
  88. Accepts: svc.AcceptStop | svc.AcceptShutdown | acceptHammerCode,
  89. }
  90. waitTime := 30 * time.Second
  91. loop:
  92. for {
  93. select {
  94. case <-g.ctx.Done():
  95. g.DoGracefulShutdown()
  96. waitTime += setting.GracefulHammerTime
  97. break loop
  98. case <-g.shutdownRequested:
  99. waitTime += setting.GracefulHammerTime
  100. break loop
  101. case change := <-changes:
  102. switch change.Cmd {
  103. case svc.Interrogate:
  104. status <- change.CurrentStatus
  105. case svc.Stop, svc.Shutdown:
  106. g.DoGracefulShutdown()
  107. waitTime += setting.GracefulHammerTime
  108. break loop
  109. case hammerCode:
  110. g.DoGracefulShutdown()
  111. g.DoImmediateHammer()
  112. break loop
  113. default:
  114. log.Debug("Unexpected control request: %v", change.Cmd)
  115. }
  116. }
  117. }
  118. status <- svc.Status{
  119. State: svc.StopPending,
  120. WaitHint: uint32(waitTime / time.Millisecond),
  121. }
  122. hammerLoop:
  123. for {
  124. select {
  125. case change := <-changes:
  126. switch change.Cmd {
  127. case svc.Interrogate:
  128. status <- change.CurrentStatus
  129. case svc.Stop, svc.Shutdown, hammerCmd:
  130. g.DoImmediateHammer()
  131. break hammerLoop
  132. default:
  133. log.Debug("Unexpected control request: %v", change.Cmd)
  134. }
  135. case <-g.hammer:
  136. break hammerLoop
  137. }
  138. }
  139. return false, 0
  140. }
  141. // DoImmediateHammer causes an immediate hammer
  142. func (g *Manager) DoImmediateHammer() {
  143. g.doHammerTime(0 * time.Second)
  144. }
  145. // DoGracefulShutdown causes a graceful shutdown
  146. func (g *Manager) DoGracefulShutdown() {
  147. g.lock.Lock()
  148. select {
  149. case <-g.shutdownRequested:
  150. g.lock.Unlock()
  151. default:
  152. close(g.shutdownRequested)
  153. g.lock.Unlock()
  154. g.doShutdown()
  155. }
  156. }
  157. // RegisterServer registers the running of a listening server.
  158. // Any call to RegisterServer must be matched by a call to ServerDone
  159. func (g *Manager) RegisterServer() {
  160. g.runningServerWaitGroup.Add(1)
  161. }
  162. func (g *Manager) awaitServer(limit time.Duration) bool {
  163. c := make(chan struct{})
  164. go func() {
  165. defer close(c)
  166. g.createServerWaitGroup.Wait()
  167. }()
  168. if limit > 0 {
  169. select {
  170. case <-c:
  171. return true // completed normally
  172. case <-time.After(limit):
  173. return false // timed out
  174. case <-g.IsShutdown():
  175. return false
  176. }
  177. } else {
  178. select {
  179. case <-c:
  180. return true // completed normally
  181. case <-g.IsShutdown():
  182. return false
  183. }
  184. }
  185. }