github.com/kaydxh/golang@v0.0.131/go/container/workqueue/queue.go (about)

     1  /*
     2   *Copyright (c) 2022, kaydxh
     3   *
     4   *Permission is hereby granted, free of charge, to any person obtaining a copy
     5   *of this software and associated documentation files (the "Software"), to deal
     6   *in the Software without restriction, including without limitation the rights
     7   *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     8   *copies of the Software, and to permit persons to whom the Software is
     9   *furnished to do so, subject to the following conditions:
    10   *
    11   *The above copyright notice and this permission notice shall be included in all
    12   *copies or substantial portions of the Software.
    13   *
    14   *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    15   *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    16   *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    17   *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    18   *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    19   *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    20   *SOFTWARE.
    21   */
    22  package workqueue
    23  
    24  import (
    25  	"sync"
    26  )
    27  
    28  type Interface interface {
    29  	Add(item interface{})
    30  	Len() int
    31  	Get() (item interface{}, shutdown bool)
    32  	Done(item interface{})
    33  	ShutDown()
    34  	ShuttingDown() bool
    35  }
    36  
    37  func NewQueue() *Type {
    38  	t := &Type{
    39  		dirty:      set{},
    40  		processing: set{},
    41  		cond:       sync.NewCond(&sync.Mutex{}),
    42  	}
    43  
    44  	return t
    45  }
    46  
    47  // Type is a work queue (see the package comment).
    48  type Type struct {
    49  	// queue defines the order in which we will work on items. Every
    50  	// element of queue should be in the dirty set and not in the
    51  	// processing set.
    52  	queue []t
    53  
    54  	// dirty defines all of the items that need to be processed.
    55  	dirty set
    56  
    57  	// Things that are currently being processed are in the processing set.
    58  	// These things may be simultaneously in the dirty set. When we finish
    59  	// processing something and remove it from this set, we'll check if
    60  	// it's in the dirty set, and if so, add it to the queue.
    61  	processing set
    62  
    63  	cond *sync.Cond
    64  
    65  	shuttingDown bool
    66  	drain        bool
    67  }
    68  
    69  type empty struct{}
    70  type t interface{}
    71  type set map[t]empty
    72  
    73  func (s set) has(item t) bool {
    74  	_, exists := s[item]
    75  	return exists
    76  }
    77  
    78  func (s set) insert(item t) {
    79  	s[item] = empty{}
    80  }
    81  
    82  func (s set) delete(item t) {
    83  	delete(s, item)
    84  }
    85  
    86  func (s set) len() int {
    87  	return len(s)
    88  }
    89  
    90  // Add marks item as needing processing.
    91  func (q *Type) Add(item interface{}) {
    92  	q.cond.L.Lock()
    93  	defer q.cond.L.Unlock()
    94  	if q.shuttingDown {
    95  		return
    96  	}
    97  	if q.dirty.has(item) {
    98  		return
    99  	}
   100  
   101  	q.dirty.insert(item)
   102  	if q.processing.has(item) {
   103  		return
   104  	}
   105  
   106  	q.queue = append(q.queue, item)
   107  	q.cond.Signal()
   108  }
   109  
   110  func (q *Type) Len() int {
   111  	q.cond.L.Lock()
   112  	defer q.cond.L.Unlock()
   113  	return len(q.queue)
   114  }
   115  
   116  func (q *Type) Get() (item interface{}, shutdown bool) {
   117  	q.cond.L.Lock()
   118  	defer q.cond.L.Unlock()
   119  	for len(q.queue) == 0 && !q.shuttingDown {
   120  		q.cond.Wait()
   121  	}
   122  	if len(q.queue) == 0 {
   123  		// We must be shutting down.
   124  		return nil, true
   125  	}
   126  
   127  	item = q.queue[0]
   128  	// The underlying array still exists and reference this object, so the object will not be garbage collected.
   129  	q.queue[0] = nil
   130  	q.queue = q.queue[1:]
   131  
   132  	q.processing.insert(item)
   133  	q.dirty.delete(item)
   134  
   135  	return item, false
   136  }
   137  
   138  func (q *Type) Done(item interface{}) {
   139  	q.cond.L.Lock()
   140  	defer q.cond.L.Unlock()
   141  
   142  	q.processing.delete(item)
   143  	if q.dirty.has(item) {
   144  		q.queue = append(q.queue, item)
   145  		q.cond.Signal()
   146  	} else if q.processing.len() == 0 {
   147  		q.cond.Signal()
   148  	}
   149  }
   150  
   151  // ShutDown will cause q to ignore all new items added to it and
   152  // immediately instruct the worker goroutines to exit.
   153  func (q *Type) ShutDown() {
   154  	q.setDrain(false)
   155  	q.shutdown()
   156  }
   157  
   158  // ShutDownWithDrain will cause q to ignore all new items added to it. As soon
   159  // as the worker goroutines have "drained", i.e: finished processing and called
   160  // Done on all existing items in the queue; they will be instructed to exit and
   161  // ShutDownWithDrain will return. Hence: a strict requirement for using this is;
   162  // your workers must ensure that Done is called on all items in the queue once
   163  // the shut down has been initiated, if that is not the case: this will block
   164  // indefinitely. It is, however, safe to call ShutDown after having called
   165  // ShutDownWithDrain, as to force the queue shut down to terminate immediately
   166  // without waiting for the drainage.
   167  func (q *Type) ShutDownWithDrain() {
   168  	q.setDrain(true)
   169  	q.shutdown()
   170  	for q.isProcessing() && q.shouldDrain() {
   171  		q.waitForProcessing()
   172  	}
   173  }
   174  
   175  // isProcessing indicates if there are still items on the work queue being
   176  // processed. It's used to drain the work queue on an eventual shutdown.
   177  func (q *Type) isProcessing() bool {
   178  	q.cond.L.Lock()
   179  	defer q.cond.L.Unlock()
   180  	return q.processing.len() != 0
   181  }
   182  
   183  // waitForProcessing waits for the worker goroutines to finish processing items
   184  // and call Done on them.
   185  func (q *Type) waitForProcessing() {
   186  	q.cond.L.Lock()
   187  	defer q.cond.L.Unlock()
   188  	// Ensure that we do not wait on a queue which is already empty, as that
   189  	// could result in waiting for Done to be called on items in an empty queue
   190  	// which has already been shut down, which will result in waiting
   191  	// indefinitely.
   192  	if q.processing.len() == 0 {
   193  		return
   194  	}
   195  	q.cond.Wait()
   196  }
   197  
   198  func (q *Type) setDrain(shouldDrain bool) {
   199  	q.cond.L.Lock()
   200  	defer q.cond.L.Unlock()
   201  	q.drain = shouldDrain
   202  }
   203  
   204  func (q *Type) shouldDrain() bool {
   205  	q.cond.L.Lock()
   206  	defer q.cond.L.Unlock()
   207  	return q.drain
   208  }
   209  
   210  func (q *Type) shutdown() {
   211  	q.cond.L.Lock()
   212  	defer q.cond.L.Unlock()
   213  	q.shuttingDown = true
   214  	q.cond.Broadcast()
   215  }
   216  
   217  func (q *Type) ShuttingDown() bool {
   218  	q.cond.L.Lock()
   219  	defer q.cond.L.Unlock()
   220  
   221  	return q.shuttingDown
   222  }