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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  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. }
  39. func newGracefulManager(ctx context.Context) *Manager {
  40. manager := &Manager{
  41. isChild: false,
  42. lock: &sync.RWMutex{},
  43. ctx: ctx,
  44. }
  45. manager.createServerWaitGroup.Add(numberOfServersToCreate)
  46. manager.start()
  47. return manager
  48. }
  49. func (g *Manager) start() {
  50. // Make channels
  51. g.terminate = make(chan struct{})
  52. g.shutdown = make(chan struct{})
  53. g.hammer = make(chan struct{})
  54. g.done = make(chan struct{})
  55. // Set the running state
  56. g.setState(stateRunning)
  57. if skip, _ := strconv.ParseBool(os.Getenv("SKIP_MINWINSVC")); skip {
  58. return
  59. }
  60. // Make SVC process
  61. run := svc.Run
  62. isInteractive, err := svc.IsAnInteractiveSession()
  63. if err != nil {
  64. log.Error("Unable to ascertain if running as an Interactive Session: %v", err)
  65. return
  66. }
  67. if isInteractive {
  68. run = debug.Run
  69. }
  70. go run(WindowsServiceName, g)
  71. }
  72. // Execute makes Manager implement svc.Handler
  73. func (g *Manager) Execute(args []string, changes <-chan svc.ChangeRequest, status chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) {
  74. if setting.StartupTimeout > 0 {
  75. status <- svc.Status{State: svc.StartPending}
  76. } else {
  77. status <- svc.Status{State: svc.StartPending, WaitHint: uint32(setting.StartupTimeout / time.Millisecond)}
  78. }
  79. // Now need to wait for everything to start...
  80. if !g.awaitServer(setting.StartupTimeout) {
  81. return false, 1
  82. }
  83. // We need to implement some way of svc.AcceptParamChange/svc.ParamChange
  84. status <- svc.Status{
  85. State: svc.Running,
  86. Accepts: svc.AcceptStop | svc.AcceptShutdown | acceptHammerCode,
  87. }
  88. waitTime := 30 * time.Second
  89. loop:
  90. for {
  91. select {
  92. case <-g.ctx.Done():
  93. g.doShutdown()
  94. waitTime += setting.GracefulHammerTime
  95. break loop
  96. case change := <-changes:
  97. switch change.Cmd {
  98. case svc.Interrogate:
  99. status <- change.CurrentStatus
  100. case svc.Stop, svc.Shutdown:
  101. g.doShutdown()
  102. waitTime += setting.GracefulHammerTime
  103. break loop
  104. case hammerCode:
  105. g.doShutdown()
  106. g.doHammerTime(0 * time.Second)
  107. break loop
  108. default:
  109. log.Debug("Unexpected control request: %v", change.Cmd)
  110. }
  111. }
  112. }
  113. status <- svc.Status{
  114. State: svc.StopPending,
  115. WaitHint: uint32(waitTime / time.Millisecond),
  116. }
  117. hammerLoop:
  118. for {
  119. select {
  120. case change := <-changes:
  121. switch change.Cmd {
  122. case svc.Interrogate:
  123. status <- change.CurrentStatus
  124. case svc.Stop, svc.Shutdown, hammerCmd:
  125. g.doHammerTime(0 * time.Second)
  126. break hammerLoop
  127. default:
  128. log.Debug("Unexpected control request: %v", change.Cmd)
  129. }
  130. case <-g.hammer:
  131. break hammerLoop
  132. }
  133. }
  134. return false, 0
  135. }
  136. // RegisterServer registers the running of a listening server.
  137. // Any call to RegisterServer must be matched by a call to ServerDone
  138. func (g *Manager) RegisterServer() {
  139. g.runningServerWaitGroup.Add(1)
  140. }
  141. func (g *Manager) awaitServer(limit time.Duration) bool {
  142. c := make(chan struct{})
  143. go func() {
  144. defer close(c)
  145. g.createServerWaitGroup.Wait()
  146. }()
  147. if limit > 0 {
  148. select {
  149. case <-c:
  150. return true // completed normally
  151. case <-time.After(limit):
  152. return false // timed out
  153. case <-g.IsShutdown():
  154. return false
  155. }
  156. } else {
  157. select {
  158. case <-c:
  159. return true // completed normally
  160. case <-g.IsShutdown():
  161. return false
  162. }
  163. }
  164. }