2020-08-11 04:38:42 +00:00
|
|
|
package events
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"strings"
|
2020-09-12 06:03:35 +00:00
|
|
|
"sync"
|
2021-01-10 01:22:39 +00:00
|
|
|
|
|
|
|
"github.com/gammazero/workerpool"
|
2020-08-11 04:38:42 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Event struct {
|
|
|
|
Data string
|
|
|
|
Topic string
|
|
|
|
}
|
|
|
|
|
|
|
|
type EventBus struct {
|
2020-09-13 03:13:59 +00:00
|
|
|
mu sync.RWMutex
|
|
|
|
pools map[string]*CallbackPool
|
2020-08-11 04:38:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func New() *EventBus {
|
|
|
|
return &EventBus{
|
2020-09-13 03:13:59 +00:00
|
|
|
pools: make(map[string]*CallbackPool),
|
2020-08-11 04:38:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Publish data to a given topic.
|
|
|
|
func (e *EventBus) Publish(topic string, data string) {
|
|
|
|
t := topic
|
|
|
|
// Some of our topics for the socket support passing a more specific namespace,
|
|
|
|
// such as "backup completed:1234" to indicate which specific backup was completed.
|
|
|
|
//
|
|
|
|
// In these cases, we still need to the send the event using the standard listener
|
|
|
|
// name of "backup completed".
|
|
|
|
if strings.Contains(topic, ":") {
|
|
|
|
parts := strings.SplitN(topic, ":", 2)
|
|
|
|
|
|
|
|
if len(parts) == 2 {
|
|
|
|
t = parts[0]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-12 16:26:17 +00:00
|
|
|
e.mu.RLock()
|
|
|
|
defer e.mu.RUnlock()
|
|
|
|
|
2020-08-11 04:38:42 +00:00
|
|
|
// Acquire a read lock and loop over all of the channels registered for the topic. This
|
|
|
|
// avoids a panic crash if the process tries to unregister the channel while this routine
|
|
|
|
// is running.
|
2020-09-13 03:13:59 +00:00
|
|
|
if cp, ok := e.pools[t]; ok {
|
|
|
|
for _, callback := range cp.callbacks {
|
|
|
|
c := *callback
|
2020-09-13 03:26:02 +00:00
|
|
|
evt := Event{Data: data, Topic: topic}
|
2020-09-13 03:13:59 +00:00
|
|
|
// Using the workerpool with one worker allows us to execute events in a FIFO manner. Running
|
|
|
|
// this using goroutines would cause things such as console output to just output in random order
|
|
|
|
// if more than one event is fired at the same time.
|
|
|
|
//
|
|
|
|
// However, the pool submission does not block the execution of this function itself, allowing
|
|
|
|
// us to call publish without blocking any of the other pathways.
|
|
|
|
//
|
|
|
|
// @see https://github.com/pterodactyl/panel/issues/2303
|
|
|
|
cp.pool.Submit(func() {
|
|
|
|
c(evt)
|
|
|
|
})
|
2020-08-11 04:38:42 +00:00
|
|
|
}
|
2020-09-12 16:26:17 +00:00
|
|
|
}
|
2020-08-11 04:38:42 +00:00
|
|
|
}
|
|
|
|
|
2020-09-12 16:26:17 +00:00
|
|
|
// Publishes a JSON message to a given topic.
|
2020-08-11 04:38:42 +00:00
|
|
|
func (e *EventBus) PublishJson(topic string, data interface{}) error {
|
|
|
|
b, err := json.Marshal(data)
|
|
|
|
if err != nil {
|
2020-11-28 23:57:10 +00:00
|
|
|
return err
|
2020-08-11 04:38:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
e.Publish(topic, string(b))
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-09-12 16:26:17 +00:00
|
|
|
// Register a callback function that will be executed each time one of the events using the topic
|
|
|
|
// name is called.
|
|
|
|
func (e *EventBus) On(topic string, callback *func(Event)) {
|
|
|
|
e.mu.Lock()
|
|
|
|
defer e.mu.Unlock()
|
2020-08-11 04:38:42 +00:00
|
|
|
|
2020-09-12 16:26:17 +00:00
|
|
|
// Check if this topic has been registered at least once for the event listener, and if
|
|
|
|
// not create an empty struct for the topic.
|
2020-09-13 03:13:59 +00:00
|
|
|
if _, exists := e.pools[topic]; !exists {
|
|
|
|
e.pools[topic] = &CallbackPool{
|
|
|
|
callbacks: make([]*func(Event), 0),
|
|
|
|
pool: workerpool.New(1),
|
|
|
|
}
|
2020-09-12 16:26:17 +00:00
|
|
|
}
|
2020-08-11 04:38:42 +00:00
|
|
|
|
2020-09-12 16:26:17 +00:00
|
|
|
// If this callback is not already registered as an event listener, go ahead and append
|
|
|
|
// it to the array of callbacks for this topic.
|
2020-09-13 03:13:59 +00:00
|
|
|
e.pools[topic].Add(callback)
|
2020-08-11 04:38:42 +00:00
|
|
|
}
|
|
|
|
|
2020-09-12 16:26:17 +00:00
|
|
|
// Removes an event listener from the bus.
|
|
|
|
func (e *EventBus) Off(topic string, callback *func(Event)) {
|
|
|
|
e.mu.Lock()
|
|
|
|
defer e.mu.Unlock()
|
2020-08-11 04:38:42 +00:00
|
|
|
|
2020-09-13 03:13:59 +00:00
|
|
|
if cp, ok := e.pools[topic]; ok {
|
|
|
|
cp.Remove(callback)
|
2020-08-11 04:38:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-13 03:13:59 +00:00
|
|
|
// Removes all of the event listeners that have been registered for any topic. Also stops the worker
|
|
|
|
// pool to close that routine.
|
|
|
|
func (e *EventBus) Destroy() {
|
2020-09-12 16:26:17 +00:00
|
|
|
e.mu.Lock()
|
|
|
|
defer e.mu.Unlock()
|
|
|
|
|
2020-09-13 03:13:59 +00:00
|
|
|
// Stop every pool that exists for a given callback topic.
|
|
|
|
for _, cp := range e.pools {
|
|
|
|
cp.pool.Stop()
|
2020-09-12 16:26:17 +00:00
|
|
|
}
|
2020-08-11 04:38:42 +00:00
|
|
|
|
2020-09-13 03:13:59 +00:00
|
|
|
e.pools = make(map[string]*CallbackPool)
|
2020-08-11 04:38:42 +00:00
|
|
|
}
|