|
|
|
@ -10,10 +10,10 @@ import (
|
|
|
|
|
"net/http"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
"sync"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/andybalholm/brotli"
|
|
|
|
|
sync "github.com/sasha-s/go-deadlock"
|
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
|
|
|
|
|
|
"github.com/gorilla/websocket"
|
|
|
|
@ -54,7 +54,7 @@ type HandlerFunc func(msg *Message) error
|
|
|
|
|
// Topic ...
|
|
|
|
|
type Topic struct {
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
Sequence uint64 `json:"seq"`
|
|
|
|
|
Sequence int `json:"seq"`
|
|
|
|
|
Created time.Time `json:"created"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -64,19 +64,32 @@ func (t *Topic) String() string {
|
|
|
|
|
|
|
|
|
|
// Message ...
|
|
|
|
|
type Message struct {
|
|
|
|
|
ID uint64 `json:"id"`
|
|
|
|
|
ID int `json:"id"`
|
|
|
|
|
Topic *Topic `json:"topic"`
|
|
|
|
|
Payload []byte `json:"payload"`
|
|
|
|
|
Created time.Time `json:"created"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ListenerOptions ...
|
|
|
|
|
type ListenerOptions struct {
|
|
|
|
|
// SubscribeOption ...
|
|
|
|
|
type SubscribeOption func(*SubscriberOptions)
|
|
|
|
|
|
|
|
|
|
// SubscriberOptions ...
|
|
|
|
|
type SubscriberOptions struct {
|
|
|
|
|
Index int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WithIndex sets the index to start subscribing from
|
|
|
|
|
func WithIndex(index int) SubscribeOption {
|
|
|
|
|
return func(o *SubscriberOptions) { o.Index = index }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SubscribersConfig ...
|
|
|
|
|
type SubscriberConfig struct {
|
|
|
|
|
BufferLength int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Listeners ...
|
|
|
|
|
type Listeners struct {
|
|
|
|
|
// Subscribers ...
|
|
|
|
|
type Subscribers struct {
|
|
|
|
|
sync.RWMutex
|
|
|
|
|
|
|
|
|
|
buflen int
|
|
|
|
@ -85,19 +98,19 @@ type Listeners struct {
|
|
|
|
|
chs map[string]chan Message
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewListeners ...
|
|
|
|
|
func NewListeners(options *ListenerOptions) *Listeners {
|
|
|
|
|
// NewSubscribers ...
|
|
|
|
|
func NewSubscribers(config *SubscriberConfig) *Subscribers {
|
|
|
|
|
var (
|
|
|
|
|
bufferLength int
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if options != nil {
|
|
|
|
|
bufferLength = options.BufferLength
|
|
|
|
|
if config != nil {
|
|
|
|
|
bufferLength = config.BufferLength
|
|
|
|
|
} else {
|
|
|
|
|
bufferLength = DefaultBufferLength
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &Listeners{
|
|
|
|
|
return &Subscribers{
|
|
|
|
|
buflen: bufferLength,
|
|
|
|
|
|
|
|
|
|
ids: make(map[string]bool),
|
|
|
|
@ -105,50 +118,57 @@ func NewListeners(options *ListenerOptions) *Listeners {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Length ...
|
|
|
|
|
func (ls *Listeners) Length() int {
|
|
|
|
|
ls.RLock()
|
|
|
|
|
defer ls.RUnlock()
|
|
|
|
|
// Len ...
|
|
|
|
|
func (subs *Subscribers) Len() int {
|
|
|
|
|
subs.RLock()
|
|
|
|
|
defer subs.RUnlock()
|
|
|
|
|
|
|
|
|
|
return len(ls.ids)
|
|
|
|
|
return len(subs.ids)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add ...
|
|
|
|
|
func (ls *Listeners) Add(id string) chan Message {
|
|
|
|
|
ls.Lock()
|
|
|
|
|
defer ls.Unlock()
|
|
|
|
|
// AddSubscriber ...
|
|
|
|
|
func (subs *Subscribers) AddSubscriber(id string) chan Message {
|
|
|
|
|
subs.Lock()
|
|
|
|
|
defer subs.Unlock()
|
|
|
|
|
|
|
|
|
|
if subs.buflen <= 0 {
|
|
|
|
|
log.Fatal("subscriber buflen <= 0")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ch := make(chan Message, subs.buflen)
|
|
|
|
|
|
|
|
|
|
subs.ids[id] = true
|
|
|
|
|
subs.chs[id] = ch
|
|
|
|
|
|
|
|
|
|
ls.ids[id] = true
|
|
|
|
|
ls.chs[id] = make(chan Message, ls.buflen)
|
|
|
|
|
return ls.chs[id]
|
|
|
|
|
return ch
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove ...
|
|
|
|
|
func (ls *Listeners) Remove(id string) {
|
|
|
|
|
ls.Lock()
|
|
|
|
|
defer ls.Unlock()
|
|
|
|
|
// RemoveSubscriber ...
|
|
|
|
|
func (subs *Subscribers) RemoveSubscriber(id string) {
|
|
|
|
|
subs.Lock()
|
|
|
|
|
defer subs.Unlock()
|
|
|
|
|
|
|
|
|
|
delete(ls.ids, id)
|
|
|
|
|
delete(subs.ids, id)
|
|
|
|
|
|
|
|
|
|
close(ls.chs[id])
|
|
|
|
|
delete(ls.chs, id)
|
|
|
|
|
close(subs.chs[id])
|
|
|
|
|
delete(subs.chs, id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Exists ...
|
|
|
|
|
func (ls *Listeners) Exists(id string) bool {
|
|
|
|
|
ls.RLock()
|
|
|
|
|
defer ls.RUnlock()
|
|
|
|
|
// HasSubscriber ...
|
|
|
|
|
func (subs *Subscribers) HasSubscriber(id string) bool {
|
|
|
|
|
subs.RLock()
|
|
|
|
|
defer subs.RUnlock()
|
|
|
|
|
|
|
|
|
|
_, ok := ls.ids[id]
|
|
|
|
|
_, ok := subs.ids[id]
|
|
|
|
|
return ok
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get ...
|
|
|
|
|
func (ls *Listeners) Get(id string) (chan Message, bool) {
|
|
|
|
|
ls.RLock()
|
|
|
|
|
defer ls.RUnlock()
|
|
|
|
|
// GetSubscriber ...
|
|
|
|
|
func (subs *Subscribers) GetSubscriber(id string) (chan Message, bool) {
|
|
|
|
|
subs.RLock()
|
|
|
|
|
defer subs.RUnlock()
|
|
|
|
|
|
|
|
|
|
ch, ok := ls.chs[id]
|
|
|
|
|
ch, ok := subs.chs[id]
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil, false
|
|
|
|
|
}
|
|
|
|
@ -156,12 +176,12 @@ func (ls *Listeners) Get(id string) (chan Message, bool) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NotifyAll ...
|
|
|
|
|
func (ls *Listeners) NotifyAll(message Message) int {
|
|
|
|
|
ls.RLock()
|
|
|
|
|
defer ls.RUnlock()
|
|
|
|
|
func (subs *Subscribers) NotifyAll(message Message) int {
|
|
|
|
|
subs.RLock()
|
|
|
|
|
defer subs.RUnlock()
|
|
|
|
|
|
|
|
|
|
i := 0
|
|
|
|
|
for id, ch := range ls.chs {
|
|
|
|
|
for id, ch := range subs.chs {
|
|
|
|
|
select {
|
|
|
|
|
case ch <- message:
|
|
|
|
|
i++
|
|
|
|
@ -193,9 +213,9 @@ type MessageBus struct {
|
|
|
|
|
maxQueueSize int
|
|
|
|
|
maxPayloadSize int
|
|
|
|
|
|
|
|
|
|
topics map[string]*Topic
|
|
|
|
|
queues map[*Topic]*Queue
|
|
|
|
|
listeners map[*Topic]*Listeners
|
|
|
|
|
topics map[string]*Topic
|
|
|
|
|
queues map[*Topic]*Queue
|
|
|
|
|
subscribers map[*Topic]*Subscribers
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// New ...
|
|
|
|
@ -312,9 +332,9 @@ func New(options *Options) *MessageBus {
|
|
|
|
|
maxQueueSize: maxQueueSize,
|
|
|
|
|
maxPayloadSize: maxPayloadSize,
|
|
|
|
|
|
|
|
|
|
topics: make(map[string]*Topic),
|
|
|
|
|
queues: make(map[*Topic]*Queue),
|
|
|
|
|
listeners: make(map[*Topic]*Listeners),
|
|
|
|
|
topics: make(map[string]*Topic),
|
|
|
|
|
queues: make(map[*Topic]*Queue),
|
|
|
|
|
subscribers: make(map[*Topic]*Subscribers),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -406,20 +426,22 @@ func (mb *MessageBus) Get(t *Topic) (Message, bool) {
|
|
|
|
|
|
|
|
|
|
// publish ...
|
|
|
|
|
func (mb *MessageBus) publish(message Message) {
|
|
|
|
|
ls, ok := mb.listeners[message.Topic]
|
|
|
|
|
subs, ok := mb.subscribers[message.Topic]
|
|
|
|
|
if !ok {
|
|
|
|
|
log.Debugf("no subscribers for %s", message.Topic.Name)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
n := ls.NotifyAll(message)
|
|
|
|
|
if n != ls.Length() && mb.metrics != nil {
|
|
|
|
|
log.Warnf("%d/%d subscribers notified", n, ls.Length())
|
|
|
|
|
mb.metrics.Counter("bus", "dropped").Add(float64(ls.Length() - n))
|
|
|
|
|
log.Debug("notifying subscribers")
|
|
|
|
|
n := subs.NotifyAll(message)
|
|
|
|
|
if n != subs.Len() && mb.metrics != nil {
|
|
|
|
|
log.Warnf("%d/%d subscribers notified", n, subs.Len())
|
|
|
|
|
mb.metrics.Counter("bus", "dropped").Add(float64(subs.Len() - n))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Subscribe ...
|
|
|
|
|
func (mb *MessageBus) Subscribe(id, topic string) chan Message {
|
|
|
|
|
func (mb *MessageBus) Subscribe(id, topic string, opts ...SubscribeOption) chan Message {
|
|
|
|
|
mb.Lock()
|
|
|
|
|
defer mb.Unlock()
|
|
|
|
|
|
|
|
|
@ -429,15 +451,16 @@ func (mb *MessageBus) Subscribe(id, topic string) chan Message {
|
|
|
|
|
mb.topics[topic] = t
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ls, ok := mb.listeners[t]
|
|
|
|
|
subs, ok := mb.subscribers[t]
|
|
|
|
|
if !ok {
|
|
|
|
|
ls = NewListeners(&ListenerOptions{BufferLength: mb.bufferLength})
|
|
|
|
|
mb.listeners[t] = ls
|
|
|
|
|
subs = NewSubscribers(&SubscriberConfig{BufferLength: mb.bufferLength})
|
|
|
|
|
mb.subscribers[t] = subs
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ls.Exists(id) {
|
|
|
|
|
if subs.HasSubscriber(id) {
|
|
|
|
|
// Already verified the listener exists
|
|
|
|
|
ch, _ := ls.Get(id)
|
|
|
|
|
log.Debugf("already have subscriber %s", id)
|
|
|
|
|
ch, _ := subs.GetSubscriber(id)
|
|
|
|
|
return ch
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -445,7 +468,34 @@ func (mb *MessageBus) Subscribe(id, topic string) chan Message {
|
|
|
|
|
mb.metrics.Gauge("bus", "subscribers").Inc()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ls.Add(id)
|
|
|
|
|
o := &SubscriberOptions{}
|
|
|
|
|
for _, opt := range opts {
|
|
|
|
|
opt(o)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ch := subs.AddSubscriber(id)
|
|
|
|
|
q, ok := mb.queues[t]
|
|
|
|
|
if !ok {
|
|
|
|
|
log.Debug("nothing in queue, returning ch")
|
|
|
|
|
return ch
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if o.Index >= 0 && o.Index <= q.Len() {
|
|
|
|
|
var n int
|
|
|
|
|
log.Debugf("subscriber wants to start from %d", o.Index)
|
|
|
|
|
q.ForEach(func(item interface{}) error {
|
|
|
|
|
msg := item.(Message)
|
|
|
|
|
log.Debugf("found #%v", msg)
|
|
|
|
|
if msg.ID >= o.Index {
|
|
|
|
|
ch <- msg
|
|
|
|
|
n++
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
})
|
|
|
|
|
log.Debugf("published %d messages", n)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ch
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Unsubscribe ...
|
|
|
|
@ -458,14 +508,14 @@ func (mb *MessageBus) Unsubscribe(id, topic string) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ls, ok := mb.listeners[t]
|
|
|
|
|
subs, ok := mb.subscribers[t]
|
|
|
|
|
if !ok {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ls.Exists(id) {
|
|
|
|
|
if subs.HasSubscriber(id) {
|
|
|
|
|
// Already verified the listener exists
|
|
|
|
|
ls.Remove(id)
|
|
|
|
|
subs.RemoveSubscriber(id)
|
|
|
|
|
|
|
|
|
|
if mb.metrics != nil {
|
|
|
|
|
mb.metrics.Gauge("bus", "subscribers").Dec()
|
|
|
|
@ -498,10 +548,10 @@ func (mb *MessageBus) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
topic := strings.TrimLeft(r.URL.Path, "/")
|
|
|
|
|
topic = strings.TrimRight(topic, "/")
|
|
|
|
|
topic := strings.Trim(r.URL.Path, "/")
|
|
|
|
|
|
|
|
|
|
t := mb.NewTopic(topic)
|
|
|
|
|
log.Debugf("request for topic %#v", t.Name)
|
|
|
|
|
|
|
|
|
|
switch r.Method {
|
|
|
|
|
case "POST", "PUT":
|
|
|
|
@ -560,7 +610,11 @@ func (mb *MessageBus) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NewClient(conn, t, mb).Start()
|
|
|
|
|
i := SafeParseInt(r.URL.Query().Get("index"), -1)
|
|
|
|
|
|
|
|
|
|
log.Debugf("new subscriber for %s from %s", t.Name, r.RemoteAddr)
|
|
|
|
|
|
|
|
|
|
NewClient(conn, t, i, mb).Start()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -591,6 +645,7 @@ func (mb *MessageBus) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
type Client struct {
|
|
|
|
|
conn *websocket.Conn
|
|
|
|
|
topic *Topic
|
|
|
|
|
index int
|
|
|
|
|
bus *MessageBus
|
|
|
|
|
|
|
|
|
|
id string
|
|
|
|
@ -598,8 +653,8 @@ type Client struct {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewClient ...
|
|
|
|
|
func NewClient(conn *websocket.Conn, topic *Topic, bus *MessageBus) *Client {
|
|
|
|
|
return &Client{conn: conn, topic: topic, bus: bus}
|
|
|
|
|
func NewClient(conn *websocket.Conn, topic *Topic, index int, bus *MessageBus) *Client {
|
|
|
|
|
return &Client{conn: conn, topic: topic, index: index, bus: bus}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) readPump() {
|
|
|
|
@ -684,7 +739,7 @@ func (c *Client) writePump() {
|
|
|
|
|
// Start ...
|
|
|
|
|
func (c *Client) Start() {
|
|
|
|
|
c.id = c.conn.RemoteAddr().String()
|
|
|
|
|
c.ch = c.bus.Subscribe(c.id, c.topic.Name)
|
|
|
|
|
c.ch = c.bus.Subscribe(c.id, c.topic.Name, WithIndex(c.index))
|
|
|
|
|
|
|
|
|
|
c.conn.SetCloseHandler(func(code int, text string) error {
|
|
|
|
|
c.bus.Unsubscribe(c.id, c.topic.Name)
|
|
|
|
|