feat(eventbus): refactor handler storage to use a slice and add tests for handler order

This commit is contained in:
mlogclub
2026-04-21 16:42:29 +08:00
parent 6c005d5daa
commit 6789a8dee0
2 changed files with 79 additions and 9 deletions
+24 -9
View File
@@ -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
}
+55
View File
@@ -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]()