vitess.io/vitess@v0.16.2/go/vt/vtorc/discovery/queue.go (about)

     1  /*
     2     Copyright 2014 Outbrain Inc.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  /*
    18  
    19  package discovery manages a queue of discovery requests: an ordered
    20  queue with no duplicates.
    21  
    22  push() operation never blocks while pop() blocks on an empty queue.
    23  
    24  */
    25  
    26  package discovery
    27  
    28  import (
    29  	"sync"
    30  	"time"
    31  
    32  	"vitess.io/vitess/go/vt/log"
    33  	"vitess.io/vitess/go/vt/vtorc/config"
    34  	"vitess.io/vitess/go/vt/vtorc/inst"
    35  )
    36  
    37  // QueueMetric contains the queue's active and queued sizes
    38  type QueueMetric struct {
    39  	Active int
    40  	Queued int
    41  }
    42  
    43  // Queue contains information for managing discovery requests
    44  type Queue struct {
    45  	sync.Mutex
    46  
    47  	name         string
    48  	done         chan struct{}
    49  	queue        chan inst.InstanceKey
    50  	queuedKeys   map[inst.InstanceKey]time.Time
    51  	consumedKeys map[inst.InstanceKey]time.Time
    52  	metrics      []QueueMetric
    53  }
    54  
    55  // DiscoveryQueue contains the discovery queue which can then be accessed via an API call for monitoring.
    56  // Currently this is accessed by ContinuousDiscovery() but also from http api calls.
    57  // I may need to protect this better?
    58  var discoveryQueue map[string](*Queue)
    59  var dcLock sync.Mutex
    60  
    61  func init() {
    62  	discoveryQueue = make(map[string](*Queue))
    63  }
    64  
    65  // StopMonitoring stops monitoring all the queues
    66  func StopMonitoring() {
    67  	for _, q := range discoveryQueue {
    68  		q.stopMonitoring()
    69  	}
    70  }
    71  
    72  // CreateOrReturnQueue allows for creation of a new discovery queue or
    73  // returning a pointer to an existing one given the name.
    74  func CreateOrReturnQueue(name string) *Queue {
    75  	dcLock.Lock()
    76  	defer dcLock.Unlock()
    77  	if q, found := discoveryQueue[name]; found {
    78  		return q
    79  	}
    80  
    81  	q := &Queue{
    82  		name:         name,
    83  		queuedKeys:   make(map[inst.InstanceKey]time.Time),
    84  		consumedKeys: make(map[inst.InstanceKey]time.Time),
    85  		queue:        make(chan inst.InstanceKey, config.DiscoveryQueueCapacity),
    86  	}
    87  	go q.startMonitoring()
    88  
    89  	discoveryQueue[name] = q
    90  
    91  	return q
    92  }
    93  
    94  // monitoring queue sizes until we are told to stop
    95  func (q *Queue) startMonitoring() {
    96  	log.Infof("Queue.startMonitoring(%s)", q.name)
    97  	ticker := time.NewTicker(time.Second) // hard-coded at every second
    98  
    99  	for {
   100  		select {
   101  		case <-ticker.C: // do the periodic expiry
   102  			q.collectStatistics()
   103  		case <-q.done:
   104  			return
   105  		}
   106  	}
   107  }
   108  
   109  // Stop monitoring the queue
   110  func (q *Queue) stopMonitoring() {
   111  	q.done <- struct{}{}
   112  }
   113  
   114  // do a check of the entries in the queue, both those active and queued
   115  func (q *Queue) collectStatistics() {
   116  	q.Lock()
   117  	defer q.Unlock()
   118  
   119  	q.metrics = append(q.metrics, QueueMetric{Queued: len(q.queuedKeys), Active: len(q.consumedKeys)})
   120  
   121  	// remove old entries if we get too big
   122  	if len(q.metrics) > config.DiscoveryQueueMaxStatisticsSize {
   123  		q.metrics = q.metrics[len(q.metrics)-config.DiscoveryQueueMaxStatisticsSize:]
   124  	}
   125  }
   126  
   127  // QueueLen returns the length of the queue (channel size + queued size)
   128  func (q *Queue) QueueLen() int {
   129  	q.Lock()
   130  	defer q.Unlock()
   131  
   132  	return len(q.queue) + len(q.queuedKeys)
   133  }
   134  
   135  // Push enqueues a key if it is not on a queue and is not being
   136  // processed; silently returns otherwise.
   137  func (q *Queue) Push(key inst.InstanceKey) {
   138  	q.Lock()
   139  	defer q.Unlock()
   140  
   141  	// is it enqueued already?
   142  	if _, found := q.queuedKeys[key]; found {
   143  		return
   144  	}
   145  
   146  	// is it being processed now?
   147  	if _, found := q.consumedKeys[key]; found {
   148  		return
   149  	}
   150  
   151  	q.queuedKeys[key] = time.Now()
   152  	q.queue <- key
   153  }
   154  
   155  // Consume fetches a key to process; blocks if queue is empty.
   156  // Release must be called once after Consume.
   157  func (q *Queue) Consume() inst.InstanceKey {
   158  	q.Lock()
   159  	queue := q.queue
   160  	q.Unlock()
   161  
   162  	key := <-queue
   163  
   164  	q.Lock()
   165  	defer q.Unlock()
   166  
   167  	// alarm if have been waiting for too long
   168  	timeOnQueue := time.Since(q.queuedKeys[key])
   169  	if timeOnQueue > time.Duration(config.Config.InstancePollSeconds)*time.Second {
   170  		log.Warningf("key %v spent %.4fs waiting on a discoveryQueue", key, timeOnQueue.Seconds())
   171  	}
   172  
   173  	q.consumedKeys[key] = q.queuedKeys[key]
   174  
   175  	delete(q.queuedKeys, key)
   176  
   177  	return key
   178  }
   179  
   180  // Release removes a key from a list of being processed keys
   181  // which allows that key to be pushed into the queue again.
   182  func (q *Queue) Release(key inst.InstanceKey) {
   183  	q.Lock()
   184  	defer q.Unlock()
   185  
   186  	delete(q.consumedKeys, key)
   187  }