test: EventPublisher publish accepted
This commit is contained in:
+4
-14
@@ -124,9 +124,8 @@ func NewEmbassy(
|
|||||||
e.logger = slog.New(cfg.handler).With(slog.Any("component", comp))
|
e.logger = slog.New(cfg.handler).With(slog.Any("component", comp))
|
||||||
}
|
}
|
||||||
|
|
||||||
e.wg.Add(2)
|
e.wg.Go(e.routeEvents)
|
||||||
go e.routeEvents()
|
e.wg.Go(e.routeInbox)
|
||||||
go e.routeInbox()
|
|
||||||
|
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
@@ -237,8 +236,6 @@ func (e *Embassy) unsubscribeInboxLock(url string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Embassy) routeEvents() {
|
func (e *Embassy) routeEvents() {
|
||||||
defer e.wg.Done()
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-e.ctx.Done():
|
case <-e.ctx.Done():
|
||||||
@@ -273,8 +270,6 @@ func (e *Embassy) routeEvents() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Embassy) routeInbox() {
|
func (e *Embassy) routeInbox() {
|
||||||
defer e.wg.Done()
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-e.ctx.Done():
|
case <-e.ctx.Done():
|
||||||
@@ -363,9 +358,8 @@ func newEnvoy(
|
|||||||
e.logger = slog.New(e.handler).With(slog.Any("component", comp))
|
e.logger = slog.New(e.handler).With(slog.Any("component", comp))
|
||||||
}
|
}
|
||||||
|
|
||||||
e.wg.Add(2)
|
e.wg.Go(e.publishEvents)
|
||||||
go e.publishEvents()
|
e.wg.Go(e.routeInbox)
|
||||||
go e.routeInbox()
|
|
||||||
|
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
@@ -440,8 +434,6 @@ func (e *Envoy) SubscribeInbox(labels []string) <-chan InboxMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Envoy) publishEvents() {
|
func (e *Envoy) publishEvents() {
|
||||||
defer e.wg.Done()
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-e.ctx.Done():
|
case <-e.ctx.Done():
|
||||||
@@ -473,8 +465,6 @@ func (e *Envoy) publishEvents() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Envoy) routeInbox() {
|
func (e *Envoy) routeInbox() {
|
||||||
defer e.wg.Done()
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-e.ctx.Done():
|
case <-e.ctx.Done():
|
||||||
|
|||||||
+138
-39
@@ -2,8 +2,11 @@ package prism
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.wisehodl.dev/jay/go-roots-ws"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@@ -74,33 +77,82 @@ type EventPublisher struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewEventPublisher(e *Envoy) *EventPublisher {
|
func NewEventPublisher(e *Envoy) *EventPublisher {
|
||||||
// SubscribeInbox([]string{"OK"})
|
ctx, cancel := context.WithCancel(e.Context())
|
||||||
// SubscribeEvents()
|
p := &EventPublisher{
|
||||||
// context.WithCancel from e.Context()
|
envoy: e,
|
||||||
// make pending map
|
pending: make(map[string]*pendingEntry),
|
||||||
// wg.Add(2); go p.routeInbox(); go p.handleEvents()
|
inbox: e.SubscribeInbox([]string{"OK"}),
|
||||||
return nil
|
events: e.SubscribeEvents(),
|
||||||
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
|
}
|
||||||
|
p.wg.Go(p.routeInbox)
|
||||||
|
p.wg.Go(p.handleEvents)
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *EventPublisher) Publish(eventID string, eventJSON []byte, timeout time.Duration) (bool, string, error) {
|
func (p *EventPublisher) Publish(eventID string, eventJSON []byte, timeout time.Duration) (bool, string, error) {
|
||||||
// build pendingEntry with result chan (buffered 1) and sync.Once
|
entry := &pendingEntry{
|
||||||
// compute deadline; create timer that fires deliver(timeout error) via once
|
eventID: eventID,
|
||||||
// timer should lock -> deregister -> record PublishTimeout -> unlock -> deliver
|
eventJSON: eventJSON,
|
||||||
// mu.Lock: insert into pending map
|
result: make(chan publishResult, 1),
|
||||||
// if envoy connected: EncloseEvent + Send
|
}
|
||||||
// on send error: deregister, deliver send error, return
|
|
||||||
// on success: mark sent, record PublishDispatched
|
entry.timer = time.AfterFunc(timeout, func() {
|
||||||
// mu.Unlock
|
p.mu.Lock()
|
||||||
// block on entry.result
|
p.deregister(eventID)
|
||||||
// return received publishResult fields
|
p.mu.Unlock()
|
||||||
return false, "", nil
|
p.envoy.Observer().Record(p.envoy.PeerID(),
|
||||||
|
PublishTimeout{EventID: eventID, At: time.Now()})
|
||||||
|
p.deliver(entry, publishResult{err: errors.New("publish timeout")})
|
||||||
|
})
|
||||||
|
|
||||||
|
p.mu.Lock()
|
||||||
|
p.pending[eventID] = entry
|
||||||
|
connected := p.envoy.IsConnected()
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
if connected {
|
||||||
|
if err := p.trySend(entry); err != nil {
|
||||||
|
p.mu.Lock()
|
||||||
|
p.deregister(eventID)
|
||||||
|
p.mu.Unlock()
|
||||||
|
p.deliver(entry, publishResult{err: err})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r := <-entry.result
|
||||||
|
return r.accepted, r.message, r.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *EventPublisher) Close() {
|
func (p *EventPublisher) Close() {
|
||||||
// p.cancel()
|
p.cancel()
|
||||||
// mu.Lock: iterate pending, call once.Do(deliver cancellation error) for each
|
|
||||||
// mu.Unlock
|
p.mu.Lock()
|
||||||
// p.wg.Wait()
|
for id, e := range p.pending {
|
||||||
|
p.deregister(id)
|
||||||
|
p.deliver(e, publishResult{err: errors.New("publisher closed")})
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
p.wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *EventPublisher) trySend(entry *pendingEntry) error {
|
||||||
|
err := p.envoy.Send([]byte(envelope.EncloseEvent(entry.eventJSON)))
|
||||||
|
if err != nil {
|
||||||
|
p.envoy.Observer().Record(p.envoy.PeerID(),
|
||||||
|
PublishSendFailed{EventID: entry.eventID, Err: err, At: time.Now()})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.mu.Lock()
|
||||||
|
entry.sent = true
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
p.envoy.Observer().Record(p.envoy.PeerID(),
|
||||||
|
PublishDispatched{EventID: entry.eventID, At: time.Now()})
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *EventPublisher) deliver(entry *pendingEntry, result publishResult) {
|
func (p *EventPublisher) deliver(entry *pendingEntry, result publishResult) {
|
||||||
@@ -111,27 +163,74 @@ func (p *EventPublisher) deliver(entry *pendingEntry, result publishResult) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *EventPublisher) routeInbox() {
|
func (p *EventPublisher) routeInbox() {
|
||||||
defer p.wg.Done()
|
for {
|
||||||
// select ctx.Done | inbox msg
|
select {
|
||||||
// msg: envelope.FindOK → on err continue
|
case <-p.ctx.Done():
|
||||||
// mu.Lock; look up pending by eventID
|
return
|
||||||
// if not found: mu.Unlock; continue
|
case msg := <-p.inbox:
|
||||||
// deregister; mu.Unlock
|
eventID, accepted, message, err := envelope.FindOK(msg.Data)
|
||||||
// record PublishAccepted or PublishRejected
|
if err != nil {
|
||||||
// deliver(entry, publishResult{accepted, message, nil})
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
p.mu.Lock()
|
||||||
|
entry, ok := p.pending[eventID]
|
||||||
|
if ok {
|
||||||
|
p.deregister(eventID)
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if accepted {
|
||||||
|
p.envoy.Observer().Record(msg.ID,
|
||||||
|
PublishAccepted{EventID: eventID, At: time.Now()})
|
||||||
|
} else {
|
||||||
|
p.envoy.Observer().Record(msg.ID,
|
||||||
|
PublishRejected{EventID: eventID, Message: message, At: time.Now()})
|
||||||
|
}
|
||||||
|
p.deliver(entry, publishResult{accepted: accepted, message: message})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *EventPublisher) handleEvents() {
|
func (p *EventPublisher) handleEvents() {
|
||||||
defer p.wg.Done()
|
for {
|
||||||
// select ctx.Done | event
|
select {
|
||||||
// EventConnected:
|
case <-p.ctx.Done():
|
||||||
// mu.Lock; iterate pending where !sent
|
return
|
||||||
// Send each; on error: deregister, deliver send error, record PublishSendFailed
|
case ev := <-p.events:
|
||||||
// on success: mark sent, record PublishDispatched
|
switch ev.Kind {
|
||||||
// mu.Unlock
|
case EventConnected:
|
||||||
// EventDisconnected:
|
p.mu.Lock()
|
||||||
// mu.Lock; mark all sent entries as unsent
|
var toSend []*pendingEntry
|
||||||
// mu.Unlock
|
for _, e := range p.pending {
|
||||||
|
if !e.sent {
|
||||||
|
toSend = append(toSend, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
for _, e := range toSend {
|
||||||
|
if err := p.trySend(e); err != nil {
|
||||||
|
p.mu.Lock()
|
||||||
|
p.deregister(e.eventID)
|
||||||
|
p.mu.Unlock()
|
||||||
|
p.deliver(e, publishResult{err: err})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case EventDisconnected:
|
||||||
|
p.mu.Lock()
|
||||||
|
for _, e := range p.pending {
|
||||||
|
e.sent = false
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *EventPublisher) deregister(eventID string) {
|
func (p *EventPublisher) deregister(eventID string) {
|
||||||
|
|||||||
+44
-6
@@ -2,16 +2,54 @@ package prism
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"git.wisehodl.dev/jay/go-roots-ws"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEventPublisher(t *testing.T) {
|
func TestEventPublisher(t *testing.T) {
|
||||||
t.Run("publish accepted", func(t *testing.T) {
|
t.Run("publish accepted", func(t *testing.T) {
|
||||||
// p, envoy := newMockEnvoy(t); p.connect()
|
p, envoy := newMockEnvoy(t)
|
||||||
// pub := NewEventPublisher(envoy); t.Cleanup(pub.Close)
|
p.connect()
|
||||||
// go: accepted, msg, err := pub.Publish(id, json, TestTimeout)
|
|
||||||
// Eventually: EVENT envelope on p.sent
|
pub := NewEventPublisher(envoy)
|
||||||
// p.receive(envelope.EncloseOK(id, true, ""))
|
t.Cleanup(pub.Close)
|
||||||
// assert accepted==true, msg=="", err==nil
|
|
||||||
|
const id = "aaaa1111"
|
||||||
|
eventJSON := []byte(`{"id":"aaaa1111","kind":1}`)
|
||||||
|
|
||||||
|
type result struct {
|
||||||
|
accepted bool
|
||||||
|
message string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
ch := make(chan result, 1)
|
||||||
|
go func() {
|
||||||
|
accepted, msg, err := pub.Publish(id, eventJSON, TestTimeout)
|
||||||
|
ch <- result{accepted, msg, err}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var sent []byte
|
||||||
|
Eventually(t, func() bool {
|
||||||
|
select {
|
||||||
|
case sent = <-p.sent:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}, "EVENT envelope not sent")
|
||||||
|
assert.Contains(t, string(sent), id)
|
||||||
|
|
||||||
|
p.receive([]byte(envelope.EncloseOK(id, true, "")))
|
||||||
|
|
||||||
|
Eventually(t, func() bool {
|
||||||
|
select {
|
||||||
|
case r := <-ch:
|
||||||
|
return r.accepted && r.message == "" && r.err == nil
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}, "Publish did not return accepted result")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("publish rejected", func(t *testing.T) {
|
t.Run("publish rejected", func(t *testing.T) {
|
||||||
|
|||||||
+2
-7
@@ -165,9 +165,8 @@ func NewRequestManager(e *Envoy) *RequestManager {
|
|||||||
m.logger = slog.New(m.handler).With(slog.Any("component", comp))
|
m.logger = slog.New(m.handler).With(slog.Any("component", comp))
|
||||||
}
|
}
|
||||||
|
|
||||||
m.wg.Add(2)
|
m.wg.Go(m.handleEvents)
|
||||||
go m.handleEvents()
|
m.wg.Go(m.routeInbox)
|
||||||
go m.routeInbox()
|
|
||||||
|
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
@@ -354,8 +353,6 @@ func (m *RequestManager) deregister(req *request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *RequestManager) handleEvents() {
|
func (m *RequestManager) handleEvents() {
|
||||||
defer m.wg.Done()
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-m.ctx.Done():
|
case <-m.ctx.Done():
|
||||||
@@ -381,8 +378,6 @@ func (m *RequestManager) handleEvents() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *RequestManager) routeInbox() {
|
func (m *RequestManager) routeInbox() {
|
||||||
defer m.wg.Done()
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-m.ctx.Done():
|
case <-m.ctx.Done():
|
||||||
|
|||||||
Reference in New Issue
Block a user