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 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
} }
+55
View File
@@ -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]()