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 ErrorHandler func(ctx context.Context, err error)
|
||||||
|
|
||||||
|
type handlerEntry[T any] struct {
|
||||||
|
id uint64
|
||||||
|
handler Handler[T]
|
||||||
|
}
|
||||||
|
|
||||||
type Bus[T any] struct {
|
type Bus[T any] struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
handlers map[uint64]Handler[T]
|
handlers []handlerEntry[T]
|
||||||
nextID uint64
|
nextID uint64
|
||||||
|
|
||||||
onError ErrorHandler
|
onError ErrorHandler
|
||||||
@@ -26,9 +31,7 @@ type Bus[T any] struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func New[T any](opts ...Option[T]) *Bus[T] {
|
func New[T any](opts ...Option[T]) *Bus[T] {
|
||||||
b := &Bus[T]{
|
b := &Bus[T]{}
|
||||||
handlers: make(map[uint64]Handler[T]),
|
|
||||||
}
|
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(b)
|
opt(b)
|
||||||
}
|
}
|
||||||
@@ -60,7 +63,10 @@ func (b *Bus[T]) Subscribe(h Handler[T]) (uint64, func()) {
|
|||||||
id := atomic.AddUint64(&b.nextID, 1)
|
id := atomic.AddUint64(&b.nextID, 1)
|
||||||
|
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
b.handlers[id] = h
|
b.handlers = append(b.handlers, handlerEntry[T]{
|
||||||
|
id: id,
|
||||||
|
handler: h,
|
||||||
|
})
|
||||||
b.mu.Unlock()
|
b.mu.Unlock()
|
||||||
|
|
||||||
return id, func() {
|
return id, func() {
|
||||||
@@ -87,7 +93,10 @@ func (b *Bus[T]) SubscribeOnce(h Handler[T]) (uint64, func()) {
|
|||||||
id = atomic.AddUint64(&b.nextID, 1)
|
id = atomic.AddUint64(&b.nextID, 1)
|
||||||
|
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
b.handlers[id] = wrapper
|
b.handlers = append(b.handlers, handlerEntry[T]{
|
||||||
|
id: id,
|
||||||
|
handler: wrapper,
|
||||||
|
})
|
||||||
b.mu.Unlock()
|
b.mu.Unlock()
|
||||||
|
|
||||||
return id, func() {
|
return id, func() {
|
||||||
@@ -97,7 +106,13 @@ func (b *Bus[T]) SubscribeOnce(h Handler[T]) (uint64, func()) {
|
|||||||
|
|
||||||
func (b *Bus[T]) Unsubscribe(id uint64) {
|
func (b *Bus[T]) Unsubscribe(id uint64) {
|
||||||
b.mu.Lock()
|
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()
|
b.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,8 +145,8 @@ func (b *Bus[T]) snapshotHandlers() []Handler[T] {
|
|||||||
defer b.mu.RUnlock()
|
defer b.mu.RUnlock()
|
||||||
|
|
||||||
handlers := make([]Handler[T], 0, len(b.handlers))
|
handlers := make([]Handler[T], 0, len(b.handlers))
|
||||||
for _, h := range b.handlers {
|
for _, entry := range b.handlers {
|
||||||
handlers = append(handlers, h)
|
handlers = append(handlers, entry.handler)
|
||||||
}
|
}
|
||||||
return handlers
|
return handlers
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package eventbus
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"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) {
|
func TestPanicReturnsError(t *testing.T) {
|
||||||
bus := New[testEvent]()
|
bus := New[testEvent]()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user