Add in-process event bus with ring buffer for workflow event broadcasting
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/claude-agent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
package eventbus
|
||||
|
||||
import "sync"
|
||||
|
||||
type Event struct {
|
||||
Seq int64
|
||||
WorkflowRunID string
|
||||
AlbumID string
|
||||
Quality string
|
||||
EventType string
|
||||
Step string
|
||||
Message string
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
type Subscription struct {
|
||||
Ring *RingBuffer[*Event]
|
||||
C chan struct{}
|
||||
done chan struct{}
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
type EventBus struct {
|
||||
mu sync.RWMutex
|
||||
topics map[string]map[*Subscription]struct{}
|
||||
global map[*Subscription]struct{}
|
||||
}
|
||||
|
||||
func New() *EventBus {
|
||||
return &EventBus{
|
||||
topics: make(map[string]map[*Subscription]struct{}),
|
||||
global: make(map[*Subscription]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *EventBus) Publish(topic string, event *Event) {
|
||||
b.mu.RLock()
|
||||
defer b.mu.RUnlock()
|
||||
|
||||
if subs, ok := b.topics[topic]; ok {
|
||||
for sub := range subs {
|
||||
sub.Ring.Push(event)
|
||||
select {
|
||||
case sub.C <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for sub := range b.global {
|
||||
sub.Ring.Push(event)
|
||||
select {
|
||||
case sub.C <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *EventBus) Subscribe(topic string) (*Subscription, func()) {
|
||||
sub := &Subscription{
|
||||
Ring: NewRingBuffer[*Event](256),
|
||||
C: make(chan struct{}, 1),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
b.mu.Lock()
|
||||
if b.topics[topic] == nil {
|
||||
b.topics[topic] = make(map[*Subscription]struct{})
|
||||
}
|
||||
b.topics[topic][sub] = struct{}{}
|
||||
b.mu.Unlock()
|
||||
|
||||
cleanup := func() {
|
||||
sub.once.Do(func() {
|
||||
b.mu.Lock()
|
||||
delete(b.topics[topic], sub)
|
||||
if len(b.topics[topic]) == 0 {
|
||||
delete(b.topics, topic)
|
||||
}
|
||||
b.mu.Unlock()
|
||||
close(sub.done)
|
||||
})
|
||||
}
|
||||
|
||||
return sub, cleanup
|
||||
}
|
||||
|
||||
func (b *EventBus) SubscribeGlobal() (*Subscription, func()) {
|
||||
sub := &Subscription{
|
||||
Ring: NewRingBuffer[*Event](256),
|
||||
C: make(chan struct{}, 1),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
b.mu.Lock()
|
||||
b.global[sub] = struct{}{}
|
||||
b.mu.Unlock()
|
||||
|
||||
cleanup := func() {
|
||||
sub.once.Do(func() {
|
||||
b.mu.Lock()
|
||||
delete(b.global, sub)
|
||||
b.mu.Unlock()
|
||||
close(sub.done)
|
||||
})
|
||||
}
|
||||
|
||||
return sub, cleanup
|
||||
}
|
||||
|
||||
func (b *EventBus) HasTopic(topic string) bool {
|
||||
b.mu.RLock()
|
||||
defer b.mu.RUnlock()
|
||||
_, ok := b.topics[topic]
|
||||
return ok
|
||||
}
|
||||
Reference in New Issue
Block a user