feat(eventbus): refactor handler storage to use a slice and add tests for handler order
This commit is contained in:
@@ -14,9 +14,14 @@ type Handler[T any] func(ctx context.Context, event T) error
|
||||
|
||||
type ErrorHandler func(ctx context.Context, err error)
|
||||
|
||||
type handlerEntry[T any] struct {
|
||||
id uint64
|
||||
handler Handler[T]
|
||||
}
|
||||
|
||||
type Bus[T any] struct {
|
||||
mu sync.RWMutex
|
||||
handlers map[uint64]Handler[T]
|
||||
handlers []handlerEntry[T]
|
||||
nextID uint64
|
||||
|
||||
onError ErrorHandler
|
||||
@@ -26,9 +31,7 @@ type Bus[T any] struct {
|
||||
}
|
||||
|
||||
func New[T any](opts ...Option[T]) *Bus[T] {
|
||||
b := &Bus[T]{
|
||||
handlers: make(map[uint64]Handler[T]),
|
||||
}
|
||||
b := &Bus[T]{}
|
||||
for _, opt := range opts {
|
||||
opt(b)
|
||||
}
|
||||
@@ -60,7 +63,10 @@ func (b *Bus[T]) Subscribe(h Handler[T]) (uint64, func()) {
|
||||
id := atomic.AddUint64(&b.nextID, 1)
|
||||
|
||||
b.mu.Lock()
|
||||
b.handlers[id] = h
|
||||
b.handlers = append(b.handlers, handlerEntry[T]{
|
||||
id: id,
|
||||
handler: h,
|
||||
})
|
||||
b.mu.Unlock()
|
||||
|
||||
return id, func() {
|
||||
@@ -87,7 +93,10 @@ func (b *Bus[T]) SubscribeOnce(h Handler[T]) (uint64, func()) {
|
||||
id = atomic.AddUint64(&b.nextID, 1)
|
||||
|
||||
b.mu.Lock()
|
||||
b.handlers[id] = wrapper
|
||||
b.handlers = append(b.handlers, handlerEntry[T]{
|
||||
id: id,
|
||||
handler: wrapper,
|
||||
})
|
||||
b.mu.Unlock()
|
||||
|
||||
return id, func() {
|
||||
@@ -97,7 +106,13 @@ func (b *Bus[T]) SubscribeOnce(h Handler[T]) (uint64, func()) {
|
||||
|
||||
func (b *Bus[T]) Unsubscribe(id uint64) {
|
||||
b.mu.Lock()
|
||||
delete(b.handlers, id)
|
||||
for i, entry := range b.handlers {
|
||||
if entry.id != id {
|
||||
continue
|
||||
}
|
||||
b.handlers = append(b.handlers[:i], b.handlers[i+1:]...)
|
||||
break
|
||||
}
|
||||
b.mu.Unlock()
|
||||
}
|
||||
|
||||
@@ -130,8 +145,8 @@ func (b *Bus[T]) snapshotHandlers() []Handler[T] {
|
||||
defer b.mu.RUnlock()
|
||||
|
||||
handlers := make([]Handler[T], 0, len(b.handlers))
|
||||
for _, h := range b.handlers {
|
||||
handlers = append(handlers, h)
|
||||
for _, entry := range b.handlers {
|
||||
handlers = append(handlers, entry.handler)
|
||||
}
|
||||
return handlers
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package eventbus
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
@@ -53,6 +54,60 @@ func TestPublishReturnsJoinedHandlerErrors(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublishCallsHandlersInSubscribeOrder(t *testing.T) {
|
||||
bus := New[testEvent]()
|
||||
calls := make([]int, 0, 3)
|
||||
|
||||
bus.Subscribe(func(ctx context.Context, event testEvent) error {
|
||||
calls = append(calls, 1)
|
||||
return nil
|
||||
})
|
||||
bus.Subscribe(func(ctx context.Context, event testEvent) error {
|
||||
calls = append(calls, 2)
|
||||
return nil
|
||||
})
|
||||
bus.Subscribe(func(ctx context.Context, event testEvent) error {
|
||||
calls = append(calls, 3)
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := bus.Publish(context.Background(), testEvent{}); err != nil {
|
||||
t.Fatalf("publish failed: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(calls, []int{1, 2, 3}) {
|
||||
t.Fatalf("expected handlers to run in subscribe order, got %#v", calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnsubscribeKeepsRemainingHandlerOrder(t *testing.T) {
|
||||
bus := New[testEvent]()
|
||||
calls := make([]int, 0, 2)
|
||||
|
||||
bus.Subscribe(func(ctx context.Context, event testEvent) error {
|
||||
calls = append(calls, 1)
|
||||
return nil
|
||||
})
|
||||
_, unsubscribe := bus.Subscribe(func(ctx context.Context, event testEvent) error {
|
||||
calls = append(calls, 2)
|
||||
return nil
|
||||
})
|
||||
bus.Subscribe(func(ctx context.Context, event testEvent) error {
|
||||
calls = append(calls, 3)
|
||||
return nil
|
||||
})
|
||||
|
||||
unsubscribe()
|
||||
|
||||
if err := bus.Publish(context.Background(), testEvent{}); err != nil {
|
||||
t.Fatalf("publish failed: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(calls, []int{1, 3}) {
|
||||
t.Fatalf("expected remaining handlers to keep order, got %#v", calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPanicReturnsError(t *testing.T) {
|
||||
bus := New[testEvent]()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user