feat(eventbus): enhance event handling with error propagation and global bus management

This commit is contained in:
mlogclub
2026-04-21 11:50:43 +08:00
parent 964b2ed6ce
commit df13d5ced5
4 changed files with 267 additions and 21 deletions
+58 -21
View File
@@ -2,13 +2,15 @@ package eventbus
import (
"context"
"errors"
"fmt"
"log/slog"
"runtime/debug"
"sync"
"sync/atomic"
)
type Handler[T any] func(ctx context.Context, event T)
type Handler[T any] func(ctx context.Context, event T) error
type ErrorHandler func(ctx context.Context, err error)
@@ -51,6 +53,10 @@ func WithAsyncConcurrency[T any](n int) Option[T] {
// Subscribe 返回 handlerID 和取消订阅函数
func (b *Bus[T]) Subscribe(h Handler[T]) (uint64, func()) {
if h == nil {
panic("eventbus: nil handler")
}
id := atomic.AddUint64(&b.nextID, 1)
b.mu.Lock()
@@ -63,11 +69,19 @@ func (b *Bus[T]) Subscribe(h Handler[T]) (uint64, func()) {
}
func (b *Bus[T]) SubscribeOnce(h Handler[T]) (uint64, func()) {
var id uint64
if h == nil {
panic("eventbus: nil handler")
}
wrapper := func(ctx context.Context, event T) {
var id uint64
var called atomic.Bool
wrapper := func(ctx context.Context, event T) error {
if !called.CompareAndSwap(false, true) {
return nil
}
b.Unsubscribe(id)
h(ctx, event)
return h(ctx, event)
}
id = atomic.AddUint64(&b.nextID, 1)
@@ -87,26 +101,21 @@ func (b *Bus[T]) Unsubscribe(id uint64) {
b.mu.Unlock()
}
func (b *Bus[T]) Publish(ctx context.Context, event T) {
func (b *Bus[T]) Publish(ctx context.Context, event T) error {
handlers := b.snapshotHandlers()
errs := make([]error, 0, len(handlers))
for _, h := range handlers {
b.callHandler(ctx, h, event)
if err := b.callHandler(ctx, h, event); err != nil {
errs = append(errs, err)
}
}
return errors.Join(errs...)
}
func (b *Bus[T]) PublishAsync(ctx context.Context, event T) {
handlers := b.snapshotHandlers()
for _, h := range handlers {
if b.asyncSem != nil {
b.asyncSem <- struct{}{}
go func() {
defer func() { <-b.asyncSem }()
b.callHandler(ctx, h, event)
}()
continue
}
go b.callHandler(ctx, h, event)
go b.callHandlerAsync(ctx, h, event)
}
}
@@ -127,13 +136,41 @@ func (b *Bus[T]) snapshotHandlers() []Handler[T] {
return handlers
}
func (b *Bus[T]) callHandler(ctx context.Context, h Handler[T], event T) {
func (b *Bus[T]) callHandlerAsync(ctx context.Context, h Handler[T], event T) {
if b.asyncSem != nil {
select {
case b.asyncSem <- struct{}{}:
defer func() { <-b.asyncSem }()
case <-ctx.Done():
b.handleError(ctx, ctx.Err())
return
}
}
if err := b.callHandler(ctx, h, event); err != nil {
b.handleError(ctx, err)
}
}
func (b *Bus[T]) callHandler(ctx context.Context, h Handler[T], event T) (err error) {
defer func() {
if r := recover(); r != nil {
if b.onError != nil {
b.onError(ctx, fmt.Errorf("event handler panic: %v\n%s", r, debug.Stack()))
}
err = fmt.Errorf("event handler panic: %v\n%s", r, debug.Stack())
}
}()
h(ctx, event)
if err = h(ctx, event); err != nil {
return err
}
return nil
}
func (b *Bus[T]) handleError(ctx context.Context, err error) {
if err == nil {
return
}
if b.onError != nil {
b.onError(ctx, err)
return
}
slog.Error("eventbus handler failed", "error", err)
}