feat(eventbus): enhance event handling with error propagation and global bus management
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user