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 }