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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package graceful
  4. import (
  5. "context"
  6. "runtime/pprof"
  7. "sync"
  8. "time"
  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. type RunCanceler interface {
  21. Run()
  22. Cancel()
  23. }
  24. // There are some places that could inherit sockets:
  25. //
  26. // * HTTP or HTTPS main listener
  27. // * HTTP or HTTPS install listener
  28. // * HTTP redirection fallback
  29. // * Builtin SSH listener
  30. //
  31. // If you add a new place you must increment this number
  32. // and add a function to call manager.InformCleanup if it's not going to be used
  33. const numberOfServersToCreate = 4
  34. // Manager represents the graceful server manager interface
  35. var manager *Manager
  36. var initOnce = sync.Once{}
  37. // GetManager returns the Manager
  38. func GetManager() *Manager {
  39. InitManager(context.Background())
  40. return manager
  41. }
  42. // InitManager creates the graceful manager in the provided context
  43. func InitManager(ctx context.Context) {
  44. initOnce.Do(func() {
  45. manager = newGracefulManager(ctx)
  46. // Set the process default context to the HammerContext
  47. process.DefaultContext = manager.HammerContext()
  48. })
  49. }
  50. // RunWithCancel helps to run a function with a custom context, the Cancel function will be called at shutdown
  51. // The Cancel function should stop the Run function in predictable time.
  52. func (g *Manager) RunWithCancel(rc RunCanceler) {
  53. g.RunAtShutdown(context.Background(), rc.Cancel)
  54. g.runningServerWaitGroup.Add(1)
  55. defer g.runningServerWaitGroup.Done()
  56. defer func() {
  57. if err := recover(); err != nil {
  58. log.Critical("PANIC during RunWithCancel: %v\nStacktrace: %s", err, log.Stack(2))
  59. g.doShutdown()
  60. }
  61. }()
  62. rc.Run()
  63. }
  64. // RunWithShutdownContext takes a function that has a context to watch for shutdown.
  65. // After the provided context is Done(), the main function must return once shutdown is complete.
  66. // (Optionally the HammerContext may be obtained and waited for however, this should be avoided if possible.)
  67. func (g *Manager) RunWithShutdownContext(run func(context.Context)) {
  68. g.runningServerWaitGroup.Add(1)
  69. defer g.runningServerWaitGroup.Done()
  70. defer func() {
  71. if err := recover(); err != nil {
  72. log.Critical("PANIC during RunWithShutdownContext: %v\nStacktrace: %s", err, log.Stack(2))
  73. g.doShutdown()
  74. }
  75. }()
  76. ctx := g.ShutdownContext()
  77. pprof.SetGoroutineLabels(ctx) // We don't have a label to restore back to but I think this is fine
  78. run(ctx)
  79. }
  80. // RunAtTerminate adds to the terminate wait group and creates a go-routine to run the provided function at termination
  81. func (g *Manager) RunAtTerminate(terminate func()) {
  82. g.terminateWaitGroup.Add(1)
  83. g.lock.Lock()
  84. defer g.lock.Unlock()
  85. g.toRunAtTerminate = append(g.toRunAtTerminate,
  86. func() {
  87. defer g.terminateWaitGroup.Done()
  88. defer func() {
  89. if err := recover(); err != nil {
  90. log.Critical("PANIC during RunAtTerminate: %v\nStacktrace: %s", err, log.Stack(2))
  91. }
  92. }()
  93. terminate()
  94. })
  95. }
  96. // RunAtShutdown creates a go-routine to run the provided function at shutdown
  97. func (g *Manager) RunAtShutdown(ctx context.Context, shutdown func()) {
  98. g.lock.Lock()
  99. defer g.lock.Unlock()
  100. g.toRunAtShutdown = append(g.toRunAtShutdown,
  101. func() {
  102. defer func() {
  103. if err := recover(); err != nil {
  104. log.Critical("PANIC during RunAtShutdown: %v\nStacktrace: %s", err, log.Stack(2))
  105. }
  106. }()
  107. select {
  108. case <-ctx.Done():
  109. return
  110. default:
  111. shutdown()
  112. }
  113. })
  114. }
  115. func (g *Manager) doShutdown() {
  116. if !g.setStateTransition(stateRunning, stateShuttingDown) {
  117. g.DoImmediateHammer()
  118. return
  119. }
  120. g.lock.Lock()
  121. g.shutdownCtxCancel()
  122. atShutdownCtx := pprof.WithLabels(g.hammerCtx, pprof.Labels("graceful-lifecycle", "post-shutdown"))
  123. pprof.SetGoroutineLabels(atShutdownCtx)
  124. for _, fn := range g.toRunAtShutdown {
  125. go fn()
  126. }
  127. g.lock.Unlock()
  128. if setting.GracefulHammerTime >= 0 {
  129. go g.doHammerTime(setting.GracefulHammerTime)
  130. }
  131. go func() {
  132. g.WaitForServers()
  133. // Mop up any remaining unclosed events.
  134. g.doHammerTime(0)
  135. <-time.After(1 * time.Second)
  136. g.doTerminate()
  137. g.WaitForTerminate()
  138. g.lock.Lock()
  139. g.managerCtxCancel()
  140. g.lock.Unlock()
  141. }()
  142. }
  143. func (g *Manager) doHammerTime(d time.Duration) {
  144. time.Sleep(d)
  145. g.lock.Lock()
  146. select {
  147. case <-g.hammerCtx.Done():
  148. default:
  149. log.Warn("Setting Hammer condition")
  150. g.hammerCtxCancel()
  151. atHammerCtx := pprof.WithLabels(g.terminateCtx, pprof.Labels("graceful-lifecycle", "post-hammer"))
  152. pprof.SetGoroutineLabels(atHammerCtx)
  153. }
  154. g.lock.Unlock()
  155. }
  156. func (g *Manager) doTerminate() {
  157. if !g.setStateTransition(stateShuttingDown, stateTerminate) {
  158. return
  159. }
  160. g.lock.Lock()
  161. select {
  162. case <-g.terminateCtx.Done():
  163. default:
  164. log.Warn("Terminating")
  165. g.terminateCtxCancel()
  166. atTerminateCtx := pprof.WithLabels(g.managerCtx, pprof.Labels("graceful-lifecycle", "post-terminate"))
  167. pprof.SetGoroutineLabels(atTerminateCtx)
  168. for _, fn := range g.toRunAtTerminate {
  169. go fn()
  170. }
  171. }
  172. g.lock.Unlock()
  173. }
  174. // IsChild returns if the current process is a child of previous Gitea process
  175. func (g *Manager) IsChild() bool {
  176. return g.isChild
  177. }
  178. // IsShutdown returns a channel which will be closed at shutdown.
  179. // The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
  180. func (g *Manager) IsShutdown() <-chan struct{} {
  181. return g.shutdownCtx.Done()
  182. }
  183. // IsHammer returns a channel which will be closed at hammer
  184. // The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
  185. // Servers running within the running server wait group should respond to IsHammer
  186. // if not shutdown already
  187. func (g *Manager) IsHammer() <-chan struct{} {
  188. return g.hammerCtx.Done()
  189. }
  190. // IsTerminate returns a channel which will be closed at terminate
  191. // The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
  192. // IsTerminate will only close once all running servers have stopped
  193. func (g *Manager) IsTerminate() <-chan struct{} {
  194. return g.terminateCtx.Done()
  195. }
  196. // ServerDone declares a running server done and subtracts one from the
  197. // running server wait group. Users probably do not want to call this
  198. // and should use one of the RunWithShutdown* functions
  199. func (g *Manager) ServerDone() {
  200. g.runningServerWaitGroup.Done()
  201. }
  202. // WaitForServers waits for all running servers to finish. Users should probably
  203. // instead use AtTerminate or IsTerminate
  204. func (g *Manager) WaitForServers() {
  205. g.runningServerWaitGroup.Wait()
  206. }
  207. // WaitForTerminate waits for all terminating actions to finish.
  208. // Only the main go-routine should use this
  209. func (g *Manager) WaitForTerminate() {
  210. g.terminateWaitGroup.Wait()
  211. }
  212. func (g *Manager) getState() state {
  213. g.lock.RLock()
  214. defer g.lock.RUnlock()
  215. return g.state
  216. }
  217. func (g *Manager) setStateTransition(old, new state) bool {
  218. if old != g.getState() {
  219. return false
  220. }
  221. g.lock.Lock()
  222. if g.state != old {
  223. g.lock.Unlock()
  224. return false
  225. }
  226. g.state = new
  227. g.lock.Unlock()
  228. return true
  229. }
  230. func (g *Manager) setState(st state) {
  231. g.lock.Lock()
  232. defer g.lock.Unlock()
  233. g.state = st
  234. }
  235. // InformCleanup tells the cleanup wait group that we have either taken a listener or will not be taking a listener.
  236. // At the moment the total number of servers (numberOfServersToCreate) are pre-defined as a const before global init,
  237. // so this function MUST be called if a server is not used.
  238. func (g *Manager) InformCleanup() {
  239. g.createServerWaitGroup.Done()
  240. }
  241. // Done allows the manager to be viewed as a context.Context, it returns a channel that is closed when the server is finished terminating
  242. func (g *Manager) Done() <-chan struct{} {
  243. return g.managerCtx.Done()
  244. }
  245. // Err allows the manager to be viewed as a context.Context done at Terminate
  246. func (g *Manager) Err() error {
  247. return g.managerCtx.Err()
  248. }
  249. // Value allows the manager to be viewed as a context.Context done at Terminate
  250. func (g *Manager) Value(key interface{}) interface{} {
  251. return g.managerCtx.Value(key)
  252. }
  253. // Deadline returns nil as there is no fixed Deadline for the manager, it allows the manager to be viewed as a context.Context
  254. func (g *Manager) Deadline() (deadline time.Time, ok bool) {
  255. return g.managerCtx.Deadline()
  256. }