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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  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. "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. // 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 = 4
  29. // Manager represents the graceful server manager interface
  30. var manager *Manager
  31. var initOnce = sync.Once{}
  32. // GetManager returns the Manager
  33. func GetManager() *Manager {
  34. InitManager(context.Background())
  35. return manager
  36. }
  37. // InitManager creates the graceful manager in the provided context
  38. func InitManager(ctx context.Context) {
  39. initOnce.Do(func() {
  40. manager = newGracefulManager(ctx)
  41. // Set the process default context to the HammerContext
  42. process.DefaultContext = manager.HammerContext()
  43. })
  44. }
  45. // WithCallback is a runnable to call when the caller has finished
  46. type WithCallback func(callback func())
  47. // RunnableWithShutdownFns is a runnable with functions to run at shutdown and terminate
  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. type RunnableWithShutdownFns func(atShutdown, atTerminate func(func()))
  54. // RunWithShutdownFns takes a function that has both atShutdown and atTerminate callbacks
  55. // After the callback to atShutdown is called and is complete, the main function must return.
  56. // Similarly the callback function provided to atTerminate must return once termination is complete.
  57. // Please note that use of the atShutdown and atTerminate callbacks will create go-routines that will wait till their respective signals
  58. // - users must therefore be careful to only call these as necessary.
  59. // If run is not expected to run indefinitely RunWithShutdownChan is likely to be more appropriate.
  60. func (g *Manager) RunWithShutdownFns(run RunnableWithShutdownFns) {
  61. g.runningServerWaitGroup.Add(1)
  62. defer g.runningServerWaitGroup.Done()
  63. defer func() {
  64. if err := recover(); err != nil {
  65. log.Critical("PANIC during RunWithShutdownFns: %v\nStacktrace: %s", err, log.Stack(2))
  66. g.doShutdown()
  67. }
  68. }()
  69. run(func(atShutdown func()) {
  70. g.lock.Lock()
  71. defer g.lock.Unlock()
  72. g.toRunAtShutdown = append(g.toRunAtShutdown,
  73. func() {
  74. defer func() {
  75. if err := recover(); err != nil {
  76. log.Critical("PANIC during RunWithShutdownFns: %v\nStacktrace: %s", err, log.Stack(2))
  77. g.doShutdown()
  78. }
  79. }()
  80. atShutdown()
  81. })
  82. }, func(atTerminate func()) {
  83. g.RunAtTerminate(atTerminate)
  84. })
  85. }
  86. // RunnableWithShutdownChan is a runnable with functions to run at shutdown and terminate.
  87. // After the atShutdown channel is closed, the main function must return once shutdown is complete.
  88. // (Optionally IsHammer may be waited for instead however, this should be avoided if possible.)
  89. // The callback function provided to atTerminate must return once termination is complete.
  90. // 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.
  91. type RunnableWithShutdownChan func(atShutdown <-chan struct{}, atTerminate WithCallback)
  92. // RunWithShutdownChan takes a function that has channel to watch for shutdown and atTerminate callbacks
  93. // After the atShutdown channel is closed, the main function must return once shutdown is complete.
  94. // (Optionally IsHammer may be waited for instead however, this should be avoided if possible.)
  95. // The callback function provided to atTerminate must return once termination is complete.
  96. // 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.
  97. func (g *Manager) RunWithShutdownChan(run RunnableWithShutdownChan) {
  98. g.runningServerWaitGroup.Add(1)
  99. defer g.runningServerWaitGroup.Done()
  100. defer func() {
  101. if err := recover(); err != nil {
  102. log.Critical("PANIC during RunWithShutdownChan: %v\nStacktrace: %s", err, log.Stack(2))
  103. g.doShutdown()
  104. }
  105. }()
  106. run(g.IsShutdown(), func(atTerminate func()) {
  107. g.RunAtTerminate(atTerminate)
  108. })
  109. }
  110. // RunWithShutdownContext takes a function that has a context to watch for shutdown.
  111. // After the provided context is Done(), the main function must return once shutdown is complete.
  112. // (Optionally the HammerContext may be obtained and waited for however, this should be avoided if possible.)
  113. func (g *Manager) RunWithShutdownContext(run func(context.Context)) {
  114. g.runningServerWaitGroup.Add(1)
  115. defer g.runningServerWaitGroup.Done()
  116. defer func() {
  117. if err := recover(); err != nil {
  118. log.Critical("PANIC during RunWithShutdownContext: %v\nStacktrace: %s", err, log.Stack(2))
  119. g.doShutdown()
  120. }
  121. }()
  122. run(g.ShutdownContext())
  123. }
  124. // RunAtTerminate adds to the terminate wait group and creates a go-routine to run the provided function at termination
  125. func (g *Manager) RunAtTerminate(terminate func()) {
  126. g.terminateWaitGroup.Add(1)
  127. g.lock.Lock()
  128. defer g.lock.Unlock()
  129. g.toRunAtTerminate = append(g.toRunAtTerminate,
  130. func() {
  131. defer g.terminateWaitGroup.Done()
  132. defer func() {
  133. if err := recover(); err != nil {
  134. log.Critical("PANIC during RunAtTerminate: %v\nStacktrace: %s", err, log.Stack(2))
  135. }
  136. }()
  137. terminate()
  138. })
  139. }
  140. // RunAtShutdown creates a go-routine to run the provided function at shutdown
  141. func (g *Manager) RunAtShutdown(ctx context.Context, shutdown func()) {
  142. g.lock.Lock()
  143. defer g.lock.Unlock()
  144. g.toRunAtShutdown = append(g.toRunAtShutdown,
  145. func() {
  146. defer func() {
  147. if err := recover(); err != nil {
  148. log.Critical("PANIC during RunAtShutdown: %v\nStacktrace: %s", err, log.Stack(2))
  149. }
  150. }()
  151. select {
  152. case <-ctx.Done():
  153. return
  154. default:
  155. shutdown()
  156. }
  157. })
  158. }
  159. // RunAtHammer creates a go-routine to run the provided function at shutdown
  160. func (g *Manager) RunAtHammer(hammer func()) {
  161. g.lock.Lock()
  162. defer g.lock.Unlock()
  163. g.toRunAtHammer = append(g.toRunAtHammer,
  164. func() {
  165. defer func() {
  166. if err := recover(); err != nil {
  167. log.Critical("PANIC during RunAtHammer: %v\nStacktrace: %s", err, log.Stack(2))
  168. }
  169. }()
  170. hammer()
  171. })
  172. }
  173. func (g *Manager) doShutdown() {
  174. if !g.setStateTransition(stateRunning, stateShuttingDown) {
  175. return
  176. }
  177. g.lock.Lock()
  178. g.shutdownCtxCancel()
  179. for _, fn := range g.toRunAtShutdown {
  180. go fn()
  181. }
  182. g.lock.Unlock()
  183. if setting.GracefulHammerTime >= 0 {
  184. go g.doHammerTime(setting.GracefulHammerTime)
  185. }
  186. go func() {
  187. g.WaitForServers()
  188. // Mop up any remaining unclosed events.
  189. g.doHammerTime(0)
  190. <-time.After(1 * time.Second)
  191. g.doTerminate()
  192. g.WaitForTerminate()
  193. g.lock.Lock()
  194. g.doneCtxCancel()
  195. g.lock.Unlock()
  196. }()
  197. }
  198. func (g *Manager) doHammerTime(d time.Duration) {
  199. time.Sleep(d)
  200. g.lock.Lock()
  201. select {
  202. case <-g.hammerCtx.Done():
  203. default:
  204. log.Warn("Setting Hammer condition")
  205. g.hammerCtxCancel()
  206. for _, fn := range g.toRunAtHammer {
  207. go fn()
  208. }
  209. }
  210. g.lock.Unlock()
  211. }
  212. func (g *Manager) doTerminate() {
  213. if !g.setStateTransition(stateShuttingDown, stateTerminate) {
  214. return
  215. }
  216. g.lock.Lock()
  217. select {
  218. case <-g.terminateCtx.Done():
  219. default:
  220. log.Warn("Terminating")
  221. g.terminateCtxCancel()
  222. for _, fn := range g.toRunAtTerminate {
  223. go fn()
  224. }
  225. }
  226. g.lock.Unlock()
  227. }
  228. // IsChild returns if the current process is a child of previous Gitea process
  229. func (g *Manager) IsChild() bool {
  230. return g.isChild
  231. }
  232. // IsShutdown returns a channel which will be closed at shutdown.
  233. // The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
  234. func (g *Manager) IsShutdown() <-chan struct{} {
  235. return g.shutdownCtx.Done()
  236. }
  237. // IsHammer returns a channel which will be closed at hammer
  238. // The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
  239. // Servers running within the running server wait group should respond to IsHammer
  240. // if not shutdown already
  241. func (g *Manager) IsHammer() <-chan struct{} {
  242. return g.hammerCtx.Done()
  243. }
  244. // IsTerminate returns a channel which will be closed at terminate
  245. // The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
  246. // IsTerminate will only close once all running servers have stopped
  247. func (g *Manager) IsTerminate() <-chan struct{} {
  248. return g.terminateCtx.Done()
  249. }
  250. // ServerDone declares a running server done and subtracts one from the
  251. // running server wait group. Users probably do not want to call this
  252. // and should use one of the RunWithShutdown* functions
  253. func (g *Manager) ServerDone() {
  254. g.runningServerWaitGroup.Done()
  255. }
  256. // WaitForServers waits for all running servers to finish. Users should probably
  257. // instead use AtTerminate or IsTerminate
  258. func (g *Manager) WaitForServers() {
  259. g.runningServerWaitGroup.Wait()
  260. }
  261. // WaitForTerminate waits for all terminating actions to finish.
  262. // Only the main go-routine should use this
  263. func (g *Manager) WaitForTerminate() {
  264. g.terminateWaitGroup.Wait()
  265. }
  266. func (g *Manager) getState() state {
  267. g.lock.RLock()
  268. defer g.lock.RUnlock()
  269. return g.state
  270. }
  271. func (g *Manager) setStateTransition(old, new state) bool {
  272. if old != g.getState() {
  273. return false
  274. }
  275. g.lock.Lock()
  276. if g.state != old {
  277. g.lock.Unlock()
  278. return false
  279. }
  280. g.state = new
  281. g.lock.Unlock()
  282. return true
  283. }
  284. func (g *Manager) setState(st state) {
  285. g.lock.Lock()
  286. defer g.lock.Unlock()
  287. g.state = st
  288. }
  289. // InformCleanup tells the cleanup wait group that we have either taken a listener
  290. // or will not be taking a listener
  291. func (g *Manager) InformCleanup() {
  292. g.createServerWaitGroup.Done()
  293. }
  294. // Done allows the manager to be viewed as a context.Context, it returns a channel that is closed when the server is finished terminating
  295. func (g *Manager) Done() <-chan struct{} {
  296. return g.doneCtx.Done()
  297. }
  298. // Err allows the manager to be viewed as a context.Context done at Terminate
  299. func (g *Manager) Err() error {
  300. return g.doneCtx.Err()
  301. }
  302. // Value allows the manager to be viewed as a context.Context done at Terminate
  303. func (g *Manager) Value(key interface{}) interface{} {
  304. return g.doneCtx.Value(key)
  305. }
  306. // Deadline returns nil as there is no fixed Deadline for the manager, it allows the manager to be viewed as a context.Context
  307. func (g *Manager) Deadline() (deadline time.Time, ok bool) {
  308. return g.doneCtx.Deadline()
  309. }