github.com/naoina/kocha@v0.7.1-0.20171129072645-78c7a531f799/event/event.go (about)

     1  package event
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sync"
     7  )
     8  
     9  var (
    10  	// DefaultEvent is the default event and is used by AddHandler, Trigger, AddQueue, Start and Stop.
    11  	DefaultEvent = New()
    12  
    13  	// ErrDone represents that a queue is finished.
    14  	ErrDone = errors.New("queue is done")
    15  
    16  	// ErrNotExist is passed to ErrorHandler if handler not exists.
    17  	ErrNotExist = errors.New("handler not exist")
    18  )
    19  
    20  // AddHandler is shorthand of the DefaultEvent.AddHandler.
    21  func AddHandler(name string, queueName string, handler func(args ...interface{}) error) error {
    22  	return DefaultEvent.AddHandler(name, queueName, handler)
    23  }
    24  
    25  // Trigger is shorthand of the DefaultEvent.Trigger.
    26  func Trigger(name string, args ...interface{}) error {
    27  	return DefaultEvent.Trigger(name, args...)
    28  }
    29  
    30  // RegisterQueue is shorthand of the DefaultEvent.RegisterQueue.
    31  func RegisterQueue(name string, queue Queue) error {
    32  	return DefaultEvent.RegisterQueue(name, queue)
    33  }
    34  
    35  // Start is shorthand of the DefaultEvent.Start.
    36  func Start() {
    37  	DefaultEvent.Start()
    38  }
    39  
    40  // Stop is shorthand of the DefaultEvent.Stop.
    41  func Stop() {
    42  	DefaultEvent.Stop()
    43  }
    44  
    45  // Event represents an Event.
    46  type Event struct {
    47  	// ErrorHandler is the error handler.
    48  	// If you want to use your own error handler, set ErrorHandler.
    49  	ErrorHandler func(err interface{})
    50  
    51  	workersPerQueue int
    52  	queues          map[string]Queue
    53  	handlerQueues   map[string]map[string][]handlerFunc
    54  	workers         []*worker
    55  	wg              struct{ enqueue, dequeue sync.WaitGroup }
    56  }
    57  
    58  // New returns a new Event.
    59  func New() *Event {
    60  	return &Event{
    61  		workersPerQueue: 1,
    62  		queues:          make(map[string]Queue),
    63  		handlerQueues:   make(map[string]map[string][]handlerFunc),
    64  		wg:              struct{ enqueue, dequeue sync.WaitGroup }{},
    65  	}
    66  }
    67  
    68  // AddHandler adds handlers that related to name and queue.
    69  // The name is an event name such as "log.error" that will be used for Trigger.
    70  // The queueName is a name of queue registered by RegisterQueue in advance.
    71  // If you add handler by name that has already been added, handler will associated
    72  // to that name additionally.
    73  // If queue of queueName still hasn't been registered, it returns error.
    74  func (e *Event) AddHandler(name string, queueName string, handler func(args ...interface{}) error) error {
    75  	queue := e.queues[queueName]
    76  	if queue == nil {
    77  		return fmt.Errorf("kocha: event: queue `%s' isn't registered", queueName)
    78  	}
    79  	if _, exist := e.handlerQueues[name]; !exist {
    80  		e.handlerQueues[name] = make(map[string][]handlerFunc)
    81  	}
    82  	hq := e.handlerQueues[name]
    83  	hq[queueName] = append(hq[queueName], handler)
    84  	return nil
    85  }
    86  
    87  // Trigger emits the event.
    88  // The name is an event name. It must be added in advance using AddHandler.
    89  // If Trigger called by not added name, it returns error.
    90  // If args are given, they will be passed to handlers added by AddHandler.
    91  func (e *Event) Trigger(name string, args ...interface{}) error {
    92  	hq, exist := e.handlerQueues[name]
    93  	if !exist {
    94  		return fmt.Errorf("kocha: event: handler `%s' isn't added", name)
    95  	}
    96  	e.triggerAll(hq, name, args...)
    97  	return nil
    98  }
    99  
   100  // RegisterQueue makes a background queue available by the provided name.
   101  // If queue is already registerd or if queue nil, it panics.
   102  func (e *Event) RegisterQueue(name string, queue Queue) error {
   103  	if queue == nil {
   104  		return fmt.Errorf("kocha: event: Register queue is nil")
   105  	}
   106  	if _, exist := e.queues[name]; exist {
   107  		return fmt.Errorf("kocha: event: Register queue `%s' is already registered", name)
   108  	}
   109  	e.queues[name] = queue
   110  	return nil
   111  }
   112  
   113  func (e *Event) triggerAll(hq map[string][]handlerFunc, name string, args ...interface{}) {
   114  	e.wg.enqueue.Add(len(hq))
   115  	for queueName := range hq {
   116  		queue := e.queues[queueName]
   117  		go func() {
   118  			defer e.wg.enqueue.Done()
   119  			defer func() {
   120  				if err := recover(); err != nil {
   121  					if e.ErrorHandler != nil {
   122  						e.ErrorHandler(err)
   123  					}
   124  				}
   125  			}()
   126  			if err := e.enqueue(queue, payload{name, args}); err != nil {
   127  				panic(err)
   128  			}
   129  		}()
   130  	}
   131  }
   132  
   133  // alias.
   134  type handlerFunc func(args ...interface{}) error
   135  
   136  func (e *Event) enqueue(queue Queue, pld payload) error {
   137  	var data string
   138  	if err := pld.encode(&data); err != nil {
   139  		return err
   140  	}
   141  	return queue.Enqueue(data)
   142  }
   143  
   144  // Start starts background event workers.
   145  // By default, workers per queue is 1. To set the workers per queue, use
   146  // SetWorkersPerQueue before Start calls.
   147  func (e *Event) Start() {
   148  	for name, queue := range e.queues {
   149  		for i := 0; i < e.workersPerQueue; i++ {
   150  			worker := e.newWorker(name, queue.New(e.workersPerQueue))
   151  			e.workers = append(e.workers, worker)
   152  			go worker.start()
   153  		}
   154  	}
   155  }
   156  
   157  // SetWorkersPerQueue sets the number of workers per queue.
   158  // It must be called before Start calls.
   159  func (e *Event) SetWorkersPerQueue(n int) {
   160  	if n < 1 {
   161  		n = 1
   162  	}
   163  	e.workersPerQueue = n
   164  }
   165  
   166  // Stop wait for all workers to complete.
   167  func (e *Event) Stop() {
   168  	e.wg.enqueue.Wait()
   169  	defer func() {
   170  		e.workers = nil
   171  	}()
   172  	defer e.wg.dequeue.Wait()
   173  	for _, worker := range e.workers {
   174  		worker.stop()
   175  	}
   176  }
   177  
   178  type worker struct {
   179  	queueName string
   180  	queue     Queue
   181  	e         *Event
   182  }
   183  
   184  func (e *Event) newWorker(queueName string, queue Queue) *worker {
   185  	return &worker{
   186  		queueName: queueName,
   187  		queue:     queue,
   188  		e:         e,
   189  	}
   190  }
   191  
   192  func (w *worker) start() {
   193  	var done bool
   194  	for !done {
   195  		func() {
   196  			defer func() {
   197  				if err := recover(); err != nil {
   198  					if w.e.ErrorHandler != nil {
   199  						w.e.ErrorHandler(err)
   200  					}
   201  				}
   202  			}()
   203  			if err := w.run(); err != nil {
   204  				if err == ErrDone {
   205  					done = true
   206  					return
   207  				}
   208  				panic(err)
   209  			}
   210  		}()
   211  	}
   212  }
   213  
   214  func (w *worker) run() (err error) {
   215  	w.e.wg.dequeue.Add(1)
   216  	defer w.e.wg.dequeue.Done()
   217  	pld, err := w.dequeue()
   218  	if err != nil {
   219  		return err
   220  	}
   221  	hq, exist := w.e.handlerQueues[pld.Name]
   222  	if !exist {
   223  		return ErrNotExist
   224  	}
   225  	w.runAll(hq, pld)
   226  	return nil
   227  }
   228  
   229  func (w *worker) runAll(hq map[string][]handlerFunc, pld payload) {
   230  	for queueName, handlers := range hq {
   231  		if w.queueName != queueName {
   232  			continue
   233  		}
   234  		w.e.wg.dequeue.Add(len(handlers))
   235  		for _, h := range handlers {
   236  			go func(handler handlerFunc) {
   237  				defer w.e.wg.dequeue.Done()
   238  				if err := handler(pld.Args...); err != nil {
   239  					if w.e.ErrorHandler != nil {
   240  						w.e.ErrorHandler(err)
   241  					}
   242  				}
   243  			}(h)
   244  		}
   245  	}
   246  }
   247  
   248  func (w *worker) dequeue() (pld payload, err error) {
   249  	data, err := w.queue.Dequeue()
   250  	if err != nil {
   251  		return pld, err
   252  	}
   253  	if err := pld.decode(data); err != nil {
   254  		return pld, err
   255  	}
   256  	return pld, nil
   257  }
   258  
   259  func (w *worker) stop() {
   260  	w.queue.Stop()
   261  }
   262  
   263  // Queue is the interface that must be implemeted by background event queue.
   264  type Queue interface {
   265  	// New returns a new Queue to launch the workers.
   266  	// You can use an argument n as a hint when you create a new queue.
   267  	// n is the number of workers per queue.
   268  	New(n int) Queue
   269  
   270  	// Enqueue add data to the queue.
   271  	Enqueue(data string) error
   272  
   273  	// Dequeue returns the data that fetch from the queue.
   274  	// It will return ErrDone as err when Stop is called.
   275  	Dequeue() (data string, err error)
   276  
   277  	// Stop wait for Enqueue and/or Dequeue to complete then will stop a queue.
   278  	Stop()
   279  }