github.com/lingyao2333/mo-zero@v1.4.1/core/queue/queue.go (about)

     1  package queue
     2  
     3  import (
     4  	"runtime"
     5  	"sync"
     6  	"sync/atomic"
     7  	"time"
     8  
     9  	"github.com/lingyao2333/mo-zero/core/logx"
    10  	"github.com/lingyao2333/mo-zero/core/rescue"
    11  	"github.com/lingyao2333/mo-zero/core/stat"
    12  	"github.com/lingyao2333/mo-zero/core/threading"
    13  	"github.com/lingyao2333/mo-zero/core/timex"
    14  )
    15  
    16  const queueName = "queue"
    17  
    18  type (
    19  	// A Queue is a message queue.
    20  	Queue struct {
    21  		name                 string
    22  		metrics              *stat.Metrics
    23  		producerFactory      ProducerFactory
    24  		producerRoutineGroup *threading.RoutineGroup
    25  		consumerFactory      ConsumerFactory
    26  		consumerRoutineGroup *threading.RoutineGroup
    27  		producerCount        int
    28  		consumerCount        int
    29  		active               int32
    30  		channel              chan string
    31  		quit                 chan struct{}
    32  		listeners            []Listener
    33  		eventLock            sync.Mutex
    34  		eventChannels        []chan interface{}
    35  	}
    36  
    37  	// A Listener interface represents a listener that can be notified with queue events.
    38  	Listener interface {
    39  		OnPause()
    40  		OnResume()
    41  	}
    42  
    43  	// A Poller interface wraps the method Poll.
    44  	Poller interface {
    45  		Name() string
    46  		Poll() string
    47  	}
    48  
    49  	// A Pusher interface wraps the method Push.
    50  	Pusher interface {
    51  		Name() string
    52  		Push(string) error
    53  	}
    54  )
    55  
    56  // NewQueue returns a Queue.
    57  func NewQueue(producerFactory ProducerFactory, consumerFactory ConsumerFactory) *Queue {
    58  	q := &Queue{
    59  		metrics:              stat.NewMetrics(queueName),
    60  		producerFactory:      producerFactory,
    61  		producerRoutineGroup: threading.NewRoutineGroup(),
    62  		consumerFactory:      consumerFactory,
    63  		consumerRoutineGroup: threading.NewRoutineGroup(),
    64  		producerCount:        runtime.NumCPU(),
    65  		consumerCount:        runtime.NumCPU() << 1,
    66  		channel:              make(chan string),
    67  		quit:                 make(chan struct{}),
    68  	}
    69  	q.SetName(queueName)
    70  
    71  	return q
    72  }
    73  
    74  // AddListener adds a listener to q.
    75  func (q *Queue) AddListener(listener Listener) {
    76  	q.listeners = append(q.listeners, listener)
    77  }
    78  
    79  // Broadcast broadcasts message to all event channels.
    80  func (q *Queue) Broadcast(message interface{}) {
    81  	go func() {
    82  		q.eventLock.Lock()
    83  		defer q.eventLock.Unlock()
    84  
    85  		for _, channel := range q.eventChannels {
    86  			channel <- message
    87  		}
    88  	}()
    89  }
    90  
    91  // SetName sets the name of q.
    92  func (q *Queue) SetName(name string) {
    93  	q.name = name
    94  	q.metrics.SetName(name)
    95  }
    96  
    97  // SetNumConsumer sets the number of consumers.
    98  func (q *Queue) SetNumConsumer(count int) {
    99  	q.consumerCount = count
   100  }
   101  
   102  // SetNumProducer sets the number of producers.
   103  func (q *Queue) SetNumProducer(count int) {
   104  	q.producerCount = count
   105  }
   106  
   107  // Start starts q.
   108  func (q *Queue) Start() {
   109  	q.startProducers(q.producerCount)
   110  	q.startConsumers(q.consumerCount)
   111  
   112  	q.producerRoutineGroup.Wait()
   113  	close(q.channel)
   114  	q.consumerRoutineGroup.Wait()
   115  }
   116  
   117  // Stop stops q.
   118  func (q *Queue) Stop() {
   119  	close(q.quit)
   120  }
   121  
   122  func (q *Queue) consume(eventChan chan interface{}) {
   123  	var consumer Consumer
   124  
   125  	for {
   126  		var err error
   127  		if consumer, err = q.consumerFactory(); err != nil {
   128  			logx.Errorf("Error on creating consumer: %v", err)
   129  			time.Sleep(time.Second)
   130  		} else {
   131  			break
   132  		}
   133  	}
   134  
   135  	for {
   136  		select {
   137  		case message, ok := <-q.channel:
   138  			if ok {
   139  				q.consumeOne(consumer, message)
   140  			} else {
   141  				logx.Info("Task channel was closed, quitting consumer...")
   142  				return
   143  			}
   144  		case event := <-eventChan:
   145  			consumer.OnEvent(event)
   146  		}
   147  	}
   148  }
   149  
   150  func (q *Queue) consumeOne(consumer Consumer, message string) {
   151  	threading.RunSafe(func() {
   152  		startTime := timex.Now()
   153  		defer func() {
   154  			duration := timex.Since(startTime)
   155  			q.metrics.Add(stat.Task{
   156  				Duration: duration,
   157  			})
   158  			logx.WithDuration(duration).Infof("%s", message)
   159  		}()
   160  
   161  		if err := consumer.Consume(message); err != nil {
   162  			logx.Errorf("Error occurred while consuming %v: %v", message, err)
   163  		}
   164  	})
   165  }
   166  
   167  func (q *Queue) pause() {
   168  	for _, listener := range q.listeners {
   169  		listener.OnPause()
   170  	}
   171  }
   172  
   173  func (q *Queue) produce() {
   174  	var producer Producer
   175  
   176  	for {
   177  		var err error
   178  		if producer, err = q.producerFactory(); err != nil {
   179  			logx.Errorf("Error on creating producer: %v", err)
   180  			time.Sleep(time.Second)
   181  		} else {
   182  			break
   183  		}
   184  	}
   185  
   186  	atomic.AddInt32(&q.active, 1)
   187  	producer.AddListener(routineListener{
   188  		queue: q,
   189  	})
   190  
   191  	for {
   192  		select {
   193  		case <-q.quit:
   194  			logx.Info("Quitting producer")
   195  			return
   196  		default:
   197  			if v, ok := q.produceOne(producer); ok {
   198  				q.channel <- v
   199  			}
   200  		}
   201  	}
   202  }
   203  
   204  func (q *Queue) produceOne(producer Producer) (string, bool) {
   205  	// avoid panic quit the producer, just log it and continue
   206  	defer rescue.Recover()
   207  
   208  	return producer.Produce()
   209  }
   210  
   211  func (q *Queue) resume() {
   212  	for _, listener := range q.listeners {
   213  		listener.OnResume()
   214  	}
   215  }
   216  
   217  func (q *Queue) startConsumers(number int) {
   218  	for i := 0; i < number; i++ {
   219  		eventChan := make(chan interface{})
   220  		q.eventLock.Lock()
   221  		q.eventChannels = append(q.eventChannels, eventChan)
   222  		q.eventLock.Unlock()
   223  		q.consumerRoutineGroup.Run(func() {
   224  			q.consume(eventChan)
   225  		})
   226  	}
   227  }
   228  
   229  func (q *Queue) startProducers(number int) {
   230  	for i := 0; i < number; i++ {
   231  		q.producerRoutineGroup.Run(func() {
   232  			q.produce()
   233  		})
   234  	}
   235  }
   236  
   237  type routineListener struct {
   238  	queue *Queue
   239  }
   240  
   241  func (rl routineListener) OnProducerPause() {
   242  	if atomic.AddInt32(&rl.queue.active, -1) <= 0 {
   243  		rl.queue.pause()
   244  	}
   245  }
   246  
   247  func (rl routineListener) OnProducerResume() {
   248  	if atomic.AddInt32(&rl.queue.active, 1) == 1 {
   249  		rl.queue.resume()
   250  	}
   251  }