gotelem/broker.go

141 lines
2.7 KiB
Go
Raw Normal View History

2023-05-09 15:25:01 +00:00
package gotelem
2023-05-25 18:01:50 +00:00
import (
"errors"
"fmt"
"sync"
2023-05-26 20:24:51 +00:00
"golang.org/x/exp/slog"
2023-05-25 18:01:50 +00:00
)
2023-05-09 15:25:01 +00:00
type BrokerRequest struct {
Source string // the name of the sender
Msg Frame // the message to send
}
type BrokerClient struct {
Name string // the name of the client
Ch chan Frame // the channel to send frames to this client
}
type Broker struct {
subs map[string]chan Frame
publishCh chan BrokerRequest
subsCh chan BrokerClient
unsubCh chan BrokerClient
}
// Start runs the broker and sends messages to the subscribers (but not the sender)
func (b *Broker) Start() {
for {
select {
case newClient := <-b.subsCh:
b.subs[newClient.Name] = newClient.Ch
case req := <-b.publishCh:
for name, ch := range b.subs {
if name == req.Source {
continue // don't send to ourselves.
}
// a kinda-inelegant non-blocking push.
// if we can't do it, we just drop it. this should ideally never happen.
select {
case ch <- req.Msg:
default:
fmt.Printf("we dropped a packet to dest %s", name)
}
}
case clientToRemove := <-b.unsubCh:
close(b.subs[clientToRemove.Name])
delete(b.subs, clientToRemove.Name)
}
}
}
func (b *Broker) Publish(name string, msg Frame) {
breq := BrokerRequest{
Source: name,
Msg: msg,
}
b.publishCh <- breq
}
func (b *Broker) Subscribe(name string) <-chan Frame {
ch := make(chan Frame, 3)
bc := BrokerClient{
Name: name,
Ch: ch,
}
b.subsCh <- bc
return ch
}
func (b *Broker) Unsubscribe(name string) {
bc := BrokerClient{
Name: name,
}
b.unsubCh <- bc
}
2023-05-25 18:01:50 +00:00
type JBroker struct {
2023-05-26 20:24:51 +00:00
subs map[string] chan CANDumpEntry // contains the channel for each subsciber
2023-05-25 18:01:50 +00:00
2023-05-26 20:24:51 +00:00
logger *slog.Logger
2023-05-25 18:01:50 +00:00
lock sync.RWMutex
2023-05-26 20:24:51 +00:00
bufsize int // size of chan buffer in elements.
}
func NewBroker(bufsize int, logger *slog.Logger) *JBroker {
return &JBroker{
subs: make(map[string]chan CANDumpEntry),
logger: logger,
bufsize: bufsize,
}
2023-05-25 18:01:50 +00:00
}
2023-05-26 20:24:51 +00:00
func (b *JBroker) Subscribe(name string) (ch chan CANDumpEntry, err error) {
2023-05-25 18:01:50 +00:00
// get rw lock.
b.lock.Lock()
defer b.lock.Unlock()
_, ok := b.subs[name]
if ok {
return nil, errors.New("name already in use")
}
2023-05-26 20:24:51 +00:00
b.logger.Info("new subscriber", "name", name)
ch = make(chan CANDumpEntry, b.bufsize)
2023-05-25 18:01:50 +00:00
2023-05-26 22:22:44 +00:00
b.subs[name] = ch
2023-05-25 18:01:50 +00:00
return
}
func (b *JBroker) Unsubscribe(name string) {
2023-05-26 20:24:51 +00:00
// remove the channel from the map. We don't need to close it.
2023-05-25 18:01:50 +00:00
b.lock.Lock()
defer b.lock.Unlock()
delete(b.subs, name)
}
2023-05-26 20:24:51 +00:00
func (b *JBroker) Publish(sender string, message CANDumpEntry) {
b.lock.RLock()
defer b.lock.RUnlock()
for name, ch := range b.subs {
if name == sender {
continue
2023-05-25 18:01:50 +00:00
}
2023-05-26 20:24:51 +00:00
// non blocking send.
select {
case ch <- message:
b.logger.Debug("sent message", "dest", name, "src", sender)
default:
b.logger.Warn("recipient buffer full", "dest", name)
}
}
2023-05-25 18:01:50 +00:00
}