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.go 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  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. package graceful
  5. import (
  6. "context"
  7. "time"
  8. "code.gitea.io/gitea/modules/git"
  9. "code.gitea.io/gitea/modules/log"
  10. "code.gitea.io/gitea/modules/process"
  11. "code.gitea.io/gitea/modules/setting"
  12. )
  13. type state uint8
  14. const (
  15. stateInit state = iota
  16. stateRunning
  17. stateShuttingDown
  18. stateTerminate
  19. )
  20. // There are three places that could inherit sockets:
  21. //
  22. // * HTTP or HTTPS main listener
  23. // * HTTP redirection fallback
  24. // * SSH
  25. //
  26. // If you add an additional place you must increment this number
  27. // and add a function to call manager.InformCleanup if it's not going to be used
  28. const numberOfServersToCreate = 3
  29. // Manager represents the graceful server manager interface
  30. var Manager *gracefulManager
  31. func init() {
  32. Manager = newGracefulManager(context.Background())
  33. // Set the git default context to the HammerContext
  34. git.DefaultContext = Manager.HammerContext()
  35. // Set the process default context to the HammerContext
  36. process.DefaultContext = Manager.HammerContext()
  37. }
  38. // CallbackWithContext is combined runnable and context to watch to see if the caller has finished
  39. type CallbackWithContext func(ctx context.Context, callback func())
  40. // RunnableWithShutdownFns is a runnable with functions to run at shutdown and terminate
  41. // After the callback to atShutdown is called and is complete, the main function must return.
  42. // Similarly the callback function provided to atTerminate must return once termination is complete.
  43. // Please note that use of the atShutdown and atTerminate callbacks will create go-routines that will wait till their respective signals
  44. // - users must therefore be careful to only call these as necessary.
  45. // If run is not expected to run indefinitely RunWithShutdownChan is likely to be more appropriate.
  46. type RunnableWithShutdownFns func(atShutdown, atTerminate func(context.Context, func()))
  47. // RunWithShutdownFns takes a function that has both atShutdown and atTerminate callbacks
  48. // After the callback to atShutdown is called and is complete, the main function must return.
  49. // Similarly the callback function provided to atTerminate must return once termination is complete.
  50. // Please note that use of the atShutdown and atTerminate callbacks will create go-routines that will wait till their respective signals
  51. // - users must therefore be careful to only call these as necessary.
  52. // If run is not expected to run indefinitely RunWithShutdownChan is likely to be more appropriate.
  53. func (g *gracefulManager) RunWithShutdownFns(run RunnableWithShutdownFns) {
  54. g.runningServerWaitGroup.Add(1)
  55. defer g.runningServerWaitGroup.Done()
  56. run(func(ctx context.Context, atShutdown func()) {
  57. go func() {
  58. select {
  59. case <-g.IsShutdown():
  60. atShutdown()
  61. case <-ctx.Done():
  62. return
  63. }
  64. }()
  65. }, func(ctx context.Context, atTerminate func()) {
  66. g.RunAtTerminate(ctx, atTerminate)
  67. })
  68. }
  69. // RunnableWithShutdownChan is a runnable with functions to run at shutdown and terminate.
  70. // After the atShutdown channel is closed, the main function must return once shutdown is complete.
  71. // (Optionally IsHammer may be waited for instead however, this should be avoided if possible.)
  72. // The callback function provided to atTerminate must return once termination is complete.
  73. // Please note that use of the atTerminate function will create a go-routine that will wait till terminate - users must therefore be careful to only call this as necessary.
  74. type RunnableWithShutdownChan func(atShutdown <-chan struct{}, atTerminate CallbackWithContext)
  75. // RunWithShutdownChan takes a function that has channel to watch for shutdown and atTerminate callbacks
  76. // After the atShutdown channel is closed, the main function must return once shutdown is complete.
  77. // (Optionally IsHammer may be waited for instead however, this should be avoided if possible.)
  78. // The callback function provided to atTerminate must return once termination is complete.
  79. // Please note that use of the atTerminate function will create a go-routine that will wait till terminate - users must therefore be careful to only call this as necessary.
  80. func (g *gracefulManager) RunWithShutdownChan(run RunnableWithShutdownChan) {
  81. g.runningServerWaitGroup.Add(1)
  82. defer g.runningServerWaitGroup.Done()
  83. run(g.IsShutdown(), func(ctx context.Context, atTerminate func()) {
  84. g.RunAtTerminate(ctx, atTerminate)
  85. })
  86. }
  87. // RunWithShutdownContext takes a function that has a context to watch for shutdown.
  88. // After the provided context is Done(), the main function must return once shutdown is complete.
  89. // (Optionally the HammerContext may be obtained and waited for however, this should be avoided if possible.)
  90. func (g *gracefulManager) RunWithShutdownContext(run func(context.Context)) {
  91. g.runningServerWaitGroup.Add(1)
  92. defer g.runningServerWaitGroup.Done()
  93. run(g.ShutdownContext())
  94. }
  95. // RunAtTerminate adds to the terminate wait group and creates a go-routine to run the provided function at termination
  96. func (g *gracefulManager) RunAtTerminate(ctx context.Context, terminate func()) {
  97. g.terminateWaitGroup.Add(1)
  98. go func() {
  99. select {
  100. case <-g.IsTerminate():
  101. terminate()
  102. case <-ctx.Done():
  103. }
  104. g.terminateWaitGroup.Done()
  105. }()
  106. }
  107. // RunAtShutdown creates a go-routine to run the provided function at shutdown
  108. func (g *gracefulManager) RunAtShutdown(ctx context.Context, shutdown func()) {
  109. go func() {
  110. select {
  111. case <-g.IsShutdown():
  112. shutdown()
  113. case <-ctx.Done():
  114. }
  115. }()
  116. }
  117. // RunAtHammer creates a go-routine to run the provided function at shutdown
  118. func (g *gracefulManager) RunAtHammer(ctx context.Context, hammer func()) {
  119. go func() {
  120. select {
  121. case <-g.IsHammer():
  122. hammer()
  123. case <-ctx.Done():
  124. }
  125. }()
  126. }
  127. func (g *gracefulManager) doShutdown() {
  128. if !g.setStateTransition(stateRunning, stateShuttingDown) {
  129. return
  130. }
  131. g.lock.Lock()
  132. close(g.shutdown)
  133. g.lock.Unlock()
  134. if setting.GracefulHammerTime >= 0 {
  135. go g.doHammerTime(setting.GracefulHammerTime)
  136. }
  137. go func() {
  138. g.WaitForServers()
  139. // Mop up any remaining unclosed events.
  140. g.doHammerTime(0)
  141. <-time.After(1 * time.Second)
  142. g.doTerminate()
  143. }()
  144. }
  145. func (g *gracefulManager) doHammerTime(d time.Duration) {
  146. time.Sleep(d)
  147. select {
  148. case <-g.hammer:
  149. default:
  150. log.Warn("Setting Hammer condition")
  151. close(g.hammer)
  152. }
  153. }
  154. func (g *gracefulManager) doTerminate() {
  155. if !g.setStateTransition(stateShuttingDown, stateTerminate) {
  156. return
  157. }
  158. g.lock.Lock()
  159. close(g.terminate)
  160. g.lock.Unlock()
  161. }
  162. // IsChild returns if the current process is a child of previous Gitea process
  163. func (g *gracefulManager) IsChild() bool {
  164. return g.isChild
  165. }
  166. // IsShutdown returns a channel which will be closed at shutdown.
  167. // The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
  168. func (g *gracefulManager) IsShutdown() <-chan struct{} {
  169. g.lock.RLock()
  170. if g.shutdown == nil {
  171. g.lock.RUnlock()
  172. g.lock.Lock()
  173. if g.shutdown == nil {
  174. g.shutdown = make(chan struct{})
  175. }
  176. defer g.lock.Unlock()
  177. return g.shutdown
  178. }
  179. defer g.lock.RUnlock()
  180. return g.shutdown
  181. }
  182. // IsHammer returns a channel which will be closed at hammer
  183. // The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
  184. // Servers running within the running server wait group should respond to IsHammer
  185. // if not shutdown already
  186. func (g *gracefulManager) IsHammer() <-chan struct{} {
  187. g.lock.RLock()
  188. if g.hammer == nil {
  189. g.lock.RUnlock()
  190. g.lock.Lock()
  191. if g.hammer == nil {
  192. g.hammer = make(chan struct{})
  193. }
  194. defer g.lock.Unlock()
  195. return g.hammer
  196. }
  197. defer g.lock.RUnlock()
  198. return g.hammer
  199. }
  200. // IsTerminate returns a channel which will be closed at terminate
  201. // The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
  202. // IsTerminate will only close once all running servers have stopped
  203. func (g *gracefulManager) IsTerminate() <-chan struct{} {
  204. g.lock.RLock()
  205. if g.terminate == nil {
  206. g.lock.RUnlock()
  207. g.lock.Lock()
  208. if g.terminate == nil {
  209. g.terminate = make(chan struct{})
  210. }
  211. defer g.lock.Unlock()
  212. return g.terminate
  213. }
  214. defer g.lock.RUnlock()
  215. return g.terminate
  216. }
  217. // ServerDone declares a running server done and subtracts one from the
  218. // running server wait group. Users probably do not want to call this
  219. // and should use one of the RunWithShutdown* functions
  220. func (g *gracefulManager) ServerDone() {
  221. g.runningServerWaitGroup.Done()
  222. }
  223. // WaitForServers waits for all running servers to finish. Users should probably
  224. // instead use AtTerminate or IsTerminate
  225. func (g *gracefulManager) WaitForServers() {
  226. g.runningServerWaitGroup.Wait()
  227. }
  228. // WaitForTerminate waits for all terminating actions to finish.
  229. // Only the main go-routine should use this
  230. func (g *gracefulManager) WaitForTerminate() {
  231. g.terminateWaitGroup.Wait()
  232. }
  233. func (g *gracefulManager) getState() state {
  234. g.lock.RLock()
  235. defer g.lock.RUnlock()
  236. return g.state
  237. }
  238. func (g *gracefulManager) setStateTransition(old, new state) bool {
  239. if old != g.getState() {
  240. return false
  241. }
  242. g.lock.Lock()
  243. if g.state != old {
  244. g.lock.Unlock()
  245. return false
  246. }
  247. g.state = new
  248. g.lock.Unlock()
  249. return true
  250. }
  251. func (g *gracefulManager) setState(st state) {
  252. g.lock.Lock()
  253. defer g.lock.Unlock()
  254. g.state = st
  255. }
  256. // InformCleanup tells the cleanup wait group that we have either taken a listener
  257. // or will not be taking a listener
  258. func (g *gracefulManager) InformCleanup() {
  259. g.createServerWaitGroup.Done()
  260. }