Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

workerpool.go 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  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 queue
  5. import (
  6. "context"
  7. "sync"
  8. "sync/atomic"
  9. "time"
  10. "code.gitea.io/gitea/modules/log"
  11. )
  12. // WorkerPool represent a dynamically growable worker pool for a
  13. // provided handler function. They have an internal channel which
  14. // they use to detect if there is a block and will grow and shrink in
  15. // response to demand as per configuration.
  16. type WorkerPool struct {
  17. lock sync.Mutex
  18. baseCtx context.Context
  19. cancel context.CancelFunc
  20. cond *sync.Cond
  21. qid int64
  22. maxNumberOfWorkers int
  23. numberOfWorkers int
  24. batchLength int
  25. handle HandlerFunc
  26. dataChan chan Data
  27. blockTimeout time.Duration
  28. boostTimeout time.Duration
  29. boostWorkers int
  30. numInQueue int64
  31. }
  32. // WorkerPoolConfiguration is the basic configuration for a WorkerPool
  33. type WorkerPoolConfiguration struct {
  34. QueueLength int
  35. BatchLength int
  36. BlockTimeout time.Duration
  37. BoostTimeout time.Duration
  38. BoostWorkers int
  39. MaxWorkers int
  40. }
  41. // NewWorkerPool creates a new worker pool
  42. func NewWorkerPool(handle HandlerFunc, config WorkerPoolConfiguration) *WorkerPool {
  43. ctx, cancel := context.WithCancel(context.Background())
  44. dataChan := make(chan Data, config.QueueLength)
  45. pool := &WorkerPool{
  46. baseCtx: ctx,
  47. cancel: cancel,
  48. batchLength: config.BatchLength,
  49. dataChan: dataChan,
  50. handle: handle,
  51. blockTimeout: config.BlockTimeout,
  52. boostTimeout: config.BoostTimeout,
  53. boostWorkers: config.BoostWorkers,
  54. maxNumberOfWorkers: config.MaxWorkers,
  55. }
  56. return pool
  57. }
  58. // Push pushes the data to the internal channel
  59. func (p *WorkerPool) Push(data Data) {
  60. atomic.AddInt64(&p.numInQueue, 1)
  61. p.lock.Lock()
  62. if p.blockTimeout > 0 && p.boostTimeout > 0 && (p.numberOfWorkers <= p.maxNumberOfWorkers || p.maxNumberOfWorkers < 0) {
  63. p.lock.Unlock()
  64. p.pushBoost(data)
  65. } else {
  66. p.lock.Unlock()
  67. p.dataChan <- data
  68. }
  69. }
  70. func (p *WorkerPool) pushBoost(data Data) {
  71. select {
  72. case p.dataChan <- data:
  73. default:
  74. p.lock.Lock()
  75. if p.blockTimeout <= 0 {
  76. p.lock.Unlock()
  77. p.dataChan <- data
  78. return
  79. }
  80. ourTimeout := p.blockTimeout
  81. timer := time.NewTimer(p.blockTimeout)
  82. p.lock.Unlock()
  83. select {
  84. case p.dataChan <- data:
  85. if timer.Stop() {
  86. select {
  87. case <-timer.C:
  88. default:
  89. }
  90. }
  91. case <-timer.C:
  92. p.lock.Lock()
  93. if p.blockTimeout > ourTimeout || (p.numberOfWorkers > p.maxNumberOfWorkers && p.maxNumberOfWorkers >= 0) {
  94. p.lock.Unlock()
  95. p.dataChan <- data
  96. return
  97. }
  98. p.blockTimeout *= 2
  99. ctx, cancel := context.WithCancel(p.baseCtx)
  100. mq := GetManager().GetManagedQueue(p.qid)
  101. boost := p.boostWorkers
  102. if (boost+p.numberOfWorkers) > p.maxNumberOfWorkers && p.maxNumberOfWorkers >= 0 {
  103. boost = p.maxNumberOfWorkers - p.numberOfWorkers
  104. }
  105. if mq != nil {
  106. log.Warn("WorkerPool: %d (for %s) Channel blocked for %v - adding %d temporary workers for %s, block timeout now %v", p.qid, mq.Name, ourTimeout, boost, p.boostTimeout, p.blockTimeout)
  107. start := time.Now()
  108. pid := mq.RegisterWorkers(boost, start, false, start, cancel, false)
  109. go func() {
  110. <-ctx.Done()
  111. mq.RemoveWorkers(pid)
  112. cancel()
  113. }()
  114. } else {
  115. log.Warn("WorkerPool: %d Channel blocked for %v - adding %d temporary workers for %s, block timeout now %v", p.qid, ourTimeout, p.boostWorkers, p.boostTimeout, p.blockTimeout)
  116. }
  117. go func() {
  118. <-time.After(p.boostTimeout)
  119. cancel()
  120. p.lock.Lock()
  121. p.blockTimeout /= 2
  122. p.lock.Unlock()
  123. }()
  124. p.addWorkers(ctx, boost)
  125. p.lock.Unlock()
  126. p.dataChan <- data
  127. }
  128. }
  129. }
  130. // NumberOfWorkers returns the number of current workers in the pool
  131. func (p *WorkerPool) NumberOfWorkers() int {
  132. p.lock.Lock()
  133. defer p.lock.Unlock()
  134. return p.numberOfWorkers
  135. }
  136. // MaxNumberOfWorkers returns the maximum number of workers automatically added to the pool
  137. func (p *WorkerPool) MaxNumberOfWorkers() int {
  138. p.lock.Lock()
  139. defer p.lock.Unlock()
  140. return p.maxNumberOfWorkers
  141. }
  142. // BoostWorkers returns the number of workers for a boost
  143. func (p *WorkerPool) BoostWorkers() int {
  144. p.lock.Lock()
  145. defer p.lock.Unlock()
  146. return p.boostWorkers
  147. }
  148. // BoostTimeout returns the timeout of the next boost
  149. func (p *WorkerPool) BoostTimeout() time.Duration {
  150. p.lock.Lock()
  151. defer p.lock.Unlock()
  152. return p.boostTimeout
  153. }
  154. // BlockTimeout returns the timeout til the next boost
  155. func (p *WorkerPool) BlockTimeout() time.Duration {
  156. p.lock.Lock()
  157. defer p.lock.Unlock()
  158. return p.blockTimeout
  159. }
  160. // SetPoolSettings sets the setable boost values
  161. func (p *WorkerPool) SetPoolSettings(maxNumberOfWorkers, boostWorkers int, timeout time.Duration) {
  162. p.lock.Lock()
  163. defer p.lock.Unlock()
  164. p.maxNumberOfWorkers = maxNumberOfWorkers
  165. p.boostWorkers = boostWorkers
  166. p.boostTimeout = timeout
  167. }
  168. // SetMaxNumberOfWorkers sets the maximum number of workers automatically added to the pool
  169. // Changing this number will not change the number of current workers but will change the limit
  170. // for future additions
  171. func (p *WorkerPool) SetMaxNumberOfWorkers(newMax int) {
  172. p.lock.Lock()
  173. defer p.lock.Unlock()
  174. p.maxNumberOfWorkers = newMax
  175. }
  176. func (p *WorkerPool) commonRegisterWorkers(number int, timeout time.Duration, isFlusher bool) (context.Context, context.CancelFunc) {
  177. var ctx context.Context
  178. var cancel context.CancelFunc
  179. start := time.Now()
  180. end := start
  181. hasTimeout := false
  182. if timeout > 0 {
  183. ctx, cancel = context.WithTimeout(p.baseCtx, timeout)
  184. end = start.Add(timeout)
  185. hasTimeout = true
  186. } else {
  187. ctx, cancel = context.WithCancel(p.baseCtx)
  188. }
  189. mq := GetManager().GetManagedQueue(p.qid)
  190. if mq != nil {
  191. pid := mq.RegisterWorkers(number, start, hasTimeout, end, cancel, isFlusher)
  192. go func() {
  193. <-ctx.Done()
  194. mq.RemoveWorkers(pid)
  195. cancel()
  196. }()
  197. log.Trace("WorkerPool: %d (for %s) adding %d workers with group id: %d", p.qid, mq.Name, number, pid)
  198. } else {
  199. log.Trace("WorkerPool: %d adding %d workers (no group id)", p.qid, number)
  200. }
  201. return ctx, cancel
  202. }
  203. // AddWorkers adds workers to the pool - this allows the number of workers to go above the limit
  204. func (p *WorkerPool) AddWorkers(number int, timeout time.Duration) context.CancelFunc {
  205. ctx, cancel := p.commonRegisterWorkers(number, timeout, false)
  206. p.addWorkers(ctx, number)
  207. return cancel
  208. }
  209. // addWorkers adds workers to the pool
  210. func (p *WorkerPool) addWorkers(ctx context.Context, number int) {
  211. for i := 0; i < number; i++ {
  212. p.lock.Lock()
  213. if p.cond == nil {
  214. p.cond = sync.NewCond(&p.lock)
  215. }
  216. p.numberOfWorkers++
  217. p.lock.Unlock()
  218. go func() {
  219. p.doWork(ctx)
  220. p.lock.Lock()
  221. p.numberOfWorkers--
  222. if p.numberOfWorkers == 0 {
  223. p.cond.Broadcast()
  224. } else if p.numberOfWorkers < 0 {
  225. // numberOfWorkers can't go negative but...
  226. log.Warn("Number of Workers < 0 for QID %d - this shouldn't happen", p.qid)
  227. p.numberOfWorkers = 0
  228. p.cond.Broadcast()
  229. }
  230. p.lock.Unlock()
  231. }()
  232. }
  233. }
  234. // Wait for WorkerPool to finish
  235. func (p *WorkerPool) Wait() {
  236. p.lock.Lock()
  237. defer p.lock.Unlock()
  238. if p.cond == nil {
  239. p.cond = sync.NewCond(&p.lock)
  240. }
  241. if p.numberOfWorkers <= 0 {
  242. return
  243. }
  244. p.cond.Wait()
  245. }
  246. // CleanUp will drain the remaining contents of the channel
  247. // This should be called after AddWorkers context is closed
  248. func (p *WorkerPool) CleanUp(ctx context.Context) {
  249. log.Trace("WorkerPool: %d CleanUp", p.qid)
  250. close(p.dataChan)
  251. for data := range p.dataChan {
  252. p.handle(data)
  253. atomic.AddInt64(&p.numInQueue, -1)
  254. select {
  255. case <-ctx.Done():
  256. log.Warn("WorkerPool: %d Cleanup context closed before finishing clean-up", p.qid)
  257. return
  258. default:
  259. }
  260. }
  261. log.Trace("WorkerPool: %d CleanUp Done", p.qid)
  262. }
  263. // Flush flushes the channel with a timeout - the Flush worker will be registered as a flush worker with the manager
  264. func (p *WorkerPool) Flush(timeout time.Duration) error {
  265. ctx, cancel := p.commonRegisterWorkers(1, timeout, true)
  266. defer cancel()
  267. return p.FlushWithContext(ctx)
  268. }
  269. // IsEmpty returns if true if the worker queue is empty
  270. func (p *WorkerPool) IsEmpty() bool {
  271. return atomic.LoadInt64(&p.numInQueue) == 0
  272. }
  273. // FlushWithContext is very similar to CleanUp but it will return as soon as the dataChan is empty
  274. // NB: The worker will not be registered with the manager.
  275. func (p *WorkerPool) FlushWithContext(ctx context.Context) error {
  276. log.Trace("WorkerPool: %d Flush", p.qid)
  277. for {
  278. select {
  279. case data := <-p.dataChan:
  280. p.handle(data)
  281. atomic.AddInt64(&p.numInQueue, -1)
  282. case <-p.baseCtx.Done():
  283. return p.baseCtx.Err()
  284. case <-ctx.Done():
  285. return ctx.Err()
  286. default:
  287. return nil
  288. }
  289. }
  290. }
  291. func (p *WorkerPool) doWork(ctx context.Context) {
  292. delay := time.Millisecond * 300
  293. var data = make([]Data, 0, p.batchLength)
  294. for {
  295. select {
  296. case <-ctx.Done():
  297. if len(data) > 0 {
  298. log.Trace("Handling: %d data, %v", len(data), data)
  299. p.handle(data...)
  300. atomic.AddInt64(&p.numInQueue, -1*int64(len(data)))
  301. }
  302. log.Trace("Worker shutting down")
  303. return
  304. case datum, ok := <-p.dataChan:
  305. if !ok {
  306. // the dataChan has been closed - we should finish up:
  307. if len(data) > 0 {
  308. log.Trace("Handling: %d data, %v", len(data), data)
  309. p.handle(data...)
  310. atomic.AddInt64(&p.numInQueue, -1*int64(len(data)))
  311. }
  312. log.Trace("Worker shutting down")
  313. return
  314. }
  315. data = append(data, datum)
  316. if len(data) >= p.batchLength {
  317. log.Trace("Handling: %d data, %v", len(data), data)
  318. p.handle(data...)
  319. atomic.AddInt64(&p.numInQueue, -1*int64(len(data)))
  320. data = make([]Data, 0, p.batchLength)
  321. }
  322. default:
  323. timer := time.NewTimer(delay)
  324. select {
  325. case <-ctx.Done():
  326. if timer.Stop() {
  327. select {
  328. case <-timer.C:
  329. default:
  330. }
  331. }
  332. if len(data) > 0 {
  333. log.Trace("Handling: %d data, %v", len(data), data)
  334. p.handle(data...)
  335. atomic.AddInt64(&p.numInQueue, -1*int64(len(data)))
  336. }
  337. log.Trace("Worker shutting down")
  338. return
  339. case datum, ok := <-p.dataChan:
  340. if timer.Stop() {
  341. select {
  342. case <-timer.C:
  343. default:
  344. }
  345. }
  346. if !ok {
  347. // the dataChan has been closed - we should finish up:
  348. if len(data) > 0 {
  349. log.Trace("Handling: %d data, %v", len(data), data)
  350. p.handle(data...)
  351. atomic.AddInt64(&p.numInQueue, -1*int64(len(data)))
  352. }
  353. log.Trace("Worker shutting down")
  354. return
  355. }
  356. data = append(data, datum)
  357. if len(data) >= p.batchLength {
  358. log.Trace("Handling: %d data, %v", len(data), data)
  359. p.handle(data...)
  360. atomic.AddInt64(&p.numInQueue, -1*int64(len(data)))
  361. data = make([]Data, 0, p.batchLength)
  362. }
  363. case <-timer.C:
  364. delay = time.Millisecond * 100
  365. if len(data) > 0 {
  366. log.Trace("Handling: %d data, %v", len(data), data)
  367. p.handle(data...)
  368. atomic.AddInt64(&p.numInQueue, -1*int64(len(data)))
  369. data = make([]Data, 0, p.batchLength)
  370. }
  371. }
  372. }
  373. }
  374. }