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

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