queue.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. /*
  2. Copyright 2015 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package workqueue
  14. import (
  15. "sync"
  16. "time"
  17. "k8s.io/utils/clock"
  18. )
  19. type Interface interface {
  20. Add(item interface{})
  21. Len() int
  22. Get() (item interface{}, shutdown bool)
  23. Done(item interface{})
  24. ShutDown()
  25. ShutDownWithDrain()
  26. ShuttingDown() bool
  27. }
  28. // QueueConfig specifies optional configurations to customize an Interface.
  29. type QueueConfig struct {
  30. // Name for the queue. If unnamed, the metrics will not be registered.
  31. Name string
  32. // MetricsProvider optionally allows specifying a metrics provider to use for the queue
  33. // instead of the global provider.
  34. MetricsProvider MetricsProvider
  35. // Clock ability to inject real or fake clock for testing purposes.
  36. Clock clock.WithTicker
  37. }
  38. // New constructs a new work queue (see the package comment).
  39. func New() *Type {
  40. return NewWithConfig(QueueConfig{
  41. Name: "",
  42. })
  43. }
  44. // NewWithConfig constructs a new workqueue with ability to
  45. // customize different properties.
  46. func NewWithConfig(config QueueConfig) *Type {
  47. return newQueueWithConfig(config, defaultUnfinishedWorkUpdatePeriod)
  48. }
  49. // NewNamed creates a new named queue.
  50. // Deprecated: Use NewWithConfig instead.
  51. func NewNamed(name string) *Type {
  52. return NewWithConfig(QueueConfig{
  53. Name: name,
  54. })
  55. }
  56. // newQueueWithConfig constructs a new named workqueue
  57. // with the ability to customize different properties for testing purposes
  58. func newQueueWithConfig(config QueueConfig, updatePeriod time.Duration) *Type {
  59. var metricsFactory *queueMetricsFactory
  60. if config.MetricsProvider != nil {
  61. metricsFactory = &queueMetricsFactory{
  62. metricsProvider: config.MetricsProvider,
  63. }
  64. } else {
  65. metricsFactory = &globalMetricsFactory
  66. }
  67. if config.Clock == nil {
  68. config.Clock = clock.RealClock{}
  69. }
  70. return newQueue(
  71. config.Clock,
  72. metricsFactory.newQueueMetrics(config.Name, config.Clock),
  73. updatePeriod,
  74. )
  75. }
  76. func newQueue(c clock.WithTicker, metrics queueMetrics, updatePeriod time.Duration) *Type {
  77. t := &Type{
  78. clock: c,
  79. dirty: set{},
  80. processing: set{},
  81. cond: sync.NewCond(&sync.Mutex{}),
  82. metrics: metrics,
  83. unfinishedWorkUpdatePeriod: updatePeriod,
  84. }
  85. // Don't start the goroutine for a type of noMetrics so we don't consume
  86. // resources unnecessarily
  87. if _, ok := metrics.(noMetrics); !ok {
  88. go t.updateUnfinishedWorkLoop()
  89. }
  90. return t
  91. }
  92. const defaultUnfinishedWorkUpdatePeriod = 500 * time.Millisecond
  93. // Type is a work queue (see the package comment).
  94. type Type struct {
  95. // queue defines the order in which we will work on items. Every
  96. // element of queue should be in the dirty set and not in the
  97. // processing set.
  98. queue []t
  99. // dirty defines all of the items that need to be processed.
  100. dirty set
  101. // Things that are currently being processed are in the processing set.
  102. // These things may be simultaneously in the dirty set. When we finish
  103. // processing something and remove it from this set, we'll check if
  104. // it's in the dirty set, and if so, add it to the queue.
  105. processing set
  106. cond *sync.Cond
  107. shuttingDown bool
  108. drain bool
  109. metrics queueMetrics
  110. unfinishedWorkUpdatePeriod time.Duration
  111. clock clock.WithTicker
  112. }
  113. type empty struct{}
  114. type t interface{}
  115. type set map[t]empty
  116. func (s set) has(item t) bool {
  117. _, exists := s[item]
  118. return exists
  119. }
  120. func (s set) insert(item t) {
  121. s[item] = empty{}
  122. }
  123. func (s set) delete(item t) {
  124. delete(s, item)
  125. }
  126. func (s set) len() int {
  127. return len(s)
  128. }
  129. // Add marks item as needing processing.
  130. func (q *Type) Add(item interface{}) {
  131. q.cond.L.Lock()
  132. defer q.cond.L.Unlock()
  133. if q.shuttingDown {
  134. return
  135. }
  136. if q.dirty.has(item) {
  137. return
  138. }
  139. q.metrics.add(item)
  140. q.dirty.insert(item)
  141. if q.processing.has(item) {
  142. return
  143. }
  144. q.queue = append(q.queue, item)
  145. q.cond.Signal()
  146. }
  147. // Len returns the current queue length, for informational purposes only. You
  148. // shouldn't e.g. gate a call to Add() or Get() on Len() being a particular
  149. // value, that can't be synchronized properly.
  150. func (q *Type) Len() int {
  151. q.cond.L.Lock()
  152. defer q.cond.L.Unlock()
  153. return len(q.queue)
  154. }
  155. // Get blocks until it can return an item to be processed. If shutdown = true,
  156. // the caller should end their goroutine. You must call Done with item when you
  157. // have finished processing it.
  158. func (q *Type) Get() (item interface{}, shutdown bool) {
  159. q.cond.L.Lock()
  160. defer q.cond.L.Unlock()
  161. for len(q.queue) == 0 && !q.shuttingDown {
  162. q.cond.Wait()
  163. }
  164. if len(q.queue) == 0 {
  165. // We must be shutting down.
  166. return nil, true
  167. }
  168. item = q.queue[0]
  169. // The underlying array still exists and reference this object, so the object will not be garbage collected.
  170. q.queue[0] = nil
  171. q.queue = q.queue[1:]
  172. q.metrics.get(item)
  173. q.processing.insert(item)
  174. q.dirty.delete(item)
  175. return item, false
  176. }
  177. // Done marks item as done processing, and if it has been marked as dirty again
  178. // while it was being processed, it will be re-added to the queue for
  179. // re-processing.
  180. func (q *Type) Done(item interface{}) {
  181. q.cond.L.Lock()
  182. defer q.cond.L.Unlock()
  183. q.metrics.done(item)
  184. q.processing.delete(item)
  185. if q.dirty.has(item) {
  186. q.queue = append(q.queue, item)
  187. q.cond.Signal()
  188. } else if q.processing.len() == 0 {
  189. q.cond.Signal()
  190. }
  191. }
  192. // ShutDown will cause q to ignore all new items added to it and
  193. // immediately instruct the worker goroutines to exit.
  194. func (q *Type) ShutDown() {
  195. q.setDrain(false)
  196. q.shutdown()
  197. }
  198. // ShutDownWithDrain will cause q to ignore all new items added to it. As soon
  199. // as the worker goroutines have "drained", i.e: finished processing and called
  200. // Done on all existing items in the queue; they will be instructed to exit and
  201. // ShutDownWithDrain will return. Hence: a strict requirement for using this is;
  202. // your workers must ensure that Done is called on all items in the queue once
  203. // the shut down has been initiated, if that is not the case: this will block
  204. // indefinitely. It is, however, safe to call ShutDown after having called
  205. // ShutDownWithDrain, as to force the queue shut down to terminate immediately
  206. // without waiting for the drainage.
  207. func (q *Type) ShutDownWithDrain() {
  208. q.setDrain(true)
  209. q.shutdown()
  210. for q.isProcessing() && q.shouldDrain() {
  211. q.waitForProcessing()
  212. }
  213. }
  214. // isProcessing indicates if there are still items on the work queue being
  215. // processed. It's used to drain the work queue on an eventual shutdown.
  216. func (q *Type) isProcessing() bool {
  217. q.cond.L.Lock()
  218. defer q.cond.L.Unlock()
  219. return q.processing.len() != 0
  220. }
  221. // waitForProcessing waits for the worker goroutines to finish processing items
  222. // and call Done on them.
  223. func (q *Type) waitForProcessing() {
  224. q.cond.L.Lock()
  225. defer q.cond.L.Unlock()
  226. // Ensure that we do not wait on a queue which is already empty, as that
  227. // could result in waiting for Done to be called on items in an empty queue
  228. // which has already been shut down, which will result in waiting
  229. // indefinitely.
  230. if q.processing.len() == 0 {
  231. return
  232. }
  233. q.cond.Wait()
  234. }
  235. func (q *Type) setDrain(shouldDrain bool) {
  236. q.cond.L.Lock()
  237. defer q.cond.L.Unlock()
  238. q.drain = shouldDrain
  239. }
  240. func (q *Type) shouldDrain() bool {
  241. q.cond.L.Lock()
  242. defer q.cond.L.Unlock()
  243. return q.drain
  244. }
  245. func (q *Type) shutdown() {
  246. q.cond.L.Lock()
  247. defer q.cond.L.Unlock()
  248. q.shuttingDown = true
  249. q.cond.Broadcast()
  250. }
  251. func (q *Type) ShuttingDown() bool {
  252. q.cond.L.Lock()
  253. defer q.cond.L.Unlock()
  254. return q.shuttingDown
  255. }
  256. func (q *Type) updateUnfinishedWorkLoop() {
  257. t := q.clock.NewTicker(q.unfinishedWorkUpdatePeriod)
  258. defer t.Stop()
  259. for range t.C() {
  260. if !func() bool {
  261. q.cond.L.Lock()
  262. defer q.cond.L.Unlock()
  263. if !q.shuttingDown {
  264. q.metrics.updateUnfinishedWork()
  265. return true
  266. }
  267. return false
  268. }() {
  269. return
  270. }
  271. }
  272. }